427 lines
20 KiB
C#
427 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
using dezentrale.model;
|
|
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 smContact Mail contact entry for the Schatzmeister account, in order to send notification mails for some events
|
|
public static void Run(Member m, ConfigEMail smContact)
|
|
{
|
|
Console.WriteLine($"Processing member {m.Number:D3} ({m.Nickname})...");
|
|
CheckGreeting(m);
|
|
CheckSpawning(m);
|
|
// CheckReducedFeeValidity(m);
|
|
CheckBalance_PreDegrade(m, smContact);
|
|
BalanceDegrade(m);
|
|
CheckBalance_PostDegrade(m, smContact);
|
|
}
|
|
//! \short Helper function for Run(m, sm). Determines sm automatically.
|
|
//! \param m Member to check
|
|
public static void Run(Member m)
|
|
{
|
|
Run(m, Program.config.Schatzmeister);
|
|
}
|
|
|
|
//! \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)sm
|
|
//! \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)
|
|
{
|
|
if (partialList == null || partialList == Program.members.Entries)
|
|
{
|
|
foreach (Member m in Program.members.Entries) Run(m, Program.config.Schatzmeister);
|
|
|
|
//GenerateReport();
|
|
if (!Program.config.LastCronjobRun.Equals(ProgramStartTime))
|
|
{
|
|
try { new MemberReport(true).Send(Program.config.Schatzmeister); }
|
|
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to Schatzmeister: {ex.Message}"); }
|
|
try { new MemberReport(false).Send(Program.config.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, Program.config.Schatzmeister);
|
|
}
|
|
}
|
|
|
|
//! \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)
|
|
{
|
|
try
|
|
{
|
|
//Send greetings mail,//send mail to vorstand
|
|
m.StartLogEvent("Sending greetings", LogEvent.eEventType.Greetings, "automatic");
|
|
try
|
|
{
|
|
FormMail tmp = Program.mailTemplates.NewMemberWelcome.ReplaceReflect(m);
|
|
LogSubEvent lse = tmp.Send();
|
|
m.CurrentLog.SubEvents.Add(lse);
|
|
if (lse.Type == LogEvent.eEventType.Error) throw new Exception("Sending mail to user failed");
|
|
} catch (Exception ex) { throw ex; }
|
|
|
|
try
|
|
{
|
|
FormMail tmp = Program.mailTemplates.NewMemberVorstand.ReplaceReflect(Program.config.VS).ReplaceReflect(m);
|
|
LogSubEvent lse = tmp.Send();
|
|
m.CurrentLog.SubEvents.Add(lse);
|
|
if (lse.Type == LogEvent.eEventType.Error) throw new Exception("Sending mail to vorstand failed");
|
|
|
|
} catch(Exception ex) { throw ex; }
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = $"Failed:\n{ex.Message}",
|
|
});
|
|
Console.WriteLine($"Cannot send mail: {ex.Message}");
|
|
m.SaveToFile();
|
|
return;
|
|
}
|
|
|
|
m.GreetedDate = DateTime.Now;
|
|
m.Status = Member.eStatus.Greeted;
|
|
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)
|
|
{
|
|
//Console.WriteLine("status != GREETED");
|
|
return;
|
|
}
|
|
{
|
|
TimeSpan ts = DateTime.Now.Subtract(m.GreetedDate);
|
|
if (ts.TotalDays < 7)
|
|
{
|
|
Console.WriteLine("TotalDays < 7");
|
|
return;
|
|
}
|
|
if (m.SpawnDate > DateTime.Now)
|
|
{
|
|
Console.WriteLine("spawnDate > now");
|
|
return;
|
|
}
|
|
if ((m.AccountBalance < (Int64) m.PaymentAmount) && (m.PaymentsTotal < m.PaymentAmount))
|
|
{
|
|
Console.WriteLine("payments < paymentAmount");
|
|
return;
|
|
}
|
|
if (m.AccountBalance < 0)
|
|
{
|
|
Console.WriteLine("accountbalance < 0");
|
|
return;
|
|
}
|
|
Console.WriteLine("Sending activation mail");
|
|
m.StartLogEvent("Sending activation mail", LogEvent.eEventType.Activation, "automatic");
|
|
//Send greetings mail, send mail to vorstand
|
|
try
|
|
{
|
|
FormMail
|
|
tmp = Program.mailTemplates.MemberAccountActivated.ReplaceReflect(Program.config).ReplaceReflect(m);
|
|
LogSubEvent
|
|
lse = tmp.Send();
|
|
m.CurrentLog.SubEvents.Add(lse);
|
|
if (lse.Type == LogEvent.eEventType.Error) throw new Exception("Sending mail to user failed");
|
|
tmp = Program.mailTemplates.MemberActiveVorstand.ReplaceReflect(Program.config).ReplaceReflect(Program.config.VS).ReplaceReflect(m);
|
|
lse = tmp.Send();
|
|
m.CurrentLog.SubEvents.Add(lse);
|
|
if (lse.Type == LogEvent.eEventType.Error) throw new Exception("Sending mail to vorstand failed");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = $"Failed:\n{ex.Message}",
|
|
});
|
|
Console.WriteLine($"Cannot send mail: {ex.Message}");
|
|
m.FinishLogEvent();
|
|
m.SaveToFile();
|
|
return;
|
|
}
|
|
m.Status = Member.eStatus.Active;
|
|
m.SaveToFile();
|
|
}
|
|
}
|
|
|
|
private static void CheckReducedFeeValidity(Member m)
|
|
{
|
|
TimeSpan ts = ProgramStartTime.Subtract(m.ReducedFeeValid);
|
|
TimeSpan tsLastMail = ProgramStartTime.Subtract(m.LastCronjobReducedFeeMail);
|
|
if (ts.TotalDays > 0 && ts.TotalDays > 6) //remind if late and max. once a week
|
|
{
|
|
m.StartLogEvent($"Reminder to hand in reduced fee prove", LogEvent.eEventType.EMail, "automatic");
|
|
LogSubEvent lse;
|
|
try
|
|
{
|
|
lse = Program.mailTemplates.ReducedFeeReminder.Send(m);
|
|
m.LastCronjobReducedFeeMail = ProgramStartTime;
|
|
} catch(Exception ex)
|
|
{
|
|
lse = new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = "CheckReducedFeeValidity",
|
|
Details = $"Cannot send reminder mail: {ex.Message}"
|
|
};
|
|
}
|
|
m.CurrentLog.SubEvents.Add(lse);
|
|
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;
|
|
|
|
|
|
uint thisMonth = (uint) DateTime.Now.Month;
|
|
uint thisYear = (uint) DateTime.Now.Year;
|
|
uint months = 0;
|
|
DateTime lastDegrade = m.LastBalanceDegrade;
|
|
if (lastDegrade < m.SpawnDate) lastDegrade = m.SpawnDate;
|
|
while (lastDegrade.Month != thisMonth) { lastDegrade = lastDegrade.AddMonths(1); months++; }
|
|
while (lastDegrade.Year < thisYear) { lastDegrade = lastDegrade.AddYears(1); months += 12; }
|
|
|
|
if (months > 0)
|
|
{
|
|
//We need to cap the negative balance here to -2 monthly fees.
|
|
if (m.AccountBalance > -2 * (Int64)m.PaymentAmount)
|
|
{
|
|
m.StartLogEvent($"Membership fee: {months} Months * {m.PaymentAmount} EURcents = {months * m.PaymentAmount} EURcents", LogEvent.eEventType.MembershipFee, "automatic");
|
|
m.AccountBalance -= (Int64)(((Int64)months) * (Int64)m.PaymentAmount);
|
|
m.LastBalanceDegrade = DateTime.Now;
|
|
} else
|
|
{
|
|
m.StartLogEvent($"Skipping membership fee for {months} Months ({months * m.PaymentAmount} EURcents) as balance is already <= 2 monthly fees!", LogEvent.eEventType.MembershipFee, "automatic");
|
|
m.LastBalanceDegrade = DateTime.Now;
|
|
}
|
|
m.SaveToFile();
|
|
}
|
|
}
|
|
|
|
//! \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, ConfigEMail smContact = null)
|
|
{
|
|
|
|
}
|
|
private static void CheckBalance_PostDegrade(Member m, ConfigEMail smContact = null)
|
|
{
|
|
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
|
|
|
|
TimeSpan tsLastMail = ProgramStartTime.Subtract(m.LastCronjobBalanceMail);
|
|
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
|
|
|
|
//Account balance is capped to -2 monthly fees
|
|
//if (m.AccountBalance < -2 * (int)m.PaymentAmount) currentDeptLevel = -3;
|
|
if (m.AccountBalance <= -2 * (int)m.PaymentAmount) currentDebtLevel = -2;
|
|
else if (m.AccountBalance < 0) currentDebtLevel = -1;
|
|
else if (m.AccountBalance > 200 * 100) currentDebtLevel = 1;
|
|
|
|
bool debtLevelDecrease = (m.DebtLevel > currentDebtLevel);
|
|
bool debtLevelIncrease = (m.DebtLevel < currentDebtLevel);
|
|
|
|
if(!skipInsufficientNotify)
|
|
{
|
|
if (debtLevelDecrease) m.DebtLevel--;
|
|
else m.DebtLevel = currentDebtLevel;
|
|
//We don't set skipInsufficientNotify here as we don't want to not-warn
|
|
//if it's still negative
|
|
}
|
|
|
|
switch(m.DebtLevel)
|
|
{
|
|
case 1:
|
|
{
|
|
if (debtLevelIncrease)
|
|
{
|
|
//Member has given much money
|
|
m.StartLogEvent($"Excess amount of payments", LogEvent.eEventType.EMail, "automatic");
|
|
if (smContact != null)
|
|
{
|
|
FormMail above200 = Program.mailTemplates.BalanceAbove200NotifySM.ReplaceReflect(m);
|
|
above200.To = smContact.ToString();
|
|
try
|
|
{
|
|
m.CurrentLog.SubEvents.Add(above200.Send());
|
|
} catch(Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = "(balance too high) Can't send Mail to Schatzmeister",
|
|
Details = ex.Message,
|
|
});
|
|
Console.WriteLine($"(balance too high) Can't send Mail to Schatzmeiste: {ex.Message}");
|
|
}
|
|
}
|
|
else
|
|
Console.WriteLine("ERROR: CheckBalance_PostDegrade: sm = null");
|
|
}
|
|
} break;
|
|
|
|
case 0:
|
|
{
|
|
//all is fine.
|
|
if (debtLevelDecrease || debtLevelIncrease)
|
|
{
|
|
m.StartLogEvent($"Debt level changed", LogEvent.eEventType.DataChange, "automatic");
|
|
}
|
|
} break;
|
|
|
|
case -1:
|
|
{
|
|
if (!skipInsufficientNotify)
|
|
{
|
|
m.StartLogEvent($"Insufficient amount of payments #1", LogEvent.eEventType.EMail, "automatic");
|
|
try
|
|
{
|
|
m.CurrentLog.SubEvents.Add(Program.mailTemplates.BalanceNegativeMemberNotify1.Send(m));
|
|
} catch(Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = "Email notification error",
|
|
Details = ex.Message,
|
|
});
|
|
Console.WriteLine($"Cannot send Insufficient amount #1 notification: {ex.Message}");
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case -2:
|
|
{
|
|
if (!skipInsufficientNotify)
|
|
{
|
|
if(debtLevelDecrease)
|
|
{
|
|
m.StartLogEvent($"Insufficient amount of payments #2", LogEvent.eEventType.EMail, "automatic");
|
|
try
|
|
{
|
|
m.CurrentLog.SubEvents.Add(Program.mailTemplates.BalanceNegativeMemberNotify2.Send(m));
|
|
} catch (Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = "Email notification error",
|
|
Details = ex.Message,
|
|
});
|
|
Console.WriteLine($"Cannot send Insufficient amount #2 notification: {ex.Message}");
|
|
}
|
|
|
|
FormMail below2sm = Program.mailTemplates.BalanceNegativeNotify2SM.ReplaceReflect(m);
|
|
below2sm.To = smContact.ToString();
|
|
try
|
|
{
|
|
m.CurrentLog.SubEvents.Add(below2sm.Send());
|
|
} catch(Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error,
|
|
Topic = "Email notification error",
|
|
Details = ex.Message,
|
|
});
|
|
Console.WriteLine($"Cannot send Insufficient amount #2 SM notification: {ex.Message}");
|
|
}
|
|
} else
|
|
{
|
|
m.StartLogEvent($"Deactivation (insufficient payment)", LogEvent.eEventType.Deactivation, "automatic");
|
|
try
|
|
{
|
|
m.CurrentLog.SubEvents.Add(Program.mailTemplates.BalanceNegativeMemberDeactivation.ReplaceReflect(Program.config.VS).Send(m));
|
|
} catch(Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error, Topic = "Email BalanceNegativeMemberDeactivation", Details = ex.Message,
|
|
});
|
|
}
|
|
try
|
|
{
|
|
m.CurrentLog.SubEvents.Add(Program.mailTemplates.BalanceNegativeMemberDeactivationVS.ReplaceReflect(Program.config.VS).Send(m));
|
|
} catch(Exception ex)
|
|
{
|
|
m.CurrentLog.SubEvents.Add(new LogSubEvent()
|
|
{
|
|
Type = LogEvent.eEventType.Error, Topic = "Email BalanceNegativeMemberDeactivationVS", Details = ex.Message,
|
|
});
|
|
}
|
|
m.Status = Member.eStatus.Disabled;
|
|
}
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
//If there are data changes to the member, there's a log and we need to store everything
|
|
if (m.CurrentLog != null)
|
|
{
|
|
m.LastCronjobBalanceMail = DateTime.Now;
|
|
m.SaveToFile();
|
|
}
|
|
}
|
|
}
|
|
}
|