dmdb/core/Cronjob.cs

282 lines
13 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 sm Member entry for the Schatzmeister account, in order to send notification mails for some events
public static void Run(Member m, Member sm)
{
Console.WriteLine($"Processing member {m.Number:D3} ({m.Nickname})...");
CheckGreeting(m);
CheckSpawning(m);
CheckBalance_PreDegrade(m, sm);
BalanceDegrade(m);
CheckBalance_PostDegrade(m, sm);
}
//! \short Helper function for Run(m, sm). Determines sm automatically.
//! \param m Member to check
public static void Run(Member m)
{
Member sm = Program.members.Find(Member.eRole.Schatzmeister);
if (sm == null)
{
Console.WriteLine($"Cronjob.Run(Member {m.Number}, {m.Nickname}): ERROR, cannot find sm account!");
}
else
Run(m, sm);
}
//! \short Perform a cronjob run on a List of members.
//! \brief Perform a cronjob run on a List of members. This may be a
//! partial List (e.g. by checking member entries from GUI)
//! \param partialList List of members to process. If null, the program will process the complete member list.
public static void Run(List<Member> partialList = null)
{
Member sm = Program.members.Find(Member.eRole.Schatzmeister);
if (sm == null)
{
Console.WriteLine($"Cronjob.Run(List of {partialList.Count} members): ERROR, cannot find schatzmeister account!");
}
if (partialList == null || partialList == Program.members.Entries)
{
foreach (Member m in Program.members.Entries) Run(m, sm);
//GenerateReport();
if (!Program.config.LastCronjobRun.Equals(ProgramStartTime))
{
Member schriftfuehrer = Program.members.Find(Member.eRole.Schriftfuehrer);
try { if (sm != null) new MemberReport(true).Send(sm); }
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to Schatzmeister: {ex.Message}"); }
try { if (schriftfuehrer != null) new MemberReport(false).Send(schriftfuehrer); }
catch (Exception ex) { Console.WriteLine($"Cronjob.GenerateReport(): Error while sending report to schriftfuehrer: {ex.Message}"); }
Console.WriteLine("Cronjob.Run(): Member Reports sent.");
}
Program.config.LastCronjobRun = ProgramStartTime;
try
{
XmlData.SaveToFile(Program.ConfigFile, Program.config);
Console.WriteLine("Cronjob.Run(): Saved config.");
}
catch (Exception ex)
{
Console.WriteLine($"Cronjob.Run(): Error while saving config: {ex.Message}");
}
}
else
{
foreach (Member m in partialList) Run(m, sm);
}
}
//! \short Send greetings E-Mail to new member, if conditions are met.
//! \note Also sends a mail to vorstand mailinglist
//! \param m Member to check
private static void CheckGreeting(Member m)
{
if (m.Status == Member.eStatus.Uninitialized && m.EMail.Length > 0)
{
try
{
//Send greetings mail,//send mail to vorstand
m.StartLogEvent("Sending greetings", LogEvent.eEventType.Greetings, "automatic");
FormMail
tmp = FormMail.GenerateNewMemberWelcome().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 = FormMail.GenerateNewMemberVorstand().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.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 = FormMail.GenerateMemberAccountActivated().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 = FormMail.GenerateMemberAccountVorstand().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();
}
}
//! \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)
{
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;
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, Member sm = null)
{
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
if (m.AccountBalance <= -2 * (int) m.PaymentAmount)
{
m.StartLogEvent($"Deactivation (insufficient payment)", LogEvent.eEventType.Deactivation, "automatic");
m.CurrentLog.SubEvents.Add(FormMail.GenerateBalanceNegativeMemberDeactivation().ReplaceReflect(Program.config.VS).Send(m));
m.CurrentLog.SubEvents.Add(FormMail.GenerateBalanceNegativeMemberDeactivationVS().ReplaceReflect(Program.config.VS).Send(m));
m.Status = Member.eStatus.Disabled;
m.SaveToFile();
}
}
private static void CheckBalance_PostDegrade(Member m, Member sm = null)
{
if (m.Status != Member.eStatus.Active || m.PaymentAmount == 0) return;
bool skipInsufficientNotify = false;
if (DateTime.Now.Day < 8) skipInsufficientNotify = true;
if (m.AccountBalance <= -2 * (int)m.PaymentAmount)
{
if (!skipInsufficientNotify)
{
m.StartLogEvent($"Insufficient amount of payments #2", LogEvent.eEventType.EMail, "automatic");
m.CurrentLog.SubEvents.Add(FormMail.GenerateBalanceNegativeMemberNotify2().Send(m));
if (sm != null)
{
FormMail below2sm = FormMail.GenerateBalanceNegativeNotify2SM().ReplaceReflect(m);
below2sm.To = $"{sm.Nickname} <{sm.EMail}>";
m.CurrentLog.SubEvents.Add(below2sm.Send());
} else
Console.WriteLine("ERROR: CheckBalance_PostDegrade: sm = null");
}
} else if (m.AccountBalance < 0)
{
if (!skipInsufficientNotify)
{
m.StartLogEvent($"Insufficient amount of payments #1", LogEvent.eEventType.EMail, "automatic");
m.CurrentLog.SubEvents.Add(FormMail.GenerateBalanceNegativeMemberNotify1().Send(m));
}
} else if (m.AccountBalance > 200 * 100)
{
m.StartLogEvent($"Excess amount of payments", LogEvent.eEventType.EMail, "automatic");
if (sm != null)
{
FormMail above200 = FormMail.GenerateBalanceAbove200NotifySM().ReplaceReflect(m);
above200.To = $"{sm.Nickname} <{sm.EMail}>";
m.CurrentLog.SubEvents.Add(above200.Send());
} else
Console.WriteLine("ERROR: CheckBalance_PostDegrade: sm = null");
}
if (m.CurrentLog != null)
m.SaveToFile();
}
}
}