Compare commits

...

10 Commits

Author SHA1 Message Date
phantomix 86f6ad9739 2023-04-06 Added workaround draft for Mail+TLS1.0, still not working
Increased framework version to 4.8
2023-04-06 10:46:23 +02:00
phantomix 0cda515187 2022-03-29 Added more exception output to TestMail/StatusMail in order to track down posteo.de SSL errors 2022-03-29 23:43:03 +02:00
phantomix 538ba06221 2022-03-27 Fixed Windows \r\n TextBox issues by replacing \n->\r\n and back to \n when handling TextBox contents 2022-03-27 18:21:08 +02:00
phantomix 64ae7f6c4b 2022-02-18 Fixed CsvImportProcess wasn't used, Changed Member banktransferregex -> Contains 2022-02-18 22:34:28 +01:00
phantomix 4d16e11b6e 2021-01-24PX Multiple changes:
- Added CsvImportProcess (porting ProcessCsv as a background process), not finished yet
- added comment window on member editing
- Changed GUI to allow editing member account balance
- Added Enable/Disable feature for Import/Export, in order to get rid of stupid messages at startup/shutdown
- Added configuration for Schatzmeister/Schriftfuehrer contact to configuration
- Fixed CurrentDebtLevel > 0 was able to prevent BalanceDegrade / postdegrade code from working
- Fixed Member list wasn't refreshed after CSV import
- Changed MembershipDonation enum type to Donation (as non-members can donate too)
2022-01-24 22:04:24 +01:00
phantomix 105afdb203 2022-01-19PX fixed mail sending didn't work with posteo, fixed multiple CC mail adresses didn't work 2022-01-19 22:14:16 +01:00
phantomix 747fc9008e 2022-01-07PX reordered program configuration in preparation to separate settings for local, db 2022-01-18 23:31:03 +01:00
phantomix d9d4d9c2be 211031PX Fixed CustomListView.cs: ItemSelection(T,selected) returned false when item was actually found
Added NewExportProcess.cs, NewImportProcess.cs in order to work towards changing import/export completely
2021-10-31 20:34:57 +01:00
phantomix a1c4512f2c 2021-07-18PX Added storage for CSV selected export fields, not fully working yet 2021-07-18 16:21:42 +02:00
phantomix 342511f03a 2021-07-18PX Added Member CSV export feature 2021-07-18 12:47:57 +02:00
28 changed files with 829 additions and 243 deletions

View File

