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
|
||||
- frmMain: Menu option to miss an MV for selected/checked users
|
||||
- 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
|
||||
- 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.
|
||||
- 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
|
||||
- Configuration window: MoneyTransferRegEx
|
||||
- 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)
|
||||
- CronjobConfig
|
||||
- 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
|
||||
----
|
||||
- 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
|
||||
- Bug: Database import fails on some PCs (seems that writing xml files during decompression is problematic)
|
||||
*/
|
||||
namespace dezentrale
|
||||
{
|
||||
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 AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
|
@ -233,142 +243,12 @@ namespace dezentrale
|
|||
Console.WriteLine("You must provide an input file by using --csvinput=<file>");
|
||||
return 1;
|
||||
}
|
||||
if (!ProcessCSV(csvInput))
|
||||
if (!ProcessCsv.ProcessCSV(csvInput))
|
||||
return 1;
|
||||
break;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
//! \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 IProcessController LogTarget { get; set; } = null;
|
||||
public bool CancelButton { get; protected set; } = false;
|
||||
//! Target for logging operations performed by the inheriting process classes
|
||||
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";
|
||||
|
||||
//! Sub-Steps of the process, to allow the user a better estimation of the duration
|
||||
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()
|
||||
{
|
||||
if (thread != null) return null;
|
||||
if (LogTarget == null) return null;
|
||||
thread = new Thread(RunAbstract) { IsBackground = true };
|
||||
thread.Start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
//! \internal Helper function for enforcing the ActionCompleted call
|
||||
private void RunAbstract()
|
||||
{
|
||||
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 HelloWorldProcess()
|
||||
|
|
|
@ -4,10 +4,87 @@ using System.Linq;
|
|||
using System.Text;
|
||||
|
||||
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
|
||||
{
|
||||
//! \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)
|
||||
{
|
||||
if (m.Status == Member.eStatus.Uninitialized && m.EMail.Length > 0)
|
||||
|
@ -43,6 +120,9 @@ namespace dezentrale
|
|||
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)
|
||||
{
|
||||
if(m.Status != Member.eStatus.Greeted)
|
||||
|
@ -103,16 +183,19 @@ namespace dezentrale
|
|||
m.Status = Member.eStatus.Active;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
//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 thisYear = (uint) DateTime.Now.Year;
|
||||
|
@ -131,10 +214,13 @@ namespace dezentrale
|
|||
}
|
||||
}
|
||||
|
||||
//During program execution, we want to keep a constant timestamp for
|
||||
//cronjob activity, to be able to compare for multiple runs (e.g. member list)
|
||||
//and not to store it every time to disk
|
||||
//! \brief During program execution, we want to keep a constant
|
||||
//! timestamp for cronjob activity, to be able to compare for
|
||||
//! multiple runs (e.g. member list) and not to store it every
|
||||
//! time to disk
|
||||
private static DateTime ProgramStartTime { get; set; } = DateTime.Now;
|
||||
|
||||
//! \short Necessary actions prior to BalanceDegrade.
|
||||
private static void CheckBalance_PreDegrade(Member m, Member sm = null)
|
||||
{
|
||||
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
|
||||
|
@ -191,66 +277,5 @@ namespace dezentrale
|
|||
if (m.CurrentLog != null)
|
||||
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
|
||||
{
|
||||
/** \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 ExportProcess()
|
||||
|
@ -16,6 +18,13 @@ namespace dezentrale.core
|
|||
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()
|
||||
{
|
||||
LogTarget.StepStarted(0, "Preparing export");
|
||||
|
|
|
@ -4,10 +4,20 @@ using dezentrale.model;
|
|||
|
||||
namespace dezentrale.core
|
||||
{
|
||||
//! \short Logging interface for files, gui and processes.
|
||||
public interface ILogger
|
||||
{
|
||||
//! \short Clear a log window, if available
|
||||
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);
|
||||
|
||||
//! \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 = "");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
namespace dezentrale.core
|
||||
{
|
||||
//! \short Interface for controlling processes and for getting more detailled information from them
|
||||
public interface IProcessController : ILogger
|
||||
{
|
||||
void ActionCompleted(bool success);
|
||||
|
|
|
@ -4,20 +4,42 @@ using System.Threading;
|
|||
|
||||
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 model.MemberImportExport ImportExportSettings { get; set; } = null;
|
||||
public string MemberDir { get; set; } = null; //! Directory that holds the application settings
|
||||
public string InputDir { get; set; } = null; //! Import working directory
|
||||
public string OutputDir { get; set; } = null; //! Export working directory
|
||||
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 InputDir { get; set; } = null; //!< Import 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}";
|
||||
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
|
||||
{
|
||||
|
|
|
@ -6,6 +6,10 @@ using dezentrale.model;
|
|||
|
||||
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 ImportProcess()
|
||||
|
@ -13,6 +17,14 @@ namespace dezentrale.core
|
|||
Caption = "Database import";
|
||||
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()
|
||||
{
|
||||
|
||||
|
|
|
@ -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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Cronjob.cs" />
|
||||
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2.cs" />
|
||||
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Constants.cs" />
|
||||
<Compile Include="ICSharpCode.SharpZipLib\BZip2\BZip2Exception.cs" />
|
||||
|
@ -190,9 +189,13 @@
|
|||
<Compile Include="view\ConsoleLogger.cs" />
|
||||
<Compile Include="core\ExportProcess.cs" />
|
||||
<Compile Include="core\ImportExportBase.cs" />
|
||||
<Compile Include="core\Cronjob.cs" />
|
||||
<Compile Include="core\ProcessCsv.cs" />
|
||||
<Compile Include="core\Utils.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="doxygen.conf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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
|
||||
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));
|
||||
if (!string.IsNullOrEmpty(Program.config.Smtp.CcTo))
|
||||
message.CC.Add(new MailAddress(Program.config.Smtp.CcTo));
|
||||
|
||||
message.SubjectEncoding = 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.Body = src.Body;
|
||||
|
@ -472,6 +482,21 @@ namespace dezentrale.model
|
|||
+ "--\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,
|
||||
}
|
||||
|
||||
private uint number = 0;
|
||||
private eRole role = eRole.Normal;
|
||||
private eType type = eType.Regulaer;
|
||||
private eStatus status = eStatus.Uninitialized;
|
||||
private string remarks = "";
|
||||
private uint mvMiss = 0;
|
||||
private Int64 accountBalance = 0;
|
||||
private bool evaluateAccountInCharge = false;
|
||||
private string bankAccountInCharge = ""; //CSV Import: this bank account is associated with the member
|
||||
private string bankTransferRegEx = "";
|
||||
private string nickname = "";
|
||||
private string firstName = "";
|
||||
private string lastName = "";
|
||||
private string street = "";
|
||||
private string houseNo = "";
|
||||
private string zipcode = "";
|
||||
private string city = "";
|
||||
private uint number = 0;
|
||||
private eRole role = eRole.Normal;
|
||||
private eType type = eType.Regulaer;
|
||||
private eStatus status = eStatus.Uninitialized;
|
||||
private string remarks = "";
|
||||
private uint mvMiss = 0;
|
||||
private Int64 accountBalance = 0;
|
||||
private bool evaluateAccountInCharge = false;
|
||||
private string bankAccountInCharge = ""; //CSV Import: this bank account is associated with the member
|
||||
private string bankTransferRegEx = "";
|
||||
private string nickname = "";
|
||||
private string firstName = "";
|
||||
private string lastName = "";
|
||||
private string street = "";
|
||||
private string houseNo = "";
|
||||
private string zipcode = "";
|
||||
private string city = "";
|
||||
private Country.eCountry countryCode = Country.eCountry.Germany;
|
||||
private DateTime birthday;
|
||||
private string email = "";
|
||||
private string pgpFingerprint = "";
|
||||
private bool mvInvitationByPost = false;
|
||||
private string email = "";
|
||||
private string emailName = ""; //Name to use via E-Mail
|
||||
private string pgpFingerprint = "";
|
||||
private bool mvInvitationByPost = false;
|
||||
private DateTime spawnDate;
|
||||
private UInt64 paymentAmount = Program.config.RegularPaymentAmount;
|
||||
private UInt64 paymentAmount = Program.config.RegularPaymentAmount;
|
||||
private ePaymentClass paymentClass = ePaymentClass.Normal;
|
||||
private bool paymentNotify = false;
|
||||
private bool paymentNotify = false;
|
||||
private DateTime memberFormDate;
|
||||
|
||||
//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.
|
||||
//Therefore, private variables are defined and the get/set methods are handled manually.
|
||||
//A property change is then tracked using LogPropertyChange()
|
||||
[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 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 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 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 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; } }
|
||||
[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 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 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 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 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; } }
|
||||
|
||||
//personal data
|
||||
[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 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 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 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; } }
|
||||
[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 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; } }
|
||||
[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 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 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 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; } }
|
||||
[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 string EMail { get { return email; } set { LogPropertyChange("EMail", email, value); email = 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
|
||||
[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 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 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 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 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 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; } }
|
||||
|
||||
//internal management data
|
||||
[XmlElement] public DateTime GreetedDate { get; set; }
|
||||
[XmlElement] public DateTime LastPaymentProcessed { get; set; } = DateTime.Now;
|
||||
[XmlElement] public uint PaymentsTotal { get; set; }
|
||||
[XmlElement] public DateTime LastBalanceDegrade { get; set; } = DateTime.Now;
|
||||
[XmlElement("MoneyTransferId")] public List<string> MoneyTransfersIds { get; set; } = new List<string>();
|
||||
[XmlElement] public DateTime GreetedDate { get; set; }
|
||||
[XmlElement] public DateTime LastPaymentProcessed { get; set; } = DateTime.Now;
|
||||
[XmlElement] public uint PaymentsTotal { get; set; }
|
||||
[XmlElement] public DateTime LastBalanceDegrade { get; set; } = DateTime.Now;
|
||||
[XmlElement("MoneyTransferId")] public List<string> MoneyTransfersIds { get; set; } = new List<string>();
|
||||
|
||||
[XmlElement("LogEvent")] public List<LogEvent> Log { get; set; } = new List<LogEvent>();
|
||||
|
||||
[XmlIgnore] public string AccountBalanceString { get { return $"{((float)accountBalance / 100)}"; } }
|
||||
[XmlIgnore] public string PaymentAmountString { get { return $"{((float)paymentAmount / 100)}"; } }
|
||||
[XmlElement("LogEvent")] public List<LogEvent> Log { get; set; } = new List<LogEvent>();
|
||||
|
||||
[XmlIgnore] public string AccountBalanceString { get { return $"{((float)accountBalance / 100)}"; } }
|
||||
[XmlIgnore] public string PaymentAmountString { get { return $"{((float)paymentAmount / 100)}"; } }
|
||||
[XmlIgnore] public string PaymentAmountCurrency { get { return Program.config.RegularPaymentCurrency; } }
|
||||
|
||||
[XmlIgnore] public string PaymentDueMonth
|
||||
[XmlIgnore]
|
||||
public string PaymentDueMonth
|
||||
{
|
||||
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);
|
||||
if (dueMonth < spawnDate)
|
||||
return spawnDate.ToString("yyyy-MM");
|
||||
|
@ -149,7 +152,7 @@ namespace dezentrale.model
|
|||
public int CompareTo(Member other)
|
||||
{
|
||||
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)
|
||||
|
@ -174,7 +177,7 @@ namespace dezentrale.model
|
|||
|
||||
string ov = oldValue.ToString();
|
||||
string nv = newValue.ToString();
|
||||
if(!ov.Equals(nv))
|
||||
if (!ov.Equals(nv))
|
||||
CurrentLog.SubEvents.Add(new LogSubEvent()
|
||||
{
|
||||
Topic = $"{propertyName} changes from \"{oldValue.ToString()}\" to \"{newValue.ToString()}\"",
|
||||
|
@ -187,7 +190,7 @@ namespace dezentrale.model
|
|||
}
|
||||
public bool SaveToFile(bool finishLog = true)
|
||||
{
|
||||
if(finishLog) FinishLogEvent();
|
||||
if (finishLog) FinishLogEvent();
|
||||
string completePath = System.IO.Path.Combine(Program.config.DbDirectory, GetFileName());
|
||||
return XmlData.SaveToFile(completePath, this);
|
||||
}
|
||||
|
@ -207,7 +210,8 @@ namespace dezentrale.model
|
|||
try
|
||||
{
|
||||
return Regex.IsMatch(t.TransferReason, BankTransferRegEx);
|
||||
} catch(Exception ex)
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Member {this.Number:D3} invalid RegEx: {ex.Message}");
|
||||
return false;
|
||||
|
@ -218,7 +222,7 @@ namespace dezentrale.model
|
|||
{
|
||||
if (CurrentLog == null) StartLogEvent("PaymentAdjustBalance", LogEvent.eEventType.MembershipPayment, user);
|
||||
Int64 result = accountBalance + amount;
|
||||
if(amount > 0) PaymentsTotal += (uint) amount;
|
||||
if (amount > 0) PaymentsTotal += (uint)amount;
|
||||
CurrentLog.SubEvents.Add(new LogSubEvent()
|
||||
{
|
||||
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)}",
|
||||
});
|
||||
accountBalance = result;
|
||||
}
|
||||
}
|
||||
|
||||
public void TestMail()
|
||||
{
|
||||
FormMail testMail = FormMail.GenerateTestmail();
|
||||
testMail.Send(this);
|
||||
}
|
||||
|
||||
public void ApplyMoneyTransfer(MoneyTransfer t, string user = null)
|
||||
{
|
||||
Console.WriteLine($"Member.ApplyMoneyTransfer(): {t.Amount} to member {Number}");
|
||||
if (CurrentLog == null) StartLogEvent($"MoneyTransfer ({t.AmountString} {t.Currency})", LogEvent.eEventType.MembershipPayment, user);
|
||||
|
||||
LogEvent.eEventType evt = LogEvent.eEventType.Generic;
|
||||
switch(t.TransferType)
|
||||
switch (t.TransferType)
|
||||
{
|
||||
case MoneyTransfer.eTransferType.MembershipDonation:
|
||||
evt = LogEvent.eEventType.MembershipDonation; break;
|
||||
|
@ -293,8 +304,8 @@ namespace dezentrale.model
|
|||
}
|
||||
}
|
||||
|
||||
try { SaveToFile(false); }
|
||||
catch(Exception ex) { Console.WriteLine($"Error while saving member: {ex.Message}"); }
|
||||
try { SaveToFile(false); }
|
||||
catch (Exception ex) { Console.WriteLine($"Error while saving member: {ex.Message}"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,10 +29,10 @@ namespace dezentrale.model
|
|||
{
|
||||
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;
|
||||
|
||||
string formattedStatus = Program.PadLeft($"{m.Status}", 8);
|
||||
string formattedStatus = core.Utils.PadLeft($"{m.Status}", 8);
|
||||
//while (formattedStatus.Length < 8) formattedStatus = formattedStatus + " ";
|
||||
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 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 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;
|
||||
tbRegularPayment.Text = Program.Int64FPToString(Program.config.RegularPaymentAmount);
|
||||
tbRegularPayment.Text = core.Utils.Int64FPToString(Program.config.RegularPaymentAmount);
|
||||
if (Program.config.RegularPaymentCurrency != "EUR")
|
||||
{
|
||||
cbRegularCurrency.Items.Add(Program.config.RegularPaymentCurrency);
|
||||
|
@ -485,7 +485,7 @@ namespace dezentrale.view
|
|||
public void FillAndSaveConfig()
|
||||
{
|
||||
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.LocalUser = tbLocalUser.Text;
|
||||
if (Program.config.KeylockCombination != tbKeylockCombination.Text)
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace dezentrale.view
|
|||
{
|
||||
private Member member;
|
||||
private bool newMember = false;
|
||||
private bool eMailNameDefaults = true;
|
||||
|
||||
private TextBox tbFirstName;
|
||||
private TextBox tbLastName;
|
||||
|
@ -24,6 +25,7 @@ namespace dezentrale.view
|
|||
private TextBox tbZipcode;
|
||||
private TextBox tbCity;
|
||||
protected ComboBoxKV cbCountry;
|
||||
private TextBox tbEMailName;
|
||||
private TextBox tbEMail;
|
||||
private TextBox tbPgpFingerprint;
|
||||
private ComboBoxKV cbType;
|
||||
|
@ -177,42 +179,98 @@ namespace dezentrale.view
|
|||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
});
|
||||
sc.Panel2.Controls.Add(new Label()
|
||||
|
||||
parent.Controls.Add(new Label()
|
||||
{
|
||||
Text = "EMail:",
|
||||
Location = new Point(0, 3 * line + labelOffs),
|
||||
Location = new Point(lm, 4 * line + tm + labelOffs),
|
||||
Size = new Size(110, labelHeight),
|
||||
TextAlign = ContentAlignment.BottomRight,
|
||||
});
|
||||
sc.Panel2.Controls.Add(tbEMail = new TextBox()
|
||||
parent.Controls.Add(tbEMailName = new TextBox()
|
||||
{
|
||||
Location = new Point(118, 3 * line),
|
||||
Width = w_pan2 - 118 + 2,
|
||||
Location = new Point(lm + 118, 4 * line + tm),
|
||||
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,
|
||||
});
|
||||
parent.Controls.Add(new Label()
|
||||
{
|
||||
Text = "PGP Fingerprint:",
|
||||
Location = new Point(lm, 4 * line + tm + labelOffs),
|
||||
Location = new Point(lm, 5 * line + tm + labelOffs),
|
||||
Size = new Size(110, labelHeight),
|
||||
TextAlign = ContentAlignment.BottomRight,
|
||||
});
|
||||
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),
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
|
||||
});
|
||||
parent.Controls.Add(new Label()
|
||||
{
|
||||
Text = "Membership type:",
|
||||
Location = new Point(lm, 5 * line + tm + labelOffs),
|
||||
Location = new Point(lm, 6 * line + tm + labelOffs),
|
||||
Size = new Size(110, labelHeight),
|
||||
TextAlign = ContentAlignment.BottomRight,
|
||||
});
|
||||
parent.Controls.Add(cbType = new ComboBoxKV()
|
||||
{
|
||||
Location = new Point(lm + 118, 5 * line + tm),
|
||||
Location = new Point(lm + 118, 6 * line + tm),
|
||||
Width = 200,
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList,
|
||||
|
@ -220,13 +278,13 @@ namespace dezentrale.view
|
|||
parent.Controls.Add(new Label()
|
||||
{
|
||||
Text = "Spawn Date:",
|
||||
Location = new Point(lm, 6 * line + tm + labelOffs),
|
||||
Location = new Point(lm, 7 * line + tm + labelOffs),
|
||||
Size = new Size(110, labelHeight),
|
||||
TextAlign = ContentAlignment.BottomRight,
|
||||
});
|
||||
parent.Controls.Add(dateSpawn = new DateTimePicker()
|
||||
{
|
||||
Location = new Point(lm + 118, 6 * line + tm),
|
||||
Location = new Point(lm + 118, 7 * line + tm),
|
||||
Width = 100,
|
||||
Format = DateTimePickerFormat.Short,
|
||||
//MinDate = new DateTime(1900, 01, 01, 00, 00, 00),
|
||||
|
@ -235,13 +293,13 @@ namespace dezentrale.view
|
|||
parent.Controls.Add(new Label()
|
||||
{
|
||||
Text = "Payment Amount:",
|
||||
Location = new Point(lm, 7 * line + tm + labelOffs),
|
||||
Location = new Point(lm, 8 * line + tm + labelOffs),
|
||||
Size = new Size(110, labelHeight),
|
||||
TextAlign = ContentAlignment.BottomRight,
|
||||
});
|
||||
parent.Controls.Add(tbPaymentAmount = new TextBox()
|
||||
{
|
||||
Location = new Point(lm + 118, 7 * line + tm),
|
||||
Location = new Point(lm + 118, 8 * line + tm),
|
||||
Width = 100,
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||
TextAlign = HorizontalAlignment.Right,
|
||||
|
@ -249,20 +307,20 @@ namespace dezentrale.view
|
|||
parent.Controls.Add(chkPaymentReduced = new CheckBox()
|
||||
{
|
||||
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),
|
||||
Anchor = AnchorStyles.Top | AnchorStyles.Left,
|
||||
});
|
||||
parent.Controls.Add(new Label()
|
||||
{
|
||||
Text = "Signed Date:",
|
||||
Location = new Point(lm, 8 * line + tm + labelOffs),
|
||||
Location = new Point(lm, 9 * line + tm + labelOffs),
|
||||
Size = new Size(110, labelHeight),
|
||||
TextAlign = ContentAlignment.BottomRight,
|
||||
});
|
||||
parent.Controls.Add(dateMemberForm = new DateTimePicker()
|
||||
{
|
||||
Location = new Point(lm + 118, 8 * line + tm),
|
||||
Location = new Point(lm + 118, 9 * line + tm),
|
||||
Width = 100,
|
||||
Format = DateTimePickerFormat.Short,
|
||||
//MinDate = new DateTime(1900, 01, 01, 00, 00, 00),
|
||||
|
@ -512,7 +570,12 @@ namespace dezentrale.view
|
|||
KeyValue kv = cbCountry.AddKV(display, c);
|
||||
if (c.Code == member.CountryCode) cbCountry.SelectedItem = kv;
|
||||
}
|
||||
|
||||
eMailNameDefaults = m.EMailName == m.Nickname;
|
||||
tbEMail.Text = member.EMail;
|
||||
tbEMailName.Text = m.EMailName;
|
||||
if (eMailNameDefaults)
|
||||
tbEMailName.ForeColor = System.Drawing.Color.DarkGray;
|
||||
tbPgpFingerprint.Text = member.PgpFingerprint;
|
||||
|
||||
foreach (Member.eType type in Enum.GetValues(typeof(Member.eType)))
|
||||
|
@ -522,7 +585,7 @@ namespace dezentrale.view
|
|||
}
|
||||
try { dateSpawn.Value = 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;
|
||||
try { dateMemberForm.Value = member.MemberFormDate; }
|
||||
catch (Exception) { if (!newMember) { MessageBox.Show("Invalid MemberFormDate: " + member.MemberFormDate + "!"); } }
|
||||
|
@ -539,7 +602,7 @@ namespace dezentrale.view
|
|||
}
|
||||
|
||||
tbMvMiss.Text = $"{member.MvMissCounter}";
|
||||
tbAccountBalance.Text = Program.Int64FPToString(member.AccountBalance);
|
||||
tbAccountBalance.Text = core.Utils.Int64FPToString(member.AccountBalance);
|
||||
cbEvaluateAccountInCharge.Checked = member.EvaluateAccountInCharge;
|
||||
tbBankAccountInCharge.ReadOnly = !cbEvaluateAccountInCharge.Checked;
|
||||
tbBankAccountInCharge.Text = member.BankAccountInCharge;
|
||||
|
@ -575,6 +638,7 @@ namespace dezentrale.view
|
|||
member.CountryCode = c.Code;
|
||||
}
|
||||
member.EMail = tbEMail.Text;
|
||||
member.EMailName = tbEMailName.Text;
|
||||
member.PgpFingerprint = tbPgpFingerprint.Text;
|
||||
if(cbType.SelectedItem != null)
|
||||
{
|
||||
|
@ -593,7 +657,7 @@ namespace dezentrale.view
|
|||
}
|
||||
try
|
||||
{
|
||||
member.PaymentAmount = (UInt64) Program.StringToInt64FP(tbPaymentAmount.Text);
|
||||
member.PaymentAmount = (UInt64) core.Utils.StringToInt64FP(tbPaymentAmount.Text);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -618,7 +682,7 @@ namespace dezentrale.view
|
|||
member.MvMissCounter = Convert.ToUInt32(tbMvMiss.Text);
|
||||
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)
|
||||
{
|
||||
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("-"),
|
||||
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("&Quit", mnuMain_File_Quit),
|
||||
} },
|
||||
|
@ -75,7 +75,8 @@ namespace dezentrale.view
|
|||
lstMembers.AddMenuItem("Cronjob selected member", lstMembers_CronjobSelected);
|
||||
//lstMembers.AddMenuItem("Cronjob checked ones", lstMembers_CronjobChecked);
|
||||
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"
|
||||
|
||||
//lstMembers.AddMenuItem("Main Settings", null);
|
||||
|
@ -158,7 +159,7 @@ namespace dezentrale.view
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void mnuMain_File_Quit(object sender, EventArgs e)
|
||||
{
|
||||
this.Close();
|
||||
|
@ -234,7 +235,7 @@ namespace dezentrale.view
|
|||
DialogResult dr = ofd.ShowDialog();
|
||||
//ofd.FilterIndex
|
||||
if (dr == DialogResult.OK)
|
||||
Program.ProcessCSV(ofd.FileName);
|
||||
ProcessCsv.ProcessCSV(ofd.FileName);
|
||||
}
|
||||
|
||||
private void mnuMain_Help_About(object sender, EventArgs e)
|
||||
|
@ -270,6 +271,12 @@ namespace dezentrale.view
|
|||
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)
|
||||
{
|
||||
Member m = lstMembers.GetFirstSelectedItem();
|
||||
|
|
|
@ -493,7 +493,7 @@ namespace dezentrale.view
|
|||
mt.TransferType = (MoneyTransfer.eTransferType)((KeyValue)cbType.SelectedItem).Value;
|
||||
}
|
||||
mt.ValutaDate = valutaDate.Value;
|
||||
mt.Amount = Program.StringToInt64FP(tbAmount.Text);
|
||||
mt.Amount = core.Utils.StringToInt64FP(tbAmount.Text);
|
||||
mt.Currency = cbCurrency.Text;
|
||||
mt.TransferReason = tbTransferReason.Text;
|
||||
|
||||
|
|
Loading…
Reference in New Issue