code from amapei
This commit is contained in:
Executable
+477
@@ -0,0 +1,477 @@
|
||||
package controller;
|
||||
import sugoi.db.Cache;
|
||||
import sugoi.Web;
|
||||
import sugoi.mail.Mail;
|
||||
import Common;
|
||||
using Lambda;
|
||||
using tools.DateTool;
|
||||
|
||||
class Cron extends Controller
|
||||
{
|
||||
|
||||
public function doDefault()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* CLI only en prod
|
||||
*/
|
||||
function canRun() {
|
||||
if (App.current.user != null && App.current.user.isAdmin()){
|
||||
return true;
|
||||
}else if (App.config.DEBUG) {
|
||||
return true;
|
||||
}else {
|
||||
|
||||
if (Web.isModNeko) {
|
||||
Sys.print("only CLI.");
|
||||
return false;
|
||||
}else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function doMinute() {
|
||||
|
||||
print("Cron.doMinute is called");
|
||||
|
||||
if (!canRun()) return;
|
||||
|
||||
app.event(MinutelyCron);
|
||||
|
||||
sendEmailsfromBuffer();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hourly Cron
|
||||
*
|
||||
* this function can be locally tested with `neko index.n cron/hour > cron.log`
|
||||
*/
|
||||
public function doHour() {
|
||||
|
||||
app.event(HourlyCron);
|
||||
|
||||
distribNotif(4,db.User.UserFlags.HasEmailNotif4h); //4h before
|
||||
distribNotif(24,db.User.UserFlags.HasEmailNotif24h); //24h before
|
||||
distribNotif(0, db.User.UserFlags.HasEmailNotifOuverture); //on command open
|
||||
|
||||
distribValidationNotif();
|
||||
//sendOrdersByProductWhenOrdersClose();
|
||||
}
|
||||
|
||||
|
||||
public function doDaily() {
|
||||
if (!canRun()) return;
|
||||
|
||||
app.event(DailyCron);
|
||||
|
||||
//ERRORS MONITORING
|
||||
var n = Date.now();
|
||||
var yest24h = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0);
|
||||
var yest0h = DateTools.delta(yest24h, -1000 * 60 * 60 * 24);
|
||||
|
||||
var errors = sugoi.db.Error.manager.search( $date < yest24h && $date > yest0h );
|
||||
if (errors.length > 0) {
|
||||
var report = new StringBuf();
|
||||
report.add("<h1>" + App.config.NAME + " : ERRORS</h1>");
|
||||
for (e in errors) {
|
||||
report.add("<div><pre>"+e.error + " at URL " + e.url + " ( user : " + (e.user!=null?e.user.toString():"none") + ", IP : " + e.ip + ")</pre></div><hr/>");
|
||||
}
|
||||
|
||||
var m = new Mail();
|
||||
m.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
m.addRecipient(App.config.get("webmaster_email"));
|
||||
m.setSubject(App.config.NAME+" Errors");
|
||||
m.setHtmlBody( app.processTemplate("mail/message.mtt", { text:report.toString() } ) );
|
||||
App.sendMail(m);
|
||||
}
|
||||
|
||||
|
||||
//DEMO CONTRATS deletion after 7 days ( see controller.Group.doCreate() )
|
||||
db.Contract.manager.delete($name == "Contrat AMAP Maraîcher Exemple" && $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7));
|
||||
db.Contract.manager.delete($name == "Contrat Poulet Exemple" && $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7));
|
||||
|
||||
|
||||
//Old Messages cleaning
|
||||
db.Message.manager.delete($date < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30 * 6));
|
||||
|
||||
//DB cleaning : I dont know how, but some people have empty string emails...
|
||||
/*for ( u in db.User.manager.search($email == "", true)){
|
||||
u.email = Std.random(9999) + "@cagette.net";
|
||||
u.update();
|
||||
}
|
||||
for ( u in db.User.manager.search($email2 == "", true)){
|
||||
u.email2 = null;
|
||||
u.update();
|
||||
}*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email notifications to users before a distribution
|
||||
* @param hour
|
||||
* @param flag
|
||||
*/
|
||||
function distribNotif(hour:Int, flag:db.User.UserFlags) {
|
||||
|
||||
//trouve les distrib qui commencent dans le nombre d'heures demandé
|
||||
//on recherche celles qui commencent jusqu'à une heure avant pour ne pas en rater
|
||||
var from = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * (hour-1));
|
||||
var to = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * hour);
|
||||
|
||||
//if (App.config.DEBUG) from = DateTools.delta(from, 1000.0 * 60 * 60 * 24 * -30);
|
||||
|
||||
// dans le cas HasEmailNotifOuverture la date à prendre est le orderStartDate
|
||||
// et non pas date qui est la date de la distribution
|
||||
var distribs;
|
||||
if ( db.User.UserFlags.HasEmailNotifOuverture == flag )
|
||||
distribs = db.Distribution.manager.search( $orderStartDate >= from && $orderStartDate <= to , false);
|
||||
else
|
||||
distribs = db.Distribution.manager.search( $date >= from && $date <= to , false);
|
||||
|
||||
//Sys.print("distribNotif "+hour+" from "+from+" to "+to+"<br/>\n");
|
||||
|
||||
//on s'arrete immédiatement si aucune distibution trouvée
|
||||
if (distribs.length == 0) return;
|
||||
|
||||
//cherche plus tard si on a pas une "grappe" de distrib
|
||||
/*while (true) {
|
||||
var extraDistribs ;
|
||||
if ( db.User.UserFlags.HasEmailNotifOuverture != flag )
|
||||
extraDistribs = db.Distribution.manager.search( $date >= to && $date <DateTools.delta(to,1000.0*60*60) , false);
|
||||
else
|
||||
extraDistribs = db.Distribution.manager.search( $orderStartDate >= to && $orderStartDate <DateTools.delta(to,1000.0*60*60) , false);
|
||||
for ( e in extraDistribs) distribs.add(e);
|
||||
if (extraDistribs.length > 0) {
|
||||
//on fait un tour de plus avec une heure plus tard
|
||||
to = DateTools.delta(h, 1000.0 * 60 * 60);
|
||||
}else {
|
||||
//plus de distribs
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
|
||||
//on vérifie dans le cache du jour que ces distrib n'ont pas deja été traitées lors d'un cron précédent
|
||||
var cacheId = Date.now().toString().substr(0, 10)+Std.string(flag);
|
||||
var dist :Array<Int> = sugoi.db.Cache.get(cacheId);
|
||||
if (dist != null) {
|
||||
for (d in Lambda.array(distribs)) {
|
||||
if (Lambda.exists(dist, function(x) return x == d.id)) {
|
||||
// Comment this line in case of local test
|
||||
distribs.remove(d);
|
||||
}
|
||||
}
|
||||
}else {
|
||||
dist = [];
|
||||
}
|
||||
|
||||
//toutes les distribs trouvées ont deja été traitées
|
||||
if (distribs.length == 0) return;
|
||||
|
||||
//stocke cache
|
||||
for (d in distribs) dist.push(d.id);
|
||||
Cache.set(cacheId, dist, 24 * 60 * 60);
|
||||
|
||||
//We have now the distribs we want to notify about.
|
||||
var distribsByContractId = new Map<Int,db.Distribution>();
|
||||
for (d in distribs) {
|
||||
if (d == null || d.contract==null) continue;
|
||||
distribsByContractId.set(d.contract.id, d);
|
||||
}
|
||||
|
||||
//Boucle sur les distributions pour gerer le cas de plusieurs distributions le même jour sur le même contrat
|
||||
var orders = [];
|
||||
for (d in distribs) {
|
||||
if (d == null || d.contract==null) continue;
|
||||
//get orders for both type of contracts
|
||||
for ( x in d.contract.getOrders(d)) orders.push(x);
|
||||
}
|
||||
|
||||
/*
|
||||
* Group orders by users-group to receive separate emails by groups for the same user.
|
||||
* Map key is $userId-$groupId
|
||||
*/
|
||||
var users = new Map <String,{
|
||||
user:db.User,
|
||||
distrib:db.Distribution,
|
||||
products:Array<db.UserContract>,
|
||||
vendors:Array<db.Vendor>
|
||||
}>();
|
||||
|
||||
for (o in orders) {
|
||||
|
||||
var x = users.get(o.user.id+"-"+o.product.contract.amap.id);
|
||||
if (x == null) x = {user:o.user,distrib:null,products:[],vendors:[]};
|
||||
x.distrib = distribsByContractId.get(o.product.contract.id);
|
||||
x.products.push(o);
|
||||
users.set(o.user.id+"-"+o.product.contract.amap.id, x);
|
||||
//trace (o.userId+"-"+o.product.contract.amap.id, x);Sys.print("<br/>\n");
|
||||
|
||||
// Prévenir également le deuxième user en cas des commandes alternées
|
||||
if (o.user2 != null) {
|
||||
var x = users.get(o.user2.id+"-"+o.product.contract.amap.id);
|
||||
if (x == null) x = {user:o.user2,distrib:null,products:[],vendors:[]};
|
||||
x.distrib = distribsByContractId.get(o.product.contract.id);
|
||||
x.products.push(o);
|
||||
users.set(o.user2.id+"-"+o.product.contract.amap.id, x);
|
||||
//trace (o.user2.id+"-"+o.product.contract.amap.id, x);Sys.print("<br/>\n");
|
||||
}
|
||||
}
|
||||
|
||||
//remove zero qt orders
|
||||
for( k in users.keys()){
|
||||
var x = users.get(k);
|
||||
var total = 0.0;
|
||||
for( o in x.products) total += o.quantity;
|
||||
if(total==0.0) users.remove(k);
|
||||
}
|
||||
|
||||
// Dans le cas de l'ouverture de commande, ce sont tous les users qu'il faut intégrer
|
||||
if ( db.User.UserFlags.HasEmailNotifOuverture == flag )
|
||||
{
|
||||
for (d in distribs) {
|
||||
var memberList = d.contract.amap.getMembers();
|
||||
for (u in memberList) {
|
||||
var x = users.get(u.id+"-"+d.contract.amap.id);
|
||||
if (x == null) x = {user:u,distrib:null,products:[],vendors:[]};
|
||||
x.distrib = distribsByContractId.get(d.contract.id);
|
||||
x.vendors.push(d.contract.vendor);
|
||||
users.set(u.id+"-"+d.contract.amap.id, x);
|
||||
//print(u.id+"-"+d.contract.amap.id, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( u in users) {
|
||||
|
||||
if (u.user.flags.has(flag) ) {
|
||||
|
||||
if (u.user.email != null) {
|
||||
var group = u.distrib.contract.amap;
|
||||
this.t = sugoi.i18n.Locale.init(u.user.lang); //switch to the user language
|
||||
|
||||
var text;
|
||||
if ( db.User.UserFlags.HasEmailNotifOuverture == flag )
|
||||
{
|
||||
//order opening notif
|
||||
text = t._("Opening of orders for the delivery of <b>::date::</b>", {date:view.hDate(u.distrib.date)});
|
||||
text += "<br/>";
|
||||
text += t._("The following suppliers are involved :");
|
||||
text += "<br/><ul>";
|
||||
for ( v in u.vendors) {
|
||||
text += "<li>" + v + "</li>";
|
||||
}
|
||||
text += "</ul>";
|
||||
|
||||
}else{
|
||||
//Distribution notif to the users
|
||||
var d = u.distrib;
|
||||
text = t._("Do not forget the delivery on <b>::day::</b> from ::from:: to ::to::<br/>", {day:view.dDate(d.date),from:view.hHour(d.date),to:view.hHour(d.end)});
|
||||
text += t._("Your products to collect :") + "<br/><ul>";
|
||||
for ( p in u.products) {
|
||||
text += "<li>"+p.quantity+" x "+p.product.getName();
|
||||
// Gerer le cas des contrats en alternance
|
||||
if (p.user2 != null) {
|
||||
text += " " + t._("alternated with") + " ";
|
||||
if (u.user == p.user)
|
||||
text += p.user2.getCoupleName();
|
||||
else
|
||||
text += p.user.getCoupleName();
|
||||
}
|
||||
text += "</li>";
|
||||
}
|
||||
text += "</ul>";
|
||||
}
|
||||
|
||||
if (u.distrib.isDistributor(u.user)) {
|
||||
text += t._("<b>Warning: you are in charge of the delivery ! Do not forget to print the attendance sheet.</b>");
|
||||
}
|
||||
|
||||
try{
|
||||
var m = new Mail();
|
||||
m.setSender(App.config.get("default_email"), "Cagette.net");
|
||||
if(group.contact!=null) m.setReplyTo(group.contact.email, group.name);
|
||||
m.addRecipient(u.user.email, u.user.getName());
|
||||
if (u.user.email2 != null) m.addRecipient(u.user.email2);
|
||||
m.setSubject( group.name+" : "+t._("Distribution on ::date::",{date:app.view.hDate(u.distrib.date)}) );
|
||||
m.setHtmlBody( app.processTemplate("mail/message.mtt", { text:text,group:group } ) );
|
||||
App.sendMail(m , u.distrib.contract.amap);
|
||||
}catch (e:Dynamic){
|
||||
app.logError(e); //email could be invalid
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if there is a multi-distrib to validate.
|
||||
*
|
||||
* Autovalidate it after 10 days
|
||||
*/
|
||||
function distribValidationNotif(){
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
var from = now.setHourMinute( now.getHours(), 0 );
|
||||
var to = now.setHourMinute( now.getHours()+1 , 0);
|
||||
|
||||
var explain = t._("<p>This step is important in order to:</p>");
|
||||
explain += t._("<ul><li>Update orders if delivered quantities are different from ordered quantities</li>");
|
||||
explain += t._("<li>Confirm the reception of payments (checks, cash, transfers) in order to mark orders as 'paid'</li></ul>");
|
||||
|
||||
/*
|
||||
* warn administrator if a distribution just ended
|
||||
*/
|
||||
var ds = db.Distribution.manager.search( !$validated && ($end >= from) && ($end < to) , false);
|
||||
|
||||
for ( d in Lambda.array(ds)){
|
||||
if ( d.contract.type != db.Contract.TYPE_VARORDER ){
|
||||
ds.remove(d);
|
||||
}else if ( !d.contract.amap.hasPayments() ){
|
||||
ds.remove(d);
|
||||
}
|
||||
}
|
||||
|
||||
var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds);
|
||||
var view = App.current.view;
|
||||
|
||||
for ( d in ds ){
|
||||
// var subj = "["+d.contract.amap.name+"] " + t._("Validation of the ::date:: distribution",{date:view.hDate(d.date)});
|
||||
var subj = t._("[::group::] Validation of the ::date:: distribution",{group : d.contract.amap.name , date : view.hDate(d.date)});
|
||||
|
||||
var url = "http://" + App.config.HOST + "/distribution/validate/"+d.date.toString().substr(0,10)+"/"+d.place.id;
|
||||
|
||||
var html = t._("<p>Your distribution just finished, don't forget to <b>validate</b> it</p>");
|
||||
html += explain;
|
||||
html += t._("<p><a href='::distriburl::'>Click here to validate the distribution</a> (You must be connected to your group Cagette)", {distriburl:url});
|
||||
|
||||
App.quickMail(d.contract.amap.contact.email, subj, html);
|
||||
}
|
||||
|
||||
/*
|
||||
* warn administrator if a distribution ended 3 days ago
|
||||
*/
|
||||
|
||||
var from = now.setHourMinute( now.getHours() , 0 ).deltaDays(-3);
|
||||
var to = now.setHourMinute( now.getHours()+1 , 0).deltaDays(-3);
|
||||
|
||||
//warn administrator if a distribution just ended
|
||||
var ds = db.Distribution.manager.search( !$validated && ($end >= from) && ($end < to) , false);
|
||||
|
||||
for ( d in Lambda.array(ds)){
|
||||
if ( d.contract.type != db.Contract.TYPE_VARORDER ){
|
||||
ds.remove(d);
|
||||
}else if ( !d.contract.amap.hasPayments() ){
|
||||
ds.remove(d);
|
||||
}
|
||||
}
|
||||
|
||||
var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds);
|
||||
|
||||
for ( d in ds ){
|
||||
// var subj = d.contract.amap.name + t._(": Validation of the delivery of the ") + App.current.view.hDate(d.date);
|
||||
var subj = t._("[::group::] Validation of the ::date:: distribution",{group : d.contract.amap.name , date : view.hDate(d.date)});
|
||||
|
||||
var url = "http://" + App.config.HOST + "/distribution/validate/"+d.date.toString().substr(0,10)+"/"+d.place.id;
|
||||
|
||||
var html = t._("<p>Reminder: you have a delivery to validate.</p>");
|
||||
html += explain;
|
||||
html += t._("<p><a href='::distriburl::'>Click here to validate the delivery</a> (You must be connected to your Cagette group)", {distriburl:url});
|
||||
|
||||
App.quickMail(d.contract.amap.contact.email, subj, html);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Autovalidate unvalidated distributions after 10 days
|
||||
*/
|
||||
var from = now.setHourMinute( now.getHours() , 0 ).deltaDays( 0 - db.Distribution.DISTRIBUTION_VALIDATION_LIMIT );
|
||||
var to = now.setHourMinute( now.getHours() + 1 , 0).deltaDays( 0 - db.Distribution.DISTRIBUTION_VALIDATION_LIMIT );
|
||||
print('AUTOVALIDATION');
|
||||
print('Find distributions from $from to $to');
|
||||
var ds = db.Distribution.manager.search( !$validated && ($end >= from) && ($end < to) , true);
|
||||
for ( d in Lambda.array(ds)){
|
||||
if ( d.contract.type != db.Contract.TYPE_VARORDER ){
|
||||
ds.remove(d);
|
||||
}else if ( !d.contract.amap.hasPayments() ){
|
||||
ds.remove(d);
|
||||
}
|
||||
}
|
||||
for ( d in ds){
|
||||
print(d.toString());
|
||||
|
||||
service.PaymentService.validateDistribution(d);
|
||||
|
||||
}
|
||||
//email
|
||||
var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds);
|
||||
for ( d in ds ){
|
||||
// var subj = d.contract.amap.name + t._(": Validation of the distribution of the ") + App.current.view.hDate(d.date);
|
||||
var subj = t._("[::group::] Validation of the ::date:: distribution",{group : d.contract.amap.name , date : view.hDate(d.date)});
|
||||
var html = t._("<p>As you did not validate it manually after 10 days, <br/>the delivery of the ::deliveryDate:: has been validated automatically</p>", {deliveryDate:App.current.view.hDate(d.date)});
|
||||
App.quickMail(d.contract.amap.contact.email, subj, html);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send emails from buffer.
|
||||
*
|
||||
* Warning, if the cron is executed each minute,
|
||||
* you should consider the right amount of emails to send each minute in order to avoid overlaping and getting in concurrency problems.
|
||||
* (like "SELECT * FROM BufferedMail WHERE sdate IS NULL ORDER BY cdate DESC LIMIT 100 FOR UPDATE Lock wait timeout exceeded; try restarting transaction")
|
||||
*/
|
||||
function sendEmailsfromBuffer(){
|
||||
print("<h3>Send Emails from Buffer</h3>");
|
||||
|
||||
//send
|
||||
for( e in sugoi.db.BufferedMail.manager.search($sdate==null,{limit:50,orderBy:-cdate},false) ){
|
||||
e.lock();
|
||||
if(e.isSent()) continue;
|
||||
|
||||
print('#${e.id} - ${e.title}');
|
||||
e.finallySend();
|
||||
Sys.sleep(0.1);
|
||||
}
|
||||
|
||||
//delete old emails
|
||||
var threeMonthsAgo = DateTools.delta(Date.now(), -1000.0*60*60*24*30*3);
|
||||
sugoi.db.BufferedMail.manager.delete($cdate < threeMonthsAgo);
|
||||
|
||||
//emails that cannot be sent
|
||||
for( e in sugoi.db.BufferedMail.manager.search($tries>100,{limit:50,orderBy:-cdate},true) ){
|
||||
if(e.sender.email != App.config.get("default_email")){
|
||||
var str = t._("Sorry, the email entitled <b>::title::</b> could not be sent.",{title:e.title});
|
||||
App.quickMail(e.sender.email,t._("Email not sent"),str);
|
||||
}
|
||||
e.delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Email product report when orders close
|
||||
**/
|
||||
function sendOrdersByProductWhenOrdersClose(){
|
||||
|
||||
var range = tools.DateTool.getLastHourRange();
|
||||
// Sys.println("Time is "+Date.now()+"<br/>");
|
||||
// Sys.println('Find all distributions that have closed in the last hour from ${range.from} to ${range.to} \n<br/>');
|
||||
|
||||
for ( d in db.Distribution.manager.search($orderEndDate >= range.from && $orderEndDate < range.to, false)){
|
||||
service.OrderService.sendOrdersByProductReport(d);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function print(text){
|
||||
Sys.println( text + "<br/>" );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user