@ -14,31 +14,16 @@ TODO
- Documentation - Documentation
- 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: 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. - 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 - Configuration window: MoneyTransferRegEx
- Bug: Generating testdata doesn't remove old xml files, thus the memberlist will be mixed after next restart
- Bug: Member list not reloaded after ProcessCSV (balance display is wrong)
- Bug: Import/Export gpg uses command line parameter for password - this can be read out by any system user via "ps uxa" - Bug: Import/Export gpg uses command line parameter for password - this can be read out by any system user via "ps uxa"
- CronjobConfig - 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
*/ */
namespace dezentrale namespace dezentrale
{ {
public class Program public class Program
{ {
public static uint VersionNumber { get; private set; } = 0x21052800; public static uint VersionNumber { get; private set; } = 0x22032900;
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);
@ -49,7 +34,11 @@ namespace dezentrale
public static MemberList members = new MemberList(); public static MemberList members = new MemberList();
public static MvList mvList = new MvList(); public static MvList mvList = new MvList();
public static bool MoneyTransfersLoaded { get { return moneyTransfers != null; } } public static bool MoneyTransfersLoaded
{
get { return moneyTransfers != null; }
set { moneyTransfers = value ? MoneyTransferList.LoadFromFile() : null; }
}
private static MoneyTransferList moneyTransfers = null; private static MoneyTransferList moneyTransfers = null;
public static MoneyTransferList MoneyTransfers public static MoneyTransferList MoneyTransfers
{ {
@ -151,6 +140,29 @@ namespace dezentrale
Console.WriteLine($"dezentrale-members, Version {VersionString}"); Console.WriteLine($"dezentrale-members, Version {VersionString}");
Console.WriteLine($"Working directory: {DmDirectory}"); Console.WriteLine($"Working directory: {DmDirectory}");
//See https://stackoverflow.com/questions/721161/how-to-detect-which-net-runtime-is-being-used-ms-vs-mono
bool isRunningOnMono = false;
try
{
isRunningOnMono = (Type.GetType("Mono.Runtime") != null);
} catch(Exception ex) { Console.WriteLine($"Mono detection failed. Assuming non-Mono. Error: {ex.Message}"); }
if(isRunningOnMono)
{
//Mono on OpenSSL (boringssl) will lead to Usage of TLS 1.0 instead of 1.2, even
//if this is explicitly set here!
Console.WriteLine("Mono detected. Setting MONO_TLS_PROVIDER to btls");
Environment.SetEnvironmentVariable("MONO_TLS_PROVIDER", "btls");
}
System.Net.ServicePointManager.Expect100Continue = true;
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) =>
{
return true;
};
List<string> clArgs = new List<string>(); List<string> clArgs = new List<string>();
foreach (string argIt in args) foreach (string argIt in args)
{ {
@ -228,8 +240,13 @@ namespace dezentrale
case eMode.Cronjob: case eMode.Cronjob:
Cronjob.Run(); Cronjob.Run();
break; break;
case eMode.Export: case eMode.Export:
{ {
if (!Program.config.ImportExport.Enabled)
{
Console.WriteLine("ImportExport is disabled in configuration");
return 1;
}
ExportProcess export = new ExportProcess() ExportProcess export = new ExportProcess()
{ {
ImportExportSettings = Program.config.ImportExport, ImportExportSettings = Program.config.ImportExport,
@ -247,7 +264,12 @@ namespace dezentrale
} break; } break;
case eMode.Import: case eMode.Import:
{ {
if(!Program.config.ImportExport.Enabled)
{
Console.WriteLine("ImportExport is disabled in configuration");
return 1;
}
ImportProcess import = new ImportProcess() ImportProcess import = new ImportProcess()
{ {
ImportExportSettings = Program.config.ImportExport, ImportExportSettings = Program.config.ImportExport,

View File

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

122
core/CsvImportProcess.cs Normal file
View File

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

1
core/NewExportProcess.cs Normal file
View File

@ -0,0 +1 @@


1
core/NewImportProcess.cs Normal file
View File

@ -0,0 +1 @@


View File

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

View File

@ -8,7 +8,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>dezentrale</RootNamespace> <RootNamespace>dezentrale</RootNamespace>
<AssemblyName>dezentrale-members</AssemblyName> <AssemblyName>dezentrale-members</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
@ -154,7 +154,7 @@
</Compile> </Compile>
<Compile Include="model\ConfigSmtp.cs" /> <Compile Include="model\ConfigSmtp.cs" />
<Compile Include="model\Configuration.cs" /> <Compile Include="model\Configuration.cs" />
<Compile Include="model\ConfigVSMail.cs" /> <Compile Include="model\ConfigEMail.cs" />
<Compile Include="model\FormMail.cs" /> <Compile Include="model\FormMail.cs" />
<Compile Include="model\Member.cs" /> <Compile Include="model\Member.cs" />
<Compile Include="model\LogEvent.cs" /> <Compile Include="model\LogEvent.cs" />
@ -210,6 +210,12 @@
<Compile Include="core\MvFinishProcess.cs" /> <Compile Include="core\MvFinishProcess.cs" />
<Compile Include="view\LvAttachments.cs" /> <Compile Include="view\LvAttachments.cs" />
<Compile Include="model\IAttachmentOwner.cs" /> <Compile Include="model\IAttachmentOwner.cs" />
<Compile Include="view\frmExportCsv.cs" />
<Compile Include="view\LvSelectFields.cs" />
<Compile Include="core\NewExportProcess.cs" />
<Compile Include="core\NewImportProcess.cs" />
<Compile Include="view\frmCommentChanges.cs" />
<Compile Include="core\CsvImportProcess.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="App.config" /> <None Include="App.config" />

19
model/ConfigEMail.cs Normal file
View File

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

View File

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

View File

@ -8,19 +8,10 @@ namespace dezentrale.model
{ {
public class Configuration : XmlData public class Configuration : XmlData
{ {
//Program-wide configuration (single-instance settings in main configuration)
[XmlElement] public ConfigSmtp Smtp { get; set; } = new ConfigSmtp(); [XmlElement] public ConfigSmtp Smtp { get; set; } = new ConfigSmtp();
[XmlElement] public ConfigVSMail VS { get; set; } = new ConfigVSMail(); [XmlElement] public string LocalUser { get; set; } = "John Doe";
[XmlElement] public MemberImportExport ImportExport{ get; set; } = new MemberImportExport();
[XmlElement] public string DbDirectory { get; set; } = DefaultDbDirectory;
//[XmlElement] public string DbBackupDirectory { get; set; } = DefaultDbBackupDirectory;
//[XmlElement] public string ImportExportDirectory { get; set; } = DefaultImportExportDirectory;
[XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents
[XmlElement] public string RegularPaymentCurrency { get; set; } = "EUR";
[XmlElement] public string LocalUser { get; set; } = "John Doe";
[XmlElement] public string KeylockCombination { get; set; } = "0000";
//UI: lstMembers: Columns //UI: lstMembers: Columns
[XmlElement("MemberListColumn")] public List<ConfigLVColumn> MemberListColumns { get; set; } = new List<ConfigLVColumn>(); [XmlElement("MemberListColumn")] public List<ConfigLVColumn> MemberListColumns { get; set; } = new List<ConfigLVColumn>();
[XmlElement("MTListColumn")] public List<ConfigLVColumn> MTListColumns { get; set; } = new List<ConfigLVColumn>(); [XmlElement("MTListColumn")] public List<ConfigLVColumn> MTListColumns { get; set; } = new List<ConfigLVColumn>();
@ -28,17 +19,44 @@ namespace dezentrale.model
[XmlElement("MvInvitationsListColumn")] [XmlElement("MvInvitationsListColumn")]
public List<ConfigLVColumn> MvInvitationsListColumns{ get; set; } = new List<ConfigLVColumn>(); public List<ConfigLVColumn> MvInvitationsListColumns{ get; set; } = new List<ConfigLVColumn>();
[XmlElement("AttachmentsColumn")]public List<ConfigLVColumn> AttachmentsColumns{ get; set; } = new List<ConfigLVColumn>(); [XmlElement("AttachmentsColumn")]public List<ConfigLVColumn> AttachmentsColumns{ get; set; } = new List<ConfigLVColumn>();
[XmlElement("SelectFieldsColumn")]public List<ConfigLVColumn> SelectFieldsColumns{ get; set; } = new List<ConfigLVColumn>();
[XmlElement] public List<KeyValue> MoneyTransferRegEx { get; set; } = new List<KeyValue>(); //This doesn't belong here! Move to new file within db-data!
[XmlElement] public DateTime LastCronjobRun { get; set; } = DateTime.Now; //This doesn't belong here! Move to new file within db-data!
[XmlIgnore] public static string DefaultDbDirectory { get; private set; } = "db-data";
//[XmlIgnore] public static string DefaultDbBackupDirectory { get; private set; } = "db-backup";
//[XmlIgnore] public static string DefaultImportExportDirectory { get; private set; } = "import-export";
//Program-wide db metadata (multiple sub-objects in main configuration)
[XmlElement] public string DbDirectory { get; set; } = DefaultDbDirectory;
//[XmlElement] public string DbBackupDirectory { get; set; } = DefaultDbBackupDirectory;
//[XmlElement] public string ImportExportDirectory { get; set; } = DefaultImportExportDirectory;
[XmlElement] public DateTime LastDbLocalChange { get; set; } [XmlElement] public DateTime LastDbLocalChange { get; set; }
[XmlElement] public DateTime LastDbExport { get; set; } [XmlElement] public DateTime LastDbExport { get; set; }
[XmlElement] public DateTime LastDbImport { get; set; } [XmlElement] public DateTime LastDbImport { get; set; }
[XmlElement] public bool DbChangedSinceExport { get; set; } = false; [XmlElement] public bool DbChangedSinceExport { get; set; } = false;
public List<string> MemberCsvExportFields { get; set; } = new List<string>();
[XmlElement] public MemberImportExport ImportExport { get; set; } = new MemberImportExport();
//db-wide configuration (in DB folder)
[XmlElement] public ConfigEMail VS { get; set; } = new ConfigEMail() { EMailName = "dezentrale Vorstand", EMail = "vorstand@dezentrale.space" };
[XmlElement] public ConfigEMail Schatzmeister { get; set; } = new ConfigEMail() { EMailName = "Schatzmeister", EMail = "kasse@dezentrale.space" };
[XmlElement] public ConfigEMail Schriftfuehrer { get; set; } = new ConfigEMail() { EMailName = "Schriftfuehrer", EMail = "vorstand@dezentrale.space" };
[XmlElement] public uint RegularPaymentAmount { get; set; } = 3200; //cents
[XmlElement] public string RegularPaymentCurrency { get; set; } = "EUR";
[XmlElement] public string KeylockCombination { get; set; } = "0000";
[XmlElement] public List<KeyValue> MoneyTransferRegEx { get; set; } = new List<KeyValue>(); //This doesn't belong here! Move to new file within db-data!
[XmlElement] public DateTime LastCronjobRun { get; set; } = DateTime.Now; //This doesn't belong here! Move to new file within db-data!
[XmlIgnore] public static string DefaultDbDirectory { get; private set; } = "db-data";
//[XmlIgnore] public static string DefaultDbBackupDirectory { get; private set; } = "db-backup";
//[XmlIgnore] public static string DefaultImportExportDirectory { get; private set; } = "import-export";
} }
} }

View File

@ -81,7 +81,9 @@ namespace dezentrale.model
List<string> line = FileContents[l]; List<string> line = FileContents[l];
for (int f = 0; f < line.Count; f++) for (int f = 0; f < line.Count; f++)
{ {
sb.Append("\"" + line[f].Replace("\"", "\"\"") + "\""); string s = line[f];
if (s == null) s = "null";
sb.Append("\"" + s.Replace("\"", "\"\"") + "\"");
if (f < line.Count - 1) sb.Append(FieldSeparator); if (f < line.Count - 1) sb.Append(FieldSeparator);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -157,7 +157,7 @@ namespace dezentrale.view
void CustomListView_MouseUp(object sender, MouseEventArgs e) void CustomListView_MouseUp(object sender, MouseEventArgs e)
{ {
Console.WriteLine($"MouseUp(): MouseButtons={Control.MouseButtons}"); //Console.WriteLine($"MouseUp(): MouseButtons={Control.MouseButtons}");
if (resizeWhileMouseDown && (resizedWhileMouseDown != null)) if (resizeWhileMouseDown && (resizedWhileMouseDown != null))
{ {
//mono: Store new width. //mono: Store new width.
@ -200,6 +200,13 @@ namespace dezentrale.view
{ {
if (this.SelectedItems.Count < 1) return null; if (this.SelectedItems.Count < 1) return null;
return (T)this.SelectedItems[0].Tag; return (T)this.SelectedItems[0].Tag;
}
public List<T> GetAllItems()
{
List<T> ret = new List<T>();
for (int i = 0; i < this.Items.Count; i++)
ret.Add((T)this.Items[i].Tag);
return ret;
} }
public List<T> GetSelectedItems() public List<T> GetSelectedItems()
{ {
@ -217,6 +224,17 @@ namespace dezentrale.view
return ret; return ret;
} }
public bool ItemSelection(T entry, bool selected)
{
foreach (ListViewItem lvi in this.Items)
if (lvi.Tag.Equals(entry))
{
lvi.Selected = selected;
return true;
}
return false;
}
public MenuItem AddMenuItem(string text, EventHandler handler = null, MenuItem parent = null, bool enabled = true) public MenuItem AddMenuItem(string text, EventHandler handler = null, MenuItem parent = null, bool enabled = true)
{ {
MenuItem mi = new MenuItem() { Text = text }; MenuItem mi = new MenuItem() { Text = text };

41
view/LvSelectFields.cs Normal file
View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using dezentrale.model;
namespace dezentrale.view
{
public class LvSelectFields : CustomListView<string>
{
protected override List<ConfigLVDataHandler> DefaultColumns
{
get
{
return new List<ConfigLVDataHandler>()
{
new ConfigLVDataHandler()
{
Name = "field",
Display = "field",
Width = 120,
CustomToString = x => ((string)x),
},
};
}
}
public LvSelectFields() : base(Program.config.SelectFieldsColumns, LvSelectFields_ColumnsChanged)
{
}
private static void LvSelectFields_ColumnsChanged(object sender, ColumnsChangedArgs e)
{
Console.WriteLine("LvSelectFields_ColumnsChanged");
Program.config.SelectFieldsColumns.Clear();
foreach (ConfigLVDataHandler c in e.Columns) Program.config.SelectFieldsColumns.Add(new ConfigLVColumn(c));
XmlData.SaveToFile(Program.ConfigFile, Program.config);
}
}
}

41
view/frmCommentChanges.cs Normal file
View File

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

View File

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

View File

@ -388,7 +388,7 @@ namespace dezentrale.view
Width = 100, Width = 100,
Anchor = AnchorStyles.Top | AnchorStyles.Left, Anchor = AnchorStyles.Top | AnchorStyles.Left,
TextAlign = HorizontalAlignment.Right, TextAlign = HorizontalAlignment.Right,
ReadOnly = true, //ReadOnly = true,
}); });
parent.Controls.Add(new Label() parent.Controls.Add(new Label()
{ {
@ -611,7 +611,7 @@ namespace dezentrale.view
cbPaymentNotification.Checked = member.PaymentNotify; cbPaymentNotification.Checked = member.PaymentNotify;
tbBankTransferRegEx.ReadOnly = !cbEvaluateAccountInCharge.Checked; tbBankTransferRegEx.ReadOnly = !cbEvaluateAccountInCharge.Checked;
tbBankTransferRegEx.Text = member.BankTransferRegEx; tbBankTransferRegEx.Text = member.BankTransferRegEx;
tbRemarks.Text = member.Remarks; tbRemarks.Text = member.Remarks.Replace("\n", "\r\n");
this.ResumeLayout(false); this.ResumeLayout(false);
} }
@ -693,11 +693,19 @@ namespace dezentrale.view
member.BankAccountInCharge = tbBankAccountInCharge.Text; member.BankAccountInCharge = tbBankAccountInCharge.Text;
member.PaymentNotify = cbPaymentNotification.Checked; member.PaymentNotify = cbPaymentNotification.Checked;
member.BankTransferRegEx = tbBankTransferRegEx.Text; member.BankTransferRegEx = tbBankTransferRegEx.Text;
member.Remarks = tbRemarks.Text; member.Remarks = tbRemarks.Text = member.Remarks.Replace("\r\n", "\n");
member.FinishLogEvent(); //member.FinishLogEvent(); //implicit in SaveToFile()
this.DialogResult = DialogResult.OK; this.DialogResult = DialogResult.OK;
member.SaveToFile();
string comment = null;
if (!newMember)
{
frmCommentChanges commentChanges = new frmCommentChanges($"{member.Number:D3} ({ member.Nickname})", "");
commentChanges.ShowDialog();
comment = commentChanges.Comment;
}
member.SaveToFile(comment);
this.Close(); this.Close();
} }

116
view/frmExportCsv.cs Normal file
View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;
using dezentrale.model;
namespace dezentrale.view
{
public class frmExportCsv<T> : FormWithActionButtons
{
private List<T> entities;
private LvSelectFields lv;
public List<string> SelectedProperties { get; set; } = new List<string>();
private bool ExportCsv()
{
SaveFileDialog sfd = new SaveFileDialog(){
FileName = $"{typeof(T)}_{entities.Count}_items.csv",
Filter = "Comma-Separated-Value files (*.csv)|*.csv"
};
DialogResult dr = sfd.ShowDialog();
if (dr != DialogResult.OK)
return false;
try
{
CsvFile csv = new CsvFile() {
};
//Build headline
csv.FileContents.Add(SelectedProperties);
foreach (T t in entities)
{
List<string> t_csv = new List<string>();
foreach (string p in SelectedProperties)
{
//Get propertyinfo from T
PropertyInfo pi = typeof(T).GetProperty(p);
if (!pi.CanRead) continue;
try
{
object o = pi.GetValue(t, null);
string propVal = (o == null ? "null" : o.ToString());
t_csv.Add(propVal);
} catch(Exception ex)
{
t_csv.Add(ex.Message);
}
}
csv.FileContents.Add(t_csv);
}
csv.SaveFile(sfd.FileName);
} catch(Exception ex)
{
MessageBox.Show($"Error({ex.GetType()}):\r\n{ex.Message}");
return false;
}
return true;
}
private void FormLoad(object sender, System.EventArgs e)
{
this.Text = $"Export {entities.Count} entities of type \"{typeof(T).ToString()}\"";
this.Controls.Add(lv = new LvSelectFields()
{
Left = 0,
Width = this.ClientSize.Width,
Top = 0,
Height = this.ClientSize.Height - 60,
Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right,
});
foreach (var p in typeof(T).GetProperties())
{
lv.AddEntry(p.Name);
}
List<string> failToSelect = new List<string>();
foreach(string s in SelectedProperties)
{
if (!lv.ItemSelection(s, true)) {
failToSelect.Add(s);
}
}
foreach (string s in failToSelect) {
SelectedProperties.Remove(s);
}
this.AddButton("All fields", (se, ev) => {
SelectedProperties = lv.GetAllItems();
if (ExportCsv()) this.Close();
});
this.AddButton("Selected fields", (se, ev) => {
SelectedProperties = lv.GetSelectedItems();
if (ExportCsv()) this.Close();
});
this.AddButton("Cancel", (se, ev) => { this.Close(); });
}
public frmExportCsv(T t)
{
entities = new List<T> { t };
this.Load += FormLoad;
}
public frmExportCsv(List<T> l)
{
entities = l;
this.Load += FormLoad;
}
}
}

View File

@ -15,6 +15,7 @@ namespace dezentrale.view
private LVMoneyTransfers mtv; private LVMoneyTransfers mtv;
private LvMv lstMv; private LvMv lstMv;
private MenuItem mnuFileExport, mnuFileImport;
private void BuildMoneyTransfers(Control parent) private void BuildMoneyTransfers(Control parent)
{ {
parent.Controls.Add(mtv = new LVMoneyTransfers() { Dock = DockStyle.Fill, }); parent.Controls.Add(mtv = new LVMoneyTransfers() { Dock = DockStyle.Fill, });
@ -31,7 +32,7 @@ namespace dezentrale.view
this.Size = new Size(640, 480); this.Size = new Size(640, 480);
this.FormClosing += (sender, e) => this.FormClosing += (sender, e) =>
{ {
if(!enforceClosing && Program.config.DbChangedSinceExport) if(!enforceClosing && Program.config.DbChangedSinceExport && Program.config.ImportExport.Enabled)
{ {
DialogResult dr = DialogResult dr =
MessageBox.Show("Database changed since last export.\r\n\r\n" MessageBox.Show("Database changed since last export.\r\n\r\n"
@ -51,8 +52,8 @@ namespace dezentrale.view
{ MenuItems = { { MenuItems = {
new MenuItem("&Configuration...", mnuMain_File_Configuration) { Enabled = true }, new MenuItem("&Configuration...", mnuMain_File_Configuration) { Enabled = true },
new MenuItem("-"), new MenuItem("-"),
new MenuItem("&Export database", mnuMain_File_Export), (mnuFileExport = new MenuItem("&Export database", mnuMain_File_Export)),
new MenuItem("&Import database", mnuMain_File_Import), (mnuFileImport = new MenuItem("&Import database", mnuMain_File_Import)),
new MenuItem("-"), new MenuItem("-"),
new MenuItem("&Quit", mnuMain_File_Quit), new MenuItem("&Quit", mnuMain_File_Quit),
} }, } },
@ -65,7 +66,9 @@ namespace dezentrale.view
new MenuItem("Generate &Testdata", mnuMain_Members_Generate_Testdata), new MenuItem("Generate &Testdata", mnuMain_Members_Generate_Testdata),
new MenuItem("-"), new MenuItem("-"),
#endif #endif
new MenuItem("Show numeric &info", lstMembers_mnuMain_Members_ShowInfo), new MenuItem("Show numeric &info", lstMembers_mnuMain_Members_ShowInfo),
new MenuItem("Export &CSV (selected)", lstMembers_mnuMain_Members_CSV_selected),
new MenuItem("Export &CSV (all)", lstMembers_mnuMain_Members_CSV_all),
} }, } },
new MenuItem("MV") new MenuItem("MV")
{ MenuItems = { { MenuItems = {
@ -86,6 +89,8 @@ namespace dezentrale.view
} }
}; };
mnuFileImport.Enabled = mnuFileExport.Enabled = Program.config.ImportExport.Enabled;
TabPage tabMembers = new TabPage("Members"); TabPage tabMembers = new TabPage("Members");
tabMembers.Controls.Add(lstMembers = new LVMembers() { Dock = DockStyle.Fill }); tabMembers.Controls.Add(lstMembers = new LVMembers() { Dock = DockStyle.Fill });
@ -127,32 +132,35 @@ namespace dezentrale.view
lstMv.LoadFromList(Program.mvList.Entries); lstMv.LoadFromList(Program.mvList.Entries);
//Check for needed import //Check for needed import
DateTime now = DateTime.Now; if (Program.config.ImportExport.Enabled)
int totalHours = (int) now.Subtract(Program.config.LastDbImport).TotalHours;
int totalDays = totalHours / 24;
if (totalHours >= 8)
{ {
totalHours %= 24; DateTime now = DateTime.Now;
string timeSpan = $"{totalDays} d, {totalHours} h"; int totalHours = (int) now.Subtract(Program.config.LastDbImport).TotalHours;
int totalDays = totalHours / 24;
if (Program.config.DbChangedSinceExport) if (totalHours >= 8)
{ {
MessageBox.Show( "Warning: There are local changes to DB since last export!\r\n" totalHours %= 24;
+ "Please check if there are changes on the server side and then\r\n" string timeSpan = $"{totalDays} d, {totalHours} h";
+ "- perform a manual backup-import-merge\r\n"
+ "- or simply an export if there are no changes.\r\n\r\n" if (Program.config.DbChangedSinceExport)
+ $"Last import was: {Program.config.LastDbImport}\r\n"
+ $"Last export was: {Program.config.LastDbExport}\r\n"
+ $"Last db change was {Program.config.LastDbLocalChange}\r\n");
}
else
{
if (totalDays > 365) timeSpan = "too long";
DialogResult dr = MessageBox.Show($"Last Db import was {timeSpan} ago.\r\nImport now?", "Import database", MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes)
{ {
mnuMain_File_Import(null, null); MessageBox.Show( "Warning: There are local changes to DB since last export!\r\n"
+ "Please check if there are changes on the server side and then\r\n"
+ "- perform a manual backup-import-merge\r\n"
+ "- or simply an export if there are no changes.\r\n\r\n"
+ $"Last import was: {Program.config.LastDbImport}\r\n"
+ $"Last export was: {Program.config.LastDbExport}\r\n"
+ $"Last db change was {Program.config.LastDbLocalChange}\r\n");
} }
else
{
if (totalDays > 365) timeSpan = "too long";
DialogResult dr = MessageBox.Show($"Last Db import was {timeSpan} ago.\r\nImport now?", "Import database", MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes)
{
mnuMain_File_Import(null, null);
}
}
} }
} }
} }
@ -163,8 +171,11 @@ namespace dezentrale.view
frmConfig.ShowDialog(); frmConfig.ShowDialog();
if (frmConfig.DialogResult == DialogResult.OK) if (frmConfig.DialogResult == DialogResult.OK)
{ {
frmConfig.FillAndSaveConfig(); frmConfig.FillAndSaveConfig();
if(frmConfig.KeylockCombiChanged && Program.config.Smtp.Enabled)
mnuFileImport.Enabled = mnuFileExport.Enabled = Program.config.ImportExport.Enabled;
if (frmConfig.KeylockCombiChanged && Program.config.Smtp.Enabled)
{ {
DialogResult dr = MessageBox.Show("You've changed the keylock combination.\n Do you want to send an E-Mail to every active member to inform them?", "Keylock-Combi changed", MessageBoxButtons.YesNo); DialogResult dr = MessageBox.Show("You've changed the keylock combination.\n Do you want to send an E-Mail to every active member to inform them?", "Keylock-Combi changed", MessageBoxButtons.YesNo);
if (dr == DialogResult.Yes) if (dr == DialogResult.Yes)
@ -396,6 +407,25 @@ namespace dezentrale.view
MessageBox.Show(new MemberReport().Body); MessageBox.Show(new MemberReport().Body);
} }
private void MemberCsvExport(List<Member> list)
{
frmExportCsv<Member> frmCsv = new frmExportCsv<Member>(list)
{
SelectedProperties = Program.config.MemberCsvExportFields,
};
frmCsv.ShowDialog();
Program.config.MemberCsvExportFields = frmCsv.SelectedProperties;
XmlData.SaveToFile(Program.ConfigFile, Program.config);
}
private void lstMembers_mnuMain_Members_CSV_selected(object sender, EventArgs e)
{
MemberCsvExport(lstMembers.GetSelectedItems());
}
private void lstMembers_mnuMain_Members_CSV_all(object sender, EventArgs e)
{
MemberCsvExport(Program.members.Entries);
}
private void mnuMain_Payments_Add(object sender, EventArgs e) private void mnuMain_Payments_Add(object sender, EventArgs e)
{ {
frmMoneyTransfer addMoney = new frmMoneyTransfer(); frmMoneyTransfer addMoney = new frmMoneyTransfer();
@ -433,7 +463,26 @@ namespace dezentrale.view
DialogResult dr = ofd.ShowDialog(); DialogResult dr = ofd.ShowDialog();
//ofd.FilterIndex //ofd.FilterIndex
if (dr == DialogResult.OK) if (dr == DialogResult.OK)
ProcessCsv.ProcessCSV(ofd.FileName); {
lstMembers.SuspendLayout();
CsvImportProcess csvImport = new CsvImportProcess()
{
FileName = ofd.FileName,
};
frmProcessWithLog frmCsvImport = new frmProcessWithLog(csvImport, true);
dr = frmCsvImport.ShowDialog();
foreach (Member m in Program.members.Entries) lstMembers.UpdateEntry(m);
if (Program.MoneyTransfersLoaded)
{
//This will enforce reloading the moneytransfer list
Program.MoneyTransfersLoaded = false;
mtv.LoadFromList(Program.MoneyTransfers.Entries);
}
lstMembers.ResumeLayout(false);
}
} }
private void mnuMain_Payments_Receipts(object sender, EventArgs e) private void mnuMain_Payments_Receipts(object sender, EventArgs e)
{ {
@ -494,21 +543,35 @@ namespace dezentrale.view
lstMembers.ResumeLayout(false); lstMembers.ResumeLayout(false);
} }
private bool SendMail(FormMail mail, Member m)
{
try
{
if (m == null) return true;
mail.Send(m);
} catch(Exception ex)
{
string msg = ex.Message;
while (ex.InnerException != null)
{
ex = ex.InnerException;
msg += "\r\n" + ex.Message;
}
MessageBox.Show($"Cannot send mail:\r\n{msg}");
}
return true;
}
private void lstMembers_TestMail(object sender, EventArgs e) private void lstMembers_TestMail(object sender, EventArgs e)
{ {
Member m = lstMembers.GetFirstSelectedItem(); Member m = lstMembers.GetFirstSelectedItem();
m?.TestMail(); FormMail testMail = FormMail.GenerateTestmail();
SendMail(testMail, m);
} }
private void lstMembers_AccountStatusMail(object sender, EventArgs e) private void lstMembers_AccountStatusMail(object sender, EventArgs e)
{ {
Member m = lstMembers.GetFirstSelectedItem(); Member m = lstMembers.GetFirstSelectedItem();
try FormMail statusMail = FormMail.GenerateSingleMemberStatusReport();
{ SendMail(statusMail, m);
m?.AccountStatusMail();
} catch(Exception ex)
{
MessageBox.Show($"Cannot send account status mail:\r\n{ex.Message}");
}
} }
private void lstMembers_Edit(object sender, EventArgs e) private void lstMembers_Edit(object sender, EventArgs e)

View File

@ -431,7 +431,7 @@ namespace dezentrale.view
cbCurrency.SelectedIndex = 1; cbCurrency.SelectedIndex = 1;
} }
typeVal = mt.TransferType; typeVal = mt.TransferType;
tbTransferReason.Text = mt.TransferReason; tbTransferReason.Text = mt.TransferReason.Replace("\n", "\r\n");
} }
foreach (MoneyTransfer.eTransferType type in Enum.GetValues(typeof(MoneyTransfer.eTransferType))) foreach (MoneyTransfer.eTransferType type in Enum.GetValues(typeof(MoneyTransfer.eTransferType)))
@ -495,7 +495,7 @@ namespace dezentrale.view
mt.ValutaDate = valutaDate.Value; mt.ValutaDate = valutaDate.Value;
mt.Amount = core.Utils.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.Replace("\r\n", "\n");
if (mt.GetType() == typeof(CashTransfer)) if (mt.GetType() == typeof(CashTransfer))
{ {

View File

@ -294,11 +294,11 @@ namespace dezentrale.view
mv.EventDate = dt; mv.EventDate = dt;
mv.Place = tbPlace.Text; mv.Place = tbPlace.Text;
mv.Agenda = tbMvAgenda.Text; mv.Agenda = tbMvAgenda.Text.Replace("\r\n", "\n");
mv.InviteHeadline = tbInviteHeadline.Text; mv.InviteHeadline = tbInviteHeadline.Text;
mv.InviteBody = tbInviteBody.Text; mv.InviteBody = tbInviteBody.Text.Replace("\r\n", "\n");
mv.Protocol = tbMvProtocol.Text; mv.Protocol = tbMvProtocol.Text.Replace("\r\n", "\n");
} }
private void BuildPageInvitationSettings(Control parent) private void BuildPageInvitationSettings(Control parent)
@ -500,12 +500,13 @@ namespace dezentrale.view
if (e.KeyChar != 13) return; if (e.KeyChar != 13) return;
e.KeyChar = (char)0; e.KeyChar = (char)0;
bool foundAuthCode = false; bool foundAuthCode = false;
string searchText = tbAuthCodePaste.Text.ToUpper();
foreach(MvInvitedMember mvi in mv.Members) foreach(MvInvitedMember mvi in mv.Members)
{ {
if (string.IsNullOrEmpty(mvi.AuthCode)) if (string.IsNullOrEmpty(mvi.AuthCode))
continue; continue;
if (tbAuthCodePaste.Text.Contains(mvi.AuthCode)) if (searchText.Contains(mvi.AuthCode.ToUpper()))
{ {
foundAuthCode = true; foundAuthCode = true;
mvi.AttendedMv = true; mvi.AttendedMv = true;
@ -607,11 +608,11 @@ namespace dezentrale.view
mvDate.Value = mv.EventDate; mvDate.Value = mv.EventDate;
mvTime.Value = mv.EventDate; mvTime.Value = mv.EventDate;
tbPlace.Text = mv.Place; tbPlace.Text = mv.Place;
tbMvAgenda.Text = mv.Agenda; tbMvAgenda.Text = mv.Agenda.Replace("\n", "\r\n");
tbInviteHeadline.Text = mv.InviteHeadline; tbInviteHeadline.Text = mv.InviteHeadline;
tbInviteBody.Text = mv.InviteBody; tbInviteBody.Text = mv.InviteBody.Replace("\n", "\r\n");
tbMvProtocol.Text = mv.Protocol; tbMvProtocol.Text = mv.Protocol.Replace("\n", "\r\n");
UpdateGui(); UpdateGui();