2019-05-15 16:42:43 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2019-05-16 16:14:54 +00:00
|
|
|
|
using System.Windows.Forms;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
|
|
|
|
using dezentrale.model;
|
2019-06-03 14:03:19 +00:00
|
|
|
|
using dezentrale.model.money;
|
2019-05-16 16:14:54 +00:00
|
|
|
|
using dezentrale.view;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
2019-06-10 22:13:35 +00:00
|
|
|
|
/*
|
|
|
|
|
TODO
|
|
|
|
|
----
|
2019-06-11 16:20:28 +00:00
|
|
|
|
- Documentation
|
2019-06-10 22:13:35 +00:00
|
|
|
|
- ErrorLog for all errors
|
|
|
|
|
- frmMain: Menu option to miss an MV for selected/checked users
|
2019-06-18 16:04:04 +00:00
|
|
|
|
- FormMail: Add mail for automatic member type setting to "Foerdermitglied"
|
|
|
|
|
- FormMail: Use manual type changing
|
2019-06-24 15:52:34 +00:00
|
|
|
|
- FormMail: Cronjob found unassigned MoneyTransfers
|
2019-06-18 16:04:04 +00:00
|
|
|
|
- Configuration window: MoneyTransferRegEx
|
2019-06-11 16:20:28 +00:00
|
|
|
|
- Bug: Generating testdata doesn't remove old xml files, thus the memberlist will be mixed after next restart
|
2019-06-13 16:09:12 +00:00
|
|
|
|
- CronjobConfig
|
2019-06-20 15:03:02 +00:00
|
|
|
|
- CustomListView: implement generic filtering
|
2019-06-10 22:13:35 +00:00
|
|
|
|
*/
|
2019-05-15 16:42:43 +00:00
|
|
|
|
namespace dezentrale
|
|
|
|
|
{
|
|
|
|
|
public class Program
|
|
|
|
|
{
|
2019-11-10 13:12:05 +00:00
|
|
|
|
public static uint VersionNumber { get; private set; } = 0x19111000;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
public static string VersionString { get; private set; } = $"{VersionNumber:x}";
|
|
|
|
|
|
|
|
|
|
public static string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
|
|
|
|
public static string DmDirectory = Path.Combine(AppData, "dezentrale-members");
|
|
|
|
|
|
|
|
|
|
public static string ConfigFile = Path.Combine(DmDirectory, "configuration.xml");
|
|
|
|
|
public static Configuration config = new Configuration();
|
|
|
|
|
public static MemberList members = new MemberList();
|
2019-06-05 15:46:17 +00:00
|
|
|
|
|
|
|
|
|
public static bool MoneyTransfersLoaded { get { return moneyTransfers != null; } }
|
|
|
|
|
private static MoneyTransferList moneyTransfers = null;
|
|
|
|
|
public static MoneyTransferList MoneyTransfers
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (moneyTransfers != null) return moneyTransfers;
|
|
|
|
|
return moneyTransfers = MoneyTransferList.LoadFromFile();
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-23 15:11:35 +00:00
|
|
|
|
|
2019-05-15 16:42:43 +00:00
|
|
|
|
public enum eMode
|
|
|
|
|
{
|
|
|
|
|
CommandLine = 0,
|
|
|
|
|
Gui = 1,
|
|
|
|
|
Cronjob = 2,
|
2019-05-27 15:03:55 +00:00
|
|
|
|
Export = 3,
|
2019-05-27 21:45:59 +00:00
|
|
|
|
Import = 4,
|
2019-06-01 01:34:56 +00:00
|
|
|
|
BankImport = 5,
|
2019-05-15 16:42:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-05-16 16:14:54 +00:00
|
|
|
|
public static eMode ProgramMode = eMode.Gui;
|
2019-06-01 01:34:56 +00:00
|
|
|
|
private static string csvInput = null;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
2019-05-16 16:14:54 +00:00
|
|
|
|
[STAThread]
|
2019-05-15 16:42:43 +00:00
|
|
|
|
static int Main(string[] args)
|
|
|
|
|
{
|
|
|
|
|
bool printHelp = false;
|
|
|
|
|
Console.WriteLine($"dezentrale-members, Version {VersionString}");
|
2019-05-17 15:53:35 +00:00
|
|
|
|
Console.WriteLine($"Working directory: {DmDirectory}");
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
|
|
|
|
List<string> clArgs = new List<string>();
|
2019-06-01 01:34:56 +00:00
|
|
|
|
foreach (string argIt in args)
|
2019-05-15 16:42:43 +00:00
|
|
|
|
{
|
|
|
|
|
string argn;
|
|
|
|
|
string argv;
|
|
|
|
|
if (argIt.Contains("="))
|
|
|
|
|
{
|
|
|
|
|
argn = argIt.Substring(0, argIt.IndexOf('='));
|
|
|
|
|
argv = argIt.Substring(argIt.IndexOf('=') + 1);
|
2019-06-01 01:34:56 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
2019-05-15 16:42:43 +00:00
|
|
|
|
{
|
|
|
|
|
argn = argIt;
|
|
|
|
|
argv = "";
|
|
|
|
|
}
|
2019-11-10 13:54:56 +00:00
|
|
|
|
if (argn.StartsWith("-", StringComparison.InvariantCulture)) argn = argn.Substring(1);
|
|
|
|
|
if (argn.StartsWith("/", StringComparison.InvariantCulture) || argn.StartsWith("-", StringComparison.InvariantCulture)) argn = argn.Substring(1);
|
2019-06-01 01:34:56 +00:00
|
|
|
|
switch (argn.ToLower())
|
2019-05-15 16:42:43 +00:00
|
|
|
|
{
|
2019-06-01 01:34:56 +00:00
|
|
|
|
case "help": printHelp = true; break;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
case "mode":
|
2019-06-01 01:34:56 +00:00
|
|
|
|
switch (argv.ToLower())
|
2019-05-15 16:42:43 +00:00
|
|
|
|
{
|
2019-06-01 01:34:56 +00:00
|
|
|
|
case "cl": ProgramMode = eMode.CommandLine; break;
|
|
|
|
|
case "gui": ProgramMode = eMode.Gui; break;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
case "cronjob":
|
2019-06-01 01:34:56 +00:00
|
|
|
|
case "auto": ProgramMode = eMode.Cronjob; break;
|
|
|
|
|
case "export": ProgramMode = eMode.Export; break;
|
|
|
|
|
case "import": ProgramMode = eMode.Import; break;
|
|
|
|
|
case "bankimport": ProgramMode = eMode.BankImport; break;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
default:
|
|
|
|
|
Console.WriteLine("Invalid mode.");
|
|
|
|
|
printHelp = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2019-05-27 21:45:59 +00:00
|
|
|
|
Console.WriteLine($"Setting Mode to {ProgramMode}");
|
2019-06-01 01:34:56 +00:00
|
|
|
|
break;
|
|
|
|
|
case "csvinput":
|
|
|
|
|
csvInput = argv;
|
2019-05-15 16:42:43 +00:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
Console.WriteLine($"Invalid command({argn})");
|
|
|
|
|
printHelp = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (printHelp)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("command line arguments:");
|
|
|
|
|
Console.WriteLine("--help Display this help message");
|
|
|
|
|
Console.WriteLine("--mode=<mode> Starts the program in given mode. Supported modes are:");
|
|
|
|
|
Console.WriteLine(" cl (default) Manual/interactive use from command line.");
|
|
|
|
|
Console.WriteLine(" gui Start in GUI mode (not implemented yet)");
|
|
|
|
|
Console.WriteLine(" auto, cronjob Check and perform pending required membership actions");
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 01:34:56 +00:00
|
|
|
|
try { Directory.CreateDirectory(DmDirectory); }
|
2019-05-16 16:14:54 +00:00
|
|
|
|
catch (Exception ex) { Console.WriteLine($"Error while creating data directory:\n{ex.Message}"); return 1; }
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2019-05-23 15:11:35 +00:00
|
|
|
|
config = (Configuration)XmlData.LoadFromFile(ConfigFile, typeof(Configuration));
|
2019-05-20 16:19:30 +00:00
|
|
|
|
}
|
|
|
|
|
catch (FileNotFoundException)
|
|
|
|
|
{
|
2019-06-03 14:03:19 +00:00
|
|
|
|
config.DbDirectory = Path.Combine(DmDirectory, Configuration.DefaultDbDirectory);
|
2019-05-20 16:19:30 +00:00
|
|
|
|
XmlData.SaveToFile(ConfigFile, config); Console.WriteLine("Created new configuration file.");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
2019-05-16 16:14:54 +00:00
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Error while accessing program data:");
|
|
|
|
|
Console.WriteLine(ex.Message);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 01:34:56 +00:00
|
|
|
|
try { Directory.CreateDirectory(config.DbDirectory); }
|
2019-05-20 16:19:30 +00:00
|
|
|
|
catch (Exception ex) { Console.WriteLine($"Error while creating member data directory:\n{ex.Message}"); return 1; }
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
2019-05-20 16:19:30 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
2019-06-01 01:34:56 +00:00
|
|
|
|
string[] memberFiles = Directory.GetFiles(config.DbDirectory, "member-*.xml");
|
2019-05-22 16:11:29 +00:00
|
|
|
|
//foreach (string s in memberFiles) Console.WriteLine(s);
|
2019-05-20 16:19:30 +00:00
|
|
|
|
members = MemberList.LoadFromFiles(memberFiles);
|
|
|
|
|
Console.WriteLine($"Loaded {members.Entries.Count} member entries");
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Error while loading member files:");
|
|
|
|
|
Console.WriteLine(ex.Message);
|
|
|
|
|
return 1;
|
2019-06-14 15:27:05 +00:00
|
|
|
|
}
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
2019-05-16 16:14:54 +00:00
|
|
|
|
switch (ProgramMode)
|
|
|
|
|
{
|
|
|
|
|
case eMode.Gui:
|
|
|
|
|
frmMain w = new frmMain();
|
|
|
|
|
Application.Run(w);
|
|
|
|
|
break;
|
|
|
|
|
case eMode.CommandLine:
|
|
|
|
|
Console.WriteLine("Not implemented yet");
|
|
|
|
|
break;
|
|
|
|
|
case eMode.Cronjob:
|
2019-05-22 16:11:29 +00:00
|
|
|
|
Cronjob.Run();
|
2019-05-16 16:14:54 +00:00
|
|
|
|
break;
|
2019-05-27 15:03:55 +00:00
|
|
|
|
case eMode.Export:
|
2019-06-01 01:34:56 +00:00
|
|
|
|
if (!config.ImportExport.Export(config.DbDirectory, DmDirectory)) return 1;
|
|
|
|
|
if (!config.ImportExport.VerifyExport(config.DbDirectory, DmDirectory)) return 1;
|
2019-05-27 21:45:59 +00:00
|
|
|
|
break;
|
|
|
|
|
case eMode.Import:
|
|
|
|
|
|
2019-06-01 01:34:56 +00:00
|
|
|
|
if (!config.ImportExport.Import(config.DbDirectory, DmDirectory))
|
|
|
|
|
return 1;
|
|
|
|
|
break;
|
|
|
|
|
case eMode.BankImport:
|
|
|
|
|
if (string.IsNullOrEmpty(csvInput))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("You must provide an input file by using --csvinput=<file>");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (!ProcessCSV(csvInput))
|
2019-05-27 21:45:59 +00:00
|
|
|
|
return 1;
|
|
|
|
|
break;
|
2019-05-16 16:14:54 +00:00
|
|
|
|
}
|
2019-05-15 16:42:43 +00:00
|
|
|
|
|
|
|
|
|
return 0;
|
2019-05-23 15:11:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-01 01:34:56 +00:00
|
|
|
|
public static bool ProcessCSV(string fileName)
|
2019-05-21 21:33:42 +00:00
|
|
|
|
{
|
2019-06-03 14:03:19 +00:00
|
|
|
|
|
2019-05-22 16:11:29 +00:00
|
|
|
|
CsvFile csv = new CsvFile();
|
2019-06-09 23:33:21 +00:00
|
|
|
|
csv.FieldSeparator = ';';
|
2019-05-22 16:11:29 +00:00
|
|
|
|
try
|
2019-06-05 15:46:17 +00:00
|
|
|
|
{
|
2019-06-03 14:03:19 +00:00
|
|
|
|
List<BankTransfer> tmpList = new List<BankTransfer>();
|
|
|
|
|
|
2019-05-22 16:11:29 +00:00
|
|
|
|
csv.ReadFile(fileName);
|
2019-06-01 01:34:56 +00:00
|
|
|
|
List<string> headlineFields = null;
|
2019-06-09 23:33:21 +00:00
|
|
|
|
List<Member> changedMembers = new List<Member>();
|
2019-06-01 01:34:56 +00:00
|
|
|
|
foreach (List<string> l in csv.FileContents)
|
2019-05-22 16:11:29 +00:00
|
|
|
|
{
|
2019-06-01 01:34:56 +00:00
|
|
|
|
if (headlineFields == null)
|
2019-05-23 15:11:35 +00:00
|
|
|
|
{
|
|
|
|
|
//The first line is expected to have the headline field first, describing the contents
|
2019-05-24 07:47:37 +00:00
|
|
|
|
headlineFields = l;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2019-06-01 01:34:56 +00:00
|
|
|
|
|
|
|
|
|
BankTransfer bt = new BankTransfer(headlineFields, l);
|
2019-06-18 16:04:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
2019-06-09 23:33:21 +00:00
|
|
|
|
}
|
2019-06-03 14:03:19 +00:00
|
|
|
|
|
|
|
|
|
//try to assign transfers to the members
|
2019-06-09 23:33:21 +00:00
|
|
|
|
foreach (BankTransfer bt in tmpList)
|
2019-06-03 14:03:19 +00:00
|
|
|
|
{
|
2019-06-09 23:33:21 +00:00
|
|
|
|
if (bt.Amount < 0)
|
|
|
|
|
{
|
|
|
|
|
bt.TransferType = MoneyTransfer.eTransferType.RunningCost;
|
|
|
|
|
Console.WriteLine($"{bt.Id}: Amount = {bt.AmountString} --> RunningCost");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (Member m in members.Entries)
|
2019-06-01 01:34:56 +00:00
|
|
|
|
{
|
|
|
|
|
if (m.CheckBankTransfer(bt))
|
2019-06-09 23:33:21 +00:00
|
|
|
|
{
|
|
|
|
|
bt.TransferType = MoneyTransfer.eTransferType.MembershipPayment;
|
|
|
|
|
changedMembers.Add(m);
|
2019-06-03 14:03:19 +00:00
|
|
|
|
m.StartLogEvent("Incoming bank transfer", LogEvent.eEventType.MembershipPayment, "automatic");
|
2019-06-09 23:33:21 +00:00
|
|
|
|
m.ApplyMoneyTransfer(bt);
|
2019-06-14 15:27:05 +00:00
|
|
|
|
bt.AssignFixed = true;
|
2019-06-09 23:33:21 +00:00
|
|
|
|
break; //this is important. We don't want to assign this to multiple members.
|
2019-06-01 01:34:56 +00:00
|
|
|
|
}
|
2019-06-03 14:03:19 +00:00
|
|
|
|
}
|
2019-05-22 16:11:29 +00:00
|
|
|
|
}
|
2019-06-01 01:34:56 +00:00
|
|
|
|
//Store bank transfer list
|
2019-06-18 16:04:04 +00:00
|
|
|
|
Console.WriteLine("ProcessCSV(): Storing money transfers...");
|
|
|
|
|
MoneyTransfers.Entries.Sort();
|
2019-06-05 15:46:17 +00:00
|
|
|
|
if (!MoneyTransfers.SaveToFile())
|
2019-06-01 01:34:56 +00:00
|
|
|
|
return false;
|
2019-06-03 14:03:19 +00:00
|
|
|
|
bool ret = true;
|
2019-06-14 15:27:05 +00:00
|
|
|
|
/*
|
|
|
|
|
//automaticly saved in ApplyMoneyTransfer()
|
2019-06-09 23:33:21 +00:00
|
|
|
|
foreach (Member m in changedMembers)
|
2019-06-01 01:34:56 +00:00
|
|
|
|
{
|
|
|
|
|
if (m.CurrentLog != null)
|
2019-06-03 14:03:19 +00:00
|
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
2019-06-01 01:34:56 +00:00
|
|
|
|
}
|
2019-06-14 15:27:05 +00:00
|
|
|
|
}*/
|
2019-06-09 23:33:21 +00:00
|
|
|
|
//TBD: mail to schatzmeister if there are unassigned transfers
|
|
|
|
|
|
2019-06-03 14:03:19 +00:00
|
|
|
|
return ret;
|
2019-06-01 01:34:56 +00:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
2019-05-22 16:11:29 +00:00
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"Error while processing csv file \"{fileName}\":");
|
|
|
|
|
Console.WriteLine(ex.Message);
|
2019-06-01 01:34:56 +00:00
|
|
|
|
return false;
|
2019-05-22 16:11:29 +00:00
|
|
|
|
}
|
2019-06-14 15:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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));
|
2019-05-21 21:33:42 +00:00
|
|
|
|
}
|
2019-05-15 16:42:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|