200304PX Changes over the last months
This commit is contained in:
parent
d1fa9744d3
commit
6ca52cfb83
148
Program.cs
148
Program.cs
|
@ -15,26 +15,36 @@ TODO
|
||||||
- ErrorLog for all errors
|
- ErrorLog for all errors
|
||||||
- frmMain: Menu option to miss an MV for selected/checked users
|
- frmMain: Menu option to miss an MV for selected/checked users
|
||||||
- FormMail: Add mail for automatic member type setting to "Foerdermitglied"
|
- FormMail: Add mail for automatic member type setting to "Foerdermitglied"
|
||||||
- FormMail: Use manual type changing
|
- FormMail: Membership type was changed manually
|
||||||
- FormMail: Cronjob found unassigned MoneyTransfers
|
- FormMail: Cronjob found unassigned MoneyTransfers
|
||||||
- frmEditEntry: Optional (checkbox) request for a comment on data changes, when hitting OK
|
- frmEditEntry: Optional (checkbox) request for a comment on data changes, when hitting OK
|
||||||
- frmMain: Indicator that the data was modified after import + messagebox to remind user to export on quitting.
|
- frmMain: Indicator that the data was modified after import + messagebox to remind user to export on quitting.
|
||||||
|
- add "database changed since last import/export" warning (e.g. you forgot to push your changes)
|
||||||
- frmMain: Member List: Column "monthly fee", Column "Last payment", disabled by default
|
- frmMain: Member List: Column "monthly fee", Column "Last payment", disabled by default
|
||||||
- Configuration window: MoneyTransferRegEx
|
- Configuration window: MoneyTransferRegEx
|
||||||
- Bug: Generating testdata doesn't remove old xml files, thus the memberlist will be mixed after next restart
|
- Bug: Generating testdata doesn't remove old xml files, thus the memberlist will be mixed after next restart
|
||||||
- Bug: Database import fails on some PCs (seems that writing xml files during decompression is problematic)
|
|
||||||
- Bug: Member list not reloaded after ProcessCSV (balance display is wrong)
|
- Bug: Member list not reloaded after ProcessCSV (balance display is wrong)
|
||||||
- CronjobConfig
|
- CronjobConfig
|
||||||
- CustomListView: implement generic filtering
|
- CustomListView: implement generic filtering
|
||||||
|
|
||||||
|
- Improve import/export handling, e.g. check for newer database on program start
|
||||||
|
- Debt handling: Store explicit flags or status for "one month behind"
|
||||||
|
or "two months behind", in order to have an escalation chain
|
||||||
|
|
||||||
|
- PGP for mails
|
||||||
|
|
||||||
DONE
|
DONE
|
||||||
----
|
----
|
||||||
|
- Member: add database field for contact name e.g.: name <mail@example.com> filled with nickname by default
|
||||||
|
- Testmail feature for every member account
|
||||||
- 2019-12-04: frmProcessWithLog: Implementation for non-immediate GUI actions (process csv, cronjob, import/export) with background thread
|
- 2019-12-04: frmProcessWithLog: Implementation for non-immediate GUI actions (process csv, cronjob, import/export) with background thread
|
||||||
|
- Bug: Database import fails on some PCs (seems that writing xml files during decompression is problematic)
|
||||||
*/
|
*/
|
||||||
namespace dezentrale
|
namespace dezentrale
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public static uint VersionNumber { get; private set; } = 0x19120400;
|
public static uint VersionNumber { get; private set; } = 0x20022500;
|
||||||
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
|
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
|
||||||
|
|
||||||
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||||
|
@ -233,142 +243,12 @@ namespace dezentrale
|
||||||
Console.WriteLine("You must provide an input file by using --csvinput=<file>");
|
Console.WriteLine("You must provide an input file by using --csvinput=<file>");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!ProcessCSV(csvInput))
|
if (!ProcessCsv.ProcessCSV(csvInput))
|
||||||
return 1;
|
return 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ProcessCSV(string fileName)
|
|
||||||
{
|
|
||||||
|
|
||||||
CsvFile csv = new CsvFile();
|
|
||||||
csv.FieldSeparator = ';';
|
|
||||||
try
|
|
||||||
{
|
|
||||||
List<BankTransfer> tmpList = new List<BankTransfer>();
|
|
||||||
|
|
||||||
csv.ReadFile(fileName);
|
|
||||||
List<string> headlineFields = null;
|
|
||||||
List<Member> changedMembers = new List<Member>();
|
|
||||||
foreach (List<string> l in csv.FileContents)
|
|
||||||
{
|
|
||||||
if (headlineFields == null)
|
|
||||||
{
|
|
||||||
//The first line is expected to have the headline field first, describing the contents
|
|
||||||
headlineFields = l;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BankTransfer bt = new BankTransfer(headlineFields, l);
|
|
||||||
|
|
||||||
|
|
||||||
MoneyTransfer duplicate = MoneyTransfers.FindEqualEntry(bt);
|
|
||||||
if (duplicate != null)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Duplicate MoneyTransfer found");
|
|
||||||
Console.WriteLine($"ValutaDate: {bt.ValutaDate}, Amount = {bt.AmountString} {bt.Currency}, Reason = \"{bt.TransferReason.Replace('\r','\\').Replace('\n','\\')}\"");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
MoneyTransfers.AddEntry(bt);
|
|
||||||
tmpList.Add(bt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//try to assign transfers to the members
|
|
||||||
foreach (BankTransfer bt in tmpList)
|
|
||||||
{
|
|
||||||
if (bt.Amount < 0)
|
|
||||||
{
|
|
||||||
bt.TransferType = MoneyTransfer.eTransferType.RunningCost;
|
|
||||||
Console.WriteLine($"{bt.Id}: Amount = {bt.AmountString} --> RunningCost");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Member m in members.Entries)
|
|
||||||
{
|
|
||||||
if (m.CheckBankTransfer(bt))
|
|
||||||
{
|
|
||||||
bt.TransferType = MoneyTransfer.eTransferType.MembershipPayment;
|
|
||||||
changedMembers.Add(m);
|
|
||||||
m.StartLogEvent("Incoming bank transfer", LogEvent.eEventType.MembershipPayment, "automatic");
|
|
||||||
m.ApplyMoneyTransfer(bt);
|
|
||||||
bt.AssignFixed = true;
|
|
||||||
break; //this is important. We don't want to assign this to multiple members.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Store bank transfer list
|
|
||||||
Console.WriteLine("ProcessCSV(): Storing money transfers...");
|
|
||||||
MoneyTransfers.Entries.Sort();
|
|
||||||
if (!MoneyTransfers.SaveToFile())
|
|
||||||
return false;
|
|
||||||
bool ret = true;
|
|
||||||
/*
|
|
||||||
//automaticly saved in ApplyMoneyTransfer()
|
|
||||||
foreach (Member m in changedMembers)
|
|
||||||
{
|
|
||||||
if (m.CurrentLog != null)
|
|
||||||
{
|
|
||||||
Console.Write($"ProcessCSV(): Storing {m.GetFileName()}... ");
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!m.SaveToFile()) return false;
|
|
||||||
Console.WriteLine("OK");
|
|
||||||
} catch(Exception ex2)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"ERROR: {ex2.Message}");
|
|
||||||
ret = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
//TBD: mail to schatzmeister if there are unassigned transfers
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Error while processing csv file \"{fileName}\":");
|
|
||||||
Console.WriteLine(ex.Message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static string PadLeft(string input, uint totalLength)
|
|
||||||
{
|
|
||||||
if (input.Length < totalLength)
|
|
||||||
return new string(' ', (int)(totalLength - input.Length)) + input;
|
|
||||||
else
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
//TBD: This is an utility function and should reside in an extra class
|
|
||||||
public static string Int64FPToString(Int64 value)
|
|
||||||
{
|
|
||||||
//float fValue = ((float) value) / 100;
|
|
||||||
//return $"{fValue:0.00}";
|
|
||||||
Int64 intPart = value / 100;
|
|
||||||
Int64 decPart = (intPart < 0 ? -1 : 1) * (value % 100);
|
|
||||||
return $"{intPart},{decPart:D2}";
|
|
||||||
}
|
|
||||||
//TBD: This is an utility function and should reside in an extra class
|
|
||||||
public static Int64 StringToInt64FP(string value)
|
|
||||||
{
|
|
||||||
if (value == null) return 0;
|
|
||||||
if (!value.Contains(".") && !value.Contains(","))
|
|
||||||
{
|
|
||||||
return Convert.ToInt64(value) * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
int i = value.IndexOf('.');
|
|
||||||
if (i < 0) i = value.IndexOf(',');
|
|
||||||
string intPart = value.Substring(0, i);
|
|
||||||
string decPart = value.Substring(i + 1);
|
|
||||||
|
|
||||||
Int64 ip64 = Convert.ToInt64(intPart);
|
|
||||||
return (ip64 * 100) + (ip64 > 0 ? Convert.ToInt64(decPart) : -Convert.ToInt64(decPart));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,28 +4,52 @@ using System.Threading;
|
||||||
|
|
||||||
namespace dezentrale.core
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
|
//! \short Common functionality for complex background behaviours
|
||||||
|
//! \brief Provides a threading solution for program behaviours that run in
|
||||||
|
//! background. Also acts as an interface for controlling and
|
||||||
|
//! presentation code (GUI)
|
||||||
|
//! \todo Implement cancelling correctly
|
||||||
public abstract class BackgroundProcess
|
public abstract class BackgroundProcess
|
||||||
{
|
{
|
||||||
public IProcessController LogTarget { get; set; } = null;
|
//! Target for logging operations performed by the inheriting process classes
|
||||||
public bool CancelButton { get; protected set; } = false;
|
public IProcessController LogTarget { get; set; }
|
||||||
|
|
||||||
|
//! Can be set by the inheriting class to define whether the GUI shall allow the user to cancel the process
|
||||||
|
public bool CancelButton { get; protected set; }
|
||||||
|
|
||||||
|
//! Textual representation of the process, for displaying
|
||||||
public string Caption { get; protected set; } = "BackgroundProcess";
|
public string Caption { get; protected set; } = "BackgroundProcess";
|
||||||
|
|
||||||
|
//! Sub-Steps of the process, to allow the user a better estimation of the duration
|
||||||
public uint Steps { get; protected set; } = 1;
|
public uint Steps { get; protected set; } = 1;
|
||||||
|
|
||||||
protected abstract bool Run();
|
|
||||||
|
|
||||||
private Thread thread = null;
|
|
||||||
|
protected abstract bool Run(); //!< Base requirement for inheriting classes: Implement a process. \return if it was successful
|
||||||
|
|
||||||
|
private Thread thread = null; //!< Thread instance to be run in background
|
||||||
|
|
||||||
|
//! \short Starts an async process run.
|
||||||
|
//! \return the generated Thread instance, so the application code can watch it too
|
||||||
public Thread StartRun()
|
public Thread StartRun()
|
||||||
{
|
{
|
||||||
if (thread != null) return null;
|
if (thread != null) return null;
|
||||||
|
if (LogTarget == null) return null;
|
||||||
thread = new Thread(RunAbstract) { IsBackground = true };
|
thread = new Thread(RunAbstract) { IsBackground = true };
|
||||||
thread.Start();
|
thread.Start();
|
||||||
return thread;
|
return thread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \internal Helper function for enforcing the ActionCompleted call
|
||||||
private void RunAbstract()
|
private void RunAbstract()
|
||||||
{
|
{
|
||||||
LogTarget.ActionCompleted(Run());
|
LogTarget.ActionCompleted(Run());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \short Example for a BackgroundProcess
|
||||||
|
//! \brief Implements a threaded solution for logging hello world strings
|
||||||
|
//! with delays in between them, to simulate background actions
|
||||||
public class HelloWorldProcess : BackgroundProcess
|
public class HelloWorldProcess : BackgroundProcess
|
||||||
{
|
{
|
||||||
public HelloWorldProcess()
|
public HelloWorldProcess()
|
||||||
|
|
|
@ -4,10 +4,87 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
using dezentrale.model;
|
using dezentrale.model;
|
||||||
namespace dezentrale
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
|
|
||||||
|
//! \short Functionality for automatic processing that can be run in a scheduled way
|
||||||
|
//! \brief A huge port of the dezentrale-members software is designed to help with
|
||||||
|
//! previously manual tasks by defining an automated manner / refresh user
|
||||||
|
//! data according to repeated actions, e.g. membership payments.
|
||||||
public class Cronjob
|
public class Cronjob
|
||||||
{
|
{
|
||||||
|
//! \brief Perform a Run on one Member. This will perform all necessary actions
|
||||||
|
//! at this point.
|
||||||
|
//! \param m Member to check
|
||||||
|
//! \param sm Member entry for the Schatzmeister account, in order to send notification mails for some events
|
||||||
|
public static void Run(Member m, Member sm)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Processing member {m.Number:D3} ({m.Nickname})...");
|
||||||
|
CheckGreeting(m);
|
||||||
|
CheckSpawning(m);
|
||||||
|
CheckBalance_PreDegrade(m, sm);
|
||||||
|
BalanceDegrade(m);
|
||||||
|
CheckBalance_PostDegrade(m, sm);
|
||||||
|
}
|
||||||
|
//! \short Helper function for Run(m, sm). Determines sm automatically.
|
||||||
|
//! \param m Member to check
|
||||||
|
public static void Run(Member m)
|
||||||
|
{
|
||||||
|
Member sm = Program.members.Find(Member.eRole.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.
|
||||||
|
//! \brief Perform a cronjob run on a List of members. This may be a
|
||||||
|
//! partial List (e.g. by checking member entries from GUI)
|
||||||
|
//! \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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
foreach (Member m in Program.members.Entries) Run(m, sm);
|
||||||
|
|
||||||
|
//GenerateReport();
|
||||||
|
if (!Program.config.LastCronjobRun.Equals(ProgramStartTime))
|
||||||
|
{
|
||||||
|
Member schriftfuehrer = Program.members.Find(Member.eRole.Schriftfuehrer);
|
||||||
|
try { if (sm != null) new MemberReport(true).Send(sm); }
|
||||||
|
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to Schatzmeister: {ex.Message}"); }
|
||||||
|
try { if (schriftfuehrer != null) new MemberReport(false).Send(schriftfuehrer); }
|
||||||
|
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to schriftfuehrer: {ex.Message}"); }
|
||||||
|
Console.WriteLine("Cronjob.Run(): Member Reports sent.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.config.LastCronjobRun = ProgramStartTime;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XmlData.SaveToFile(Program.ConfigFile, Program.config);
|
||||||
|
Console.WriteLine("Cronjob.Run(): Saved config.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Cronjob.Run(): Error while saving config: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (Member m in partialList) Run(m, sm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! \short Send greetings E-Mail to new member, if conditions are met.
|
||||||
|
//! \note Also sends a mail to vorstand mailinglist
|
||||||
|
//! \param m Member to check
|
||||||
private static void CheckGreeting(Member m)
|
private static void CheckGreeting(Member m)
|
||||||
{
|
{
|
||||||
if (m.Status == Member.eStatus.Uninitialized && m.EMail.Length > 0)
|
if (m.Status == Member.eStatus.Uninitialized && m.EMail.Length > 0)
|
||||||
|
@ -43,6 +120,9 @@ namespace dezentrale
|
||||||
m.SaveToFile();
|
m.SaveToFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \short Check if a previously greeted member is ready to become active.
|
||||||
|
//! \note Also sends a mail to vorstand mailinglist
|
||||||
private static void CheckSpawning(Member m)
|
private static void CheckSpawning(Member m)
|
||||||
{
|
{
|
||||||
if(m.Status != Member.eStatus.Greeted)
|
if(m.Status != Member.eStatus.Greeted)
|
||||||
|
@ -103,16 +183,19 @@ namespace dezentrale
|
||||||
m.Status = Member.eStatus.Active;
|
m.Status = Member.eStatus.Active;
|
||||||
m.SaveToFile();
|
m.SaveToFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! \brief This shall degrade the balance by a membership fee per month.
|
||||||
|
//! Calls to this function within the same month will not degrade
|
||||||
|
//! balance. Subsequent calls at the end of the month, and then
|
||||||
|
//! at the start of the next month, will degrade balance.
|
||||||
|
//! \param m Member to check
|
||||||
private static void BalanceDegrade(Member m)
|
private static void BalanceDegrade(Member m)
|
||||||
{
|
{
|
||||||
if (m.Status == Member.eStatus.Greeted && (DateTime.Now < m.SpawnDate)) return;
|
if (m.Status == Member.eStatus.Greeted && (DateTime.Now < m.SpawnDate)) return;
|
||||||
if (m.Status == Member.eStatus.Uninitialized || m.Status == Member.eStatus.Disabled || m.Status == Member.eStatus.Deleted) return;
|
if (m.Status == Member.eStatus.Uninitialized || m.Status == Member.eStatus.Disabled || m.Status == Member.eStatus.Deleted) return;
|
||||||
|
|
||||||
//this shall degrade the balance by a membership fee per month.
|
|
||||||
//calls to this function within the same month must not degrade balance.
|
|
||||||
//subsequent calls at the end of the month, and then at the start of the next month, must degrade balance.
|
|
||||||
|
|
||||||
uint thisMonth = (uint) DateTime.Now.Month;
|
uint thisMonth = (uint) DateTime.Now.Month;
|
||||||
uint thisYear = (uint) DateTime.Now.Year;
|
uint thisYear = (uint) DateTime.Now.Year;
|
||||||
|
@ -131,10 +214,13 @@ namespace dezentrale
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//During program execution, we want to keep a constant timestamp for
|
//! \brief During program execution, we want to keep a constant
|
||||||
//cronjob activity, to be able to compare for multiple runs (e.g. member list)
|
//! timestamp for cronjob activity, to be able to compare for
|
||||||
//and not to store it every time to disk
|
//! multiple runs (e.g. member list) and not to store it every
|
||||||
|
//! time to disk
|
||||||
private static DateTime ProgramStartTime { get; set; } = DateTime.Now;
|
private static DateTime ProgramStartTime { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
//! \short Necessary actions prior to BalanceDegrade.
|
||||||
private static void CheckBalance_PreDegrade(Member m, Member sm = null)
|
private static void CheckBalance_PreDegrade(Member m, Member sm = null)
|
||||||
{
|
{
|
||||||
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
|
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
|
||||||
|
@ -191,66 +277,5 @@ namespace dezentrale
|
||||||
if (m.CurrentLog != null)
|
if (m.CurrentLog != null)
|
||||||
m.SaveToFile();
|
m.SaveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateReport()
|
|
||||||
{
|
|
||||||
if (!Program.config.LastCronjobRun.Equals(ProgramStartTime))
|
|
||||||
{
|
|
||||||
Member schatzmeister = Program.members.Find(Member.eRole.Schatzmeister);
|
|
||||||
Member schriftfuehrer = Program.members.Find(Member.eRole.Schriftfuehrer);
|
|
||||||
try { if (schatzmeister != null) new MemberReport(true).Send(schatzmeister); }
|
|
||||||
catch(Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to Schatzmeister: {ex.Message}"); }
|
|
||||||
try { if (schriftfuehrer != null) new MemberReport(false).Send(schriftfuehrer); }
|
|
||||||
catch(Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to schriftfuehrer: {ex.Message}"); }
|
|
||||||
Console.WriteLine("Cronjob.Run(): Member Reports sent.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static void Run(Member m, Member sm)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Processing member {m.Number:D3} ({m.Nickname})...");
|
|
||||||
CheckGreeting(m);
|
|
||||||
CheckSpawning(m);
|
|
||||||
CheckBalance_PreDegrade (m, sm);
|
|
||||||
BalanceDegrade(m);
|
|
||||||
CheckBalance_PostDegrade(m, sm);
|
|
||||||
}
|
|
||||||
public static void Run(Member m)
|
|
||||||
{
|
|
||||||
Member sm = Program.members.Find(Member.eRole.Schatzmeister);
|
|
||||||
if (sm == null)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Cronjob.Run(Member {m.Number}, {m.Nickname}): ERROR, cannot find sm account!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Run(m, sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 sm account!");
|
|
||||||
}
|
|
||||||
if (partialList == null || partialList == Program.members.Entries)
|
|
||||||
{
|
|
||||||
foreach (Member m in Program.members.Entries) Run(m, sm);
|
|
||||||
GenerateReport();
|
|
||||||
|
|
||||||
Program.config.LastCronjobRun = ProgramStartTime;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
XmlData.SaveToFile(Program.ConfigFile, Program.config);
|
|
||||||
Console.WriteLine("Cronjob.Run(): Saved config.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Cronjob.Run(): Error while saving config: {ex.Message}");
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
foreach (Member m in partialList) Run(m, sm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,10 @@ using dezentrale.model;
|
||||||
|
|
||||||
namespace dezentrale.core
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
/** \brief Export functionality for the database contents
|
//! \short Export functionality for the database contents
|
||||||
*/
|
//! \brief The normal export workflow is to pack every xml file from the
|
||||||
|
//! data directory to the zip file configured in the Configuration.
|
||||||
|
//! After that, a mercurial or git commit is issued, followed by a push.
|
||||||
public class ExportProcess : ImportExportBase
|
public class ExportProcess : ImportExportBase
|
||||||
{
|
{
|
||||||
public ExportProcess()
|
public ExportProcess()
|
||||||
|
@ -16,6 +18,13 @@ namespace dezentrale.core
|
||||||
Steps = 6;
|
Steps = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \short Run export
|
||||||
|
//! \brief Zips the files in the data folder, then runs gpg to encrypt
|
||||||
|
//! and commits/pushes it to a hg or git repo
|
||||||
|
//! \return true if the import was successful.
|
||||||
|
//! \note May lead to a broken repository structure ("multiple heads")
|
||||||
|
//! if push fails
|
||||||
|
//! \todo Re-Implement validation (commented out)
|
||||||
protected override bool Run()
|
protected override bool Run()
|
||||||
{
|
{
|
||||||
LogTarget.StepStarted(0, "Preparing export");
|
LogTarget.StepStarted(0, "Preparing export");
|
||||||
|
|
|
@ -4,10 +4,20 @@ using dezentrale.model;
|
||||||
|
|
||||||
namespace dezentrale.core
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
|
//! \short Logging interface for files, gui and processes.
|
||||||
public interface ILogger
|
public interface ILogger
|
||||||
{
|
{
|
||||||
|
//! \short Clear a log window, if available
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
|
//! \short Log the string to the output, only appending a newline
|
||||||
|
//! \param text The informational payload, free-text. Can be multi-line too
|
||||||
void LogRaw(string text);
|
void LogRaw(string text);
|
||||||
|
|
||||||
|
//! \short Log a formatted line using the given info from the parameters
|
||||||
|
//! \param text The informational payload, free-text. use one text line only
|
||||||
|
//! \param logLevel Severity of the log event
|
||||||
|
//! \param module Current code unit, can be used to sort out debugging issues later
|
||||||
void LogLine(string text, LogEvent.ELogLevel logLevel = LogEvent.ELogLevel.Info, string module = "");
|
void LogLine(string text, LogEvent.ELogLevel logLevel = LogEvent.ELogLevel.Info, string module = "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
namespace dezentrale.core
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
|
//! \short Interface for controlling processes and for getting more detailled information from them
|
||||||
public interface IProcessController : ILogger
|
public interface IProcessController : ILogger
|
||||||
{
|
{
|
||||||
void ActionCompleted(bool success);
|
void ActionCompleted(bool success);
|
||||||
|
|
|
@ -4,20 +4,42 @@ using System.Threading;
|
||||||
|
|
||||||
namespace dezentrale.core
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
|
//! \short Support functionality for import/export processes
|
||||||
|
//! \brief Provides methods for executing git/mercurial, holds variables
|
||||||
|
//! used by the subclasses. Makes use of an ILogger instance
|
||||||
|
//! for program output.
|
||||||
public abstract class ImportExportBase : BackgroundProcess
|
public abstract class ImportExportBase : BackgroundProcess
|
||||||
{
|
{
|
||||||
public model.MemberImportExport ImportExportSettings { get; set; } = null;
|
public model.MemberImportExport ImportExportSettings { get; set; } = null; //!< Instance of the MemberImportExport settings from the configuration class
|
||||||
public string MemberDir { get; set; } = null; //! Directory that holds the application settings
|
public string MemberDir { get; set; } = null; //!< Directory that holds the application settings
|
||||||
public string InputDir { get; set; } = null; //! Import working directory
|
public string InputDir { get; set; } = null; //!< Import working directory
|
||||||
public string OutputDir { get; set; } = null; //! Export working directory
|
public string OutputDir { get; set; } = null; //!< Export working directory
|
||||||
|
|
||||||
public static bool Gpg(string args, string gpgPassword, core.ILogger log = null)
|
//! \brief Run the gpg command with a specified passphrase, in batch-mode without user interaction
|
||||||
|
//! \param args Custom args to be appended to the gpg call
|
||||||
|
//! \param gpgPassword Passphrase to be used by GPG to encrypt/decrypt stuff
|
||||||
|
//! \param log Optional logger instance. If ommitted, Console.WriteLine() will be used
|
||||||
|
//! \return true if the program executed successfully
|
||||||
|
//! \return false if gpg was not found or there was another error (see log output)
|
||||||
|
public static bool Gpg(string args, string gpgPassword, ILogger log = null)
|
||||||
{
|
{
|
||||||
string argsFull = $"--batch --yes --passphrase \"{gpgPassword}\" {args}";
|
string argsFull = $"--batch --yes --passphrase \"{gpgPassword}\" {args}";
|
||||||
return RunProcess("gpg", argsFull, ".", log);
|
return RunProcess("gpg", argsFull, ".", log);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool RunProcess(string cmd, string args, string workingDir = ".", core.ILogger log = null)
|
//! \short Run a background process (executable)
|
||||||
|
//! \brief Start a process given by command and arguments,
|
||||||
|
//! wait for it to finish and log the results.
|
||||||
|
//! \param cmd Command to execute
|
||||||
|
//! \param args Argument string (arguments are space-separated).
|
||||||
|
//! \param workingDir Directory to work in for the external program.
|
||||||
|
//! \param log Optional logger instance. If ommitted, Console.WriteLine() will be used
|
||||||
|
//! \return true if the program executed successfully
|
||||||
|
//! \return false if gpg was not found or there was another error (see log output)
|
||||||
|
//! \note Spaces within arguments that aren't catched by quotes or escaped with
|
||||||
|
//! backslash will lead to broken argument parsing by the external program
|
||||||
|
|
||||||
|
public static bool RunProcess(string cmd, string args, string workingDir = ".", ILogger log = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,6 +6,10 @@ using dezentrale.model;
|
||||||
|
|
||||||
namespace dezentrale.core
|
namespace dezentrale.core
|
||||||
{
|
{
|
||||||
|
//! \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 ImportProcess : ImportExportBase
|
public class ImportProcess : ImportExportBase
|
||||||
{
|
{
|
||||||
public ImportProcess()
|
public ImportProcess()
|
||||||
|
@ -13,6 +17,14 @@ namespace dezentrale.core
|
||||||
Caption = "Database import";
|
Caption = "Database import";
|
||||||
Steps = 5;
|
Steps = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \short Run import
|
||||||
|
//! \brief Requests mercurial/git commits from remote, applies them
|
||||||
|
//! locally, then decrypts using gpg and unzips the result to
|
||||||
|
//! the member directory.
|
||||||
|
//! \return true if the import was successful.
|
||||||
|
//! \note This may corrupt your local db if it fails!
|
||||||
|
//! \todo Recover from failure by restoring backups
|
||||||
protected override bool Run()
|
protected override bool Run()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using dezentrale.model;
|
||||||
|
using dezentrale.model.money;
|
||||||
|
|
||||||
|
namespace dezentrale.core
|
||||||
|
{
|
||||||
|
public class ProcessCsv
|
||||||
|
{
|
||||||
|
public static bool ProcessCSV(string fileName)
|
||||||
|
{
|
||||||
|
|
||||||
|
CsvFile csv = new CsvFile();
|
||||||
|
csv.FieldSeparator = ';';
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<BankTransfer> tmpList = new List<BankTransfer>();
|
||||||
|
|
||||||
|
csv.ReadFile(fileName);
|
||||||
|
List<string> headlineFields = null;
|
||||||
|
List<Member> changedMembers = new List<Member>();
|
||||||
|
foreach (List<string> l in csv.FileContents)
|
||||||
|
{
|
||||||
|
if (headlineFields == null)
|
||||||
|
{
|
||||||
|
//The first line is expected to have the headline field first, describing the contents
|
||||||
|
headlineFields = l;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BankTransfer bt = new BankTransfer(headlineFields, l);
|
||||||
|
|
||||||
|
|
||||||
|
MoneyTransfer duplicate = Program.MoneyTransfers.FindEqualEntry(bt);
|
||||||
|
if (duplicate != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Duplicate MoneyTransfer found");
|
||||||
|
Console.WriteLine($"ValutaDate: {bt.ValutaDate}, Amount = {bt.AmountString} {bt.Currency}, Reason = \"{bt.TransferReason.Replace('\r', '\\').Replace('\n', '\\')}\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Program.MoneyTransfers.AddEntry(bt);
|
||||||
|
tmpList.Add(bt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//try to assign transfers to the members
|
||||||
|
foreach (BankTransfer bt in tmpList)
|
||||||
|
{
|
||||||
|
if (bt.Amount < 0)
|
||||||
|
{
|
||||||
|
bt.TransferType = MoneyTransfer.eTransferType.RunningCost;
|
||||||
|
Console.WriteLine($"{bt.Id}: Amount = {bt.AmountString} --> RunningCost");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Member m in Program.members.Entries)
|
||||||
|
{
|
||||||
|
if (m.CheckBankTransfer(bt))
|
||||||
|
{
|
||||||
|
bt.TransferType = MoneyTransfer.eTransferType.MembershipPayment;
|
||||||
|
changedMembers.Add(m);
|
||||||
|
m.StartLogEvent("Incoming bank transfer", LogEvent.eEventType.MembershipPayment, "automatic");
|
||||||
|
m.ApplyMoneyTransfer(bt);
|
||||||
|
bt.AssignFixed = true;
|
||||||
|
break; //this is important. We don't want to assign this to multiple members.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Store bank transfer list
|
||||||
|
Console.WriteLine("ProcessCSV(): Storing money transfers...");
|
||||||
|
Program.MoneyTransfers.Entries.Sort();
|
||||||
|
if (!Program.MoneyTransfers.SaveToFile())
|
||||||
|
return false;
|
||||||
|
bool ret = true;
|
||||||
|
/*
|
||||||
|
//automaticly saved in ApplyMoneyTransfer()
|
||||||
|
foreach (Member m in changedMembers)
|
||||||
|
{
|
||||||
|
if (m.CurrentLog != null)
|
||||||
|
{
|
||||||
|
Console.Write($"ProcessCSV(): Storing {m.GetFileName()}... ");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!m.SaveToFile()) return false;
|
||||||
|
Console.WriteLine("OK");
|
||||||
|
} catch(Exception ex2)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"ERROR: {ex2.Message}");
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
//TBD: mail to schatzmeister if there are unassigned transfers
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error while processing csv file \"{fileName}\":");
|
||||||
|
Console.WriteLine(ex.Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
using System;
|
||||||
|
namespace dezentrale.core
|
||||||
|
{
|
||||||
|
public class Utils
|
||||||
|
{
|
||||||
|
public static string PadLeft(string input, uint totalLength)
|
||||||
|
{
|
||||||
|
if (input.Length < totalLength)
|
||||||
|
return new string(' ', (int)(totalLength - input.Length)) + input;
|
||||||
|
else
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
//TBD: This is an utility function and should reside in an extra class
|
||||||
|
public static string Int64FPToString(Int64 value)
|
||||||
|
{
|
||||||
|
//float fValue = ((float) value) / 100;
|
||||||
|
//return $"{fValue:0.00}";
|
||||||
|
Int64 intPart = value / 100;
|
||||||
|
Int64 decPart = (intPart < 0 ? -1 : 1) * (value % 100);
|
||||||
|
return $"{intPart},{decPart:D2}";
|
||||||
|
}
|
||||||
|
//TBD: This is an utility function and should reside in an extra class
|
||||||
|
public static Int64 StringToInt64FP(string value)
|
||||||
|
{
|
||||||
|
if (value == null) return 0;
|
||||||
|
if (!value.Contains(".") && !value.Contains(","))
|
||||||
|
{
|
||||||
|
return Convert.ToInt64(value) * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = value.IndexOf('.');
|
||||||
|
if (i < 0) i = value.IndexOf(',');
|
||||||
|
string intPart = value.Substring(0, i);
|
||||||
|
string decPart = value.Substring(i + 1);
|
||||||
|
|
||||||
|
Int64 ip64 = Convert.ToInt64(intPart);
|
||||||
|
return (ip64 * 100) + (ip64 > 0 ? Convert.ToInt64(decPart) : -Convert.ToInt64(decPart));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,6 @@
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Cronjob.cs" />
|
|
||||||
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2.cs" />
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2.cs" />
|
||||||
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Constants.cs" />
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Constants.cs" />
|
||||||
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Exception.cs" />
|
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Exception.cs" />
|
||||||
|
@ -190,9 +189,13 @@
|
||||||
<Compile Include="view\ConsoleLogger.cs" />
|
<Compile Include="view\ConsoleLogger.cs" />
|
||||||
<Compile Include="core\ExportProcess.cs" />
|
<Compile Include="core\ExportProcess.cs" />
|
||||||
<Compile Include="core\ImportExportBase.cs" />
|
<Compile Include="core\ImportExportBase.cs" />
|
||||||
|
<Compile Include="core\Cronjob.cs" />
|
||||||
|
<Compile Include="core\ProcessCsv.cs" />
|
||||||
|
<Compile Include="core\Utils.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="App.config" />
|
<None Include="App.config" />
|
||||||
|
<None Include="doxygen.conf" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -90,13 +90,23 @@ namespace dezentrale.model
|
||||||
|
|
||||||
//Build up Mail Message
|
//Build up Mail Message
|
||||||
MailMessage message = new MailMessage();
|
MailMessage message = new MailMessage();
|
||||||
message.From = new MailAddress(Program.config.Smtp.From);
|
MailAddress fromAddress = new MailAddress(Program.config.Smtp.From);
|
||||||
|
message.From = fromAddress;
|
||||||
message.To.Add(new MailAddress(src.To));
|
message.To.Add(new MailAddress(src.To));
|
||||||
if (!string.IsNullOrEmpty(Program.config.Smtp.CcTo))
|
if (!string.IsNullOrEmpty(Program.config.Smtp.CcTo))
|
||||||
message.CC.Add(new MailAddress(Program.config.Smtp.CcTo));
|
message.CC.Add(new MailAddress(Program.config.Smtp.CcTo));
|
||||||
|
|
||||||
message.SubjectEncoding = Encoding.UTF8;
|
message.SubjectEncoding = Encoding.UTF8;
|
||||||
message.BodyEncoding = Encoding.UTF8;
|
message.BodyEncoding = Encoding.UTF8;
|
||||||
|
|
||||||
|
//For spam reasons, we need to include an User-Agent and a valid message ID
|
||||||
|
|
||||||
|
//string mid;
|
||||||
|
//message.Headers.Add("User-Agent", $"dezentrale-members ver. {Program.VersionNumber} https://hg.macht-albern.de/dezentrale-members/");
|
||||||
|
//message.Headers.Add("User-Agent", @"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 Thunderbird/60.7.0 Lightning/6.2.7");
|
||||||
|
message.Headers.Add("Message-Id",
|
||||||
|
$"<{Guid.NewGuid()}@{fromAddress.Host}>");
|
||||||
|
//System.Windows.Forms.MessageBox.Show($"Message-Id: {mid}");
|
||||||
|
|
||||||
message.Subject = src.Subject;
|
message.Subject = src.Subject;
|
||||||
message.Body = src.Body;
|
message.Body = src.Body;
|
||||||
|
@ -472,6 +482,21 @@ namespace dezentrale.model
|
||||||
+ "--\n"
|
+ "--\n"
|
||||||
+ "Dies ist eine automatisch generierte E-Mail.\n",
|
+ "Dies ist eine automatisch generierte E-Mail.\n",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FormMail GenerateTestmail()
|
||||||
|
{
|
||||||
|
return new FormMail()
|
||||||
|
{
|
||||||
|
To = "{EMailName} <{EMail}>",
|
||||||
|
Subject = "dezentrale-members - Testmail",
|
||||||
|
Body = "Hallo {EMailName}!\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "Dies ist ein Test der Mail-Einstellungen von dezentrale-members.\n"
|
||||||
|
+ "\n"
|
||||||
|
+ "--\n"
|
||||||
|
+ "Dies ist eine automatisch generierte E-Mail.\n",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
151
model/Member.cs
151
model/Member.cs
|
@ -42,89 +42,92 @@ namespace dezentrale.model
|
||||||
Normal,
|
Normal,
|
||||||
}
|
}
|
||||||
|
|
||||||
private uint number = 0;
|
private uint number = 0;
|
||||||
private eRole role = eRole.Normal;
|
private eRole role = eRole.Normal;
|
||||||
private eType type = eType.Regulaer;
|
private eType type = eType.Regulaer;
|
||||||
private eStatus status = eStatus.Uninitialized;
|
private eStatus status = eStatus.Uninitialized;
|
||||||
private string remarks = "";
|
private string remarks = "";
|
||||||
private uint mvMiss = 0;
|
private uint mvMiss = 0;
|
||||||
private Int64 accountBalance = 0;
|
private Int64 accountBalance = 0;
|
||||||
private bool evaluateAccountInCharge = false;
|
private bool evaluateAccountInCharge = false;
|
||||||
private string bankAccountInCharge = ""; //CSV Import: this bank account is associated with the member
|
private string bankAccountInCharge = ""; //CSV Import: this bank account is associated with the member
|
||||||
private string bankTransferRegEx = "";
|
private string bankTransferRegEx = "";
|
||||||
private string nickname = "";
|
private string nickname = "";
|
||||||
private string firstName = "";
|
private string firstName = "";
|
||||||
private string lastName = "";
|
private string lastName = "";
|
||||||
private string street = "";
|
private string street = "";
|
||||||
private string houseNo = "";
|
private string houseNo = "";
|
||||||
private string zipcode = "";
|
private string zipcode = "";
|
||||||
private string city = "";
|
private string city = "";
|
||||||
private Country.eCountry countryCode = Country.eCountry.Germany;
|
private Country.eCountry countryCode = Country.eCountry.Germany;
|
||||||
private DateTime birthday;
|
private DateTime birthday;
|
||||||
private string email = "";
|
private string email = "";
|
||||||
private string pgpFingerprint = "";
|
private string emailName = ""; //Name to use via E-Mail
|
||||||
private bool mvInvitationByPost = false;
|
private string pgpFingerprint = "";
|
||||||
|
private bool mvInvitationByPost = false;
|
||||||
private DateTime spawnDate;
|
private DateTime spawnDate;
|
||||||
private UInt64 paymentAmount = Program.config.RegularPaymentAmount;
|
private UInt64 paymentAmount = Program.config.RegularPaymentAmount;
|
||||||
private ePaymentClass paymentClass = ePaymentClass.Normal;
|
private ePaymentClass paymentClass = ePaymentClass.Normal;
|
||||||
private bool paymentNotify = false;
|
private bool paymentNotify = false;
|
||||||
private DateTime memberFormDate;
|
private DateTime memberFormDate;
|
||||||
|
|
||||||
//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.
|
||||||
//Therefore, private variables are defined and the get/set methods are handled manually.
|
//Therefore, private variables are defined and the get/set methods are handled manually.
|
||||||
//A property change is then tracked using LogPropertyChange()
|
//A property change is then tracked using LogPropertyChange()
|
||||||
[XmlAttribute] public uint Number { get { return number; } set { LogPropertyChange("Number", number, value); number = value; } }
|
[XmlAttribute] public uint Number { get { return number; } set { LogPropertyChange("Number", number, value); number = value; } }
|
||||||
[XmlAttribute] public eType Type { get { return type; } set { LogPropertyChange("Type", type, value); type = value; } }
|
[XmlAttribute] public eType Type { get { return type; } set { LogPropertyChange("Type", type, value); type = value; } }
|
||||||
[XmlAttribute] public eStatus Status { get { return status; } set { LogPropertyChange("Status", status, value); status = value; } }
|
[XmlAttribute] public eStatus Status { get { return status; } set { LogPropertyChange("Status", status, value); status = value; } }
|
||||||
[XmlElement] public eRole Role { get { return role; } set { LogPropertyChange("Role", role, value); role = value; } }
|
[XmlElement] public eRole Role { get { return role; } set { LogPropertyChange("Role", role, value); role = value; } }
|
||||||
[XmlElement] public string Remarks { get { return remarks; } set { LogPropertyChange("Remarks", remarks, value); remarks = value; } }
|
[XmlElement] public string Remarks { get { return remarks; } set { LogPropertyChange("Remarks", remarks, value); remarks = value; } }
|
||||||
[XmlElement] public uint MvMissCounter { get { return mvMiss; } set { LogPropertyChange("MvMissCounter", mvMiss, value); mvMiss = value; } }
|
[XmlElement] public uint MvMissCounter { get { return mvMiss; } set { LogPropertyChange("MvMissCounter", mvMiss, value); mvMiss = value; } }
|
||||||
[XmlElement] public Int64 AccountBalance { get { return accountBalance;} set{LogPropertyChange("AccountBalance",accountBalance,value);accountBalance=value;}}
|
[XmlElement] public Int64 AccountBalance { get { return accountBalance; } set { LogPropertyChange("AccountBalance", accountBalance, value); accountBalance = value; } }
|
||||||
[XmlElement] public bool EvaluateAccountInCharge { get { return evaluateAccountInCharge; } set { LogPropertyChange("EvaluateAccountInCharge", evaluateAccountInCharge, value); evaluateAccountInCharge = value; } }
|
[XmlElement] public bool EvaluateAccountInCharge { get { return evaluateAccountInCharge; } set { LogPropertyChange("EvaluateAccountInCharge", evaluateAccountInCharge, value); evaluateAccountInCharge = value; } }
|
||||||
[XmlElement] public string BankAccountInCharge { get { return bankAccountInCharge; }set{LogPropertyChange("BankAccountInCharge", bankAccountInCharge, value); bankAccountInCharge = value;} }
|
[XmlElement] public string BankAccountInCharge { get { return bankAccountInCharge; } set { LogPropertyChange("BankAccountInCharge", bankAccountInCharge, value); bankAccountInCharge = value; } }
|
||||||
[XmlElement] public string BankTransferRegEx { get { return bankTransferRegEx; } set { LogPropertyChange("BankTransferRegEx", bankTransferRegEx, value); bankTransferRegEx = value; } }
|
[XmlElement] public string BankTransferRegEx { get { return bankTransferRegEx; } set { LogPropertyChange("BankTransferRegEx", bankTransferRegEx, value); bankTransferRegEx = value; } }
|
||||||
|
|
||||||
//personal data
|
//personal data
|
||||||
[XmlAttribute] public string Nickname { get { return nickname; } set { LogPropertyChange("NickName", nickname, value); nickname = value; } }
|
[XmlAttribute] public string Nickname { get { return nickname; } set { LogPropertyChange("NickName", nickname, value); nickname = value; } }
|
||||||
[XmlElement] public string FirstName { get { return firstName; } set { LogPropertyChange("FirstName", firstName, value); firstName = value; } }
|
[XmlElement] public string FirstName { get { return firstName; } set { LogPropertyChange("FirstName", firstName, value); firstName = value; } }
|
||||||
[XmlElement] public string LastName { get { return lastName; } set { LogPropertyChange("LastName", lastName, value); lastName = value; } }
|
[XmlElement] public string LastName { get { return lastName; } set { LogPropertyChange("LastName", lastName, value); lastName = value; } }
|
||||||
[XmlElement] public string Street { get { return street; } set { LogPropertyChange("Street", street, value); street = value; } }
|
[XmlElement] public string Street { get { return street; } set { LogPropertyChange("Street", street, value); street = value; } }
|
||||||
[XmlElement] public string HouseNumber { get { return houseNo; } set { LogPropertyChange("HouseNumber", houseNo, value); houseNo = value; } }
|
[XmlElement] public string HouseNumber { get { return houseNo; } set { LogPropertyChange("HouseNumber", houseNo, value); houseNo = value; } }
|
||||||
[XmlElement] public string Zipcode { get { return zipcode; } set { LogPropertyChange("Zipcode", zipcode, value); zipcode = value; } }
|
[XmlElement] public string Zipcode { get { return zipcode; } set { LogPropertyChange("Zipcode", zipcode, value); zipcode = value; } }
|
||||||
[XmlElement] public string City { get { return city; } set { LogPropertyChange("City", city, value); city = value; } }
|
[XmlElement] public string City { get { return city; } set { LogPropertyChange("City", city, value); city = value; } }
|
||||||
[XmlIgnore] public Country.eCountry CountryCode{ get {return countryCode;} set { LogPropertyChange("CountryCode", countryCode,value); countryCode = value; } }
|
[XmlIgnore] public Country.eCountry CountryCode { get { return countryCode; } set { LogPropertyChange("CountryCode", countryCode, value); countryCode = value; } }
|
||||||
[XmlElement("Country")] public uint CountryUInt32{ get { return (uint)CountryCode; } set { CountryCode = (Country.eCountry)value; }}
|
[XmlElement("Country")] public uint CountryUInt32 { get { return (uint)CountryCode; } set { CountryCode = (Country.eCountry)value; } }
|
||||||
[XmlElement] public DateTime Birthday { get { return birthday; } set { LogPropertyChange("Birthday", birthday, value); birthday = value; } }
|
[XmlElement] public DateTime Birthday { get { return birthday; } set { LogPropertyChange("Birthday", birthday, value); birthday = value; } }
|
||||||
[XmlElement] public string EMail { get { return email; } set { LogPropertyChange("EMail", email, value); email = value; } }
|
[XmlElement] public string EMail { get { return email; } set { LogPropertyChange("EMail", email, value); email = value; } }
|
||||||
[XmlElement] public string PgpFingerprint { get{return pgpFingerprint;}set{ LogPropertyChange("PgpFingerprint",pgpFingerprint,value); pgpFingerprint = value; } }
|
[XmlElement] public string EMailName { get { return emailName; } set { LogPropertyChange("EMailName", emailName, value); emailName = value; } }
|
||||||
|
[XmlElement] public string PgpFingerprint { get { return pgpFingerprint; } set { LogPropertyChange("PgpFingerprint", pgpFingerprint, value); pgpFingerprint = value; } }
|
||||||
|
|
||||||
//membership organizational data
|
//membership organizational data
|
||||||
[XmlElement] public bool MvInvitationByPost { get { return mvInvitationByPost; } set { LogPropertyChange("MvInvitationByPost", mvInvitationByPost, value); mvInvitationByPost = value; } }
|
[XmlElement] public bool MvInvitationByPost { get { return mvInvitationByPost; } set { LogPropertyChange("MvInvitationByPost", mvInvitationByPost, value); mvInvitationByPost = value; } }
|
||||||
[XmlElement] public DateTime SpawnDate { get { return spawnDate; } set { LogPropertyChange("SpawnDate", spawnDate, value); spawnDate = value; } }
|
[XmlElement] public DateTime SpawnDate { get { return spawnDate; } set { LogPropertyChange("SpawnDate", spawnDate, value); spawnDate = value; } }
|
||||||
[XmlElement] public UInt64 PaymentAmount { get { return paymentAmount;} set { LogPropertyChange("PaymentAmount", paymentAmount, value); paymentAmount = value; } }
|
[XmlElement] public UInt64 PaymentAmount { get { return paymentAmount; } set { LogPropertyChange("PaymentAmount", paymentAmount, value); paymentAmount = value; } }
|
||||||
[XmlElement] public ePaymentClass PaymentClass { get { return paymentClass; } set { LogPropertyChange("PaymentClass", paymentClass, value); paymentClass = value; } }
|
[XmlElement] public ePaymentClass PaymentClass { get { return paymentClass; } set { LogPropertyChange("PaymentClass", paymentClass, value); paymentClass = value; } }
|
||||||
[XmlElement] public bool PaymentNotify { get { return paymentNotify; } set { LogPropertyChange("PaymentNotify", paymentNotify, value); paymentNotify = value; } }
|
[XmlElement] public bool PaymentNotify { get { return paymentNotify; } set { LogPropertyChange("PaymentNotify", paymentNotify, value); paymentNotify = value; } }
|
||||||
[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 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("MoneyTransferId")] public List<string> MoneyTransfersIds { get; set; } = new List<string>();
|
[XmlElement("MoneyTransferId")] public List<string> MoneyTransfersIds { get; set; } = new List<string>();
|
||||||
|
|
||||||
[XmlElement("LogEvent")] public List<LogEvent> Log { get; set; } = new List<LogEvent>();
|
[XmlElement("LogEvent")] public List<LogEvent> Log { get; set; } = new List<LogEvent>();
|
||||||
|
|
||||||
[XmlIgnore] public string AccountBalanceString { get { return $"{((float)accountBalance / 100)}"; } }
|
[XmlIgnore] public string AccountBalanceString { get { return $"{((float)accountBalance / 100)}"; } }
|
||||||
[XmlIgnore] public string PaymentAmountString { get { return $"{((float)paymentAmount / 100)}"; } }
|
[XmlIgnore] public string PaymentAmountString { get { return $"{((float)paymentAmount / 100)}"; } }
|
||||||
[XmlIgnore] public string PaymentAmountCurrency { get { return Program.config.RegularPaymentCurrency; } }
|
[XmlIgnore] public string PaymentAmountCurrency { get { return Program.config.RegularPaymentCurrency; } }
|
||||||
|
|
||||||
[XmlIgnore] public string PaymentDueMonth
|
[XmlIgnore]
|
||||||
|
public string PaymentDueMonth
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
int monthsPaid = (int) Math.Floor( (float)AccountBalance / (float)PaymentAmount ) + 1;
|
int monthsPaid = (int)Math.Floor((float)AccountBalance / (float)PaymentAmount) + 1;
|
||||||
DateTime dueMonth = DateTime.Now.AddMonths(monthsPaid);
|
DateTime dueMonth = DateTime.Now.AddMonths(monthsPaid);
|
||||||
if (dueMonth < spawnDate)
|
if (dueMonth < spawnDate)
|
||||||
return spawnDate.ToString("yyyy-MM");
|
return spawnDate.ToString("yyyy-MM");
|
||||||
|
@ -149,7 +152,7 @@ namespace dezentrale.model
|
||||||
public int CompareTo(Member other)
|
public int CompareTo(Member other)
|
||||||
{
|
{
|
||||||
if (other == null) return 1;
|
if (other == null) return 1;
|
||||||
else return number.CompareTo(other.Number);
|
else return number.CompareTo(other.Number);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LogEvent StartLogEvent(string topic, LogEvent.eEventType type, string user = null)
|
public LogEvent StartLogEvent(string topic, LogEvent.eEventType type, string user = null)
|
||||||
|
@ -174,7 +177,7 @@ namespace dezentrale.model
|
||||||
|
|
||||||
string ov = oldValue.ToString();
|
string ov = oldValue.ToString();
|
||||||
string nv = newValue.ToString();
|
string nv = newValue.ToString();
|
||||||
if(!ov.Equals(nv))
|
if (!ov.Equals(nv))
|
||||||
CurrentLog.SubEvents.Add(new LogSubEvent()
|
CurrentLog.SubEvents.Add(new LogSubEvent()
|
||||||
{
|
{
|
||||||
Topic = $"{propertyName} changes from \"{oldValue.ToString()}\" to \"{newValue.ToString()}\"",
|
Topic = $"{propertyName} changes from \"{oldValue.ToString()}\" to \"{newValue.ToString()}\"",
|
||||||
|
@ -187,7 +190,7 @@ namespace dezentrale.model
|
||||||
}
|
}
|
||||||
public bool SaveToFile(bool finishLog = true)
|
public bool SaveToFile(bool finishLog = true)
|
||||||
{
|
{
|
||||||
if(finishLog) FinishLogEvent();
|
if (finishLog) FinishLogEvent();
|
||||||
string completePath = System.IO.Path.Combine(Program.config.DbDirectory, GetFileName());
|
string completePath = System.IO.Path.Combine(Program.config.DbDirectory, GetFileName());
|
||||||
return XmlData.SaveToFile(completePath, this);
|
return XmlData.SaveToFile(completePath, this);
|
||||||
}
|
}
|
||||||
|
@ -207,7 +210,8 @@ namespace dezentrale.model
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Regex.IsMatch(t.TransferReason, BankTransferRegEx);
|
return Regex.IsMatch(t.TransferReason, BankTransferRegEx);
|
||||||
} 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 false;
|
||||||
|
@ -218,7 +222,7 @@ namespace dezentrale.model
|
||||||
{
|
{
|
||||||
if (CurrentLog == null) StartLogEvent("PaymentAdjustBalance", LogEvent.eEventType.MembershipPayment, user);
|
if (CurrentLog == null) StartLogEvent("PaymentAdjustBalance", LogEvent.eEventType.MembershipPayment, user);
|
||||||
Int64 result = accountBalance + amount;
|
Int64 result = accountBalance + amount;
|
||||||
if(amount > 0) PaymentsTotal += (uint) amount;
|
if (amount > 0) PaymentsTotal += (uint)amount;
|
||||||
CurrentLog.SubEvents.Add(new LogSubEvent()
|
CurrentLog.SubEvents.Add(new LogSubEvent()
|
||||||
{
|
{
|
||||||
Topic = $"Amount = {((float)amount / 100)}",
|
Topic = $"Amount = {((float)amount / 100)}",
|
||||||
|
@ -226,14 +230,21 @@ namespace dezentrale.model
|
||||||
Details = $"Old balance = {((float)accountBalance / 100)}\nAdd Amount = {((float)amount / 100)}\nNew balance = {((float)result / 100)}",
|
Details = $"Old balance = {((float)accountBalance / 100)}\nAdd Amount = {((float)amount / 100)}\nNew balance = {((float)result / 100)}",
|
||||||
});
|
});
|
||||||
accountBalance = result;
|
accountBalance = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TestMail()
|
||||||
|
{
|
||||||
|
FormMail testMail = FormMail.GenerateTestmail();
|
||||||
|
testMail.Send(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void ApplyMoneyTransfer(MoneyTransfer t, string user = null)
|
public void ApplyMoneyTransfer(MoneyTransfer t, string user = null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Member.ApplyMoneyTransfer(): {t.Amount} to member {Number}");
|
Console.WriteLine($"Member.ApplyMoneyTransfer(): {t.Amount} to member {Number}");
|
||||||
if (CurrentLog == null) StartLogEvent($"MoneyTransfer ({t.AmountString} {t.Currency})", LogEvent.eEventType.MembershipPayment, user);
|
if (CurrentLog == null) StartLogEvent($"MoneyTransfer ({t.AmountString} {t.Currency})", LogEvent.eEventType.MembershipPayment, user);
|
||||||
|
|
||||||
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.MembershipDonation:
|
||||||
evt = LogEvent.eEventType.MembershipDonation; break;
|
evt = LogEvent.eEventType.MembershipDonation; break;
|
||||||
|
@ -293,8 +304,8 @@ namespace dezentrale.model
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try { SaveToFile(false); }
|
try { SaveToFile(false); }
|
||||||
catch(Exception ex) { Console.WriteLine($"Error while saving member: {ex.Message}"); }
|
catch (Exception ex) { Console.WriteLine($"Error while saving member: {ex.Message}"); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,10 +29,10 @@ namespace dezentrale.model
|
||||||
{
|
{
|
||||||
if (memberList && m.AccountBalance != 0)
|
if (memberList && m.AccountBalance != 0)
|
||||||
{
|
{
|
||||||
string formattedBalance = Program.PadLeft(Program.Int64FPToString(m.AccountBalance), 8);
|
string formattedBalance = core.Utils.PadLeft(core.Utils.Int64FPToString(m.AccountBalance), 8);
|
||||||
//while (formattedBalance.Length < 8) formattedBalance = " " + formattedBalance;
|
//while (formattedBalance.Length < 8) formattedBalance = " " + formattedBalance;
|
||||||
|
|
||||||
string formattedStatus = Program.PadLeft($"{m.Status}", 8);
|
string formattedStatus = core.Utils.PadLeft($"{m.Status}", 8);
|
||||||
//while (formattedStatus.Length < 8) formattedStatus = formattedStatus + " ";
|
//while (formattedStatus.Length < 8) formattedStatus = formattedStatus + " ";
|
||||||
mList += $"{m.Number:D3} | {formattedBalance} | {formattedStatus} | {m.Nickname}\n";
|
mList += $"{m.Number:D3} | {formattedBalance} | {formattedStatus} | {m.Nickname}\n";
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace dezentrale.model.money
|
||||||
[XmlElement] public string Currency { get; set; } = "EUR";
|
[XmlElement] public string Currency { get; set; } = "EUR";
|
||||||
[XmlElement] public string TransferReason { get; set; } = "";
|
[XmlElement] public string TransferReason { get; set; } = "";
|
||||||
|
|
||||||
[XmlIgnore] public string AmountString { get { return Program.Int64FPToString(Amount);/* $"{((float) Amount / 100)}";*/ } }
|
[XmlIgnore] public string AmountString { get { return core.Utils.Int64FPToString(Amount);/* $"{((float) Amount / 100)}";*/ } }
|
||||||
|
|
||||||
public override string ToString() { return Id; }
|
public override string ToString() { return Id; }
|
||||||
public MoneyTransfer()
|
public MoneyTransfer()
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace dezentrale.view
|
||||||
|
{
|
||||||
|
public class CurrencyBox : Panel
|
||||||
|
{
|
||||||
|
public Int64 Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Convert.ToInt64(intPart.Text) * 100 + Convert.ToInt64(decimals.Text);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
//todo: this might be wrong for negative values
|
||||||
|
intPart.Text = $"{(Int64) (value / 100)}";
|
||||||
|
decimals.Text = $"{(Int64) (value % 100)}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private TextBox intPart;
|
||||||
|
private TextBox decimals;
|
||||||
|
|
||||||
|
public override Color BackColor
|
||||||
|
{
|
||||||
|
get { return base.BackColor; }
|
||||||
|
set { base.BackColor = value; }
|
||||||
|
}
|
||||||
|
public CurrencyBox()
|
||||||
|
{
|
||||||
|
this.MinimumSize = new Size(100,16);
|
||||||
|
this.Controls.Add(intPart = new TextBox()
|
||||||
|
{
|
||||||
|
Width = this.Width - 30,
|
||||||
|
Anchor = AnchorStyles.Left | AnchorStyles.Right,
|
||||||
|
Text = "0"
|
||||||
|
});
|
||||||
|
intPart.TextChanged += IntPart_TextChanged;
|
||||||
|
|
||||||
|
this.Controls.Add(decimals = new TextBox()
|
||||||
|
{
|
||||||
|
Left = 75,
|
||||||
|
Width = 25,
|
||||||
|
Anchor = AnchorStyles.Right,
|
||||||
|
Text = "00"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IntPart_TextChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -151,7 +151,7 @@ namespace dezentrale.view
|
||||||
});
|
});
|
||||||
|
|
||||||
tbDbDirectory.Text = Program.config.DbDirectory;
|
tbDbDirectory.Text = Program.config.DbDirectory;
|
||||||
tbRegularPayment.Text = Program.Int64FPToString(Program.config.RegularPaymentAmount);
|
tbRegularPayment.Text = core.Utils.Int64FPToString(Program.config.RegularPaymentAmount);
|
||||||
if (Program.config.RegularPaymentCurrency != "EUR")
|
if (Program.config.RegularPaymentCurrency != "EUR")
|
||||||
{
|
{
|
||||||
cbRegularCurrency.Items.Add(Program.config.RegularPaymentCurrency);
|
cbRegularCurrency.Items.Add(Program.config.RegularPaymentCurrency);
|
||||||
|
@ -485,7 +485,7 @@ namespace dezentrale.view
|
||||||
public void FillAndSaveConfig()
|
public void FillAndSaveConfig()
|
||||||
{
|
{
|
||||||
Program.config.DbDirectory = tbDbDirectory.Text;
|
Program.config.DbDirectory = tbDbDirectory.Text;
|
||||||
Program.config.RegularPaymentAmount = (uint) Program.StringToInt64FP(tbRegularPayment.Text);
|
Program.config.RegularPaymentAmount = (uint) core.Utils.StringToInt64FP(tbRegularPayment.Text);
|
||||||
Program.config.RegularPaymentCurrency = cbRegularCurrency.Text;
|
Program.config.RegularPaymentCurrency = cbRegularCurrency.Text;
|
||||||
Program.config.LocalUser = tbLocalUser.Text;
|
Program.config.LocalUser = tbLocalUser.Text;
|
||||||
if (Program.config.KeylockCombination != tbKeylockCombination.Text)
|
if (Program.config.KeylockCombination != tbKeylockCombination.Text)
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace dezentrale.view
|
||||||
{
|
{
|
||||||
private Member member;
|
private Member member;
|
||||||
private bool newMember = false;
|
private bool newMember = false;
|
||||||
|
private bool eMailNameDefaults = true;
|
||||||
|
|
||||||
private TextBox tbFirstName;
|
private TextBox tbFirstName;
|
||||||
private TextBox tbLastName;
|
private TextBox tbLastName;
|
||||||
|
@ -24,6 +25,7 @@ namespace dezentrale.view
|
||||||
private TextBox tbZipcode;
|
private TextBox tbZipcode;
|
||||||
private TextBox tbCity;
|
private TextBox tbCity;
|
||||||
protected ComboBoxKV cbCountry;
|
protected ComboBoxKV cbCountry;
|
||||||
|
private TextBox tbEMailName;
|
||||||
private TextBox tbEMail;
|
private TextBox tbEMail;
|
||||||
private TextBox tbPgpFingerprint;
|
private TextBox tbPgpFingerprint;
|
||||||
private ComboBoxKV cbType;
|
private ComboBoxKV cbType;
|
||||||
|
@ -177,42 +179,98 @@ namespace dezentrale.view
|
||||||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
||||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||||
});
|
});
|
||||||
sc.Panel2.Controls.Add(new Label()
|
|
||||||
|
parent.Controls.Add(new Label()
|
||||||
{
|
{
|
||||||
Text = "EMail:",
|
Text = "EMail:",
|
||||||
Location = new Point(0, 3 * line + 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,
|
||||||
});
|
});
|
||||||
sc.Panel2.Controls.Add(tbEMail = new TextBox()
|
parent.Controls.Add(tbEMailName = new TextBox()
|
||||||
{
|
{
|
||||||
Location = new Point(118, 3 * line),
|
Location = new Point(lm + 118, 4 * line + tm),
|
||||||
Width = w_pan2 - 118 + 2,
|
Width = 183,
|
||||||
|
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||||
|
});
|
||||||
|
tbNickname.TextChanged += (sender, e) =>
|
||||||
|
{
|
||||||
|
if(eMailNameDefaults)
|
||||||
|
{
|
||||||
|
tbEMailName.Text = tbNickname.Text;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (tbEMailName.Text == tbNickname.Text)
|
||||||
|
{
|
||||||
|
eMailNameDefaults = true;
|
||||||
|
tbEMailName.ForeColor = System.Drawing.Color.DarkGray;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tbEMailName.TextChanged += (sender, e) =>
|
||||||
|
{
|
||||||
|
if(eMailNameDefaults)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (tbEMailName.Text != tbNickname.Text)
|
||||||
|
{
|
||||||
|
eMailNameDefaults = false;
|
||||||
|
tbEMailName.ForeColor = System.Drawing.Color.Black;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
if (tbEMailName.Text == tbNickname.Text)
|
||||||
|
{
|
||||||
|
if(!eMailNameDefaults)
|
||||||
|
{
|
||||||
|
eMailNameDefaults = true;
|
||||||
|
tbEMailName.ForeColor = System.Drawing.Color.DarkGray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
parent.Controls.Add(new Label()
|
||||||
|
{
|
||||||
|
Text = "<",
|
||||||
|
Location = new Point(lm + 118 + 185, 4 * line + tm + labelOffs),
|
||||||
|
Size = new Size(12, labelHeight),
|
||||||
|
TextAlign = ContentAlignment.BottomRight,
|
||||||
|
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||||
|
});
|
||||||
|
parent.Controls.Add(new Label()
|
||||||
|
{
|
||||||
|
Text = ">",
|
||||||
|
Location = new Point(w - 15, 4 * line + tm + labelOffs),
|
||||||
|
Size = new Size(12, labelHeight),
|
||||||
|
TextAlign = ContentAlignment.BottomRight,
|
||||||
|
Anchor = AnchorStyles.Top | AnchorStyles.Right,
|
||||||
|
});
|
||||||
|
parent.Controls.Add(tbEMail = new TextBox()
|
||||||
|
{
|
||||||
|
Location = new Point(lm + 118 + 205, 4 * line + tm),
|
||||||
|
Width = w - (lm + 118) - 222,
|
||||||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(new Label()
|
parent.Controls.Add(new Label()
|
||||||
{
|
{
|
||||||
Text = "PGP Fingerprint:",
|
Text = "PGP Fingerprint:",
|
||||||
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,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(tbPgpFingerprint = new TextBox()
|
parent.Controls.Add(tbPgpFingerprint = new TextBox()
|
||||||
{
|
{
|
||||||
Location = new Point(lm + 118, 4 * line + tm),
|
Location = new Point(lm + 118, 5 * line + tm),
|
||||||
Width = w - (lm + 118),
|
Width = w - (lm + 118),
|
||||||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(new Label()
|
parent.Controls.Add(new Label()
|
||||||
{
|
{
|
||||||
Text = "Membership type:",
|
Text = "Membership type:",
|
||||||
Location = new Point(lm, 5 * line + tm + labelOffs),
|
Location = new Point(lm, 6 * line + tm + labelOffs),
|
||||||
Size = new Size(110, labelHeight),
|
Size = new Size(110, labelHeight),
|
||||||
TextAlign = ContentAlignment.BottomRight,
|
TextAlign = ContentAlignment.BottomRight,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(cbType = new ComboBoxKV()
|
parent.Controls.Add(cbType = new ComboBoxKV()
|
||||||
{
|
{
|
||||||
Location = new Point(lm + 118, 5 * line + tm),
|
Location = new Point(lm + 118, 6 * line + tm),
|
||||||
Width = 200,
|
Width = 200,
|
||||||
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||||
|
@ -220,13 +278,13 @@ namespace dezentrale.view
|
||||||
parent.Controls.Add(new Label()
|
parent.Controls.Add(new Label()
|
||||||
{
|
{
|
||||||
Text = "Spawn Date:",
|
Text = "Spawn Date:",
|
||||||
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,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(dateSpawn = new DateTimePicker()
|
parent.Controls.Add(dateSpawn = new DateTimePicker()
|
||||||
{
|
{
|
||||||
Location = new Point(lm + 118, 6 * line + tm),
|
Location = new Point(lm + 118, 7 * line + tm),
|
||||||
Width = 100,
|
Width = 100,
|
||||||
Format = DateTimePickerFormat.Short,
|
Format = DateTimePickerFormat.Short,
|
||||||
//MinDate = new DateTime(1900, 01, 01, 00, 00, 00),
|
//MinDate = new DateTime(1900, 01, 01, 00, 00, 00),
|
||||||
|
@ -235,13 +293,13 @@ namespace dezentrale.view
|
||||||
parent.Controls.Add(new Label()
|
parent.Controls.Add(new Label()
|
||||||
{
|
{
|
||||||
Text = "Payment Amount:",
|
Text = "Payment Amount:",
|
||||||
Location = new Point(lm, 7 * 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,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(tbPaymentAmount = new TextBox()
|
parent.Controls.Add(tbPaymentAmount = new TextBox()
|
||||||
{
|
{
|
||||||
Location = new Point(lm + 118, 7 * line + tm),
|
Location = new Point(lm + 118, 8 * line + tm),
|
||||||
Width = 100,
|
Width = 100,
|
||||||
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||||
TextAlign = HorizontalAlignment.Right,
|
TextAlign = HorizontalAlignment.Right,
|
||||||
|
@ -249,20 +307,20 @@ namespace dezentrale.view
|
||||||
parent.Controls.Add(chkPaymentReduced = new CheckBox()
|
parent.Controls.Add(chkPaymentReduced = new CheckBox()
|
||||||
{
|
{
|
||||||
Text = "Reduced fee (document needed)",
|
Text = "Reduced fee (document needed)",
|
||||||
Location = new Point(lm + 118 + 100 + 15, 7 * line + tm),
|
Location = new Point(lm + 118 + 100 + 15, 8 * line + tm),
|
||||||
Width = w - (lm + 118 + 100 + 15),
|
Width = w - (lm + 118 + 100 + 15),
|
||||||
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(new Label()
|
parent.Controls.Add(new Label()
|
||||||
{
|
{
|
||||||
Text = "Signed Date:",
|
Text = "Signed Date:",
|
||||||
Location = new Point(lm, 8 * 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,
|
||||||
});
|
});
|
||||||
parent.Controls.Add(dateMemberForm = new DateTimePicker()
|
parent.Controls.Add(dateMemberForm = new DateTimePicker()
|
||||||
{
|
{
|
||||||
Location = new Point(lm + 118, 8 * line + tm),
|
Location = new Point(lm + 118, 9 * line + tm),
|
||||||
Width = 100,
|
Width = 100,
|
||||||
Format = DateTimePickerFormat.Short,
|
Format = DateTimePickerFormat.Short,
|
||||||
//MinDate = new DateTime(1900, 01, 01, 00, 00, 00),
|
//MinDate = new DateTime(1900, 01, 01, 00, 00, 00),
|
||||||
|
@ -512,7 +570,12 @@ namespace dezentrale.view
|
||||||
KeyValue kv = cbCountry.AddKV(display, c);
|
KeyValue kv = cbCountry.AddKV(display, c);
|
||||||
if (c.Code == member.CountryCode) cbCountry.SelectedItem = kv;
|
if (c.Code == member.CountryCode) cbCountry.SelectedItem = kv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eMailNameDefaults = m.EMailName == m.Nickname;
|
||||||
tbEMail.Text = member.EMail;
|
tbEMail.Text = member.EMail;
|
||||||
|
tbEMailName.Text = m.EMailName;
|
||||||
|
if (eMailNameDefaults)
|
||||||
|
tbEMailName.ForeColor = System.Drawing.Color.DarkGray;
|
||||||
tbPgpFingerprint.Text = member.PgpFingerprint;
|
tbPgpFingerprint.Text = member.PgpFingerprint;
|
||||||
|
|
||||||
foreach (Member.eType type in Enum.GetValues(typeof(Member.eType)))
|
foreach (Member.eType type in Enum.GetValues(typeof(Member.eType)))
|
||||||
|
@ -522,7 +585,7 @@ namespace dezentrale.view
|
||||||
}
|
}
|
||||||
try { dateSpawn.Value = member.SpawnDate; }
|
try { dateSpawn.Value = member.SpawnDate; }
|
||||||
catch (Exception) { if (!newMember) { MessageBox.Show("Invalid SpawnDate: " + member.SpawnDate + "!"); } }
|
catch (Exception) { if (!newMember) { MessageBox.Show("Invalid SpawnDate: " + member.SpawnDate + "!"); } }
|
||||||
tbPaymentAmount.Text = Program.Int64FPToString((Int64) member.PaymentAmount);
|
tbPaymentAmount.Text = core.Utils.Int64FPToString((Int64) member.PaymentAmount);
|
||||||
chkPaymentReduced.Checked = member.PaymentClass == Member.ePaymentClass.Reduced;
|
chkPaymentReduced.Checked = member.PaymentClass == Member.ePaymentClass.Reduced;
|
||||||
try { dateMemberForm.Value = member.MemberFormDate; }
|
try { dateMemberForm.Value = member.MemberFormDate; }
|
||||||
catch (Exception) { if (!newMember) { MessageBox.Show("Invalid MemberFormDate: " + member.MemberFormDate + "!"); } }
|
catch (Exception) { if (!newMember) { MessageBox.Show("Invalid MemberFormDate: " + member.MemberFormDate + "!"); } }
|
||||||
|
@ -539,7 +602,7 @@ namespace dezentrale.view
|
||||||
}
|
}
|
||||||
|
|
||||||
tbMvMiss.Text = $"{member.MvMissCounter}";
|
tbMvMiss.Text = $"{member.MvMissCounter}";
|
||||||
tbAccountBalance.Text = Program.Int64FPToString(member.AccountBalance);
|
tbAccountBalance.Text = core.Utils.Int64FPToString(member.AccountBalance);
|
||||||
cbEvaluateAccountInCharge.Checked = member.EvaluateAccountInCharge;
|
cbEvaluateAccountInCharge.Checked = member.EvaluateAccountInCharge;
|
||||||
tbBankAccountInCharge.ReadOnly = !cbEvaluateAccountInCharge.Checked;
|
tbBankAccountInCharge.ReadOnly = !cbEvaluateAccountInCharge.Checked;
|
||||||
tbBankAccountInCharge.Text = member.BankAccountInCharge;
|
tbBankAccountInCharge.Text = member.BankAccountInCharge;
|
||||||
|
@ -575,6 +638,7 @@ namespace dezentrale.view
|
||||||
member.CountryCode = c.Code;
|
member.CountryCode = c.Code;
|
||||||
}
|
}
|
||||||
member.EMail = tbEMail.Text;
|
member.EMail = tbEMail.Text;
|
||||||
|
member.EMailName = tbEMailName.Text;
|
||||||
member.PgpFingerprint = tbPgpFingerprint.Text;
|
member.PgpFingerprint = tbPgpFingerprint.Text;
|
||||||
if(cbType.SelectedItem != null)
|
if(cbType.SelectedItem != null)
|
||||||
{
|
{
|
||||||
|
@ -593,7 +657,7 @@ namespace dezentrale.view
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
member.PaymentAmount = (UInt64) Program.StringToInt64FP(tbPaymentAmount.Text);
|
member.PaymentAmount = (UInt64) core.Utils.StringToInt64FP(tbPaymentAmount.Text);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -618,7 +682,7 @@ namespace dezentrale.view
|
||||||
member.MvMissCounter = Convert.ToUInt32(tbMvMiss.Text);
|
member.MvMissCounter = Convert.ToUInt32(tbMvMiss.Text);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
member.AccountBalance = Program.StringToInt64FP(tbAccountBalance.Text);//Convert.ToInt32(tbAccountBalance.Text);
|
member.AccountBalance = core.Utils.StringToInt64FP(tbAccountBalance.Text);//Convert.ToInt32(tbAccountBalance.Text);
|
||||||
} catch(Exception ex)
|
} catch(Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Cannot convert Account balance to fixed point value:\n{ex.Message}");
|
Console.WriteLine($"Cannot convert Account balance to fixed point value:\n{ex.Message}");
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace dezentrale.view
|
||||||
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),
|
new MenuItem("&Export database", mnuMain_File_Export),
|
||||||
new MenuItem("&Import database", mnuMain_File_Import),
|
new MenuItem("&Import database", mnuMain_File_Import),
|
||||||
new MenuItem("-"),
|
new MenuItem("-"),
|
||||||
new MenuItem("&Quit", mnuMain_File_Quit),
|
new MenuItem("&Quit", mnuMain_File_Quit),
|
||||||
} },
|
} },
|
||||||
|
@ -75,7 +75,8 @@ namespace dezentrale.view
|
||||||
lstMembers.AddMenuItem("Cronjob selected member", lstMembers_CronjobSelected);
|
lstMembers.AddMenuItem("Cronjob selected member", lstMembers_CronjobSelected);
|
||||||
//lstMembers.AddMenuItem("Cronjob checked ones", lstMembers_CronjobChecked);
|
//lstMembers.AddMenuItem("Cronjob checked ones", lstMembers_CronjobChecked);
|
||||||
lstMembers.AddMenuItem("Cronjob all", lstMembers_CronjobAll);
|
lstMembers.AddMenuItem("Cronjob all", lstMembers_CronjobAll);
|
||||||
//lstMembers.AddMenuItem("-", null);
|
lstMembers.AddMenuItem("-", null);
|
||||||
|
lstMembers.AddMenuItem("Send Testmail to member", lstMembers_TestMail);
|
||||||
//TBD: "Selected users missed an MV"
|
//TBD: "Selected users missed an MV"
|
||||||
|
|
||||||
//lstMembers.AddMenuItem("Main Settings", null);
|
//lstMembers.AddMenuItem("Main Settings", null);
|
||||||
|
@ -158,7 +159,7 @@ namespace dezentrale.view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mnuMain_File_Quit(object sender, EventArgs e)
|
private void mnuMain_File_Quit(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
this.Close();
|
this.Close();
|
||||||
|
@ -234,7 +235,7 @@ namespace dezentrale.view
|
||||||
DialogResult dr = ofd.ShowDialog();
|
DialogResult dr = ofd.ShowDialog();
|
||||||
//ofd.FilterIndex
|
//ofd.FilterIndex
|
||||||
if (dr == DialogResult.OK)
|
if (dr == DialogResult.OK)
|
||||||
Program.ProcessCSV(ofd.FileName);
|
ProcessCsv.ProcessCSV(ofd.FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mnuMain_Help_About(object sender, EventArgs e)
|
private void mnuMain_Help_About(object sender, EventArgs e)
|
||||||
|
@ -270,6 +271,12 @@ namespace dezentrale.view
|
||||||
lstMembers.ResumeLayout(false);
|
lstMembers.ResumeLayout(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void lstMembers_TestMail(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Member m = lstMembers.GetFirstSelectedItem();
|
||||||
|
m?.TestMail();
|
||||||
|
}
|
||||||
|
|
||||||
private void lstMembers_Edit(object sender, EventArgs e)
|
private void lstMembers_Edit(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Member m = lstMembers.GetFirstSelectedItem();
|
Member m = lstMembers.GetFirstSelectedItem();
|
||||||
|
|
|
@ -493,7 +493,7 @@ namespace dezentrale.view
|
||||||
mt.TransferType = (MoneyTransfer.eTransferType)((KeyValue)cbType.SelectedItem).Value;
|
mt.TransferType = (MoneyTransfer.eTransferType)((KeyValue)cbType.SelectedItem).Value;
|
||||||
}
|
}
|
||||||
mt.ValutaDate = valutaDate.Value;
|
mt.ValutaDate = valutaDate.Value;
|
||||||
mt.Amount = Program.StringToInt64FP(tbAmount.Text);
|
mt.Amount = core.Utils.StringToInt64FP(tbAmount.Text);
|
||||||
mt.Currency = cbCurrency.Text;
|
mt.Currency = cbCurrency.Text;
|
||||||
mt.TransferReason = tbTransferReason.Text;
|
mt.TransferReason = tbTransferReason.Text;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue