code from amapei
This commit is contained in:
Executable
+306
@@ -0,0 +1,306 @@
|
||||
import db.User;
|
||||
import thx.semver.Version;
|
||||
import Common;
|
||||
|
||||
class App extends sugoi.BaseApp {
|
||||
|
||||
public static var current : App = null;
|
||||
public static var t : sugoi.i18n.translator.ITranslator;
|
||||
public static var config = sugoi.BaseApp.config;
|
||||
|
||||
public var eventDispatcher :hxevents.Dispatcher<Event>;
|
||||
public var plugins : Array<sugoi.plugin.IPlugIn>;
|
||||
|
||||
/**
|
||||
* Version management
|
||||
* @doc https://github.com/fponticelli/thx.semver
|
||||
*/
|
||||
//public static var VERSION = ([0,9,2] : Version).withPre("july");
|
||||
public static var VERSION = ([0,9,2] : Version).withPre(MyMacros.getGitShortSHA(), MyMacros.getGitCommitDate());
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
}
|
||||
|
||||
public static function main() {
|
||||
|
||||
App.t = sugoi.form.Form.translator = new sugoi.i18n.translator.TMap(getTranslationArray(), "fr");
|
||||
sugoi.BaseApp.main();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init plugins and event dispatcher just before launching the app
|
||||
*/
|
||||
override public function mainLoop() {
|
||||
eventDispatcher = new hxevents.Dispatcher<Event>();
|
||||
plugins = [];
|
||||
//internal plugins
|
||||
plugins.push(new plugin.Tutorial());
|
||||
|
||||
//optionnal plugins
|
||||
#if plugins
|
||||
plugins.push( new hosted.HostedPlugIn() );
|
||||
plugins.push( new pro.ProPlugIn() );
|
||||
plugins.push( new connector.ConnectorPlugIn() );
|
||||
plugins.push( new pro.LemonwayEC() );
|
||||
plugins.push( new who.WhoPlugIn() );
|
||||
#end
|
||||
|
||||
super.mainLoop();
|
||||
}
|
||||
|
||||
public function getCurrentGroup(){
|
||||
if (session == null) return null;
|
||||
if (session.data == null ) return null;
|
||||
var a = session.data.amapId;
|
||||
if (a == null) {
|
||||
return null;
|
||||
}else {
|
||||
return db.Amap.manager.get(a,false);
|
||||
}
|
||||
}
|
||||
|
||||
override function beforeDispatch() {
|
||||
|
||||
//send "current page" event
|
||||
event( Page(this.uri) );
|
||||
|
||||
super.beforeDispatch();
|
||||
}
|
||||
|
||||
public function getPlugin(name:String):sugoi.plugin.IPlugIn {
|
||||
for (p in plugins) {
|
||||
if (p.getName() == name) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function log(t:Dynamic) {
|
||||
if(App.config.DEBUG) {
|
||||
neko.Web.logMessage(Std.string(t)); //write in Apache error log
|
||||
#if weblog
|
||||
Weblog.log(t); //write en Weblog console (https://lib.haxe.org/p/weblog/)
|
||||
#end
|
||||
}
|
||||
}
|
||||
|
||||
public function event(e:Event) {
|
||||
if(e==null) return null;
|
||||
this.eventDispatcher.dispatch(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate DB objects fields in forms
|
||||
*/
|
||||
public static function getTranslationArray() {
|
||||
//var t = sugoi.i18n.Locale.texts;
|
||||
var out = new Map<String,String>();
|
||||
//out.set("firstName", t._("First name") );
|
||||
//out.set("lastName", t._("Last name"));
|
||||
out.set("firstName2", "Prénom du conjoint");
|
||||
out.set("lastName2", "Nom du conjoint");
|
||||
out.set("email2", "e-mail du conjoint");
|
||||
//out.set("pass", t._("Password") );
|
||||
//out.set("address1", t._("address") );
|
||||
//out.set("address2", t._("address") );
|
||||
out.set("zipCode", "code postal");
|
||||
out.set("city", "commune");
|
||||
out.set("phone", "téléphone");
|
||||
out.set("phone2", "téléphone du conjoint");
|
||||
out.set("select", "sélectionnez");
|
||||
out.set("contract", "Contrat");
|
||||
out.set("place", "Lieu");
|
||||
out.set("name", "Nom");
|
||||
out.set("cdate", "Date d'entrée dans le groupe");
|
||||
out.set("quantity", "Quantité");
|
||||
out.set("paid", "Payé");
|
||||
out.set("user2", "(facultatif) partagé avec ");
|
||||
out.set("product", "Produit");
|
||||
out.set("user", "Adhérent");
|
||||
out.set("txtIntro", "Texte de présentation du groupe");
|
||||
out.set("txtHome", "Texte en page d'accueil pour les adhérents connectés");
|
||||
out.set("txtDistrib", "Texte à faire figurer sur les listes d'émargement lors des distributions");
|
||||
out.set("extUrl", "URL du site du groupe.");
|
||||
|
||||
out.set("distributor1", "Distributeur 1");
|
||||
out.set("distributor2", "Distributeur 2");
|
||||
out.set("distributor3", "Distributeur 3");
|
||||
out.set("distributor4", "Distributeur 4");
|
||||
out.set("distributorNum", "Nbre de distributeurs nécessaires (de 0 à 4)");
|
||||
|
||||
out.set("startDate", "Date de début");
|
||||
out.set("endDate", "Date de fin");
|
||||
|
||||
out.set("orderStartDate", "Date ouverture des commandes");
|
||||
out.set("orderEndDate", "Date fermeture des commandes");
|
||||
out.set("openingHour", "Heure d'ouverture");
|
||||
out.set("closingHour", "Heure de fermeture");
|
||||
|
||||
out.set("date", "Date de distribution");
|
||||
out.set("active", "actif");
|
||||
|
||||
out.set("contact", "Reponsable");
|
||||
out.set("vendor", "Producteur");
|
||||
out.set("text", "Texte");
|
||||
out.set("flags", "Options");
|
||||
out.set("4h", "Recevoir des notifications par email 4h avant les distributions");
|
||||
out.set("HasEmailNotif4h", "Recevoir des notifications par email 4h avant les distributions");
|
||||
out.set("24h", "Recevoir des notifications par email 24h avant les distributions");
|
||||
out.set("HasEmailNotif24h", "Recevoir des notifications par email 24h avant les distributions");
|
||||
out.set("Ouverture", "Recevoir des notifications par email pour l'ouverture des commandes");
|
||||
out.set("Tuto", "Activer tutoriels");
|
||||
out.set("HasMembership", "Gestion des adhésions");
|
||||
out.set("DayOfWeek", "Jour de la semaine");
|
||||
out.set("Monday", "Lundi");
|
||||
out.set("Tuesday", "Mardi");
|
||||
out.set("Wednesday", "Mercredi");
|
||||
out.set("Thursday", "Jeudi");
|
||||
out.set("Friday", "Vendredi");
|
||||
out.set("Saturday", "Samedi");
|
||||
out.set("Sunday", "Dimanche");
|
||||
out.set("cycleType", "Récurrence");
|
||||
out.set("Weekly", "hebdomadaire");
|
||||
out.set("Monthly", "mensuelle");
|
||||
out.set("BiWeekly", "toutes les deux semaines");
|
||||
out.set("TriWeekly", "toutes les 3 semaines");
|
||||
out.set("price", "prix TTC");
|
||||
out.set("uname", "Nom");
|
||||
out.set("pname", "Produit");
|
||||
out.set("organic", "Agriculture biologique");
|
||||
out.set("hasFloatQt", "Autoriser quantités \"à virgule\"");
|
||||
|
||||
out.set("membershipRenewalDate", "Adhésions : Date de renouvellement");
|
||||
out.set("membershipPrice", "Adhésions : Coût de l'adhésion");
|
||||
out.set("UsersCanOrder", "Les adhérents peuvent saisir leur commande en ligne");
|
||||
out.set("StockManagement", "Gestion des stocks");
|
||||
out.set("contact", "Responsable");
|
||||
out.set("PercentageOnOrders", "Ajouter des frais au pourcentage de la commande");
|
||||
out.set("percentageValue", "Pourcentage des frais");
|
||||
out.set("percentageName", "Libellé pour ces frais");
|
||||
out.set("fees", "frais");
|
||||
out.set("AmapAdmin", "Administrateur du groupe");
|
||||
out.set("Membership", "Accès à la gestion des adhérents");
|
||||
out.set("Messages", "Accès à la messagerie");
|
||||
out.set("vat", "TVA");
|
||||
out.set("desc", "Description");
|
||||
out.set("ShopMode", "Mode boutique");
|
||||
out.set("ComputeMargin", "Appliquer une marge à la place des pourcentages");
|
||||
out.set("ShopCategoriesFromTaxonomy", "Catégoriser automatiquement les produits");
|
||||
out.set("HidePhone", "Masquer le téléphone du responsable sur la page publique");
|
||||
out.set("PhoneRequired", "Saisie du numéro de téléphone obligatoire");
|
||||
out.set("ref", "Référence");
|
||||
out.set("linkText", "Intitulé du lien");
|
||||
out.set("linkUrl", "URL du lien");
|
||||
|
||||
out.set("Amap", "AMAP");
|
||||
out.set("GroupedOrders", "Groupement d'achat");
|
||||
out.set("ProducerDrive", "Collectif de producteurs");
|
||||
out.set("FarmShop", "Vente à la ferme");
|
||||
|
||||
out.set("regOption", "Inscription de nouveaux adhérents");
|
||||
out.set("Closed", "Fermé : Le coordinateur ajoute les nouveaux adhérents");
|
||||
out.set("WaitingList", "Liste d'attente");
|
||||
out.set("Open", "Ouvert : tout le monde peut s'inscrire");
|
||||
out.set("Full", "Complet : Le groupe n'accepte plus de nouveaux adhérents");
|
||||
out.set("percent", "Pourcentage");
|
||||
out.set("pinned", "Mets en avant les produits");
|
||||
|
||||
out.set("CagetteNetwork", "Me lister dans l'annuaire des groupes Cagette.net");
|
||||
out.set("unitType", "Unité");
|
||||
out.set("qt", "Quantité");
|
||||
out.set("Unit", "Pièce");
|
||||
out.set("Kilogram", "Kilogrammes");
|
||||
out.set("Gram", "Grammes");
|
||||
out.set("Litre", "Litres");
|
||||
out.set("htPrice", "Prix H.T");
|
||||
out.set("amount", "Montant");
|
||||
|
||||
out.set("HasPayments", "Gestion des paiements");
|
||||
|
||||
out.set("byMember", "Par adhérent");
|
||||
out.set("byProduct", "Par produit");
|
||||
|
||||
out.set("variablePrice", "Variable price based on weight");
|
||||
return out;
|
||||
}
|
||||
|
||||
public function populateAmapMembers() {
|
||||
return user.amap.getMembersFormElementData();
|
||||
}
|
||||
|
||||
public static function getMailer():sugoi.mail.IMailer {
|
||||
|
||||
var mailer : sugoi.mail.IMailer = new sugoi.mail.BufferedMailer();
|
||||
|
||||
if(App.config.DEBUG){
|
||||
|
||||
//Dev env : emails are written to tmp folder
|
||||
mailer = new sugoi.mail.DebugMailer();
|
||||
}else{
|
||||
|
||||
if (sugoi.db.Variable.get("mailer") == null){
|
||||
var msg = sugoi.i18n.Locale.texts._("Please configure the email settings in a <href='/admin/emails'>this section</a>");
|
||||
throw sugoi.ControllerAction.ErrorAction("/",msg);
|
||||
}
|
||||
|
||||
if (sugoi.db.Variable.get("mailer") == "mandrill"){
|
||||
//Buffered emails with Mandrill
|
||||
untyped mailer.defineFinalMailer("mandrill");
|
||||
}else{
|
||||
//Buffered emails with SMTP
|
||||
untyped mailer.defineFinalMailer("smtp");
|
||||
}
|
||||
}
|
||||
return mailer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email
|
||||
*/
|
||||
public static function sendMail(m:sugoi.mail.Mail, ?group:db.Amap, ?listId:String, ?sender:db.User){
|
||||
|
||||
if (group == null) group = App.current.user == null ? null:App.current.user.getAmap();
|
||||
|
||||
current.event(SendEmail(m));
|
||||
|
||||
var params = group==null ? null : {remoteId:group.id};
|
||||
|
||||
getMailer().send(m,params,function(o){});
|
||||
|
||||
}
|
||||
|
||||
public static function quickMail(to:String, subject:String, html:String,?group:db.Amap){
|
||||
var e = new sugoi.mail.Mail();
|
||||
e.setSubject(subject);
|
||||
e.setRecipient(to);
|
||||
e.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
var html = App.current.processTemplate("mail/message.mtt", {text:html,group:group});
|
||||
e.setHtmlBody(html);
|
||||
App.sendMail(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* process a template and returns the generated string
|
||||
* @param tpl
|
||||
* @param ctx
|
||||
*/
|
||||
public function processTemplate(tpl:String, ctx:Dynamic):String {
|
||||
|
||||
Reflect.setField(ctx, 'HOST', App.config.HOST);
|
||||
Reflect.setField(ctx, 'hDate', App.current.view.hDate);
|
||||
//i18n functions
|
||||
ctx._ = App.current.view._;
|
||||
ctx.__ = App.current.view.__;
|
||||
|
||||
var tpl = loadTemplate(tpl);
|
||||
var html = tpl.execute(ctx);
|
||||
#if php
|
||||
if ( html.substr(0, 4) == "null") html = html.substr(4);
|
||||
#end
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package;
|
||||
|
||||
typedef CalEvent = {
|
||||
|
||||
name:String,
|
||||
//start:Date,
|
||||
//end:Date,
|
||||
color:Int,
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendar utility
|
||||
*
|
||||
* @author fbarbut<francois.barbut@gmail.com>
|
||||
*/
|
||||
class Calendar
|
||||
{
|
||||
|
||||
public static var COLOR_CONTRACT = 0xC91F25;
|
||||
public static var COLOR_DELIVERY = 0x7BAD1C;
|
||||
public static var COLOR_ORDER = 0xFF9615;
|
||||
|
||||
public function new()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static function getMonthViewMap():Map<String,Array<CalEvent>> {
|
||||
|
||||
var n = Date.now();
|
||||
var m = n.getMonth();//0-11
|
||||
var pointer = Date.now();
|
||||
|
||||
var out = new Map<String,Array<CalEvent>>();
|
||||
|
||||
//find last monday
|
||||
for ( i in 0...40) {
|
||||
if ( pointer.getDay() == 1 ) {
|
||||
break;
|
||||
}
|
||||
pointer = DateTools.delta(pointer, -1000.0 * 60 * 60 * 24);
|
||||
|
||||
}
|
||||
|
||||
//go ahead for at least 27 days
|
||||
for ( i in 0...28) {
|
||||
out.set( pointer.toString().substr(0, 10), [] );
|
||||
pointer = DateTools.delta(pointer, 1000.0 * 60 * 60 * 24);
|
||||
}
|
||||
|
||||
//find end
|
||||
for ( i in 0...40) {
|
||||
//if ( pointer.getDay() == 1 && pointer.getMonth() != m) break;
|
||||
out.set( pointer.toString().substr(0, 10), [] );
|
||||
pointer = DateTools.delta(pointer, 1000.0 * 60 * 60 * 24);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* get ordered CalEvents from an unordered stringMap
|
||||
*/
|
||||
public static function mapToArray(input : Map<String,Array<CalEvent>>) : Array<{d:Date,events:Array<CalEvent>}> {
|
||||
|
||||
var keys = [];
|
||||
for (k in input.keys()) keys.push(k);
|
||||
keys.sort(function(a, b) {
|
||||
return Math.round(Date.fromString(a).getTime()/1000) - Math.round(Date.fromString(b).getTime()/1000);
|
||||
});
|
||||
|
||||
|
||||
var out = [];
|
||||
for ( k in keys) {
|
||||
|
||||
var x = input.get(k);
|
||||
out.push( { d:Date.fromString(k),events:x } );
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
package;
|
||||
import Common;
|
||||
using tools.ObjectListTool;
|
||||
using Lambda;
|
||||
using tools.ObjectListTool;
|
||||
|
||||
/**
|
||||
* MultiDistrib represents many db.Distribution
|
||||
which happen on the same day + same place.
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class MultiDistrib
|
||||
{
|
||||
public var distributions : Array<db.Distribution>;
|
||||
public var contracts : Array<db.Contract>;
|
||||
//public var actions : Array<Link>;
|
||||
public var extraHtml : String;
|
||||
public var type : Null<Int>; //contract type, both contract types cannot be mixed in a same multidistrib.
|
||||
|
||||
public function new(){
|
||||
distributions = [];
|
||||
contracts = [];
|
||||
extraHtml = "";
|
||||
//actions = [];
|
||||
}
|
||||
|
||||
public static function get(date:Date, place:db.Place,contractType:Int){
|
||||
var m = new MultiDistrib();
|
||||
|
||||
var start = tools.DateTool.setHourMinute(date, 0, 0);
|
||||
var end = tools.DateTool.setHourMinute(date, 23, 59);
|
||||
|
||||
var contracts = place.amap.getContracts().array();
|
||||
|
||||
//filter by type
|
||||
if(contractType==db.Contract.TYPE_VARORDER){
|
||||
for(c in contracts.copy() ){
|
||||
if(c.type!=db.Contract.TYPE_VARORDER) contracts.remove(c);
|
||||
}
|
||||
}else if(contractType==db.Contract.TYPE_CONSTORDERS){
|
||||
for(c in contracts.copy() ){
|
||||
if(c.type!=db.Contract.TYPE_CONSTORDERS) contracts.remove(c);
|
||||
}
|
||||
}
|
||||
var cids = contracts.getIds();
|
||||
m.distributions = db.Distribution.manager.search(($contractId in cids) && ($date >= start) && ($date <= end) && $place==place, { orderBy:date }, false).array();
|
||||
m.type = contractType;
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
Get multidistribs from a time range + place + type
|
||||
**/
|
||||
public static function getFromTimeRange(group:db.Amap,from:Date,to:Date,?contractType:Int):Array<MultiDistrib>{
|
||||
var multidistribs = [];
|
||||
var start = tools.DateTool.setHourMinute(from, 0, 0);
|
||||
var end = tools.DateTool.setHourMinute(to, 23, 59);
|
||||
var cids = group.getContracts().getIds();
|
||||
var distributions = db.Distribution.manager.search(($contractId in cids) && ($date >= start) && ($date <= end) , { orderBy:date }, false).array();
|
||||
|
||||
//sort by day-place-type
|
||||
var multidistribs = new Map<String,MultiDistrib>();
|
||||
for ( d in distributions){
|
||||
|
||||
//filter by contractType
|
||||
if(contractType!=null){
|
||||
if(d.contract.type!=contractType) continue;
|
||||
}
|
||||
|
||||
var key = d.getKey() + "-" + d.contract.type;
|
||||
|
||||
if(multidistribs[key]==null){
|
||||
var m = new MultiDistrib();
|
||||
m.distributions.push(d);
|
||||
m.type = d.contract.type;
|
||||
multidistribs[key] = m;
|
||||
}else{
|
||||
multidistribs[key].distributions.push(d);
|
||||
}
|
||||
}
|
||||
var multidistribs = Lambda.array(multidistribs);
|
||||
|
||||
//trigger event
|
||||
for(md in multidistribs) App.current.event(MultiDistribEvent(md));
|
||||
|
||||
//sort by date desc
|
||||
multidistribs.sort(function(x,y){
|
||||
return Math.round( x.getDate().getTime()/1000 ) - Math.round(y.getDate().getTime()/1000 );
|
||||
});
|
||||
|
||||
return multidistribs;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO : refacto this to use getFromTimeRange();
|
||||
*/
|
||||
/*public static function getNextMultiDeliveries(group:db.Amap){
|
||||
|
||||
var out = new Map < String, {
|
||||
place:db.Place, //common delivery place
|
||||
startDate:Date, //global delivery start
|
||||
endDate:Date, //global delivery stop
|
||||
orderStartDate:Date, //global orders opening date
|
||||
orderEndDate:Date, //global orders closing date
|
||||
active:Bool,
|
||||
products:Array<ProductInfo>, //available products ( if no order )
|
||||
myOrders:Array<{distrib:db.Distribution,orders:Array<UserOrder>}> //my orders
|
||||
}>();
|
||||
|
||||
var n = Date.now();
|
||||
var now = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0);
|
||||
|
||||
var contracts = db.Contract.getActiveContracts(group);
|
||||
var cids = Lambda.map(contracts, function(p) return p.id);
|
||||
|
||||
//var pids = Lambda.map(db.Product.manager.search($contractId in cids,false), function(x) return x.id);
|
||||
//var out = UserContract.manager.search(($userId == id || $userId2 == id) && $productId in pids, lock);
|
||||
|
||||
//available deliveries
|
||||
var inSixMonth = DateTools.delta(now, 1000.0 * 60 * 60 * 24 * 30 * 6);
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= now && $date <= inSixMonth , { orderBy:date }, false);
|
||||
|
||||
for (d in distribs) {
|
||||
|
||||
//we had the distribution key ( place+date ) and the contract type in order to separate constant and varying contracts
|
||||
var key = d.getKey() + "|" + d.contract.type;
|
||||
var o = out.get(key);
|
||||
if (o == null) o = {place:d.place, startDate:d.date, active:null, endDate:d.end, products:[], myOrders:[], orderStartDate:null,orderEndDate:null};
|
||||
|
||||
//user orders
|
||||
var orders = [];
|
||||
if(App.current.user!=null) orders = d.contract.getUserOrders(App.current.user,d);
|
||||
if (orders.length > 0){
|
||||
o.myOrders.push({distrib:d,orders:service.OrderService.prepare(orders)});
|
||||
}else{
|
||||
//no "order block" if no shop mode
|
||||
if (!group.hasShopMode() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//if its a constant order contract, skip this delivery
|
||||
if (d.contract.type == db.Contract.TYPE_CONSTORDERS){
|
||||
continue;
|
||||
}
|
||||
|
||||
//products preview if no orders
|
||||
for ( p in d.contract.getProductsPreview(9)){
|
||||
o.products.push( p.infos(null,false) );
|
||||
}
|
||||
}
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_VARORDER){
|
||||
|
||||
//old distribs may have an empty orderStartDate
|
||||
if (d.orderStartDate == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//if order opening is more far than 1 month, skip it
|
||||
// if (d.orderStartDate.getTime() > inOneMonth.getTime() ){
|
||||
// continue;
|
||||
// }
|
||||
|
||||
//display closest opening date
|
||||
if (o.orderStartDate == null){
|
||||
o.orderStartDate = d.orderStartDate;
|
||||
}else if (o.orderStartDate.getTime() > d.orderStartDate.getTime()){
|
||||
o.orderStartDate = d.orderStartDate;
|
||||
}
|
||||
|
||||
//display most far closing date
|
||||
if (o.orderEndDate == null){
|
||||
o.orderEndDate = d.orderEndDate;
|
||||
}else if (o.orderEndDate.getTime() < d.orderEndDate.getTime()){
|
||||
o.orderEndDate = d.orderEndDate;
|
||||
}
|
||||
|
||||
out.set(key, o);
|
||||
|
||||
}else{
|
||||
//in constant orders, add block only if there is an order
|
||||
if(o.myOrders.length>0) out.set(key, o);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//shuffle and limit product lists
|
||||
for ( o in out){
|
||||
o.products = thx.Arrays.shuffle(o.products);
|
||||
o.products = o.products.slice(0, 9);
|
||||
}
|
||||
|
||||
//decide if active or not
|
||||
var now = Date.now();
|
||||
for( o in out){
|
||||
|
||||
if (o.orderStartDate == null) continue; //constant orders
|
||||
|
||||
if (now.getTime() >= o.orderStartDate.getTime() && now.getTime() <= o.orderEndDate.getTime() ){
|
||||
//order currently open
|
||||
o.active = true;
|
||||
|
||||
}else {
|
||||
o.active = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return Lambda.array(out);
|
||||
}*/
|
||||
|
||||
public function getPlace(){
|
||||
if(distributions.length==0) throw "This multidistrib is empty";
|
||||
return distributions[0].place;
|
||||
}
|
||||
|
||||
public function getDate(){
|
||||
if(distributions.length==0) throw "This multidistrib is empty";
|
||||
return distributions[0].date;
|
||||
}
|
||||
|
||||
public function getEndDate(){
|
||||
if(distributions.length==0) throw "This multidistrib is empty";
|
||||
return distributions[0].end;
|
||||
}
|
||||
|
||||
public function getProductsExcerpt():Array<ProductInfo>{
|
||||
var key = "productsExcerpt-"+getKey();
|
||||
var cache:Array<Int> = sugoi.db.Cache.get(key);
|
||||
if(cache!=null){
|
||||
var out = [];
|
||||
//try{
|
||||
for( pid in cache.array()){
|
||||
var p = db.Product.manager.get(pid,false);
|
||||
if(p!=null) out.push(p.infos());
|
||||
}
|
||||
//}catch(e:Dynamic){
|
||||
// sugoi.db.Cache.destroy(key);
|
||||
// }
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
var products = [];
|
||||
for( d in distributions){
|
||||
for ( p in d.contract.getProductsPreview(9)){
|
||||
products.push( p.infos(null,false) );
|
||||
}
|
||||
}
|
||||
products = thx.Arrays.shuffle(products);
|
||||
products = products.slice(0, 9);
|
||||
sugoi.db.Cache.set(key, products.map(function(p)return p.id).array(), 3600 );
|
||||
return products;
|
||||
|
||||
}
|
||||
|
||||
public function userHasOrders(user:db.User):Bool{
|
||||
if(user==null) return false;
|
||||
for ( d in distributions){
|
||||
if(d.getUserOrders(user).length>0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
orders currently open ?
|
||||
**/
|
||||
public function isActive(){
|
||||
|
||||
if (getOrdersStartDate() == null) return false; //constant orders
|
||||
|
||||
var now = Date.now();
|
||||
if (now.getTime() >= getOrdersStartDate().getTime() && now.getTime() <= getOrdersEndDate().getTime() ){
|
||||
return true;
|
||||
}else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrdersStartDate(){
|
||||
var date = null;
|
||||
|
||||
for( d in distributions ){
|
||||
if(d.orderStartDate==null) continue;
|
||||
//display closest opening date
|
||||
if (date == null){
|
||||
date = d.orderStartDate;
|
||||
}else if (date.getTime() > d.orderStartDate.getTime()){
|
||||
date = d.orderStartDate;
|
||||
}
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/*public function hasOnlyConstantOrders(){
|
||||
for(d in distributions){
|
||||
if( d.contract.type==db.Contract.TYPE_VARORDER ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}*/
|
||||
|
||||
public function getOrdersEndDate(){
|
||||
var date = null;
|
||||
|
||||
for( d in distributions ){
|
||||
if(d.orderEndDate==null) continue;
|
||||
//display most far closing date
|
||||
if (date == null){
|
||||
date = d.orderEndDate;
|
||||
}else if (date.getTime() < d.orderEndDate.getTime()){
|
||||
date = d.orderEndDate;
|
||||
}
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all orders involved in this multidistrib
|
||||
*/
|
||||
public function getOrders(){
|
||||
var out = [];
|
||||
for ( d in distributions){
|
||||
out = out.concat(d.getOrders().array());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get orders for a user in this multidistrib
|
||||
* @param user
|
||||
*/
|
||||
public function getUserOrders(user:db.User){
|
||||
var out = [];
|
||||
for ( d in distributions){
|
||||
var pids = Lambda.map( d.contract.getProducts(false), function(x) return x.id);
|
||||
var userOrders = db.UserContract.manager.search( $userId == user.id && $distributionId==d.id && $productId in pids , false);
|
||||
for( o in userOrders ){
|
||||
out.push(o);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public function getUsers(){
|
||||
var users = [];
|
||||
for ( o in getOrders()) users.push(o.user);
|
||||
return users.deduplicate();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function isConfirmed():Bool{
|
||||
//cannot be in future
|
||||
if(getDate().getTime()>Date.now().getTime()) return false;
|
||||
|
||||
return Lambda.count( distributions, function(d) return d.validated) == distributions.length;
|
||||
}
|
||||
|
||||
public function checkConfirmed():Bool{
|
||||
var orders = getOrders();
|
||||
var c = Lambda.count( orders, function(d) return d.paid) == orders.length;
|
||||
|
||||
if (c){
|
||||
for ( d in distributions){
|
||||
if (!d.validated){
|
||||
d.lock();
|
||||
d.validated = true;
|
||||
d.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
//get key by date-place-type
|
||||
public function getKey(){
|
||||
return distributions[0].getKey() + "-" + distributions[0].contract.type;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.Context;
|
||||
import StringTools;
|
||||
|
||||
class MyMacros {
|
||||
/* based on
|
||||
http://code.haxe.org/category/macros/add-git-commit-hash-in-build.html
|
||||
http://stackoverflow.com/questions/8611486/how-to-get-the-last-commit-date-for-a-bunch-of-files-in-git*/
|
||||
public static macro function getGitCommitDate():haxe.macro.ExprOf<String> {
|
||||
#if !display
|
||||
var process = new sys.io.Process('git', ['log', '-1', '--format=%ci']);
|
||||
if (process.exitCode() != 0) {
|
||||
var message = process.stderr.readAll().toString();
|
||||
var pos = haxe.macro.Context.currentPos();
|
||||
Context.error("Cannot execute `git log -1 --format=%ci`. " + message, pos);
|
||||
}
|
||||
|
||||
// read the output of the process
|
||||
var commitDate:String = process.stdout.readLine();
|
||||
commitDate = StringTools.replace(commitDate, " ", ".");
|
||||
commitDate = StringTools.replace(commitDate, "-", ".");
|
||||
commitDate = StringTools.replace(commitDate, ":", ".");
|
||||
commitDate = commitDate.substr(0,16);
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{commitDate};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case returning an
|
||||
// empty string is good enough; We don't want to call git on every hint.
|
||||
var commitDate:String = "";
|
||||
return macro $v{commitDate};
|
||||
#end
|
||||
}
|
||||
|
||||
public static macro function getGitShortSHA():haxe.macro.ExprOf<String> {
|
||||
#if !display
|
||||
var process = new sys.io.Process('git', ['log', '-1', '--format=%h']);
|
||||
if (process.exitCode() != 0) {
|
||||
var message = process.stderr.readAll().toString();
|
||||
var pos = haxe.macro.Context.currentPos();
|
||||
Context.error("Cannot execute `git log -1 --format=%h`. " + message, pos);
|
||||
}
|
||||
|
||||
// read the output of the process
|
||||
var commitShortSHA:String = process.stdout.readLine();
|
||||
commitShortSHA = "C"+commitShortSHA;
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{commitShortSHA};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case returning an
|
||||
// empty string is good enough; We don't want to call git on every hint.
|
||||
var commitShortSHA:String = "xxxxxxx";
|
||||
return macro $v{commitShortSHA};
|
||||
#end
|
||||
}
|
||||
|
||||
public static macro function getGitBranch():haxe.macro.ExprOf<String> {
|
||||
#if !display
|
||||
var process = new sys.io.Process('git', ['symbolic-ref', 'HEAD', '--short']);
|
||||
if (process.exitCode() != 0) {
|
||||
var message = process.stderr.readAll().toString();
|
||||
var pos = haxe.macro.Context.currentPos();
|
||||
Context.error("Cannot execute `git symbolic-ref HEAD --short`. " + message, pos);
|
||||
}
|
||||
|
||||
// read the output of the process
|
||||
var commitBranch:String = process.stdout.readLine();
|
||||
commitBranch = StringTools.replace(commitBranch, "-", ".");
|
||||
commitBranch = StringTools.replace(commitBranch, "_", ".");
|
||||
|
||||
// Generates a string expression
|
||||
return macro $v{commitBranch};
|
||||
#else
|
||||
// `#if display` is used for code completion. In this case returning an
|
||||
// empty string is good enough; We don't want to call git on every hint.
|
||||
var commitBranch:String = "";
|
||||
return macro $v{commitBranch};
|
||||
#end
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+318
@@ -0,0 +1,318 @@
|
||||
using Std;
|
||||
import Common;
|
||||
import haxe.Utf8;
|
||||
import tools.ArrayTool;
|
||||
|
||||
class View extends sugoi.BaseView {
|
||||
|
||||
var t : sugoi.i18n.GetText;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
this.Std = Std;
|
||||
this.Date = Date;
|
||||
this.Web = sugoi.Web;
|
||||
this.Lambda = Lambda;
|
||||
this.VERSION = App.VERSION.toString();
|
||||
this.ArrayTool = ArrayTool;
|
||||
this.t = sugoi.i18n.Locale.texts;
|
||||
}
|
||||
|
||||
public function count(i) {
|
||||
return Lambda.count(i);
|
||||
}
|
||||
|
||||
public function abs(n){
|
||||
return Math.abs(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* init view in main loop, just before rendering
|
||||
*/
|
||||
override function init() {
|
||||
super.init();
|
||||
|
||||
//tuto widget display
|
||||
var u = App.current.user;
|
||||
if (u!=null && u.tutoState!=null) {
|
||||
//trace("view init "+u.tutoState.name+" , "+u.tutoState.step);
|
||||
this.displayTuto(u.tutoState.name, u.tutoState.step);
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentGroup(){
|
||||
return App.current.getCurrentGroup();
|
||||
}
|
||||
|
||||
|
||||
function getUser(uid:Int):db.User {
|
||||
return db.User.manager.get(uid, false);
|
||||
}
|
||||
|
||||
function getProduct (pid:Int){
|
||||
return db.Product.manager.get(pid, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Round a number to r digits after coma.
|
||||
*
|
||||
* @param n
|
||||
* @param r
|
||||
*/
|
||||
public function roundTo(n:Float, r:Int):Float {
|
||||
return Math.round(n * Math.pow(10,r)) / Math.pow(10,r) ;
|
||||
}
|
||||
|
||||
|
||||
public function color(id:Int) {
|
||||
if (id == null) throw "color cant be null";
|
||||
//try{
|
||||
return intToHex(db.CategoryGroup.COLORS[id]);
|
||||
//}catch (e:Dynamic) return "#000000";
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a RVB color from Int to Hexa
|
||||
* @param c
|
||||
* @param leadingZeros=6
|
||||
*/
|
||||
public function intToHex(c:Int, ?leadingZeros=6):String {
|
||||
var h = StringTools.hex(c);
|
||||
while (h.length<leadingZeros)
|
||||
h="0"+h;
|
||||
return "#"+h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format prices
|
||||
*/
|
||||
public function formatNum(n:Float):String {
|
||||
if (n == null) return "";
|
||||
|
||||
//round with 2 digits after comma
|
||||
var out = Std.string(roundTo(n, 2));
|
||||
|
||||
//add a zero, 1,8-->1,80
|
||||
if (out.indexOf(".")!=-1 && out.split(".")[1].length == 1) out = out +"0";
|
||||
|
||||
//french : replace point by comma
|
||||
return out.split(".").join(",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Price per Kg/Liter...
|
||||
* @param qt
|
||||
* @param unit
|
||||
*/
|
||||
public function pricePerUnit(price:Float,qt:Float, unit:Unit){
|
||||
if (unit==null || qt == null || qt == 0 || price==null || price==0) return "";
|
||||
var _price = price / qt;
|
||||
var _unit = unit;
|
||||
|
||||
//turn small prices in Kg
|
||||
if (_price < 1 ){
|
||||
switch(unit){
|
||||
case Gram:
|
||||
_price *= 1000;
|
||||
_unit = Kilogram;
|
||||
case Centilitre:
|
||||
_price *= 100;
|
||||
_unit = Litre;
|
||||
default :
|
||||
}
|
||||
}
|
||||
return formatNum(_price) + " " + currency() + "/" + this.unit(_unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* clean numbers in views
|
||||
* to avoid bugs like : 13.79 - 13.79 = 1.77635683940025e-15
|
||||
*/
|
||||
public function numClean(f:Float):Float{
|
||||
return Math.round(f * 100) / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* max length for strings, usefull for tables
|
||||
*/
|
||||
public function short(text:String, length:Int){
|
||||
if (Utf8.length(text) > length){
|
||||
|
||||
return Utf8.sub(text,0, length)+"…";
|
||||
}else{
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public function isToday(d:Date) {
|
||||
var n = Date.now();
|
||||
return d.getDate() == n.getDate() && d.getMonth() == n.getMonth() && d.getFullYear() == n.getFullYear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a measuring unit
|
||||
*/
|
||||
public function unit(u:Unit,?plural=false){
|
||||
t = sugoi.i18n.Locale.texts;
|
||||
return switch(u){
|
||||
case Kilogram: t._("Kg.||kilogramms");
|
||||
case Gram: t._("g.||gramms");
|
||||
case null,Piece: if(plural) t._("pieces||unit of a product)") else t._("piece||unit of a product)");
|
||||
case Litre: t._("L.||liter");
|
||||
case Centilitre: t._("cl.||centiliter");
|
||||
}
|
||||
}
|
||||
|
||||
public function currency(){
|
||||
if (App.current.user == null || App.current.user.amap == null){
|
||||
return "€";
|
||||
}else{
|
||||
return App.current.user.amap.getCurrency();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static var DAYS = null;
|
||||
public static var MONTHS = null;
|
||||
public static var HOURS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
|
||||
public static var MINUTES = [0,5,10,15,20,25,30,35,40,45,50,55];
|
||||
|
||||
|
||||
public function initDate(){
|
||||
t = sugoi.i18n.Locale.texts;
|
||||
DAYS = [t._("Sunday"), t._("Monday"), t._("Tuesday"), t._("Wednesday"), t._("Thursday"), t._("Friday"), t._("Saturday")];
|
||||
MONTHS = [t._("January"), t._("February"), t._("March"), t._("April"), t._("May"), t._("June"), t._("July"), t._("August"), t._("September"), t._("October"), t._("November"), t._("December")];
|
||||
this.DAYS = DAYS;
|
||||
this.MONTHS = MONTHS;
|
||||
this.HOURS = HOURS;
|
||||
this.MINUTES = MINUTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* human readable date + time
|
||||
*/
|
||||
public function hDate(date:Date):String {
|
||||
if (date == null) return t._("no date set");
|
||||
if (DAYS == null) initDate();
|
||||
|
||||
var out = DAYS[date.getDay()] + " " + date.getDate() + " " + MONTHS[date.getMonth()];
|
||||
out += " " + date.getFullYear();
|
||||
if ( date.getHours() != 0 || date.getMinutes() != 0){
|
||||
|
||||
out += " " + sugoi.i18n.Locale.texts._("at||time : at 12:30") + " " + StringTools.lpad(Std.string(date.getHours()), "0", 2) + ":" + StringTools.lpad(Std.string(date.getMinutes()), "0", 2);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human readable hour
|
||||
*/
|
||||
public function hHour(date:Date){
|
||||
return StringTools.lpad(date.getHours().string(), "0", 2) + ":" + StringTools.lpad(date.getMinutes().string(), "0", 2);
|
||||
}
|
||||
|
||||
public function oHour(hour:Int,min:Int){
|
||||
return StringTools.lpad(hour.string(), "0", 2) + ":" + StringTools.lpad(min.string(), "0", 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* human readable date
|
||||
*/
|
||||
public function dDate(date:Date):String {
|
||||
if (date == null) return t._("no date set");
|
||||
if (DAYS == null) initDate();
|
||||
|
||||
return DAYS[date.getDay()] + " " + date.getDate() + " " + MONTHS[date.getMonth()] + " " + date.getFullYear();
|
||||
}
|
||||
|
||||
|
||||
public function getDate(date:Date) {
|
||||
if (date == null) throw "date is null";
|
||||
if (DAYS == null) initDate();
|
||||
|
||||
return {
|
||||
dow: DAYS[date.getDay()],
|
||||
d : date.getDate(),
|
||||
m: MONTHS[date.getMonth()],
|
||||
y: date.getFullYear(),
|
||||
h: StringTools.lpad(Std.string(date.getHours()),"0",2),
|
||||
i: StringTools.lpad(Std.string(date.getMinutes()),"0",2)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getProductImage(e):String {
|
||||
return Std.string(e).substr(2).toLowerCase()+".png";
|
||||
}
|
||||
|
||||
public function prepare(orders:Iterable<db.UserContract>){
|
||||
return service.OrderService.prepare(orders);
|
||||
}
|
||||
|
||||
|
||||
public function displayTuto(tuto:String, step:Int) {
|
||||
if (tuto == null) return;
|
||||
var t = plugin.Tutorial.all().get(tuto);
|
||||
|
||||
//check if we are on the correct page (last step page)
|
||||
//otherwise the popovers could be displayed on wrong elements
|
||||
var previous = t.steps[step - 1];
|
||||
if (previous != null) {
|
||||
switch(previous.action) {
|
||||
case TAPage(uri):
|
||||
var here = sugoi.Web.getURI();
|
||||
if (!plugin.Tutorial.match(uri,here)) {
|
||||
return;
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
this.tuto = { name:tuto, step:step };
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie 0 si c'est user.firstName qui est connecté,
|
||||
* renvoie 1 si c'est user.firstName2 qui est connecté
|
||||
* @return
|
||||
*/
|
||||
public function whichUser():Int {
|
||||
if (App.current.session.data == null) return 0;
|
||||
return App.current.session.data.whichUser == null?0:App.current.session.data.whichUser;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function isAmap(){
|
||||
return App.current.user.amap.groupType == db.Amap.GroupType.Amap;
|
||||
}
|
||||
|
||||
|
||||
public function getBasket(userId, placeId, date){
|
||||
var user = getUser(userId);
|
||||
var place = db.Place.manager.get(placeId, false);
|
||||
return db.Basket.getOrCreate(user, place, date);
|
||||
}
|
||||
|
||||
public function getPlatform(){
|
||||
return #if neko "Neko" #else "PHP" #end ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart quantity (tm) : displays human readable quantity
|
||||
* 0.33 x Lemon 12kg => 2kg Lemon
|
||||
*/
|
||||
public function smartQt(orderQt:Float, productQt:Float, unit:Unit):String{
|
||||
if (orderQt == null) orderQt = 1;
|
||||
if (productQt == null) productQt = 1;
|
||||
if (unit == null) unit = Unit.Piece;
|
||||
if (unit == Unit.Piece && productQt == 1 ){
|
||||
return this.formatNum(orderQt);
|
||||
}else{
|
||||
return this.formatNum(orderQt * productQt) + " " + this.unit(unit,orderQt*productQt>1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
package controller;
|
||||
import sugoi.form.Form;
|
||||
|
||||
class Account extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function doDefault() {
|
||||
}
|
||||
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doEdit() {
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(app.user);
|
||||
form.removeElement(form.getElement("lang"));
|
||||
form.removeElement(form.getElement("pass"));
|
||||
form.removeElement(form.getElement("rights"));
|
||||
form.removeElement(form.getElement("cdate"));
|
||||
form.removeElement(form.getElement("ldate"));
|
||||
form.removeElement( form.getElement("apiKey") );
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
if (app.user.id != form.getValueOf("id")) {
|
||||
throw "access forbidden";
|
||||
}
|
||||
var admin = app.user.isAdmin();
|
||||
|
||||
form.toSpod(app.user);
|
||||
|
||||
//check email is valid
|
||||
if (!sugoi.form.validators.EmailValidator.check(app.user.email)){
|
||||
throw Error("/account/edit", t._("Email ::em:: is invalid", {em:app.user.email}));
|
||||
}
|
||||
|
||||
if (app.user.email2!=null && !sugoi.form.validators.EmailValidator.check(app.user.email2)){
|
||||
throw Error("/account/edit", t._("Email ::em:: is invalid", {em:app.user.email2}));
|
||||
}
|
||||
|
||||
//check email is available
|
||||
var sameEmail = db.User.getSameEmail(app.user.email,app.user.email2);
|
||||
if( sameEmail.length > 0 && sameEmail.first().id!=app.user.id){
|
||||
throw Error("/account/edit", t._("This email is already used by another account."));
|
||||
}
|
||||
|
||||
if (!admin) { app.user.rights.unset(Admin); }
|
||||
|
||||
app.user.update();
|
||||
throw Ok('/contract', t._("Your account has been updated"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
view.title = t._("Modify my account");
|
||||
}
|
||||
|
||||
function doQuit(){
|
||||
|
||||
if (checkToken()){
|
||||
|
||||
var name = app.user.amap.name;
|
||||
|
||||
var ua = db.UserAmap.get(app.user, app.user.amap,true);
|
||||
ua.delete();
|
||||
|
||||
App.current.session.data.amapId = null;
|
||||
throw Ok("/user/choose?show=1", t._("You left the group ::groupName::", {groupName:name}));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* user payments history
|
||||
*/
|
||||
@tpl('account/payments.mtt')
|
||||
function doPayments(){
|
||||
var m = app.user;
|
||||
var browse:Int->Int->List<Dynamic>;
|
||||
|
||||
//default display
|
||||
browse = function(index:Int, limit:Int) {
|
||||
return db.Operation.getOperationsWithIndex(m,app.user.amap,index,limit,true);
|
||||
}
|
||||
|
||||
var count = db.Operation.countOperations(m,app.user.amap);
|
||||
var rb = new sugoi.tools.ResultsBrowser(count, 10, browse);
|
||||
view.rb = rb;
|
||||
view.member = m;
|
||||
view.balance = db.UserAmap.get(m,app.user.amap).balance;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
package controller;
|
||||
import db.UserContract;
|
||||
import sugoi.form.Form;
|
||||
|
||||
class Amap extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@tpl("amap/default.mtt")
|
||||
function doDefault() {
|
||||
var contracts = db.Contract.getActiveContracts(app.user.amap, true, false);
|
||||
for ( c in Lambda.array(contracts).copy()) {
|
||||
if (c.endDate.getTime() < Date.now().getTime() ) contracts.remove(c);
|
||||
}
|
||||
view.contracts = contracts;
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
function doEdit() {
|
||||
|
||||
if (!app.user.isAmapManager()) throw t._("You don't have access to this section");
|
||||
|
||||
var group = app.user.amap;
|
||||
|
||||
var form = Form.fromSpod(group);
|
||||
|
||||
if (form.checkToken()) {
|
||||
|
||||
if(form.getValueOf("id") != app.user.amap.id) {
|
||||
var editedGroup = db.Amap.manager.get(form.getValueOf("id"),false);
|
||||
throw Error("/amap/edit",'Erreur, vous êtes en train de modifier "${editedGroup.name}" alors que vous êtes connecté à "${app.user.amap.name}"');
|
||||
}
|
||||
|
||||
form.toSpod(group);
|
||||
|
||||
if (group.extUrl != null){
|
||||
if ( group.extUrl.indexOf("http://") ==-1 && group.extUrl.indexOf("https://") ==-1 ){
|
||||
group.extUrl = "http://" + group.extUrl;
|
||||
}
|
||||
}
|
||||
|
||||
group.update();
|
||||
throw Ok("/amapadmin", t._("The group has been updated."));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+367
@@ -0,0 +1,367 @@
|
||||
package controller;
|
||||
import db.UserAmap;
|
||||
import haxe.Http;
|
||||
import neko.Web;
|
||||
import sugoi.form.Form;
|
||||
import Common;
|
||||
import sugoi.form.elements.IntSelect;
|
||||
import sugoi.form.elements.StringInput;
|
||||
|
||||
|
||||
class AmapAdmin extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
if (!app.user.isAmapManager()) throw Error("/", t._("Access forbidden"));
|
||||
|
||||
//lance un event pour demander aux plugins si ils veulent ajouter un item dans la nav
|
||||
var nav = new Array<Link>();
|
||||
|
||||
if (app.user.amap.hasPayments()){
|
||||
nav.push({id:"payments",link:"/amapadmin/payments",name: t._("Payments") });
|
||||
}
|
||||
|
||||
var e = Nav(nav,"groupAdmin");
|
||||
app.event(e);
|
||||
view.nav = e.getParameters()[0];
|
||||
}
|
||||
|
||||
|
||||
@tpl("amapadmin/default.mtt")
|
||||
function doDefault() {
|
||||
view.membersNum = UserAmap.manager.count($amap == app.user.amap);
|
||||
view.contractsNum = app.user.amap.getActiveContracts().length;
|
||||
|
||||
//ping cagette groups directory
|
||||
if (Std.random(10) == 0 && app.user.amap.flags.has(db.Amap.AmapFlags.CagetteNetwork)){
|
||||
var req = new Http("http://annuaire.cagette.net/api/ping?url="+StringTools.urlEncode( "http://" + App.config.HOST ) );
|
||||
try{
|
||||
req.request();
|
||||
}catch (e:Dynamic){
|
||||
App.current.logError("Error while contacting annuaire.cagette.net : "+e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@tpl("amapadmin/addimage.mtt")
|
||||
function doAddimage() {
|
||||
|
||||
if (!app.user.isAmapManager()) throw Error("/", t._("Access forbidden"));
|
||||
|
||||
var user = app.user;
|
||||
view.image = user.amap.image;
|
||||
|
||||
var request = new Map();
|
||||
try {
|
||||
request = sugoi.tools.Utils.getMultipart(1024 * 1024 * 12); //12Mb
|
||||
}catch (e:Dynamic) {
|
||||
throw Error("/amapadmin", t._("The sent image was too big. The maximum allowed size is 12MB"));
|
||||
}
|
||||
|
||||
if (request.exists("image")) {
|
||||
|
||||
//Image
|
||||
var image = request.get("image");
|
||||
|
||||
if (image != null && image.length > 0) {
|
||||
|
||||
var img : sugoi.db.File = null;
|
||||
if ( Sys.systemName() == "Windows") {
|
||||
img = sugoi.db.File.create(request.get("image"), request.get("image_filename"));
|
||||
}else {
|
||||
img = sugoi.tools.UploadedImage.resizeAndStore(request.get("image"), request.get("image_filename"), 400, 400);
|
||||
}
|
||||
|
||||
user.amap.lock();
|
||||
|
||||
if (user.amap.image != null) {
|
||||
//delete previous file
|
||||
user.amap.image.lock();
|
||||
user.amap.image.delete();
|
||||
}
|
||||
|
||||
user.amap.image = img;
|
||||
user.amap.update();
|
||||
|
||||
throw Ok('/amapadmin/', t._("Image updated"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@tpl("amapadmin/rights.mtt")
|
||||
public function doRights() {
|
||||
|
||||
//liste les gens qui ont des droits dans le groupe
|
||||
var users = db.UserAmap.manager.search($rights != null && $amap == app.user.amap, false);
|
||||
|
||||
//cleaning
|
||||
for ( u in Lambda.array(users)) {
|
||||
|
||||
//rights peut etre null (null seralisé) et pas null en DB
|
||||
if (u.rights == null || u.rights.length == 0) {
|
||||
u.lock();
|
||||
Reflect.setField(u, "rights", null);
|
||||
u.update();
|
||||
users.remove(u);
|
||||
continue;
|
||||
}
|
||||
|
||||
//rights on a deleted contract
|
||||
for ( r in u.rights) {
|
||||
switch(r) {
|
||||
case ContractAdmin(cid):
|
||||
if (cid == null) continue;
|
||||
var c = db.Contract.manager.get(cid);
|
||||
if (c == null) {
|
||||
u.lock();
|
||||
u.removeRight(r);
|
||||
u.update();
|
||||
}
|
||||
default :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.users = users;
|
||||
}
|
||||
|
||||
|
||||
@tpl("form.mtt")
|
||||
public function doEditRight(?u:db.User) {
|
||||
|
||||
var form = new sugoi.form.Form("editRight");
|
||||
|
||||
if (u == null) {
|
||||
form.addElement( new IntSelect("user", t._("Member") , app.user.amap.getMembersFormElementData(), null, true) );
|
||||
}
|
||||
|
||||
var data = [];
|
||||
//for (r in db.UserAmap.Right.getConstructors()) {
|
||||
//if (r == "ContractAdmin") continue; //managed later
|
||||
//data.push({label:r,value:r});
|
||||
//}
|
||||
data.push({label:t._("Group administrator"), value:"GroupAdmin"});
|
||||
data.push({label:t._("Membership management"),value:"Membership"});
|
||||
data.push({label:t._("Messages"),value:"Messages"});
|
||||
|
||||
var ua : db.UserAmap = null;
|
||||
var populate :Array<String> = null;
|
||||
if (u != null) {
|
||||
ua = db.UserAmap.get(u, app.user.amap, true);
|
||||
if (ua == null) throw "no user";
|
||||
if (ua.rights == null) ua.rights = [];
|
||||
//populate form
|
||||
populate = ua.rights.map(function(x) return x.getName());
|
||||
}
|
||||
|
||||
form.addElement( new sugoi.form.elements.CheckboxGroup("rights", t._("Rights"), data, populate, true, true) );
|
||||
form.addElement( new sugoi.form.elements.Html("html","<hr/>"));
|
||||
|
||||
//Rights on contracts
|
||||
var data = [];
|
||||
var populate :Array<String> = [];
|
||||
data.push({value:"contractAll",label:t._("All contracts")});
|
||||
for (r in app.user.amap.getActiveContracts(true)) {
|
||||
data.push( { label:r.name , value:"contract"+Std.string(r.id) } );
|
||||
}
|
||||
|
||||
if(ua!=null && ua.rights!=null){
|
||||
for ( r in ua.rights) {
|
||||
switch(r) {
|
||||
case Right.ContractAdmin(cid):
|
||||
if (cid == null) {
|
||||
populate.push("contractAll");
|
||||
}else {
|
||||
populate.push("contract"+cid);
|
||||
}
|
||||
|
||||
default://
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
form.addElement( new sugoi.form.elements.CheckboxGroup("rights", t._("Contracts management") , data, populate, true, true) );
|
||||
|
||||
if (form.checkToken()) {
|
||||
|
||||
var wasManager = app.user.isAmapManager();
|
||||
|
||||
if (u == null) {
|
||||
ua = db.UserAmap.manager.select($userId == Std.parseInt(form.getValueOf("user")) && $amapId == app.user.amap.id, true);
|
||||
}
|
||||
ua.rights = [];
|
||||
|
||||
var arr : Array<String> = cast form.getElement("rights").value;
|
||||
for ( r in arr) {
|
||||
if (r.substr(0, 8) == "contract") {
|
||||
if (r == "contractAll") {
|
||||
ua.rights.push( Right.ContractAdmin() );
|
||||
}else {
|
||||
ua.rights.push( Right.ContractAdmin(Std.parseInt(r.substr(8)) ) );
|
||||
}
|
||||
|
||||
}else {
|
||||
ua.rights.push( db.UserAmap.Right.createByName(r) );
|
||||
}
|
||||
}
|
||||
|
||||
//avoid "cut my own hands" problem
|
||||
if (ua.user.id == app.user.id && wasManager ) {
|
||||
var isManager = false;
|
||||
for ( r in ua.rights) {
|
||||
if (r.equals(db.UserAmap.Right.GroupAdmin)) {
|
||||
isManager = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isManager == false) {
|
||||
throw Error("/amapadmin/rights", t._("You cannot strip yourself of admin rights."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ua.rights.length == 0) ua.rights = null;
|
||||
ua.update();
|
||||
if (ua.rights == null) {
|
||||
throw Ok("/amapadmin/rights", t._("Rights removed"));
|
||||
}else {
|
||||
throw Ok("/amapadmin/rights", t._("Rights created or modified"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (u == null) {
|
||||
view.title = t._("Give rights to a user");
|
||||
}else {
|
||||
view.title = t._("Modify the rights of ::user::",{user:u.getName()});
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
public function doVatRates() {
|
||||
|
||||
var f = new sugoi.form.Form("vat");
|
||||
var a = app.user.amap;
|
||||
|
||||
if (a.vatRates == null) {
|
||||
a.lock();
|
||||
var x = new db.Amap();
|
||||
a.vatRates = x.vatRates;
|
||||
a.update();
|
||||
}
|
||||
|
||||
var i = 1;
|
||||
for (k in a.vatRates.keys()) {
|
||||
f.addElement(new StringInput(i+"-k", t._("Name ")+i, k));
|
||||
f.addElement(new StringInput(i + "-v", t._("Rate ")+i, Std.string(a.vatRates.get(k)) ));
|
||||
//f.addElement(new sugoi.form.elements.Html("<hr/>"));
|
||||
i++;
|
||||
}
|
||||
var j = i;
|
||||
|
||||
for (x in 0...5 - i) {
|
||||
f.addElement(new StringInput(i+"-k", t._("Name ")+i, ""));
|
||||
f.addElement(new StringInput(i + "-v", t._("Rate ")+i, ""));
|
||||
//f.addElement(new sugoi.form.elements.Html("<hr/>"));
|
||||
i++;
|
||||
}
|
||||
|
||||
if (f.isValid()) {
|
||||
var d = f.getData();
|
||||
var vats = new Map<String,Float>();
|
||||
var filter = new sugoi.form.filters.FloatFilter();
|
||||
for (i in 1...5) {
|
||||
if (d.get(i + "-k") == null) continue;
|
||||
vats.set(d.get(i + "-k"), filter.filter( d.get(i + "-v")) );
|
||||
}
|
||||
a.lock();
|
||||
a.vatRates = vats;
|
||||
a.update();
|
||||
throw Ok("/amapadmin", t._("Rate updated"));
|
||||
|
||||
}
|
||||
view.title = t._("Edit VAT rates");
|
||||
view.form = f;
|
||||
|
||||
}
|
||||
|
||||
function doCategories(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new controller.Categories());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up group currency. Default is EURO
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
function doCurrency(){
|
||||
|
||||
view.title = t._("Currency used by your group.");
|
||||
|
||||
var f = new sugoi.form.Form("curr");
|
||||
f.addElement(new sugoi.form.elements.StringInput("currency", t._("Currency symbol"), app.user.amap.getCurrency()));
|
||||
f.addElement(new sugoi.form.elements.StringInput("currencyCode", t._("3 digit ISO code"), app.user.amap.currencyCode));
|
||||
|
||||
if ( f.isValid()){
|
||||
|
||||
app.user.amap.lock();
|
||||
app.user.amap.currency = f.getValueOf("currency");
|
||||
app.user.amap.currencyCode = f.getValueOf("currencyCode");
|
||||
app.user.amap.update();
|
||||
|
||||
throw Ok("/amapadmin/currency", t._("Currency updated"));
|
||||
}
|
||||
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* payment configuration
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
function doPayments(){
|
||||
|
||||
var f = new sugoi.form.Form("paymentTypes");
|
||||
var types = service.PaymentService.getAllPaymentTypes();
|
||||
var formdata = [for (t in types){label:t.name, value:t.type}];
|
||||
var selected = app.user.amap.allowedPaymentsType;
|
||||
f.addElement(new sugoi.form.elements.CheckboxGroup("paymentTypes", t._("Authorized payment types"),formdata, selected) );
|
||||
|
||||
if (app.user.amap.checkOrder == ""){
|
||||
app.user.amap.lock();
|
||||
app.user.amap.checkOrder = app.user.amap.name;
|
||||
app.user.amap.update();
|
||||
}
|
||||
f.addElement( new sugoi.form.elements.StringInput("checkOrder", t._("Make the check payable to"), app.user.amap.checkOrder, false));
|
||||
f.addElement( new sugoi.form.elements.StringInput("IBAN", t._("IBAN of your bank account for transfers"), app.user.amap.IBAN, false));
|
||||
f.addElement(new sugoi.form.elements.Checkbox("allowMoneyPotWithNegativeBalance", t._("Allow money pots with negative balance"), app.user.amap.allowMoneyPotWithNegativeBalance));
|
||||
|
||||
if (f.isValid()){
|
||||
|
||||
var p = f.getValueOf("paymentTypes");
|
||||
var a = app.user.amap;
|
||||
a.lock();
|
||||
a.allowedPaymentsType = p;
|
||||
a.checkOrder = f.getValueOf("checkOrder");
|
||||
a.IBAN = f.getValueOf("IBAN");
|
||||
a.allowMoneyPotWithNegativeBalance = f.getValueOf("allowMoneyPotWithNegativeBalance");
|
||||
a.update();
|
||||
|
||||
throw Ok("/amapadmin/payments", t._("Payment options updated"));
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Options of payment");
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package controller;
|
||||
import db.UserAmap;
|
||||
import haxe.Json;
|
||||
import neko.Web;
|
||||
|
||||
/**
|
||||
* REST JSON API
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Api extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Public infos about this Cagette.net installation
|
||||
*/
|
||||
public function doDefault(){
|
||||
|
||||
var json : Dynamic = {
|
||||
version:App.VERSION.toString(),
|
||||
debug:App.config.DEBUG,
|
||||
email:App.config.get("webmaster_email"),
|
||||
groups:[]
|
||||
|
||||
};
|
||||
|
||||
for ( g in db.Amap.manager.all()){
|
||||
|
||||
//a strange way to exclude "test" accounts
|
||||
if ( UserAmap.manager.count($amapId == g.id) > 20){
|
||||
|
||||
var place = g.getMainPlace();
|
||||
|
||||
var d = {
|
||||
name:g.name,
|
||||
cagetteNetwork:g.flags.has(db.Amap.AmapFlags.CagetteNetwork),
|
||||
id:g.id,
|
||||
url:"http://" + Web.getHostName() + "/group/" + g.id,
|
||||
membersNum : g.getMembersNum(),
|
||||
contracts: Lambda.array(Lambda.map(g.getActiveContracts(false), function(c) return c.name)),
|
||||
place : {name:place.name, address1:place.address1,address2:place.address2,zipCode:place.zipCode,city:place.city }
|
||||
};
|
||||
json.groups.push(d);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Sys.print( Json.stringify(json) );
|
||||
|
||||
}
|
||||
|
||||
/*public function doError(){
|
||||
sugoi.Web.setReturnCode(403);
|
||||
}*/
|
||||
|
||||
|
||||
#if plugins
|
||||
//cagette-pro
|
||||
public function doPro(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new pro.controller.api.Main());
|
||||
}
|
||||
#end
|
||||
|
||||
public function doShop(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new controller.api.Shop());
|
||||
}
|
||||
|
||||
public function doOrder(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new controller.api.Order());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distribution planning for this group
|
||||
*
|
||||
* @param group
|
||||
*/
|
||||
public function doPlanning(group:db.Amap){
|
||||
|
||||
var contracts = group.getActiveContracts(true);
|
||||
var cids = Lambda.map(contracts, function(p) return p.id);
|
||||
var now = Date.now();
|
||||
var now = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
|
||||
var twoMonths = new Date(now.getFullYear(), now.getMonth()+2, now.getDate(), 0, 0, 0);
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && ($date >= now) && ($date<=twoMonths), { orderBy:date }, false);
|
||||
|
||||
var out = new Array<{id:Int,start:Date,end:Date,contract:String,contractId:Int,place:Dynamic}>();
|
||||
|
||||
for ( d in distribs){
|
||||
|
||||
var place = d.place;
|
||||
var p = {name:place.name, address1:place.address1,address2:place.address2,zipCode:place.zipCode,city:place.city }
|
||||
out.push({id:d.id,start:d.date,end:d.end,contract:d.contract.name,contractId:d.contract.id,place:p});
|
||||
}
|
||||
|
||||
Sys.print(Json.stringify(out));
|
||||
|
||||
}
|
||||
|
||||
public function doUser(d:haxe.web.Dispatch){
|
||||
d.dispatch(new controller.api.User());
|
||||
}
|
||||
|
||||
public function doGroup(d:haxe.web.Dispatch){
|
||||
d.dispatch(new controller.api.Group());
|
||||
}
|
||||
|
||||
public function doProduct(d:haxe.web.Dispatch){
|
||||
d.dispatch(new controller.api.Product());
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
package controller ;
|
||||
|
||||
class Categories extends controller.Controller
|
||||
{
|
||||
@tpl("categories/default.mtt")
|
||||
public function doDefault() {
|
||||
|
||||
view.groups = db.CategoryGroup.manager.search($amap == app.user.amap, false);
|
||||
|
||||
checkToken();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* genere le set par défaut de catégories
|
||||
*/
|
||||
public function doGenerate() {
|
||||
|
||||
if ( db.CategoryGroup.manager.search($amap == app.user.amap, false).length != 0) {
|
||||
throw Error("/amapadmin/categories", t._("The category list is not empty.") );
|
||||
}
|
||||
|
||||
function gen(catGroupName:String,color:Int,cats:Array<String>) {
|
||||
|
||||
var cg = new db.CategoryGroup();
|
||||
cg.name = catGroupName;
|
||||
cg.color = color;
|
||||
cg.amap = app.user.amap;
|
||||
cg.insert();
|
||||
|
||||
for (c in cats) {
|
||||
var x = new db.Category();
|
||||
x.categoryGroup = cg;
|
||||
x.name = c;
|
||||
x.insert();
|
||||
}
|
||||
|
||||
}
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
gen(t._("Product types"),2, [t._("Vegetables"), t._("Fruits"), t._("Fish"), t._("Red meat"), t._("Breads"), t._("Grocery"), t._("Beverages") ]);
|
||||
gen(t._("Labels"),0, [t._("Certified organic agriculture"), t._("Uncertified organic agriculture"), t._("Non organic") ]);
|
||||
|
||||
throw Ok("/amapadmin/categories", t._("Default categories have been created") );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* modifie un groupe de categories
|
||||
*/
|
||||
@tpl('form.mtt')
|
||||
function doEditGroup(g:db.CategoryGroup) {
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(g);
|
||||
|
||||
form.removeElementByName("color");
|
||||
form.removeElementByName("amapId");
|
||||
form.addElement(new form.ColorRadioGroup("color", t._("Color") , Std.string(g.color) ));
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
form.toSpod(g);
|
||||
g.update();
|
||||
throw Ok("/amapadmin/categories", t._("Group modified"));
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Modify the group ") + g.name;
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doInsertGroup() {
|
||||
var g = new db.CategoryGroup();
|
||||
var form = sugoi.form.Form.fromSpod(g );
|
||||
|
||||
form.removeElementByName("color");
|
||||
form.removeElementByName("amapId");
|
||||
form.addElement(new form.ColorRadioGroup("color", "Couleur", Std.string(g.color)));
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
form.toSpod(g);
|
||||
g.amap = app.user.amap;
|
||||
g.insert();
|
||||
throw Ok("/amapadmin/categories", t._("Group added"));
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Create a group of categories");
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doInsert(g:db.CategoryGroup) {
|
||||
var c = new db.Category();
|
||||
var form = sugoi.form.Form.fromSpod(c);
|
||||
|
||||
form.removeElementByName("categoryGroupId");
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
form.toSpod(c);
|
||||
c.categoryGroup = g;
|
||||
c.insert();
|
||||
throw Ok("/amapadmin/categories", t._("Category added"));
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Create a category");
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doEdit(c:db.Category) {
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(c);
|
||||
|
||||
form.removeElementByName("categoryGroupId");
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
form.toSpod(c);
|
||||
c.update();
|
||||
throw Ok("/amapadmin/categories","Category modified");
|
||||
}
|
||||
|
||||
view.title = t._("Modify the category ") + c.name;
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
|
||||
function doDeleteGroup(g:db.CategoryGroup,args:{token:String}) {
|
||||
|
||||
if ( checkToken()) {
|
||||
if (g.getCategories().length > 0) throw Error("/amapadmin/categories", t._("All categories must be removed from this group before it can be deleted."));
|
||||
|
||||
g.lock();
|
||||
g.delete();
|
||||
throw Ok("/amapadmin/categories", t._("Group deleted"));
|
||||
}else {
|
||||
throw Redirect("/amapadmin/categories");
|
||||
}
|
||||
}
|
||||
|
||||
function doDelete(c:db.Category,args:{token:String}) {
|
||||
|
||||
if ( checkToken()) {
|
||||
c.lock();
|
||||
c.delete();
|
||||
throw Ok("/amapadmin/categories", t._("Category deleted"));
|
||||
}else {
|
||||
throw Redirect("/amapadmin/categories");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+619
@@ -0,0 +1,619 @@
|
||||
package controller;
|
||||
import db.UserContract;
|
||||
import sugoi.form.elements.DateDropdowns;
|
||||
import sugoi.form.elements.Input;
|
||||
import sugoi.form.elements.Selectbox;
|
||||
import sugoi.form.Form;
|
||||
import db.Contract;
|
||||
import Common;
|
||||
import plugin.Tutorial;
|
||||
using Std;
|
||||
import service.OrderService;
|
||||
|
||||
class Contract extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@tpl("contract/view.mtt")
|
||||
public function doView(c:db.Contract) {
|
||||
view.category = 'amap';
|
||||
view.c = c;
|
||||
}
|
||||
|
||||
/**
|
||||
* "my account" page
|
||||
*/
|
||||
@tpl("contract/default.mtt")
|
||||
function doDefault() {
|
||||
|
||||
//Create the list of links to change the language
|
||||
var langs = App.config.get("langs").split(";");
|
||||
var langNames = App.config.get("langnames").split(";");
|
||||
var i=0;
|
||||
var langLinks = "";
|
||||
for (lang in langs)
|
||||
{
|
||||
langLinks += "<li><a href=\"?lang=" + langs[i] + "\">" + langNames[i] + "</a></li>";
|
||||
i++;
|
||||
}
|
||||
view.langLinks = langLinks;
|
||||
view.langText = langNames[langs.indexOf(app.session.lang)];
|
||||
|
||||
//change account lang
|
||||
if (app.params.exists("lang") && app.user!=null){
|
||||
app.user.lock();
|
||||
app.user.lang = app.params.get("lang");
|
||||
app.user.update();
|
||||
}
|
||||
|
||||
var ua = db.UserAmap.get(app.user, app.user.amap);
|
||||
if (ua == null) throw Error("/", t._("You are not a member of this group"));
|
||||
|
||||
var constOrders = null;
|
||||
var varOrders = new Map<String,Array<db.UserContract>>();
|
||||
|
||||
var a = App.current.user.amap;
|
||||
var oneMonthAgo = DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30);
|
||||
|
||||
//constant orders
|
||||
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_CONSTORDERS && $amap == a && $endDate > oneMonthAgo, false);
|
||||
constOrders = [];
|
||||
for ( c in contracts){
|
||||
var orders = app.user.getOrdersFromContracts([c]);
|
||||
if (orders.length == 0) continue;
|
||||
constOrders.push({contract:c, orders:service.OrderService.prepare(orders) });
|
||||
}
|
||||
|
||||
//variable orders, grouped by date
|
||||
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_VARORDER && $amap == a && $endDate > oneMonthAgo, false);
|
||||
|
||||
for (c in contracts) {
|
||||
var ds = c.getDistribs(false);
|
||||
for (d in ds) {
|
||||
//store orders in a stringmap like "2015-01-01" => [order1,order2,...]
|
||||
var k = d.date.toString().substr(0, 10);
|
||||
var orders = app.user.getOrdersFromDistrib(d);
|
||||
if (orders.length > 0) {
|
||||
if (!varOrders.exists(k)) {
|
||||
varOrders.set(k, Lambda.array(orders));
|
||||
}else {
|
||||
var z = varOrders.get(k).concat(Lambda.array(orders));
|
||||
varOrders.set(k, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//final structure
|
||||
var varOrders2 = new Array<{date:Date,orders:Array<UserOrder>}>();
|
||||
for ( k in varOrders.keys()) {
|
||||
|
||||
var d = new Date(k.split("-")[0].parseInt(), k.split("-")[1].parseInt() - 1, k.split("-")[2].parseInt(), 0, 0, 0);
|
||||
var orders = service.OrderService.prepare( Lambda.list(varOrders[k]) );
|
||||
|
||||
varOrders2.push({date:d,orders:orders});
|
||||
}
|
||||
|
||||
//sort by date desc
|
||||
varOrders2.sort(function(b, a) {
|
||||
return Math.round(a.date.getTime()/1000)-Math.round(b.date.getTime()/1000);
|
||||
});
|
||||
|
||||
view.varOrders = varOrders2;
|
||||
view.constOrders = constOrders;
|
||||
|
||||
|
||||
// tutorials
|
||||
if (app.user.isAmapManager()) {
|
||||
|
||||
|
||||
//actions
|
||||
if (app.params.exists('startTuto') ) {
|
||||
|
||||
//start a tuto
|
||||
app.user.lock();
|
||||
var t = app.params.get('startTuto');
|
||||
app.user.tutoState = {name:t,step:0};
|
||||
app.user.update();
|
||||
}
|
||||
|
||||
|
||||
//tuto state
|
||||
var tutos = new Array<{name:String,completion:Float,key:String}>();
|
||||
|
||||
for ( k in Tutorial.all().keys() ) {
|
||||
var t = Tutorial.all().get(k);
|
||||
|
||||
var completion = null;
|
||||
if (app.user.tutoState!=null && app.user.tutoState.name == k) completion = app.user.tutoState.step / t.steps.length;
|
||||
|
||||
tutos.push( { name:t.name, completion:completion , key:k } );
|
||||
}
|
||||
|
||||
view.tutos = tutos;
|
||||
}
|
||||
|
||||
//should be able to stop tuto in any case
|
||||
if (app.params.exists('stopTuto')) {
|
||||
//stopped tuto from a tuto window
|
||||
app.user.lock();
|
||||
app.user.tutoState = null;
|
||||
app.user.update();
|
||||
view.stopTuto = true;
|
||||
}
|
||||
|
||||
checkToken();
|
||||
view.userAmap = ua;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a contract
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
function doEdit(c:db.Contract) {
|
||||
|
||||
view.category = 'contractadmin';
|
||||
if (!app.user.isContractManager(c)) throw Error('/', t._("Forbidden action"));
|
||||
|
||||
view.title = t._("Edit contract ::contractName::",{contractName:c.name});
|
||||
|
||||
var group = c.amap;
|
||||
var currentContact = c.contact;
|
||||
|
||||
var form = Form.fromSpod(c);
|
||||
form.removeElement( form.getElement("amapId") );
|
||||
form.removeElement(form.getElement("type"));
|
||||
form.getElement("userId").required = true;
|
||||
|
||||
app.event(EditContract(c,form));
|
||||
|
||||
if (form.checkToken()) {
|
||||
form.toSpod(c);
|
||||
c.amap = group;
|
||||
|
||||
//checks & warnings
|
||||
if (c.hasPercentageOnOrders() && c.percentageValue==null) throw Error("/contract/edit/"+c.id, t._("If you would like to add fees to the order, define a rate (%) and a label."));
|
||||
|
||||
if (c.hasStockManagement()) {
|
||||
for (p in c.getProducts()) {
|
||||
if (p.stock == null) {
|
||||
app.session.addMessage(t._("Warning about management of stock. Please fill the field \"stock\" for all your products"), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//no stock mgmt for constant orders
|
||||
if (c.hasStockManagement() && c.type==db.Contract.TYPE_CONSTORDERS) {
|
||||
c.flags.unset(ContractFlags.StockManagement);
|
||||
app.session.addMessage(t._("Managing stock is not available for CSA contracts"), true);
|
||||
}
|
||||
|
||||
|
||||
c.update();
|
||||
|
||||
//update rights
|
||||
if ( c.contact != null && (currentContact==null || c.contact.id!=currentContact.id) ) {
|
||||
var ua = db.UserAmap.get(c.contact, app.user.amap, true);
|
||||
ua.giveRight(ContractAdmin(c.id));
|
||||
ua.giveRight(Messages);
|
||||
ua.giveRight(Membership);
|
||||
ua.update();
|
||||
|
||||
//remove rights to old contact
|
||||
if (currentContact != null) {
|
||||
var x = db.UserAmap.get(currentContact, c.amap, true);
|
||||
if (x != null) {
|
||||
x.removeRight(ContractAdmin(c.id));
|
||||
x.update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw Ok("/contractAdmin/view/"+c.id, t._("Contract updated"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
@tpl("contract/insertChoose.mtt")
|
||||
function doInsertChoose() {
|
||||
//checkToken();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Créé un nouveau contrat
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
function doInsert(?type:Int) {
|
||||
if (!app.user.canManageAllContracts()) throw Error('/', t._("Forbidden action"));
|
||||
if (type == null) throw Redirect('/contract/insertChoose');
|
||||
|
||||
view.title = if (type == db.Contract.TYPE_CONSTORDERS)t._("Create a contract with fixed orders") else t._("Create a contract with variable orders");
|
||||
|
||||
var c = new db.Contract();
|
||||
|
||||
var form = Form.fromSpod(c);
|
||||
form.removeElement( form.getElement("amapId") );
|
||||
form.removeElement(form.getElement("type"));
|
||||
form.getElement("userId").required = true;
|
||||
|
||||
if (form.checkToken()) {
|
||||
form.toSpod(c);
|
||||
c.amap = app.user.amap;
|
||||
//trace(app.user.amap);
|
||||
//trace(c.amap);
|
||||
c.type = type;
|
||||
c.insert();
|
||||
|
||||
//right
|
||||
if (c.contact != null) {
|
||||
var ua = db.UserAmap.get(c.contact, app.user.amap, true);
|
||||
ua.giveRight(ContractAdmin(c.id));
|
||||
ua.giveRight(Messages);
|
||||
ua.giveRight(Membership);
|
||||
ua.update();
|
||||
}
|
||||
|
||||
throw Ok("/contractAdmin/view/"+c.id, t._("New contract created"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a contract (... and its products, orders & distributions)
|
||||
*/
|
||||
function doDelete(c:db.Contract) {
|
||||
|
||||
if (!app.user.canManageAllContracts()) throw Error("/contractAdmin", t._("You don't have the authorization to remove a contract"));
|
||||
|
||||
if (checkToken()) {
|
||||
c.lock();
|
||||
|
||||
//check if there is orders in this contract
|
||||
var products = c.getProducts();
|
||||
|
||||
var orders = db.UserContract.manager.search($productId in Lambda.map(products, function(p) return p.id));
|
||||
var qt = 0.0;
|
||||
for ( o in orders) qt += o.quantity; //there could be "zero c qt" orders
|
||||
if (qt > 0) {
|
||||
throw Error("/contractAdmin", t._("You cannot delete this contract because some orders are linked to it."));
|
||||
}
|
||||
|
||||
//remove admin rights and delete contract
|
||||
if(c.contact!=null){
|
||||
var ua = db.UserAmap.get(c.contact, c.amap, true);
|
||||
if (ua != null) {
|
||||
ua.removeRight(ContractAdmin(c.id));
|
||||
ua.update();
|
||||
}
|
||||
}
|
||||
|
||||
app.event(DeleteContract(c));
|
||||
|
||||
c.delete();
|
||||
throw Ok("/contractAdmin", t._("Contract deleted"));
|
||||
}
|
||||
|
||||
throw Error("/contractAdmin", t._("Token error"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an order by contract ( standard mode )
|
||||
* The form is prepopulated if orders have already been made.
|
||||
*
|
||||
* It should work for constant orders ( will display one column )
|
||||
* or varying orders ( with as many columns as distributions dates )
|
||||
*
|
||||
*/
|
||||
@tpl("contract/order.mtt")
|
||||
function doOrder(c:db.Contract ) {
|
||||
|
||||
//checks
|
||||
if (app.user.amap.hasPayments()) throw Redirect("/contract/orderAndPay/" + c.id);
|
||||
if (app.user.amap.hasShopMode()) throw Redirect("/shop");
|
||||
if (!c.isUserOrderAvailable()) throw Error("/", t._("This contract is not opened for orders"));
|
||||
|
||||
|
||||
var distributions = [];
|
||||
// If its a varying contract, we display a column by distribution
|
||||
if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
distributions = db.Distribution.getOpenToOrdersDeliveries(c);
|
||||
}else{
|
||||
distributions = [null];
|
||||
}
|
||||
|
||||
//list of distribs with a list of product and optionnaly an order
|
||||
var userOrders = new Array< {distrib:db.Distribution,datas:Array<{order:db.UserContract,product:db.Product}>} >();
|
||||
var products = c.getProducts();
|
||||
|
||||
if ( c.type == db.Contract.TYPE_VARORDER ){
|
||||
|
||||
for ( d in distributions){
|
||||
var datas = [];
|
||||
for ( p in products) {
|
||||
var ua = { order:null, product:p };
|
||||
|
||||
var order = db.UserContract.manager.select($user == app.user && $productId == p.id && $distributionId==d.id, true);
|
||||
|
||||
if (order != null) ua.order = order;
|
||||
datas.push(ua);
|
||||
}
|
||||
|
||||
userOrders.push({distrib:d,datas:datas});
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
var datas = [];
|
||||
for ( p in products) {
|
||||
var ua = { order:null, product:p };
|
||||
|
||||
var order = db.UserContract.manager.select($user == app.user && $productId == p.id, true);
|
||||
|
||||
if (order != null) ua.order = order;
|
||||
datas.push(ua);
|
||||
}
|
||||
|
||||
userOrders.push({distrib:null,datas:datas});
|
||||
|
||||
}
|
||||
|
||||
|
||||
//form check
|
||||
if (checkToken()) {
|
||||
|
||||
//get dsitrib if needed
|
||||
//var distrib : db.Distribution = null;
|
||||
//if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
//distrib = db.Distribution.manager.get(Std.parseInt(app.params.get("distribution")), false);
|
||||
//}
|
||||
|
||||
for (k in app.params.keys()) {
|
||||
|
||||
if (k.substr(0, 1) != "d") continue;
|
||||
var qt = app.params.get(k);
|
||||
if (qt == "") continue;
|
||||
|
||||
var pid = null;
|
||||
var did = null;
|
||||
try{
|
||||
pid = Std.parseInt(k.split("-")[1].substr(1));
|
||||
did = Std.parseInt(k.split("-")[0].substr(1));
|
||||
}catch (e:Dynamic){trace("unable to parse key "+k); }
|
||||
|
||||
//find related element in userOrders
|
||||
var uo = null;
|
||||
for ( x in userOrders){
|
||||
if (x.distrib!=null && x.distrib.id != did) {
|
||||
continue;
|
||||
}else{
|
||||
for (a in x.datas){
|
||||
if (a.product.id == pid){
|
||||
uo = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uo == null) throw t._("Could not find the product ::produ:: and delivery ::deliv::", {produ:pid, deliv:did});
|
||||
|
||||
var q = 0.0;
|
||||
|
||||
if (uo.product.hasFloatQt ) {
|
||||
var param = StringTools.replace(qt, ",", ".");
|
||||
q = Std.parseFloat(param);
|
||||
}else {
|
||||
q = Std.parseInt(qt);
|
||||
}
|
||||
|
||||
|
||||
if (uo.order != null) {
|
||||
OrderService.edit(uo.order, q);
|
||||
}else {
|
||||
OrderService.make(app.user, q, uo.product, did);
|
||||
}
|
||||
|
||||
}
|
||||
throw Ok("/contract/order/"+c.id, t._("Your order has been updated"));
|
||||
}
|
||||
|
||||
view.c = view.contract = c;
|
||||
view.userOrders = userOrders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Make an order by contract ( standard mode ) + payment process
|
||||
*/
|
||||
@tpl("contract/orderAndPay.mtt")
|
||||
function doOrderAndPay(c:db.Contract ) {
|
||||
|
||||
//checks
|
||||
if (!app.user.amap.hasPayments()) throw Redirect("/contract/order/" + c.id);
|
||||
if (app.user.amap.hasShopMode()) throw Redirect("/");
|
||||
if (!c.isUserOrderAvailable()) throw Error("/", t._("This contract is not opened for orders"));
|
||||
|
||||
var distributions = [];
|
||||
/* If its a varying contract, we display a column by distribution*/
|
||||
if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
distributions = db.Distribution.getOpenToOrdersDeliveries(c);
|
||||
}
|
||||
|
||||
//list of distribs with a list of product and optionnaly an order
|
||||
var userOrders = new Array< {distrib:db.Distribution,datas:Array<{order:db.UserContract,product:db.Product}>} >();
|
||||
var products = c.getProducts();
|
||||
|
||||
for ( d in distributions){
|
||||
var datas = [];
|
||||
for ( p in products) {
|
||||
var ua = { order:null, product:p };
|
||||
|
||||
var order : db.UserContract = null;
|
||||
if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
order = db.UserContract.manager.select($user == app.user && $productId == p.id && $distributionId==d.id, true);
|
||||
}else {
|
||||
order = db.UserContract.manager.select($user == app.user && $productId == p.id, true);
|
||||
}
|
||||
|
||||
if (order != null) ua.order = order;
|
||||
datas.push(ua);
|
||||
}
|
||||
|
||||
userOrders.push({distrib:d,datas:datas});
|
||||
}
|
||||
|
||||
|
||||
//form check
|
||||
if (checkToken()) {
|
||||
|
||||
//get distrib if needed
|
||||
var distrib = null;
|
||||
if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
distrib = db.Distribution.manager.get(Std.parseInt(app.params.get("distribution")), false);
|
||||
}
|
||||
|
||||
var orders : OrderInSession = {products:[],userId:app.user.id,total:0};
|
||||
|
||||
for (k in app.params.keys()) {
|
||||
|
||||
if (k.substr(0, 1) != "d") continue;
|
||||
var qt = app.params.get(k);
|
||||
if (qt == "") continue;
|
||||
|
||||
var pid = null;
|
||||
var did = null;
|
||||
try{
|
||||
pid = Std.parseInt(k.split("-")[1].substr(1));
|
||||
did = Std.parseInt(k.split("-")[0].substr(1));
|
||||
}catch (e:Dynamic){
|
||||
trace("unable to parse key "+k);
|
||||
}
|
||||
|
||||
//find related element in userOrders
|
||||
var uo = null;
|
||||
for ( x in userOrders){
|
||||
if (x.distrib!=null && x.distrib.id != did) {
|
||||
continue;
|
||||
}else{
|
||||
for (a in x.datas){
|
||||
if (a.product.id == pid){
|
||||
uo = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uo == null) throw t._("Could not find the product ::produ:: and delivery ::deliv::", {produ:pid, deliv:did});
|
||||
|
||||
//quantity
|
||||
var q = 0.0;
|
||||
if (uo.product.hasFloatQt ) {
|
||||
var param = StringTools.replace(qt, ",", ".");
|
||||
q = Std.parseFloat(param);
|
||||
}else {
|
||||
q = Std.parseInt(qt);
|
||||
}
|
||||
|
||||
orders.products.push({productId:pid, quantity:q, distributionId:did});
|
||||
|
||||
var p = db.Product.manager.get(pid, false);
|
||||
orders.total += p.getPrice() * q;
|
||||
|
||||
}
|
||||
|
||||
App.current.session.data.order = orders;
|
||||
|
||||
//Go to payments page
|
||||
if (c.type == db.Contract.TYPE_CONSTORDERS) {
|
||||
throw Ok("/contract/order/"+c.id, t._("Your CSA order has been saved"));
|
||||
}else{
|
||||
throw Ok("/transaction/pay/", t._("In order to save your order, please choose a means of payment."));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
view.c = view.contract = c;
|
||||
view.userOrders = userOrders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A user edit an order for a multidistrib.
|
||||
*/
|
||||
@tpl("contract/orderByDate.mtt")
|
||||
function doEditOrderByDate(date:Date) {
|
||||
|
||||
if (app.user.amap.hasPayments()) {
|
||||
//when payments are active, the user cannot modify his order
|
||||
throw Redirect("/");
|
||||
}
|
||||
|
||||
// cannot edit order if date is in the past
|
||||
if (Date.now().getTime() > date.getTime()) {
|
||||
|
||||
var msg = t._("This delivery has already taken place, you can no longer modify the order.");
|
||||
if (app.user.isContractManager()) msg += t._("<br/>As the manager of the contract you can modify the order from this page: <a href='/contractAdmin'>Management of contracts</a>");
|
||||
|
||||
throw Error("/contract", msg);
|
||||
}
|
||||
|
||||
// Il faut regarder le contrat de chaque produit et verifier si le contrat est toujours ouvert à la commande.
|
||||
var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
||||
var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
|
||||
|
||||
var cids = Lambda.map(app.user.amap.getActiveContracts(true), function(c) return c.id);
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <=d2 , false);
|
||||
var orders = db.UserContract.manager.search($userId==app.user.id && $distributionId in Lambda.map(distribs,function(d)return d.id) );
|
||||
view.orders = service.OrderService.prepare(orders);
|
||||
view.date = date;
|
||||
|
||||
//form check
|
||||
if (checkToken()) {
|
||||
|
||||
var orders_out = [];
|
||||
|
||||
for (k in app.params.keys()) {
|
||||
var param = app.params.get(k);
|
||||
if (k.substr(0, "product".length) == "product") {
|
||||
|
||||
//trouve le produit dans userOrders
|
||||
var pid = Std.parseInt(k.substr("product".length));
|
||||
var order = Lambda.find(orders, function(uo) return uo.product.id == pid);
|
||||
if (order == null) throw t._("Error, could not find the order");
|
||||
|
||||
var q = 0.0;
|
||||
if (order.product.hasFloatQt ) {
|
||||
param = StringTools.replace(param, ",", ".");
|
||||
q = Std.parseFloat(param);
|
||||
}else {
|
||||
q = Std.parseInt(param);
|
||||
}
|
||||
|
||||
var quantity = Math.abs( q==null?0:q );
|
||||
|
||||
if ( order.distribution.canOrderNow() ) {
|
||||
//met a jour la commande
|
||||
var o = OrderService.edit(order, quantity);
|
||||
if(o!=null) orders_out.push( o );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.event(MakeOrder(orders_out));
|
||||
|
||||
throw Ok("/contract", t._("Your order has been updated"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+1027
File diff suppressed because it is too large
Load Diff
Executable
+28
@@ -0,0 +1,28 @@
|
||||
package controller;
|
||||
|
||||
/**
|
||||
* Base Cagette.net Controller
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Controller extends sugoi.BaseController
|
||||
{
|
||||
|
||||
var t: sugoi.i18n.GetText;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
//gettext translator
|
||||
this.t = sugoi.i18n.Locale.texts;
|
||||
|
||||
}
|
||||
|
||||
public function checkIsLogged(){
|
||||
if(app.user==null) {
|
||||
throw new tink.core.Error(t._("You should be logged in to perform this action."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
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/>" );
|
||||
}
|
||||
}
|
||||
Executable
+577
@@ -0,0 +1,577 @@
|
||||
package controller;
|
||||
import db.UserContract;
|
||||
import sugoi.form.Form;
|
||||
import sugoi.form.elements.HourDropDowns;
|
||||
using tools.DateTool;
|
||||
import Common;
|
||||
|
||||
class Distribution extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* Attendance sheet by user-product (single distrib)
|
||||
*/
|
||||
@tpl('distribution/list.mtt')
|
||||
function doList(d:db.Distribution) {
|
||||
view.distrib = d;
|
||||
view.place = d.place;
|
||||
view.contract = d.contract;
|
||||
view.orders = service.OrderService.prepare(d.getOrders());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attendance sheet by product-user (single distrib)
|
||||
*/
|
||||
@tpl('distribution/listByProductUser.mtt')
|
||||
function doListByProductUser(d:db.Distribution) {
|
||||
view.distrib = d;
|
||||
view.place = d.place;
|
||||
view.contract = d.contract;
|
||||
// view.orders = UserContract.prepare(d.getOrders());
|
||||
|
||||
//make a 2 dimensons table : data[userId][productId]
|
||||
//WARNING : BUGS WILL APPEAR if there is many Order line for the same product
|
||||
var data = new Map<Int,Map<Int,UserOrder>>();
|
||||
var products = [];
|
||||
var uo = d.getOrders();
|
||||
|
||||
for(o in uo){
|
||||
products.push(o.product);
|
||||
}
|
||||
|
||||
for ( o in service.OrderService.prepare(uo)) {
|
||||
|
||||
var user = data[o.userId];
|
||||
if (user == null) user = new Map();
|
||||
user[o.productId] = o;
|
||||
data[o.userId] = user;
|
||||
|
||||
}
|
||||
|
||||
//products
|
||||
var products = tools.ObjectListTool.deduplicate(products);
|
||||
products.sort(function(b, a) {
|
||||
return (a.name < b.name)?1:-1;
|
||||
});
|
||||
view.products = products;
|
||||
|
||||
//users
|
||||
var users = Lambda.array(d.getUsers());
|
||||
// var usersMap = tools.ObjectListTool.toIdMap(users);
|
||||
users.sort(function(b, a) {
|
||||
return (a.lastName < b.lastName)?1:-1;
|
||||
});
|
||||
view.users = users;
|
||||
// view.usersMap = usersMap;
|
||||
|
||||
view.orders = data;
|
||||
|
||||
//total to pay by user
|
||||
view.totalByUser = function(uid:Int){
|
||||
var total = 0.0;
|
||||
for( o in data[uid]) total+= o.total;
|
||||
return total;
|
||||
}
|
||||
|
||||
//total qty of product
|
||||
view.totalByProduct = function(pid:Int){
|
||||
var total = 0.0;
|
||||
for( uid in data.keys()){
|
||||
var x = data[uid][pid];
|
||||
if(x!=null) total+= x.quantity;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attendance sheet to print ( mutidistrib )
|
||||
*/
|
||||
@tpl('distribution/listByDate.mtt')
|
||||
function doListByDate(date:Date,place:db.Place, ?type:String, ?fontSize:String) {
|
||||
|
||||
if (!app.user.isContractManager()) throw Error('/', t._("Forbidden action"));
|
||||
|
||||
view.place = place;
|
||||
|
||||
if (type == null) {
|
||||
|
||||
//display form
|
||||
var f = new sugoi.form.Form("listBydate", null, sugoi.form.Form.FormMethod.GET);
|
||||
f.addElement(new sugoi.form.elements.RadioGroup("type", "Affichage", [
|
||||
{ value:"one", label:t._("One person per page") },
|
||||
{ value:"contract", label:t._("One person per page sorted by contract") },
|
||||
{ value:"all", label:t._("All") },
|
||||
{ value:"allshort", label:t._("All but without prices and totals") },
|
||||
],"all"));
|
||||
f.addElement(new sugoi.form.elements.RadioGroup("fontSize", "Taille de police", [
|
||||
{ value:"S" , label:"S" },
|
||||
{ value:"M" , label:"M" },
|
||||
{ value:"L" , label:"L" },
|
||||
{ value:"XL", label:"XL" },
|
||||
], "S", "S", false));
|
||||
|
||||
view.form = f;
|
||||
app.setTemplate("form.mtt");
|
||||
|
||||
if (f.checkToken()) {
|
||||
var suburl = f.getValueOf("type")+"/"+f.getValueOf("fontSize");
|
||||
var url = '/distribution/listByDate/' + date.toString().substr(0, 10)+"/"+place.id+"/"+suburl;
|
||||
throw Redirect( url );
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
}else {
|
||||
|
||||
view.date = date;
|
||||
view.fontRatio = switch(fontSize){
|
||||
case "M" : 125; //100x1.25
|
||||
case "L" : 156; //125x1.25
|
||||
case "XL": 195; //156x1.25
|
||||
default : 100;
|
||||
};
|
||||
|
||||
switch(type) {
|
||||
case "one":
|
||||
app.setTemplate("distribution/listByDateOnePage.mtt");
|
||||
case "allshort" :
|
||||
app.setTemplate("distribution/listByDateShort.mtt");
|
||||
case "contract" :
|
||||
app.setTemplate("distribution/listByDateOnePageContract.mtt");
|
||||
}
|
||||
|
||||
var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
||||
var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
|
||||
var contracts = app.user.amap.getActiveContracts(true);
|
||||
//var cids = Lambda.map(contracts, function(c) return c.id);
|
||||
var cconst = [];
|
||||
var cvar = [];
|
||||
for ( c in contracts) {
|
||||
if (c.type == db.Contract.TYPE_CONSTORDERS) cconst.push(c.id);
|
||||
if (c.type == db.Contract.TYPE_VARORDER) cvar.push(c.id);
|
||||
}
|
||||
|
||||
//commandes variables
|
||||
var distribs = db.Distribution.manager.search(($contractId in cvar) && $date >= d1 && $date <= d2 && $place==place, false);
|
||||
var orders = db.UserContract.manager.search($distributionId in Lambda.map(distribs, function(d) return d.id) , { orderBy:userId } );
|
||||
|
||||
//commandes fixes
|
||||
var distribs = db.Distribution.manager.search(($contractId in cconst) && $date >= d1 && $date <= d2 && $place==place, false);
|
||||
var orders = Lambda.array(orders);
|
||||
for ( d in distribs) {
|
||||
var orders2 = db.UserContract.manager.search($productId in Lambda.map(d.contract.getProducts(), function(d) return d.id) , { orderBy:userId } );
|
||||
orders = orders.concat(Lambda.array(orders2));
|
||||
}
|
||||
|
||||
var orders3 = service.OrderService.prepare(Lambda.list(orders));
|
||||
view.orders = orders3;
|
||||
|
||||
if (type == "csv") {
|
||||
var data = new Array<Dynamic>();
|
||||
|
||||
for (o in orders3) {
|
||||
data.push( {
|
||||
"name":o.userName,
|
||||
"productName":o.productName,
|
||||
"price":view.formatNum(o.productPrice),
|
||||
"quantity":o.quantity,
|
||||
"fees":view.formatNum(o.fees),
|
||||
"total":view.formatNum(o.total),
|
||||
"paid":o.paid
|
||||
});
|
||||
}
|
||||
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(data, ["name", "productName", "price", "quantity","fees","total", "paid"],"Export-commandes-"+date.toString().substr(0,10)+"-Cagette");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function doDelete(d:db.Distribution) {
|
||||
|
||||
if (!app.user.isContractManager(d.contract)) throw Error('/', t._("Forbidden action"));
|
||||
|
||||
var contractId = d.contract.id;
|
||||
try {
|
||||
service.DistributionService.delete(d);
|
||||
}
|
||||
catch(e:tink.core.Error){
|
||||
throw Error("/contractAdmin/distributions/" + contractId, e.message);
|
||||
}
|
||||
|
||||
throw Ok("/contractAdmin/distributions/" + contractId, t._("the delivery has been deleted"));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a distribution
|
||||
*/
|
||||
@tpl('form.mtt')
|
||||
function doEdit(d:db.Distribution) {
|
||||
if (!app.user.isContractManager(d.contract)) throw Error('/', t._('Forbidden action') );
|
||||
var contract = d.contract;
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(d);
|
||||
form.removeElement(form.getElement("contractId"));
|
||||
form.removeElement(form.getElement("end"));
|
||||
form.removeElement(form.getElement("distributionCycleId"));
|
||||
var x = new sugoi.form.elements.HourDropDowns("end", t._("End time") ,d.end,true);
|
||||
form.addElement(x, 3);
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
form.addElement(new sugoi.form.elements.DatePicker("orderStartDate", t._("Orders opening date"), d.orderStartDate));
|
||||
form.addElement(new sugoi.form.elements.DatePicker("orderEndDate", t._("Orders closing date"), d.orderEndDate));
|
||||
}
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
var orderStartDate = null;
|
||||
var orderEndDate = null;
|
||||
|
||||
try{
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
orderStartDate = form.getValueOf("orderStartDate");
|
||||
orderEndDate = form.getValueOf("orderEndDate");
|
||||
}
|
||||
|
||||
d = service.DistributionService.edit(d,
|
||||
form.getValueOf("date"),
|
||||
form.getValueOf("end"),
|
||||
form.getValueOf("placeId"),
|
||||
form.getValueOf("distributor1Id"),
|
||||
form.getValueOf("distributor2Id"),
|
||||
form.getValueOf("distributor3Id"),
|
||||
form.getValueOf("distributor4Id"),
|
||||
orderStartDate,
|
||||
orderEndDate);
|
||||
|
||||
}
|
||||
catch(e:tink.core.Error){
|
||||
throw Error('/contractAdmin/distributions/' + contract.id,e.message);
|
||||
}
|
||||
|
||||
if (d.date == null) {
|
||||
var msg = t._("The distribution has been proposed to the supplier, please wait for its validation");
|
||||
throw Ok('/contractAdmin/distributions/'+contract.id, msg );
|
||||
}
|
||||
else {
|
||||
throw Ok('/contractAdmin/distributions/'+contract.id, t._("The distribution has been recorded") );
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
app.event(PreEditDistrib(d));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
view.title = t._("Edit a distribution");
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doEditCycle(d:db.DistributionCycle) {
|
||||
|
||||
if (!app.user.isContractManager(d.contract)) throw Error('/', 'Action interdite');
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(d);
|
||||
form.removeElement(form.getElement("contractId"));
|
||||
|
||||
if (form.isValid()) {
|
||||
form.toSpod(d); //update model
|
||||
d.update();
|
||||
throw Ok('/contractAdmin/distributions/'+d.contract.id, t._("The delivery is now up to date"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
view.title = t._("Modify a delivery");
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
public function doInsert(contract:db.Contract) {
|
||||
|
||||
if (!app.user.isContractManager(contract)) throw Error('/', t._('Forbidden action') );
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.place = contract.amap.getMainPlace();
|
||||
var form = sugoi.form.Form.fromSpod(d);
|
||||
form.removeElement(form.getElement("contractId"));
|
||||
form.removeElement(form.getElement("distributionCycleId"));
|
||||
form.removeElement(form.getElement("end"));
|
||||
var x = new sugoi.form.elements.HourDropDowns("end", t._("End time") );
|
||||
form.addElement(x, 3);
|
||||
|
||||
//default values
|
||||
form.getElement("date").value = DateTool.now().deltaDays(30).setHourMinute(19, 0);
|
||||
form.getElement("end").value = DateTool.now().deltaDays(30).setHourMinute(20, 0);
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
form.addElement(new sugoi.form.elements.DatePicker("orderStartDate", t._("Orders opening date"),DateTool.now().deltaDays(10).setHourMinute(8, 0)));
|
||||
form.addElement(new sugoi.form.elements.DatePicker("orderEndDate", t._("Orders closing date"),DateTool.now().deltaDays(20).setHourMinute(23, 59)));
|
||||
}
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
var createdDistrib = null;
|
||||
var orderStartDate = null;
|
||||
var orderEndDate = null;
|
||||
|
||||
try {
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
orderStartDate = form.getValueOf("orderStartDate");
|
||||
orderEndDate = form.getValueOf("orderEndDate");
|
||||
}
|
||||
|
||||
createdDistrib = service.DistributionService.create(
|
||||
contract,
|
||||
form.getValueOf("date"),
|
||||
form.getValueOf("end"),
|
||||
form.getValueOf("placeId"),
|
||||
form.getValueOf("distributor1Id"),
|
||||
form.getValueOf("distributor2Id"),
|
||||
form.getValueOf("distributor3Id"),
|
||||
form.getValueOf("distributor4Id"),
|
||||
orderStartDate,
|
||||
orderEndDate);
|
||||
}
|
||||
catch(e:tink.core.Error){
|
||||
throw Error('/contractAdmin/distributions/' + contract.id,e.message);
|
||||
}
|
||||
|
||||
if (createdDistrib.date == null) {
|
||||
var html = t._("Your request for a delivery has been sent to <b>::supplierName::</b>.<br/>Be patient, you will receive an e-mail indicating if the request has been validated or refused.", {supplierName:contract.vendor.name});
|
||||
var btn = "<a href='/contractAdmin/distributions/" + contract.id + "' class='btn btn-primary'>OK</a>";
|
||||
App.current.view.extraNotifBlock = App.current.processTemplate("block/modal.mtt",{html:html,title:t._("Distribution request sent"),btn:btn} );
|
||||
} else {
|
||||
throw Ok('/contractAdmin/distributions/'+ createdDistrib.contract.id , t._("The distribution has been recorded") );
|
||||
}
|
||||
|
||||
}else{
|
||||
//event
|
||||
app.event(PreNewDistrib(contract));
|
||||
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
view.title = t._("Create a distribution");
|
||||
}
|
||||
|
||||
/**
|
||||
* create a distribution cycle for a contract
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
public function doInsertCycle(contract:db.Contract) {
|
||||
|
||||
if (!app.user.isContractManager(contract)) throw Error('/', t._("Forbidden action"));
|
||||
|
||||
var dc = new db.DistributionCycle();
|
||||
dc.place = contract.amap.getMainPlace();
|
||||
var form = sugoi.form.Form.fromSpod(dc);
|
||||
form.removeElementByName("contractId");
|
||||
|
||||
form.getElement("startDate").value = DateTool.now();
|
||||
form.getElement("endDate").value = DateTool.now().deltaDays(30);
|
||||
|
||||
//start hour
|
||||
form.removeElementByName("startHour");
|
||||
var x = new HourDropDowns("startHour", t._("Start time"), DateTool.now().setHourMinute( 19, 0) , true);
|
||||
form.addElement(x, 5);
|
||||
|
||||
//end hour
|
||||
form.removeElement(form.getElement("endHour"));
|
||||
var x = new HourDropDowns("endHour", t._("End time"), DateTool.now().setHourMinute(20, 0), true);
|
||||
form.addElement(x, 6);
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER){
|
||||
|
||||
form.getElement("daysBeforeOrderStart").value = 10;
|
||||
form.getElement("daysBeforeOrderStart").required = true;
|
||||
form.removeElementByName("openingHour");
|
||||
var x = new HourDropDowns("openingHour", t._("Opening time"), DateTool.now().setHourMinute(8, 0) , true);
|
||||
form.addElement(x, 8);
|
||||
|
||||
form.getElement("daysBeforeOrderEnd").value = 2;
|
||||
form.getElement("daysBeforeOrderEnd").required = true;
|
||||
form.removeElementByName("closingHour");
|
||||
var x = new HourDropDowns("closingHour", t._("Closing time"), DateTool.now().setHourMinute(23, 0) , true);
|
||||
form.addElement(x, 10);
|
||||
|
||||
}else{
|
||||
|
||||
form.removeElementByName("daysBeforeOrderStart");
|
||||
form.removeElementByName("daysBeforeOrderEnd");
|
||||
form.removeElementByName("openingHour");
|
||||
form.removeElementByName("closingHour");
|
||||
}
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
var createdDistribCycle = null;
|
||||
var daysBeforeOrderStart = null;
|
||||
var daysBeforeOrderEnd = null;
|
||||
var openingHour = null;
|
||||
var closingHour = null;
|
||||
|
||||
try{
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
daysBeforeOrderStart = form.getValueOf("daysBeforeOrderStart");
|
||||
daysBeforeOrderEnd = form.getValueOf("daysBeforeOrderEnd");
|
||||
openingHour = form.getValueOf("openingHour");
|
||||
closingHour = form.getValueOf("closingHour");
|
||||
}
|
||||
|
||||
createdDistribCycle = service.DistributionService.createCycle(
|
||||
contract,
|
||||
form.getElement("cycleType").getValue(),
|
||||
form.getValueOf("startDate"),
|
||||
form.getValueOf("endDate"),
|
||||
form.getValueOf("startHour"),
|
||||
form.getValueOf("endHour"),
|
||||
daysBeforeOrderStart,
|
||||
daysBeforeOrderEnd,
|
||||
openingHour,
|
||||
closingHour,
|
||||
form.getValueOf("placeId"));
|
||||
}
|
||||
catch(e:tink.core.Error){
|
||||
throw Error('/contractAdmin/distributions/' + contract.id,e.message);
|
||||
}
|
||||
|
||||
if (createdDistribCycle != null) {
|
||||
throw Ok('/contractAdmin/distributions/'+ contract.id, t._("The delivery has been saved"));
|
||||
}
|
||||
|
||||
}
|
||||
else{
|
||||
dc.contract = contract;
|
||||
app.event(PreNewDistribCycle(dc));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
view.title = t._("Schedule a recurrent delivery");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a distribution cycle
|
||||
*/
|
||||
public function doDeleteCycle(cycle:db.DistributionCycle){
|
||||
|
||||
if (!app.user.isContractManager(cycle.contract)) throw Error('/', t._("Forbidden action"));
|
||||
|
||||
var contractId = cycle.contract.id;
|
||||
var messages = service.DistributionService.deleteCycleDistribs(cycle);
|
||||
if (messages.length > 0){
|
||||
App.current.session.addMessage( messages.join("<br/>"),true);
|
||||
}
|
||||
|
||||
throw Ok("/contractAdmin/distributions/" + contractId, t._("Recurrent deliveries deleted"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Doodle-like participation planning
|
||||
*/
|
||||
@tpl("distribution/planning.mtt")
|
||||
public function doPlanning(contract:db.Contract) {
|
||||
|
||||
view.contract = contract;
|
||||
|
||||
var doodle = new Map<Int,{user:db.User,planning:Map<Int,Bool>}>();
|
||||
var distribs = contract.getDistribs(true, 150);
|
||||
|
||||
for ( d in distribs ) {
|
||||
for (u in [d.distributor1, d.distributor2, d.distributor3, d.distributor4]) {
|
||||
if (u != null) {
|
||||
|
||||
var udoodle = doodle.get(u.id);
|
||||
|
||||
if (udoodle == null) udoodle = { user:u, planning:new Map<Int,Bool>() };
|
||||
udoodle.planning.set(d.id, true);
|
||||
doodle.set(u.id, udoodle);
|
||||
}
|
||||
}
|
||||
}
|
||||
view.distribs = distribs;
|
||||
view.doodle = doodle;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax service for doPlanning()
|
||||
*/
|
||||
public function doRegister(args: { register:Bool, distrib:db.Distribution } ) {
|
||||
|
||||
if (args != null) {
|
||||
var d = args.distrib;
|
||||
d.lock();
|
||||
|
||||
if (args.register) {
|
||||
|
||||
if (d.distributor1 == null) d.distributor1 = app.user;
|
||||
else if (d.distributor2 == null) d.distributor2 = app.user;
|
||||
else if (d.distributor3 == null) d.distributor3 = app.user;
|
||||
else if (d.distributor4 == null) d.distributor4 = app.user;
|
||||
|
||||
}else {
|
||||
if (d.distributor1 == app.user) d.distributor1 = null;
|
||||
else if (d.distributor2 == app.user) d.distributor2 = null;
|
||||
else if (d.distributor3 == app.user) d.distributor3 = null;
|
||||
else if (d.distributor4 == app.user) d.distributor4 = null;
|
||||
}
|
||||
|
||||
d.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a multi-distrib
|
||||
* @param date
|
||||
* @param place
|
||||
*/
|
||||
@tpl('distribution/validate.mtt')
|
||||
public function doValidate(date:Date, place:db.Place){
|
||||
|
||||
if (!app.user.isAmapManager()) throw t._("Forbidden access");
|
||||
|
||||
var md = MultiDistrib.get(date, place, db.Contract.TYPE_VARORDER);
|
||||
|
||||
view.confirmed = md.checkConfirmed();
|
||||
view.users = md.getUsers();
|
||||
view.date = date;
|
||||
view.place = place;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Admin can autovalidate a multidistrib
|
||||
*/
|
||||
@admin
|
||||
public function doAutovalidate(date:Date,place:db.Place){
|
||||
|
||||
var md = MultiDistrib.get(date,place,db.Contract.TYPE_VARORDER);
|
||||
for ( d in md.distributions){
|
||||
if(d.validated) continue;
|
||||
service.PaymentService.validateDistribution(d);
|
||||
}
|
||||
throw Ok("/contractAdmin",t._("This distribution have been validated"));
|
||||
}
|
||||
|
||||
@admin
|
||||
public function doUnvalidate(date:Date,place:db.Place){
|
||||
|
||||
var md = MultiDistrib.get(date,place,db.Contract.TYPE_VARORDER);
|
||||
for ( d in md.distributions){
|
||||
if(!d.validated) continue;
|
||||
service.PaymentService.unvalidateDistribution(d);
|
||||
}
|
||||
throw Ok("/contractAdmin",t._("This distribution have been Unvalidated"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package controller;
|
||||
import sugoi.form.elements.StringInput;
|
||||
import service.OrderService;
|
||||
import service.WaitingListService;
|
||||
|
||||
/**
|
||||
* Groups
|
||||
*/
|
||||
class Group extends controller.Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Public page of a group
|
||||
*/
|
||||
@tpl('group/view.mtt')
|
||||
function doDefault(group:db.Amap){
|
||||
|
||||
if (group.regOption == db.Amap.RegOption.Open) {
|
||||
app.session.data.amapId = group.id;
|
||||
throw Redirect("/");
|
||||
}
|
||||
|
||||
view.group = group;
|
||||
view.contracts = group.getActiveContracts();
|
||||
view.pageTitle = group.name;
|
||||
group.getMainPlace(); //just to update cache
|
||||
if (app.user != null){
|
||||
|
||||
view.isMember = Lambda.has(app.user.getAmaps(), group);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register to a waiting list.
|
||||
* the user can be logged or not !
|
||||
*/
|
||||
@tpl('form.mtt')
|
||||
function doList(group:db.Amap){
|
||||
|
||||
//checks
|
||||
if (group.regOption != db.Amap.RegOption.WaitingList) throw Redirect("/group/" + group.id);
|
||||
if (app.user != null) {
|
||||
try{
|
||||
WaitingListService.canRegister(app.user,group);
|
||||
}catch(e:tink.core.Error){
|
||||
throw Error("/group/" + group.id,e.message);
|
||||
}
|
||||
}
|
||||
|
||||
//build form
|
||||
var form = new sugoi.form.Form("reg");
|
||||
if (app.user == null){
|
||||
form.addElement(new StringInput("userFirstName", t._("Your firstname"),"",true));
|
||||
form.addElement(new StringInput("userLastName", t._("Your lastname") ,"",true));
|
||||
form.addElement(new StringInput("userEmail", t._("Your e-mail"), "", true));
|
||||
}
|
||||
form.addElement(new sugoi.form.elements.TextArea("msg", t._("Leave a message")));
|
||||
|
||||
if (form.isValid()){
|
||||
try{
|
||||
if (app.user == null){
|
||||
var f = form;
|
||||
var user = service.UserService.softRegistration(f.getValueOf("userFirstName"),f.getValueOf("userLastName"), f.getValueOf("userEmail") );
|
||||
db.User.login(user, user.email);
|
||||
}
|
||||
|
||||
WaitingListService.registerToWl(app.user,group,form.getValueOf("msg"));
|
||||
throw Ok("/group/" + group.id,t._("Your subscription to the waiting list has been recorded. You will receive an e-mail as soon as your request is processed.") );
|
||||
}catch(e:tink.core.Error){
|
||||
throw Error("/group/list/" + group.id,e.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Subscription to \"::groupeName::\" waiting list", {groupeName:group.name});
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
/**
|
||||
Cancel suscription request
|
||||
**/
|
||||
function doListCancel(group:db.Amap){
|
||||
try{
|
||||
WaitingListService.removeFromWl(app.user,group);
|
||||
}catch(e:tink.core.Error){
|
||||
throw Error("/group/" + group.id,e.message);
|
||||
}
|
||||
throw Ok("/group/" + group.id,t._("You've been removed from the waiting list"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register direclty in an open group
|
||||
*
|
||||
* the user can be logged or not !
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doRegister(group:db.Amap){
|
||||
|
||||
if (group.regOption != db.Amap.RegOption.Open) throw Redirect("/group/" + group.id);
|
||||
if (app.user != null){
|
||||
if ( db.UserAmap.manager.select($amapId == group.id && $user == app.user) != null) throw Error("/group/" + group.id, t._("You are already member of this group."));
|
||||
}
|
||||
|
||||
var form = new sugoi.form.Form("reg");
|
||||
form.submitButtonLabel = t._("Join the group");
|
||||
form.addElement(new sugoi.form.elements.Html("html",t._("Confirm your subscription to \"::groupName::\"", {groupName:group.name})));
|
||||
if (app.user == null){
|
||||
form.addElement(new StringInput("userFirstName", t._("Your firstname"),"",true));
|
||||
form.addElement(new StringInput("userLastName", t._("Your lastname"), "", true));
|
||||
var em = new StringInput("userEmail", t._("Your e-mail"), "", true);
|
||||
em.addValidator(new EmailValidator());
|
||||
form.addElement(em);
|
||||
form.addElement(new StringInput("address", t._("Address"), "", true));
|
||||
form.addElement(new StringInput("zipCode", t._("Zip code"), "", true));
|
||||
form.addElement(new StringInput("city", t._("City"), "", true));
|
||||
form.addElement(new StringInput("phone", t._("Phone"), "", true));
|
||||
}
|
||||
|
||||
if (form.isValid()){
|
||||
|
||||
if (app.user == null){
|
||||
var f = form;
|
||||
var user = new db.User();
|
||||
user.email = f.getValueOf("userEmail");
|
||||
user.firstName = f.getValueOf("userFirstName");
|
||||
user.lastName = f.getValueOf("userLastName");
|
||||
user.address1 = f.getValueOf("address");
|
||||
user.zipCode = f.getValueOf("zipCode");
|
||||
user.city = f.getValueOf("city");
|
||||
user.phone = f.getValueOf("phone");
|
||||
|
||||
if ( db.User.getSameEmail(user.email).length > 0 ) {
|
||||
throw Ok("/user/login",t._("You already subscribed to Cagette.net, please log in on this page"));
|
||||
}
|
||||
|
||||
user.insert();
|
||||
app.session.setUser(user);
|
||||
|
||||
}
|
||||
|
||||
var w = new db.UserAmap();
|
||||
w.user = app.user;
|
||||
w.amap = group;
|
||||
w.insert();
|
||||
|
||||
throw Ok("/user/choose", t._("Your subscription has been taken into account"));
|
||||
}
|
||||
|
||||
view.title = t._("Subscription to \"::groupName::\"", {groupName:group.name});
|
||||
view.form = form;
|
||||
|
||||
}*/
|
||||
|
||||
/**
|
||||
* create a new group
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
function doCreate() {
|
||||
|
||||
view.title = t._("Create a new Cagette Group");
|
||||
|
||||
var f = new sugoi.form.Form("c");
|
||||
f.addElement(new StringInput("name", t._("Name of your group"), "", true));
|
||||
|
||||
//group type
|
||||
var data = [
|
||||
{label:t._("CSA"),value:"0"},
|
||||
{label:t._("Grouped orders"),value:"1"},
|
||||
{label:t._("Farmers collective"),value:"2"},
|
||||
{label:t._("Farm shop"),value:"3"},
|
||||
];
|
||||
var gt = new sugoi.form.elements.RadioGroup("type", t._("Group type"), data ,"1","1",true,true,true);
|
||||
f.addElement(gt);
|
||||
|
||||
if (f.checkToken()) {
|
||||
|
||||
var user = app.user;
|
||||
|
||||
var g = new db.Amap();
|
||||
g.name = f.getValueOf("name");
|
||||
g.contact = user;
|
||||
|
||||
var type:db.Amap.GroupType = Type.createEnumIndex(db.Amap.GroupType, Std.parseInt(f.getValueOf("type")) );
|
||||
|
||||
switch(type){
|
||||
case null :
|
||||
throw "unknown group type";
|
||||
case db.Amap.GroupType.Amap :
|
||||
g.flags.set(db.Amap.AmapFlags.HasMembership);
|
||||
g.regOption = db.Amap.RegOption.WaitingList;
|
||||
|
||||
case db.Amap.GroupType.GroupedOrders :
|
||||
g.flags.set(db.Amap.AmapFlags.ShopMode);
|
||||
g.flags.set(db.Amap.AmapFlags.HasMembership);
|
||||
g.regOption = db.Amap.RegOption.WaitingList;
|
||||
|
||||
case db.Amap.GroupType.ProducerDrive :
|
||||
g.flags.set(db.Amap.AmapFlags.ShopMode);
|
||||
g.regOption = db.Amap.RegOption.Open;
|
||||
g.flags.set(db.Amap.AmapFlags.PhoneRequired);
|
||||
|
||||
case db.Amap.GroupType.FarmShop :
|
||||
g.flags.set(db.Amap.AmapFlags.ShopMode);
|
||||
g.regOption = db.Amap.RegOption.Open;
|
||||
g.flags.set(db.Amap.AmapFlags.PhoneRequired);
|
||||
}
|
||||
|
||||
g.groupType = type;
|
||||
g.insert();
|
||||
|
||||
var ua = new db.UserAmap();
|
||||
ua.user = user;
|
||||
ua.amap = g;
|
||||
ua.rights = [db.UserAmap.Right.GroupAdmin,db.UserAmap.Right.Membership,db.UserAmap.Right.Messages,db.UserAmap.Right.ContractAdmin(null)];
|
||||
ua.insert();
|
||||
|
||||
//example datas
|
||||
var place = new db.Place();
|
||||
place.name = t._("Market square");
|
||||
place.zipCode = "000";
|
||||
place.city = "St Martin de la Cagette";
|
||||
place.amap = g;
|
||||
place.insert();
|
||||
|
||||
//contrat AMAP
|
||||
var vendor = new db.Vendor();
|
||||
vendor.amap = g;
|
||||
vendor.name = "Jean Martin EARL";
|
||||
vendor.zipCode = "000";
|
||||
vendor.city = "Langon";
|
||||
vendor.email = "jean@cagette.net";
|
||||
vendor.insert();
|
||||
|
||||
if (type == Amap){
|
||||
var contract = new db.Contract();
|
||||
contract.name = t._("CSA contract Vegetables - Example");
|
||||
contract.description = t._("This contract is an example where the customer has to commit to buy the whole year as with AMAPs");
|
||||
contract.amap = g;
|
||||
contract.type = 0;
|
||||
contract.vendor = vendor;
|
||||
contract.startDate = Date.now();
|
||||
contract.endDate = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 364);
|
||||
contract.contact = user;
|
||||
contract.distributorNum = 2;
|
||||
contract.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = t._("Big basket of vegetables");
|
||||
p.price = 15;
|
||||
p.organic = true;
|
||||
p.contract = contract;
|
||||
p.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = t._("Small basket of vegetables");
|
||||
p.price = 10;
|
||||
p.organic = true;
|
||||
p.contract = contract;
|
||||
p.insert();
|
||||
|
||||
OrderService.make(user, 1, p, null, true);
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.contract = contract;
|
||||
d.date = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 14);
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 90);
|
||||
d.place = place;
|
||||
d.insert();
|
||||
|
||||
}
|
||||
|
||||
//contrat variable
|
||||
var vendor = new db.Vendor();
|
||||
vendor.amap = g;
|
||||
vendor.name = t._("Farm Galinette");
|
||||
vendor.zipCode = "000";
|
||||
vendor.city = t._("Bazas");
|
||||
vendor.email = "galinette@cagette.net";
|
||||
vendor.insert();
|
||||
|
||||
var contract = new db.Contract();
|
||||
contract.name = t._("Chicken Contract - Example");
|
||||
contract.description = t._("Example of contract with variable orders. It is allowed to order something else at every delivery.");
|
||||
contract.amap = g;
|
||||
contract.type = 1;
|
||||
contract.vendor = vendor;
|
||||
contract.startDate = Date.now();
|
||||
contract.endDate = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 364);
|
||||
contract.contact = user;
|
||||
contract.distributorNum = 2;
|
||||
contract.flags.set(db.Contract.ContractFlags.UsersCanOrder);
|
||||
contract.insert();
|
||||
|
||||
var egg = new db.Product();
|
||||
egg.name = t._("12 eggs");
|
||||
egg.price = 5;
|
||||
//egg.type = 6;
|
||||
egg.organic = true;
|
||||
egg.contract = contract;
|
||||
egg.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = t._("Chicken");
|
||||
//p.type = 2;
|
||||
p.price = 9.50;
|
||||
p.organic = true;
|
||||
p.contract = contract;
|
||||
p.insert();
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.contract = contract;
|
||||
d.orderStartDate = Date.now();
|
||||
d.orderEndDate = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 19);
|
||||
d.date = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 21);
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 90);
|
||||
d.place = place;
|
||||
d.insert();
|
||||
|
||||
OrderService.make(user, 2, egg, d.id);
|
||||
OrderService.make(user, 1, p, d.id);
|
||||
|
||||
App.current.session.data.amapId = g.id;
|
||||
app.session.data.newGroup = true;
|
||||
throw Redirect("/");
|
||||
}
|
||||
|
||||
view.form= f;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Displays a google map in a popup
|
||||
**/
|
||||
@tpl('group/place.mtt')
|
||||
public function doPlace(place:db.Place){
|
||||
view.place = place;
|
||||
|
||||
//build adress for google maps
|
||||
var addr = "";
|
||||
if (place.address1 != null) addr += place.address1;
|
||||
if (place.address2 != null) addr += ", " + place.address2;
|
||||
if (place.zipCode != null) addr += " " + place.zipCode;
|
||||
if (place.city != null) addr += " " + place.city;
|
||||
|
||||
view.addr = view.escapeJS(addr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups map
|
||||
*/
|
||||
@tpl("group/map.mtt")
|
||||
public function doMap(?args:{?lat:Float,?lng:Float,?address:String}){
|
||||
|
||||
view.container = "container-fluid";
|
||||
|
||||
//if no param is sent, focus on Paris
|
||||
if (args == null || (args.address == null && args.lat == null && args.lng == null)){
|
||||
args = {lat:48.855675, lng:2.3472365};
|
||||
}
|
||||
|
||||
view.lat = args.lat;
|
||||
view.lng = args.lng;
|
||||
view.address = args.address;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package controller;
|
||||
import sugoi.db.Variable;
|
||||
import sugoi.form.elements.StringInput;
|
||||
import thx.semver.Version;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut<francois.barbut@gmail.com>
|
||||
*/
|
||||
class Install extends controller.Controller
|
||||
{
|
||||
/**
|
||||
* checks if its a first install or an update
|
||||
*/
|
||||
@tpl("install/default.mtt")
|
||||
public function doDefault() {
|
||||
if (db.User.manager.get(1) == null) {
|
||||
|
||||
throw Redirect("/install/firstInstall");
|
||||
|
||||
}else {
|
||||
//throw Error("/", "L'utilisateur admin a déjà été créé. Essayez de vous connecter avec admin@cagette.net, mot de passe : admin");
|
||||
|
||||
var status = new Array<{parameter:String,valid:Bool,message:String}>();
|
||||
|
||||
status.push(getVersionStatus());
|
||||
|
||||
view.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
public function doDiagnostics(){
|
||||
|
||||
var webroot = sugoi.Web.getCwd();
|
||||
|
||||
if(!sys.FileSystem.exists(webroot+"file")){
|
||||
Sys.println("no File directory : created");
|
||||
sys.FileSystem.createDirectory(webroot+"file");
|
||||
}
|
||||
if(!sys.FileSystem.exists(webroot+"file/.htaccess")){
|
||||
Sys.println("no .htaccess file in 'File' directory");
|
||||
}
|
||||
if(!sys.FileSystem.exists(webroot+"../tmp")) {
|
||||
Sys.println("no tmp directory : created");
|
||||
sys.FileSystem.createDirectory(webroot+"../tmp");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* First install
|
||||
*/
|
||||
@tpl("form.mtt")
|
||||
public function doFirstInstall(){
|
||||
view.title = "Installation de Cagette.net";
|
||||
|
||||
var f = new sugoi.form.Form("c");
|
||||
f.addElement(new StringInput("amapName", t._("Name of your group"),"",true));
|
||||
f.addElement(new StringInput("userFirstName", t._("Your firstname"),"",true));
|
||||
f.addElement(new StringInput("userLastName", t._("Your lastname"),"",true));
|
||||
|
||||
if (f.checkToken()) {
|
||||
|
||||
var user = new db.User();
|
||||
user.firstName = f.getValueOf("userFirstName");
|
||||
user.lastName = f.getValueOf("userLastName");
|
||||
user.email = "admin@cagette.net";
|
||||
user.setPass("admin");
|
||||
user.insert();
|
||||
|
||||
var amap = new db.Amap();
|
||||
amap.name = f.getValueOf("amapName");
|
||||
amap.contact = user;
|
||||
|
||||
amap.flags.set(db.Amap.AmapFlags.HasMembership);
|
||||
//amap.flags.set(db.Amap.AmapFlags.IsAmap);
|
||||
amap.insert();
|
||||
|
||||
var ua = new db.UserAmap();
|
||||
ua.user = user;
|
||||
ua.amap = amap;
|
||||
ua.rights = [db.UserAmap.Right.GroupAdmin,db.UserAmap.Right.Membership,db.UserAmap.Right.Messages,db.UserAmap.Right.ContractAdmin(null)];
|
||||
ua.insert();
|
||||
|
||||
//example datas
|
||||
var place = new db.Place();
|
||||
place.name = t._("Marketplace");
|
||||
place.amap = amap;
|
||||
place.address1 = t._("Place Jules Verne");
|
||||
place.zipCode = "00000";
|
||||
place.city = t._("St Martin de la Cagette");
|
||||
place.insert();
|
||||
|
||||
var vendor = new db.Vendor();
|
||||
vendor.amap = amap;
|
||||
|
||||
vendor.name = t._("Jean Martin EURL");
|
||||
vendor.email = "jean.martin@cagette.net";
|
||||
vendor.zipCode = "00000";
|
||||
vendor.city = "Martignac";
|
||||
vendor.insert();
|
||||
|
||||
var contract = new db.Contract();
|
||||
contract.name = t._("Vegetables Contract Example");
|
||||
contract.amap = amap;
|
||||
contract.type = 0;
|
||||
contract.vendor = vendor;
|
||||
contract.startDate = Date.now();
|
||||
contract.endDate = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 364);
|
||||
contract.contact = user;
|
||||
contract.distributorNum = 2;
|
||||
contract.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = t._("Big basket of vegetables");
|
||||
p.price = 15;
|
||||
p.vat = 5;
|
||||
p.contract = contract;
|
||||
p.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = t._("Small basket of vegetables");
|
||||
p.price = 10;
|
||||
p.vat = 5;
|
||||
p.contract = contract;
|
||||
p.insert();
|
||||
|
||||
var uc = new db.UserContract();
|
||||
uc.user = user;
|
||||
uc.product = p;
|
||||
uc.paid = true;
|
||||
uc.quantity = 1;
|
||||
uc.productPrice = 10;
|
||||
uc.insert();
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.contract = contract;
|
||||
d.date = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 14);
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 90);
|
||||
d.place = place;
|
||||
d.insert();
|
||||
|
||||
App.current.user = null;
|
||||
App.current.session.setUser(user);
|
||||
App.current.session.data.amapId = amap.id;
|
||||
|
||||
throw Ok("/", t._("Group and user 'admin' created. Your email is 'admin@cagette.net' and your password is 'admin'"));
|
||||
}
|
||||
|
||||
view.form= f;
|
||||
}
|
||||
|
||||
/**
|
||||
* get version status
|
||||
*/
|
||||
private function getVersionStatus(){
|
||||
|
||||
|
||||
var out = {parameter:"version", valid:false, message:""};
|
||||
|
||||
var v = Variable.get("version");
|
||||
if (v == null || v=="") {
|
||||
Variable.set("version", App.VERSION.toString());
|
||||
v = App.VERSION.toString();
|
||||
}
|
||||
|
||||
var v :thx.semver.Version = thx.semver.Version.stringToVersion(v);
|
||||
|
||||
if (v.lessThan(App.VERSION)){
|
||||
|
||||
//need update !
|
||||
out.valid = false;
|
||||
out.message = t._("You must update your database to version ") +App.VERSION.toString()+"";
|
||||
|
||||
}else{
|
||||
out.valid = true;
|
||||
out.message = t._("Current version") +v.toString()+"";
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* perform migrations from a version to another
|
||||
*/
|
||||
@admin
|
||||
public function doUpdateversion(){
|
||||
|
||||
var log = [];
|
||||
|
||||
var currentVersion = thx.semver.Version.stringToVersion(Variable.get("version"));
|
||||
|
||||
//Migrations to 0.9.2
|
||||
if (currentVersion.lessThan( thx.semver.Version.arrayToVersion([0,9,2]) )){
|
||||
|
||||
log.push(t._("Installation of the dictionnary of products (taxonomy)"));
|
||||
_0_9_2_installTaxonomy();
|
||||
|
||||
log.push(t._("Improvement on saving orders"));
|
||||
_0_9_2_dbMigration();
|
||||
|
||||
sugoi.db.Variable.set("version", "0.9.2");
|
||||
}
|
||||
|
||||
//Migrations to 1.0.0
|
||||
//...
|
||||
|
||||
throw Ok("/install", t._("Following update have been performed:<ul>")+Lambda.map(log,function(x) return "<li>"+x+"</li>").join("")+"</ul>");
|
||||
|
||||
}
|
||||
|
||||
@admin
|
||||
function _0_9_2_installTaxonomy(){
|
||||
|
||||
db.TxpCategory.manager.delete(true);
|
||||
db.TxpSubCategory.manager.delete(true);
|
||||
db.TxpProduct.manager.delete(true);
|
||||
|
||||
var taxo = sys.io.File.getContent(sugoi.Web.getCwd() + "../data/productTaxonomy.json");
|
||||
var taxo = haxe.Json.parse(taxo);
|
||||
|
||||
var categories : Array<Dynamic> = taxo.categories;
|
||||
var subcategories : Array<Dynamic> = taxo.subCategories;
|
||||
var products : Array<Dynamic> = taxo.products;
|
||||
|
||||
for ( c in categories){
|
||||
var cat = new db.TxpCategory();
|
||||
cat.id = c.id;
|
||||
cat.name = c.name;
|
||||
cat.insert();
|
||||
}
|
||||
|
||||
for ( sc in subcategories){
|
||||
var scat = new db.TxpSubCategory();
|
||||
scat.id = sc.id;
|
||||
scat.name = sc.name;
|
||||
scat.category = db.TxpCategory.manager.get(Std.parseInt(sc.category));
|
||||
scat.insert();
|
||||
|
||||
}
|
||||
|
||||
for ( p in products){
|
||||
var pro = new db.TxpProduct();
|
||||
pro.name = p.name;
|
||||
pro.id = p.id;
|
||||
pro.category = db.TxpCategory.manager.get(Std.parseInt(p.category));
|
||||
pro.subCategory = db.TxpSubCategory.manager.get(Std.parseInt(p.subCategory));
|
||||
pro.insert();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@admin
|
||||
function _0_9_2_dbMigration(){
|
||||
|
||||
//recompute prices on orders
|
||||
for ( order in db.UserContract.manager.all(true)){
|
||||
order.productPrice = order.product.price;
|
||||
order.feesRate = order.product.contract.percentageValue;
|
||||
order.update();
|
||||
}
|
||||
|
||||
|
||||
//activate payment orders
|
||||
for ( a in db.Amap.manager.all(true)){
|
||||
a.allowedPaymentsType = ["cash", "transfer", "check"];
|
||||
a.update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+261
@@ -0,0 +1,261 @@
|
||||
package controller;
|
||||
import db.Distribution;
|
||||
import db.UserContract;
|
||||
import haxe.Json;
|
||||
import haxe.web.Dispatch;
|
||||
import sugoi.form.elements.StringInput;
|
||||
import sugoi.tools.ResultsBrowser;
|
||||
import Common;
|
||||
import tools.ArrayTool;
|
||||
|
||||
class Main extends Controller {
|
||||
|
||||
|
||||
/**
|
||||
* public pages
|
||||
*/
|
||||
function doGroup(d:haxe.web.Dispatch){
|
||||
d.dispatch(new controller.Group());
|
||||
}
|
||||
|
||||
/**
|
||||
Group homepage
|
||||
**/
|
||||
@tpl("home.mtt")
|
||||
function doDefault() {
|
||||
view.category = 'home';
|
||||
|
||||
var group = app.getCurrentGroup();
|
||||
if ( app.user!=null && group == null) {
|
||||
throw Redirect("/user/choose");
|
||||
}else if (app.user == null && (group==null || group.regOption!=db.Amap.RegOption.Open) ) {
|
||||
throw Redirect("/user/login");
|
||||
}
|
||||
|
||||
view.amap = group;
|
||||
|
||||
//contract with open orders
|
||||
var openContracts = Lambda.filter(group.getActiveContracts(), function(c) return c.isUserOrderAvailable());
|
||||
view.openContracts = openContracts;
|
||||
|
||||
//register to become "distributor"
|
||||
view.contractsWithDistributors = app.user==null ? [] : Lambda.filter(app.user.amap.getActiveContracts(), function(c) return c.distributorNum > 0);
|
||||
|
||||
//freshly created group
|
||||
view.newGroup = app.session.data.newGroup == true;
|
||||
|
||||
var n = Date.now();
|
||||
var now = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0);
|
||||
var in3Month = DateTools.delta(now, 1000.0 * 60 * 60 * 24 * 30 * 3);
|
||||
|
||||
var distribs = MultiDistrib.getFromTimeRange(group,now,in3Month);
|
||||
view.distribs = distribs;
|
||||
|
||||
//view functions
|
||||
view.getWhosTurn = function(orderId:Int, distrib:Distribution) {
|
||||
return db.UserContract.manager.get(orderId, false).getWhosTurn(distrib);
|
||||
}
|
||||
|
||||
//event for additionnal blocks on home page
|
||||
var e = Blocks([], "home");
|
||||
app.event(e);
|
||||
view.blocks = e.getParameters()[0];
|
||||
|
||||
//message if phone is required
|
||||
if(app.user!=null && app.user.amap.flags.has(db.Amap.AmapFlags.PhoneRequired) && app.user.phone==null){
|
||||
app.session.addMessage(t._("Members of this group should provide a phone number. <a href='/account/edit'>Please click here to update your account</a>."),true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//login and stuff
|
||||
function doUser(d:Dispatch) {
|
||||
d.dispatch(new controller.User());
|
||||
}
|
||||
|
||||
function doCron(d:Dispatch) {
|
||||
d.dispatch(new controller.Cron());
|
||||
}
|
||||
|
||||
/**
|
||||
* JSON REST API Entry point
|
||||
*/
|
||||
function doApi(d:Dispatch) {
|
||||
|
||||
try {
|
||||
|
||||
d.dispatch(new controller.Api());
|
||||
|
||||
}catch (e:tink.core.Error){
|
||||
|
||||
//manage tink Errors (service errors)
|
||||
sugoi.Web.setReturnCode(e.code);
|
||||
Sys.print(Json.stringify( {error:{code:e.code,message:e.message,stack:e.exceptionStack}} ));
|
||||
|
||||
}catch (e:Dynamic){
|
||||
|
||||
//manage other errors
|
||||
sugoi.Web.setReturnCode(500);
|
||||
var stack = if ( App.config.DEBUG ) haxe.CallStack.toString(haxe.CallStack.exceptionStack()) else "";
|
||||
App.current.logError(e, stack);
|
||||
Sys.print(Json.stringify( {error:{code:500,message : Std.string(e), stack:stack }} ));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@tpl("cssDemo.mtt")
|
||||
function doCssdemo() {
|
||||
view.category = 'home';
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
function doInstall(d:Dispatch) {
|
||||
d.dispatch(new controller.Install());
|
||||
}
|
||||
|
||||
|
||||
function doP(d:Dispatch) {
|
||||
|
||||
/*
|
||||
* Invalid array access
|
||||
Stack (ADMIN|DEBUG)
|
||||
|
||||
Called from C:\HaxeToolkit\haxe\std/haxe/web/Dispatch.hx line 463
|
||||
Called from controller/Main.hx line 117
|
||||
*
|
||||
var plugin = d.parts.shift();
|
||||
for ( p in App.plugins) {
|
||||
var n = Type.getClassName(Type.getClass(p)).toLowerCase();
|
||||
n = n.split(".").pop();
|
||||
if (plugin == n) {
|
||||
d.dispatch( p.getController() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw Error("/","Plugin '"+plugin+"' introuvable.");
|
||||
*/
|
||||
|
||||
d.dispatch(new controller.Plugin());
|
||||
}
|
||||
|
||||
|
||||
@logged
|
||||
function doMember(d:Dispatch) {
|
||||
view.category = 'members';
|
||||
d.dispatch(new controller.Member());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doStats(d:Dispatch) {
|
||||
view.category = 'stats';
|
||||
d.dispatch(new Stats());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doAccount(d:Dispatch) {
|
||||
view.category = 'account';
|
||||
d.dispatch(new controller.Account());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doVendor(d:Dispatch) {
|
||||
view.category = 'contractadmin';
|
||||
d.dispatch(new controller.Vendor());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doPlace(d:Dispatch) {
|
||||
view.category = 'contractadmin';
|
||||
d.dispatch(new controller.Place());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doTransaction(d:Dispatch) {
|
||||
view.category = 'members';
|
||||
d.dispatch(new controller.Transaction());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doDistribution(d:Dispatch) {
|
||||
view.category = 'contractadmin';
|
||||
d.dispatch(new controller.Distribution());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doMembership(d:Dispatch) {
|
||||
view.category = 'members';
|
||||
d.dispatch(new controller.Membership());
|
||||
}
|
||||
|
||||
function doShop(d:Dispatch) {
|
||||
view.category = 'shop';
|
||||
d.dispatch(new controller.Shop());
|
||||
}
|
||||
|
||||
@tpl('shop/default2.mtt')
|
||||
function doShop2(place:db.Place, date:String) {
|
||||
view.category = 'shop';
|
||||
view.place = place;
|
||||
view.date = date;
|
||||
}
|
||||
|
||||
@logged
|
||||
function doProduct(d:Dispatch) {
|
||||
view.category = 'contractadmin';
|
||||
d.dispatch(new controller.Product());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doAmap(d:Dispatch) {
|
||||
view.category = 'amap';
|
||||
d.dispatch(new controller.Amap());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doContract(d:Dispatch) {
|
||||
view.category = 'contract';
|
||||
d.dispatch(new Contract());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doContractAdmin(d:Dispatch) {
|
||||
view.category = 'contractadmin';
|
||||
d.dispatch(new ContractAdmin());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doMessages(d:Dispatch) {
|
||||
view.category = 'messages';
|
||||
d.dispatch(new Messages());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doAmapadmin(d:Dispatch) {
|
||||
view.category = 'amapadmin';
|
||||
d.dispatch(new AmapAdmin());
|
||||
}
|
||||
|
||||
@logged
|
||||
function doValidate(date:Date, place:db.Place, user:db.User, d:haxe.web.Dispatch){
|
||||
|
||||
var v = new controller.Validate();
|
||||
v.date = date;
|
||||
v.place = place;
|
||||
v.user = user;
|
||||
d.dispatch(v);
|
||||
}
|
||||
|
||||
@admin
|
||||
function doAdmin(d:Dispatch) {
|
||||
d.dispatch(new controller.admin.Admin());
|
||||
}
|
||||
|
||||
@admin
|
||||
function doDb(d:Dispatch) {
|
||||
d.parts = []; //disable haxe.web.Dispatch
|
||||
sys.db.Admin.handler();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+741
@@ -0,0 +1,741 @@
|
||||
package controller;
|
||||
import Common;
|
||||
import haxe.Utf8;
|
||||
import sugoi.form.Form;
|
||||
import sugoi.form.elements.Selectbox;
|
||||
import sugoi.form.validators.EmailValidator;
|
||||
import sugoi.tools.Utils;
|
||||
|
||||
|
||||
class Member extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
if (!app.user.canAccessMembership()) throw Redirect("/");
|
||||
}
|
||||
|
||||
@logged
|
||||
@tpl('member/default.mtt')
|
||||
function doDefault(?args: { ?search:String, ?select:String } ) {
|
||||
checkToken();
|
||||
|
||||
var browse:Int->Int->List<Dynamic>;
|
||||
var uids = db.UserAmap.manager.search($amap == app.user.getAmap(), false);
|
||||
var uids = Lambda.map(uids, function(ua) return ua.user.id);
|
||||
if (args != null && args.search != null) {
|
||||
|
||||
//SEARCH
|
||||
browse = function(index:Int, limit:Int) {
|
||||
var search = "%"+StringTools.trim(args.search)+"%";
|
||||
return db.User.manager.search(
|
||||
($lastName.like(search) ||
|
||||
$lastName2.like(search) ||
|
||||
$address1.like(search) ||
|
||||
$address2.like(search) ||
|
||||
$firstName.like(search) ||
|
||||
$firstName2.like(search)
|
||||
) && $id in uids , { orderBy:-id }, false);
|
||||
}
|
||||
view.search = args.search;
|
||||
|
||||
}else if(args!=null && args.select!=null){
|
||||
|
||||
//SELECTION
|
||||
|
||||
switch(args.select) {
|
||||
case "nocontract":
|
||||
if (app.params.exists("csv")) {
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(db.User.getUsers_NoContracts()), ["firstName", "lastName", "email"], t._("Without contracts"));
|
||||
return;
|
||||
}else {
|
||||
browse = function(index:Int, limit:Int) { return db.User.getUsers_NoContracts(index, limit); }
|
||||
}
|
||||
case "contract":
|
||||
|
||||
if (app.params.exists("csv")) {
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(db.User.getUsers_Contracts()), ["firstName", "lastName", "email"], t._("With orders"));
|
||||
return;
|
||||
}else {
|
||||
browse = function(index:Int, limit:Int) { return db.User.getUsers_Contracts(index, limit); }
|
||||
}
|
||||
|
||||
case "nomembership" :
|
||||
if (app.params.exists("csv")) {
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(db.User.getUsers_NoMembership()), ["firstName", "lastName", "email"], t._("Memberships to be renewed"));
|
||||
return;
|
||||
}else {
|
||||
browse = function(index:Int, limit:Int) { return db.User.getUsers_NoMembership(index, limit); }
|
||||
}
|
||||
case "newusers" :
|
||||
if (app.params.exists("csv")) {
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(db.User.getUsers_NewUsers()), ["firstName", "lastName", "email"], t._("Never connected"));
|
||||
return;
|
||||
}else {
|
||||
browse = function(index:Int, limit:Int) { return db.User.getUsers_NewUsers(index, limit); }
|
||||
}
|
||||
default:
|
||||
throw t._("Unknown selection");
|
||||
}
|
||||
view.select = args.select;
|
||||
|
||||
}else {
|
||||
if (app.params.exists("csv")) {
|
||||
var headers = ["firstName", "lastName", "email","phone", "firstName2", "lastName2","email2","phone2", "address1","address2","zipCode","city"];
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(db.User.manager.search( $id in uids, {orderBy:lastName}, false)), headers, t._("Members"));
|
||||
return;
|
||||
}else {
|
||||
//default display
|
||||
browse = function(index:Int, limit:Int) {
|
||||
return db.User.manager.search( $id in uids, { limit:[index,limit], orderBy:lastName }, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var count = uids.length;
|
||||
var rb = new sugoi.tools.ResultsBrowser(count, (args.select!=null||args.search!=null)?1000:10, browse);
|
||||
view.members = rb;
|
||||
|
||||
if (args.select == null || args.select != "newusers") {
|
||||
//count new users
|
||||
view.newUsers = db.User.getUsers_NewUsers().length;
|
||||
}
|
||||
|
||||
view.waitingList = db.WaitingList.manager.count($group == app.user.amap);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Move to waiting list
|
||||
*/
|
||||
function doMovetowl(u:db.User){
|
||||
|
||||
var ua = db.UserAmap.get(u, app.user.amap, true);
|
||||
ua.delete();
|
||||
|
||||
var wl = new db.WaitingList();
|
||||
wl.user = u;
|
||||
wl.group = app.user.amap;
|
||||
wl.insert();
|
||||
|
||||
throw Ok("/member", u.getName() +" "+ t._("is now on waiting list.") );
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display waiting list
|
||||
*/
|
||||
@tpl('member/waiting.mtt')
|
||||
function doWaiting(?args:{?add:db.User,?remove:db.User}){
|
||||
|
||||
if (args != null){
|
||||
if (args.add != null){
|
||||
|
||||
service.WaitingListService.approveRequest(args.add,app.user.amap);
|
||||
throw Ok("/member/waiting", t._("Membership request accepted") );
|
||||
|
||||
}else if (args.remove != null){
|
||||
|
||||
service.WaitingListService.cancelRequest(args.remove,app.user.amap);
|
||||
throw Ok("/member/waiting", t._("Membership request refused") );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
view.waitingList = db.WaitingList.manager.search($group == app.user.amap,{orderBy:-date});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an invitation to a new member
|
||||
*/
|
||||
function doInviteMember(u:db.User){
|
||||
|
||||
if (checkToken() ) {
|
||||
u.sendInvitation(app.user.amap);
|
||||
throw Ok('/member/view/'+u.id, t._("Invitation sent.") );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite 'never logged' users
|
||||
*/
|
||||
function doInvite() {
|
||||
|
||||
if (checkToken()) {
|
||||
|
||||
var users = db.User.getUsers_NewUsers();
|
||||
try{
|
||||
for ( u in users) {
|
||||
u.sendInvitation(app.user.amap);
|
||||
Sys.sleep(0.2);
|
||||
}
|
||||
}catch (e:String){
|
||||
if (e.indexOf("curl") >-1) {
|
||||
App.current.logError(e, haxe.CallStack.toString(haxe.CallStack.exceptionStack()));
|
||||
throw Error("/member", t._("An error occurred while sending emails, please retry"));
|
||||
}
|
||||
}
|
||||
|
||||
throw Ok('/member', t._("Congratulations, you just sent <b>::userLength::</b> invitations", {userLength:users.length}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@tpl("member/view.mtt")
|
||||
function doView(member:db.User) {
|
||||
|
||||
view.member = member;
|
||||
var userAmap = db.UserAmap.get(member, app.user.amap);
|
||||
if (userAmap == null) throw Error("/member", t._("This person does not belong to your group"));
|
||||
|
||||
view.userAmap = userAmap;
|
||||
view.canLoginAs = (db.UserAmap.manager.count($userId == member.id) == 1 && app.user.isAmapManager()) || app.user.isAdmin();
|
||||
|
||||
//orders
|
||||
var row = { constOrders:new Array<UserOrder>(), varOrders:new Map<String,Array<UserOrder>>() };
|
||||
|
||||
//commandes fixes
|
||||
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_CONSTORDERS && $amap == app.user.amap && $endDate > DateTools.delta(Date.now(),-1000.0*60*60*24*30), false);
|
||||
var orders = member.getOrdersFromContracts(contracts);
|
||||
row.constOrders = service.OrderService.prepare(orders);
|
||||
|
||||
//commandes variables groupées par date de distrib
|
||||
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_VARORDER && $amap == app.user.amap && $endDate > DateTools.delta(Date.now(),-1000.0*60*60*24*30), false);
|
||||
var distribs = new Map<String,List<db.UserContract>>();
|
||||
for (c in contracts) {
|
||||
var ds = c.getDistribs();
|
||||
for (d in ds) {
|
||||
var k = d.date.toString().substr(0, 10);
|
||||
var orders = member.getOrdersFromDistrib(d);
|
||||
if (orders.length > 0) {
|
||||
if (!distribs.exists(k)) {
|
||||
distribs.set(k, orders);
|
||||
}else {
|
||||
|
||||
var v = distribs.get(k);
|
||||
for ( o in orders ) v.add(o);
|
||||
distribs.set(k, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for ( k in distribs.keys()){
|
||||
var d = distribs.get(k);
|
||||
var d2 = service.OrderService.prepare(d);
|
||||
row.varOrders.set(k,d2);
|
||||
}
|
||||
|
||||
|
||||
view.userContracts = row;
|
||||
checkToken(); //to insert a token in tpl
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin : Log in as this user for debugging purpose
|
||||
* @param user
|
||||
* @param amap
|
||||
*/
|
||||
function doLoginas(member:db.User, amap:db.Amap) {
|
||||
|
||||
if (!app.user.isAdmin()){
|
||||
if (!app.user.isAmapManager()) return;
|
||||
if (member.isAdmin()) return;
|
||||
if ( db.UserAmap.manager.count($userId == member.id) > 1 ) return;
|
||||
|
||||
}
|
||||
|
||||
App.current.session.setUser(member);
|
||||
App.current.session.data.amapId = amap.id;
|
||||
throw Redirect("/member/view/" + member.id );
|
||||
}
|
||||
|
||||
@tpl('member/lastMessages.mtt')
|
||||
function doLastMessages(member:db.User){
|
||||
|
||||
var out = new Array<{date:Date,subject:String,success:String,failure:String}>();
|
||||
var threeMonth = DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30.5 * 3);
|
||||
|
||||
for ( m in sugoi.db.BufferedMail.manager.search($remoteId == app.user.amap.id && $cdate > threeMonth, {limit:10, orderBy:-cdate})){
|
||||
|
||||
var status : sugoi.mail.IMailer.MailerResult = m.status;
|
||||
|
||||
if ( status!=null && status.get(member.email)!=null ){
|
||||
|
||||
var r = m.getMailerResultMessage(member.email);
|
||||
out.push( {date:m.cdate,subject:m.title,success:r.success,failure:r.failure} );
|
||||
}
|
||||
|
||||
}
|
||||
view.emails = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a Member
|
||||
*/
|
||||
@tpl('form.mtt')
|
||||
function doEdit(member:db.User) {
|
||||
|
||||
if (member.isAdmin() && !app.user.isAdmin()) throw Error("/", t._("You cannot modify the account of an administrator"));
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(member);
|
||||
|
||||
//cleaning
|
||||
form.removeElement( form.getElement("rights") );
|
||||
form.removeElement( form.getElement("lang") );
|
||||
form.removeElement( form.getElement("ldate") );
|
||||
form.removeElement( form.getElement("apiKey") );
|
||||
|
||||
|
||||
var isReg = member.isFullyRegistred();
|
||||
var groupNum = db.UserAmap.manager.count($userId == member.id);
|
||||
|
||||
//an administrator can modify a user's email only if he's not member elsewhere
|
||||
if (groupNum > 1){
|
||||
form.removeElementByName("email");
|
||||
form.removeElementByName("email2");
|
||||
app.session.addMessage(t._("For security reasons, you cannot modify the e-mail of this person because this person is a member of more than 1 group."));
|
||||
}
|
||||
|
||||
//an administrator can modify a user's pass only if he's a not registred user.
|
||||
if (!isReg){
|
||||
app.session.addMessage(t._("This person did not define yet a password. You are exceptionaly authorized to do it. Please don't forget to tell this person."));
|
||||
form.getElement("pass").required = false;
|
||||
}else{
|
||||
form.removeElement( form.getElement("pass") );
|
||||
}
|
||||
|
||||
if (form.checkToken()) {
|
||||
|
||||
if (app.user.amap.flags.has(db.Amap.AmapFlags.PhoneRequired) && form.getValueOf("phone") == null ){
|
||||
throw Error("/member/edit/"+member.id, t._("Phone number is required in this group."));
|
||||
}
|
||||
|
||||
form.toSpod(member);
|
||||
|
||||
//check that the given emails are not already used elsewhere
|
||||
var sim = db.User.getSameEmail(member.email,member.email2);
|
||||
for ( s in sim) {
|
||||
if (s.id == member.id) sim.remove(s);
|
||||
}
|
||||
if (sim.length > 0) {
|
||||
|
||||
//Let's merge the 2 users if it has no orders.
|
||||
var id = sim.first().id;
|
||||
if (db.UserContract.manager.search( $userId == id || $userId2 == id , false).length == 0) {
|
||||
//merge
|
||||
member.merge( sim.first() );
|
||||
app.session.addMessage(t._("This e-mail was used by another user account. As this user account was not used, it has been merged into the current user account."));
|
||||
|
||||
} else {
|
||||
var str = t._("Warning, this e-mail or this name already exists for another account : ");
|
||||
str += Lambda.map(sim, function(u) return "<a href='/member/view/" + u.id + "'>" + u.getCoupleName() + "</a>").join(",");
|
||||
str += " "+t._("These accounts can't be merged because the second account has orders");
|
||||
throw Error("/member/edit/" + member.id, str);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isReg) member.setPass(form.getValueOf("pass"));
|
||||
|
||||
member.update();
|
||||
|
||||
if (!App.config.DEBUG && groupNum == 1) {
|
||||
|
||||
//warn the user that his email has been updated
|
||||
if (form.getValueOf("email") != member.email) {
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
|
||||
m.addRecipient(member.email);
|
||||
m.setSubject(t._("Change your e-mail in your account Cagette.net"));
|
||||
m.setHtmlBody( app.processTemplate("mail/message.mtt", { text:app.user.getName() + t._(" just modified your e-mail in your account Cagette.net.<br/>Your e-mail is now:")+form.getValueOf("email") } ) );
|
||||
App.sendMail(m);
|
||||
|
||||
}
|
||||
if (form.getValueOf("email2") != member.email2 && member.email2!=null) {
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
m.addRecipient(member.email2);
|
||||
m.setSubject(t._("Change the e-mail of your account Cagette.net"));
|
||||
m.setHtmlBody( app.processTemplate("mail/message.mtt", { text:app.user.getName() +t._(" just modified your e-mail in your account Cagette.net.<br/>Your e-mail is now:")+form.getValueOf("email2") } ) );
|
||||
App.sendMail(m);
|
||||
}
|
||||
}
|
||||
|
||||
throw Ok('/member/view/'+member.id, t._("This member has beed updated"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user from this group
|
||||
*/
|
||||
function doDelete(user:db.User,?args:{confirm:Bool,token:String}) {
|
||||
|
||||
if (checkToken()) {
|
||||
if (!app.user.canAccessMembership()) throw t._("You cannot do that.");
|
||||
if (user.id == app.user.id) throw Error("/member/view/" + user.id, t._("You cannot delete yourself."));
|
||||
if ( Lambda.count(user.getOrders(app.user.amap),function(x) return x.quantity>0) > 0 && !args.confirm) {
|
||||
throw Error("/member/view/"+user.id, t._("Warning, this account has orders. <a class='btn btn-default btn-xs' href='/member/delete/::userid::?token=::argstoken::&confirm=1'>Remove anyway</a>", {userid:user.id, argstoken:args.token}));
|
||||
}
|
||||
|
||||
var ua = db.UserAmap.get(user, app.user.amap, true);
|
||||
if (ua != null) {
|
||||
ua.delete();
|
||||
throw Ok("/member", t._("::user:: has been removed from your group",{user:user.getName()}));
|
||||
}else {
|
||||
throw Error("/member", t._("This person does not belong to \"::amapname::\"", {amapname:app.user.amap.name}));
|
||||
}
|
||||
}else {
|
||||
throw Redirect("/member/view/"+user.id);
|
||||
}
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doMerge(user:db.User) {
|
||||
|
||||
if (!app.user.canAccessMembership()) throw Error("/","Action interdite");
|
||||
|
||||
view.title = t._("Merge an account with another one");
|
||||
view.text = t._("This action allows you to merge two accounts (when you have duplicates in the database for example).<br/>Contracts of account 2 will be moved to account 1, and account 2 will be deleted. Warning, it is not possible to cancel this action.");
|
||||
|
||||
var form = new Form("merge");
|
||||
|
||||
var members = app.user.amap.getMembers();
|
||||
var members = Lambda.array(Lambda.map(members, function(x) return { key:Std.string(x.id), value:x.getName() } ));
|
||||
var mlist = new Selectbox("member1", t._("Account 1"), members, Std.string(user.id));
|
||||
form.addElement( mlist );
|
||||
var mlist = new Selectbox("member2", t._("Account 2"), members);
|
||||
form.addElement( mlist );
|
||||
|
||||
if (form.checkToken()) {
|
||||
|
||||
var m1 = Std.parseInt(form.getElement("member1").value);
|
||||
var m2 = Std.parseInt(form.getElement("member2").value);
|
||||
var m1 = db.User.manager.get(m1,true);
|
||||
var m2 = db.User.manager.get(m2,true);
|
||||
|
||||
//if (m1.amapId != m2.amapId) throw "ils ne sont pas de la même amap !";
|
||||
|
||||
//on prend tout à m2 pour donner à m1
|
||||
//change usercontracts
|
||||
var contracts = db.UserContract.manager.search($user==m2 || $user2==m2,true);
|
||||
for (c in contracts) {
|
||||
if (c.user.id == m2.id) c.user = m1;
|
||||
if (c.user2!=null && c.user2.id == m2.id) c.user2 = m1;
|
||||
c.update();
|
||||
}
|
||||
|
||||
//group memberships
|
||||
var adh = db.UserAmap.manager.search($user == m2, true);
|
||||
for ( a in adh) {
|
||||
a.user = m1;
|
||||
a.update();
|
||||
}
|
||||
|
||||
//change contacts
|
||||
var contacts = db.Contract.manager.search($contact==m2,true);
|
||||
for (c in contacts) {
|
||||
c.contact = m1;
|
||||
c.update();
|
||||
}
|
||||
//if (m2.amap.contact == m2) {
|
||||
//m1.amap.lock();
|
||||
//m1.amap.contact = m1;
|
||||
//m1.amap.update();
|
||||
//}
|
||||
|
||||
m2.delete();
|
||||
|
||||
throw Ok("/member/view/" + m1.id, t._("Both accounts have been merged"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@tpl('member/import.mtt')
|
||||
function doImport(?args: { confirm:Bool } ) {
|
||||
|
||||
var step = 1;
|
||||
var request = Utils.getMultipart(1024 * 1024 * 4); //4mb
|
||||
|
||||
//on recupere le contenu de l'upload
|
||||
var data = request.get("file");
|
||||
if ( data != null) {
|
||||
|
||||
var csv = new sugoi.tools.Csv();
|
||||
csv.setHeaders([t._("Firstname"), t._("Lastname"), t._("E-mail"), t._("Mobile phone"), t._("Partner's firstname"), t._("Partner's lastname"), t._("Partner's e-mail"), t._("Partner's Mobile phone"), t._("Address 1"), t._("Address 2"), t._("Post code"), t._("City")]);
|
||||
|
||||
//utf8 encode if needed
|
||||
try{
|
||||
if (!haxe.Utf8.validate(data)){
|
||||
data = haxe.Utf8.encode(data);
|
||||
}
|
||||
}catch (e:Dynamic){ }
|
||||
var unregistred = csv.importDatas(data);
|
||||
|
||||
/*var checkEmail = function(email){
|
||||
if ( !sugoi.form.validators.EmailValidator.check(email) ) {
|
||||
throw Error("/member", t._("The email <b>::email::</b> is invalid, please update your CSV file",{email:email}) );
|
||||
}
|
||||
}*/
|
||||
|
||||
//cleaning
|
||||
for ( user in unregistred.copy() ) {
|
||||
|
||||
//check nom+prenom
|
||||
if (user[0] == null || user[1] == null) {
|
||||
throw Error("/member/import", t._("You must fill the name and the firstname of the person. This line is incomplete: ") + user);
|
||||
}
|
||||
if (user[2] == null) {
|
||||
throw Error("/member/import", t._("Each person must have an e-mail to be able to log in. ::user0:: ::user1:: don't have one. ", {user0:user[0], user1:user[1]}) +user);
|
||||
}
|
||||
//uppercase du nom
|
||||
if (user[1] != null) user[1] = user[1].toUpperCase();
|
||||
if (user[5] != null) user[5] = user[5].toUpperCase();
|
||||
//lowercase email
|
||||
if (user[2] != null){
|
||||
user[2] = user[2].toLowerCase();
|
||||
//checkEmail(user[2]);
|
||||
}
|
||||
if (user[6] != null){
|
||||
user[6] = user[6].toLowerCase();
|
||||
//checkEmail(user[6]);
|
||||
}
|
||||
}
|
||||
|
||||
//utf-8 check
|
||||
for ( row in unregistred.copy()) {
|
||||
|
||||
for ( i in 0...row.length) {
|
||||
var t = row[i];
|
||||
if (t != "" && t != null) {
|
||||
try{
|
||||
if (!Utf8.validate(t)) {
|
||||
t = Utf8.encode(t);
|
||||
}
|
||||
}catch (e:Dynamic) {}
|
||||
row[i] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//put already registered people in another list
|
||||
var registred = [];
|
||||
for (r in unregistred.copy()) {
|
||||
//var firstName = r[0];
|
||||
//var lastName = r[1];
|
||||
var email = r[2];
|
||||
|
||||
//var firstName2 = r[4];
|
||||
//var lastName2 = r[5];
|
||||
var email2 = r[6];
|
||||
|
||||
var us = db.User.getSameEmail(email, email2);
|
||||
|
||||
if (us.length > 0) {
|
||||
unregistred.remove(r);
|
||||
registred.push(r);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app.session.data.csvUnregistered = unregistred;
|
||||
app.session.data.csvRegistered = registred;
|
||||
|
||||
view.data = unregistred;
|
||||
view.data2 = registred;
|
||||
step = 2;
|
||||
}
|
||||
|
||||
|
||||
if (args != null && args.confirm) {
|
||||
|
||||
//import unregistered members
|
||||
var i : Iterable<Dynamic> = cast app.session.data.csvUnregistered;
|
||||
for (u in i) {
|
||||
if (u[0] == null || u[0] == "null" || u[0] == "") continue;
|
||||
|
||||
var user = new db.User();
|
||||
user.firstName = u[0];
|
||||
user.lastName = u[1];
|
||||
user.email = u[2];
|
||||
if (user.email != null && user.email != "null" &&!EmailValidator.check(user.email)) {
|
||||
throw t._("The E-mail ::useremail:: is invalid, please modify your file", {useremail:user.email});
|
||||
}
|
||||
user.phone = u[3];
|
||||
|
||||
user.firstName2 = u[4];
|
||||
user.lastName2 = u[5];
|
||||
user.email2 = u[6];
|
||||
if (user.email2 != null && user.email2 != "null" && !EmailValidator.check(user.email2)) {
|
||||
App.log(u);
|
||||
throw t._("The E-mail of the partner of ::userFirstName:: ::userLastName:: '::userEmail::' is invalid, please check your file", {userFirstName:user.firstName, userLastName:user.lastName, userEmail:user.email2});
|
||||
}
|
||||
user.phone2 = u[7];
|
||||
user.address1 = u[8];
|
||||
user.address2 = u[9];
|
||||
user.zipCode = u[10];
|
||||
user.city = u[11];
|
||||
user.insert();
|
||||
|
||||
var ua = new db.UserAmap();
|
||||
ua.user = user;
|
||||
ua.amap = app.user.amap;
|
||||
ua.insert();
|
||||
}
|
||||
|
||||
//import registered members
|
||||
var i : Iterable<Array<String>> = cast app.session.data.csvRegistered;
|
||||
for (u in i) {
|
||||
var email = u[2];
|
||||
var email2 = u[6];
|
||||
|
||||
var us = db.User.getSameEmail(email, email2);
|
||||
var userAmaps = db.UserAmap.manager.search($amap == app.user.amap && $userId in Lambda.map(us, function(u) return u.id), false);
|
||||
|
||||
//member exists but is not member of this group.
|
||||
if (userAmaps.length == 0) {
|
||||
var ua = new db.UserAmap();
|
||||
ua.user = us.first();
|
||||
ua.amap = app.user.amap;
|
||||
ua.insert();
|
||||
}
|
||||
}
|
||||
|
||||
view.numImported = app.session.data.csvUnregistered.length + app.session.data.csvRegistered.length;
|
||||
app.session.data.csvUnregistered = null;
|
||||
app.session.data.csvRegistered = null;
|
||||
|
||||
step = 3;
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
//reset import when back to import page
|
||||
app.session.data.csvUnregistered = null;
|
||||
app.session.data.csvRegistered = null;
|
||||
}
|
||||
|
||||
view.step = step;
|
||||
}
|
||||
|
||||
@tpl("user/insert.mtt")
|
||||
public function doInsert() {
|
||||
|
||||
if (!app.user.canAccessMembership()) throw Error("/", t._("Forbidden action"));
|
||||
|
||||
var m = new db.User();
|
||||
var form = sugoi.form.Form.fromSpod(m);
|
||||
form.removeElement(form.getElement("lang"));
|
||||
form.removeElement(form.getElement("rights"));
|
||||
form.removeElement(form.getElement("pass"));
|
||||
form.removeElement(form.getElement("ldate") );
|
||||
form.removeElement( form.getElement("apiKey") );
|
||||
form.addElement(new sugoi.form.elements.Checkbox("warnAmapManager", t._("Send an E-mail to the person in charge of the group"), true));
|
||||
form.getElement("email").addValidator(new EmailValidator());
|
||||
form.getElement("email2").addValidator(new EmailValidator());
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
//check doublon de User et de UserAmap
|
||||
var userSims = db.User.getSameEmail(form.getValueOf("email"),form.getValueOf("email2"));
|
||||
view.userSims = userSims;
|
||||
var userAmaps = db.UserAmap.manager.search($amap == app.user.amap && $userId in Lambda.map(userSims, function(u) return u.id), false);
|
||||
view.userAmaps = userAmaps;
|
||||
|
||||
if (userAmaps.length > 0) {
|
||||
//user deja enregistré dans cette amap
|
||||
throw Error('/member/view/' + userAmaps.first().user.id, t._("This person is already member of this group"));
|
||||
|
||||
}else if (userSims.length > 0) {
|
||||
//des users existent avec ce nom ,
|
||||
//if (userSims.length == 1) {
|
||||
// si yen a qu'un on l'inserte
|
||||
var ua = new db.UserAmap();
|
||||
ua.user = userSims.first();
|
||||
ua.amap = app.user.amap;
|
||||
ua.insert();
|
||||
throw Ok('/member/', t._("This person already had an account on Cagette.net, and is now member of your group."));
|
||||
/*}else {
|
||||
//demander validation avant d'inserer le userAmap
|
||||
//TODO
|
||||
throw Error('/member', t._("Not possible to add this person because there are already some people in the database having the same firstname and name. Please contact the administrator.")+userSims);
|
||||
}*/
|
||||
return;
|
||||
}else {
|
||||
|
||||
if (app.user.amap.flags.has(db.Amap.AmapFlags.PhoneRequired) && form.getValueOf("phone") == null ){
|
||||
throw Error("/member/insert", t._("Phone number is required in this group."));
|
||||
}
|
||||
|
||||
//insert user
|
||||
var u = new db.User();
|
||||
form.toSpod(u);
|
||||
u.lang = app.user.lang;
|
||||
u.insert();
|
||||
|
||||
//insert userAmap
|
||||
var ua = new db.UserAmap();
|
||||
ua.user = u;
|
||||
ua.amap = app.user.getAmap();
|
||||
ua.insert();
|
||||
|
||||
if (form.getValueOf("warnAmapManager") == "1") {
|
||||
var url = "http://" + App.config.HOST + "/member/view/" + u.id;
|
||||
var text = t._("::admin:: just keyed-in contact details of a new member: <br/><strong>::newMember::</strong><br/> <a href='::url::'>See contact details</a>",{admin:app.user.getName(),newMember:u.getCoupleName(),url:url});
|
||||
App.quickMail(
|
||||
app.user.getAmap().contact.email,
|
||||
app.user.amap.name +" - "+ t._("New member") + " : " + u.getCoupleName(),
|
||||
app.processTemplate("mail/message.mtt", { text:text } )
|
||||
);
|
||||
}
|
||||
|
||||
throw Ok('/member/', t._("This person is now member of the group"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* user payments history
|
||||
*/
|
||||
@tpl('member/payments.mtt')
|
||||
function doPayments(m:db.User){
|
||||
|
||||
service.PaymentService.updateUserBalance(m, app.user.amap);
|
||||
var browse:Int->Int->List<Dynamic>;
|
||||
|
||||
//default display
|
||||
browse = function(index:Int, limit:Int) {
|
||||
return db.Operation.getOperationsWithIndex(m,app.user.amap,index,limit,true);
|
||||
}
|
||||
|
||||
var count = db.Operation.countOperations(m,app.user.amap);
|
||||
var rb = new sugoi.tools.ResultsBrowser(count, 10, browse);
|
||||
view.rb = rb;
|
||||
view.member = m;
|
||||
view.balance = db.UserAmap.get(m, app.user.amap).balance;
|
||||
|
||||
checkToken();
|
||||
}
|
||||
|
||||
@tpl('member/balance.mtt')
|
||||
function doBalance(){
|
||||
view.balanced = db.UserAmap.manager.search($amap == app.user.amap && $balance == 0.0, false);
|
||||
view.credit = db.UserAmap.manager.search($amap == app.user.amap && $balance > 0, false);
|
||||
view.debt = db.UserAmap.manager.search($amap == app.user.amap && $balance < 0, false);
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
package controller;
|
||||
import sugoi.form.elements.IntSelect;
|
||||
using Std;
|
||||
/**
|
||||
* Membership management
|
||||
*
|
||||
* @author fbarbut<francois.barbut@gmail.com>
|
||||
*/
|
||||
class Membership extends controller.Controller
|
||||
{
|
||||
|
||||
@tpl("membership/default.mtt")
|
||||
function doDefault(member:db.User) {
|
||||
var userAmap = db.UserAmap.get(member, app.user.amap,true);
|
||||
if (userAmap == null) throw Error("/member", t._("This person is not a member of your group"));
|
||||
|
||||
//formulaire
|
||||
var f = new sugoi.form.Form("membership");
|
||||
var year = Date.now().getFullYear();
|
||||
var data = [];
|
||||
var now = Date.now();
|
||||
for ( x in 0...5) {
|
||||
|
||||
var y = now.getFullYear() - x;
|
||||
var yy = DateTools.delta(now, DateTools.days(365) * -x);
|
||||
data.push({label:app.user.amap.getPeriodName(yy),value:app.user.amap.getMembershipYear(yy)});
|
||||
}
|
||||
f.addElement(new IntSelect("year", t._("Period"), data,app.user.amap.getMembershipYear(),true));
|
||||
f.addElement(new sugoi.form.elements.DateDropdowns("date", t._("Date of payment of subscription"), null, true));
|
||||
if (f.isValid()) {
|
||||
var y : Int = f.getValueOf("year");
|
||||
|
||||
if (db.Membership.get(member, app.user.amap, y) != null) throw Error("/membership/"+member.id, t._("This subscription has been already keyed-in"));
|
||||
|
||||
var cotis = new db.Membership();
|
||||
cotis.amap = app.user.amap;
|
||||
cotis.user = member;
|
||||
cotis.year = y;
|
||||
cotis.date = f.getElement("date").value;
|
||||
cotis.insert();
|
||||
throw Ok("/membership/"+member.id, t._("Subscription saved"));
|
||||
}
|
||||
|
||||
//années de cotisation
|
||||
var memberships = db.Membership.manager.search($user == member && $amap == app.user.amap,{orderBy:-year}, false);
|
||||
//for ( m in memberships) {
|
||||
//Reflect.setField(m, 'yearDate', new Date(m.year, 1, 1, 1, 1, 1));
|
||||
//}
|
||||
view.memberships = memberships;
|
||||
|
||||
//view
|
||||
view.form = f;
|
||||
view.member = member;
|
||||
checkToken();
|
||||
}
|
||||
|
||||
|
||||
public function doDelete(member:db.User, year:Int,?args:{token:String}) {
|
||||
|
||||
if (checkToken()) {
|
||||
var cotis = db.Membership.get(member, app.user.amap, year, true);
|
||||
if (cotis == null) throw Error("/", t._("This subscription does not exist"));
|
||||
|
||||
cotis.delete();
|
||||
throw Ok("/membership/" + member.id, t._("Subscription deleted"));
|
||||
}else {
|
||||
throw Error("/", "Bad Token");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+211
@@ -0,0 +1,211 @@
|
||||
package controller;
|
||||
import db.Message;
|
||||
import db.UserContract;
|
||||
import sugoi.form.ListData;
|
||||
import sugoi.form.elements.*;
|
||||
import sugoi.form.Form;
|
||||
|
||||
class Messages extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
if (!app.user.canAccessMessages()) throw Redirect("/");
|
||||
}
|
||||
|
||||
@tpl("messages/default.mtt")
|
||||
function doDefault() {
|
||||
|
||||
var form = new Form("msg");
|
||||
|
||||
var senderName = "";
|
||||
var senderMail = "";
|
||||
|
||||
if (App.current.session.data.whichUser == 1 && app.user.email2 != null) {
|
||||
senderMail = app.user.email2;
|
||||
senderName = app.user.firstName2 + " " + app.user.lastName2;
|
||||
|
||||
}else {
|
||||
senderMail = app.user.email;
|
||||
senderName = app.user.firstName + " " + app.user.lastName;
|
||||
}
|
||||
|
||||
var lists = getLists();
|
||||
form.addElement( new StringInput("senderName", t._("Sender name"),senderName,true));
|
||||
form.addElement( new StringInput("senderMail", t._("Sender E-Mail"),senderMail,true));
|
||||
form.addElement( new StringSelect("list", t._("Recipients"),lists,"1", true,null,"style='width:500px;'"));
|
||||
form.addElement( new StringInput("subject", t._("Subject:"),"",false,null,"style='width:500px;'") );
|
||||
form.addElement( new TextArea("text", t._("Message:"), "", false, null, "style='width:500px;height:350px;'") );
|
||||
|
||||
if (form.checkToken()) {
|
||||
|
||||
var listId = form.getElement("list").value;
|
||||
var dest = getSelection(listId);
|
||||
var mails = [];
|
||||
for ( d in dest) {
|
||||
if (d.email != null) mails.push(d.email);
|
||||
if (d.email2 != null) mails.push(d.email2);
|
||||
}
|
||||
|
||||
//send mail confirmation link
|
||||
var e = new sugoi.mail.Mail();
|
||||
e.setSubject(form.getValueOf("subject"));
|
||||
for ( x in mails) e.addRecipient(x);
|
||||
|
||||
e.setSender(App.config.get("default_email"),form.getValueOf("senderName"));
|
||||
e.setReplyTo(form.getValueOf("senderMail"),form.getValueOf("senderName"));
|
||||
//sender : default email ( explicitly tells that the server send an email on behalf of the user )
|
||||
//e.setHeader("Sender", App.config.get("default_email"));
|
||||
var text :String = form.getValueOf("text");
|
||||
var html = app.processTemplate("mail/message.mtt", { text:text,group:app.user.amap,list:getListName(listId) });
|
||||
e.setHtmlBody(html);
|
||||
|
||||
App.sendMail(e,app.user.getAmap(),listId,app.user);
|
||||
|
||||
//store message
|
||||
var lm = new db.Message();
|
||||
lm.amap = app.user.amap;
|
||||
lm.recipients = Lambda.array(Lambda.map(e.getRecipients(), function(x) return x.email));
|
||||
lm.title = e.getSubject();
|
||||
lm.date = Date.now();
|
||||
lm.body = e.getHtmlBody();
|
||||
if (listId != null) lm.recipientListId = listId;
|
||||
lm.sender = app.user;
|
||||
lm.insert();
|
||||
|
||||
|
||||
throw Ok("/messages", t._("The message has been sent"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
|
||||
if (app.user.isAmapManager()) {
|
||||
view.sentMessages = Message.manager.search($amap == app.user.amap && $recipientListId!=null, {orderBy:-date,limit:20}, false);
|
||||
}else {
|
||||
view.sentMessages = Message.manager.search($sender == app.user && $recipientListId!=null && $amap == app.user.amap , {orderBy:-date,limit:20}, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@tpl("messages/message.mtt")
|
||||
public function doMessage(msg:Message) {
|
||||
|
||||
if (!app.user.isAmapManager() && msg.sender.id != app.user.id) throw Error("/", t._("Non authorized access"));
|
||||
|
||||
view.list = getListName(msg.recipientListId);
|
||||
view.msg = msg;
|
||||
|
||||
//make status easier to display
|
||||
var s = new Array<{email:String,success:String,failure:String}>();
|
||||
/*if (msg.status != null){
|
||||
for ( k in msg.status.keys()) {
|
||||
var r = msg.getMailerResultMessage(k);
|
||||
s.push({email:k,success:r.success,failure:r.failure});
|
||||
}
|
||||
}*/
|
||||
|
||||
view.status = s;
|
||||
|
||||
}
|
||||
|
||||
function getLists() :FormData<String>{
|
||||
var out = [
|
||||
{value:'1', label: t._("Everyone")},
|
||||
{value:'2', label: t._("The board: persons in charge + contracts + memberships")},
|
||||
];
|
||||
|
||||
out.push( { value:'3', label: t._("TEST: me + spouse") } );
|
||||
out.push( { value:'4', label: t._("Members without contract/order") } );
|
||||
if(app.user.amap.hasMembership()) out.push( { value:'5', label:t._("Memberships to be renewed")} );
|
||||
|
||||
|
||||
var contracts = db.Contract.getActiveContracts(app.user.amap,true);
|
||||
for ( c in contracts) {
|
||||
var label = t._("Subscribers") + " " + c.toString();
|
||||
out.push({value:'c'+c.id,label:label});
|
||||
}
|
||||
return out ;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* get list name from id
|
||||
* @param listId
|
||||
*/
|
||||
function getListName(listId:String) {
|
||||
var l = getLists();
|
||||
|
||||
for (ll in l) {
|
||||
if (ll.value == listId) return ll.label;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
function getSelection(listId:String) {
|
||||
if (listId.substr(0, 1) == "c") {
|
||||
//contrats
|
||||
var contract = Std.parseInt(listId.substr(1));
|
||||
|
||||
var pids = db.Product.manager.search($contractId == contract, false);
|
||||
var pids = Lambda.map(pids, function(x) return x.id);
|
||||
var up = db.UserContract.manager.search($productId in pids, false);
|
||||
|
||||
|
||||
var users = [];
|
||||
for ( order in up) {
|
||||
if (!Lambda.has(users, order.user)) {
|
||||
users.push(order.user);
|
||||
}
|
||||
if (order.user2 != null && !Lambda.has(users, order.user2)) {
|
||||
users.push(order.user2);
|
||||
}
|
||||
}
|
||||
return users;
|
||||
|
||||
}else {
|
||||
var out = [];
|
||||
switch(listId) {
|
||||
case "1":
|
||||
//tout le monde
|
||||
out = Lambda.array(app.user.amap.getMembers());
|
||||
|
||||
case "2":
|
||||
var users = [];
|
||||
users.push(app.user.amap.contact);
|
||||
for ( c in db.Contract.manager.search($amap == app.user.amap)) {
|
||||
if (!Lambda.has(users, c.contact)) {
|
||||
users.push(c.contact);
|
||||
}
|
||||
}
|
||||
|
||||
//ajouter les autres personnes ayant les droits Admin ou Gestion Adhérents ou Gestion Contrats
|
||||
for (ua in Lambda.array(db.UserAmap.manager.search($rights != null && $amap == app.user.amap, false))) {
|
||||
if (ua.hasRight(GroupAdmin) || ua.hasRight(Membership) || ua.hasRight(ContractAdmin())) {
|
||||
if (!Lambda.has(users, ua.user)) users.push(ua.user);
|
||||
}
|
||||
}
|
||||
|
||||
out = users;
|
||||
|
||||
case "3":
|
||||
//moi
|
||||
return [app.user];
|
||||
case "4":
|
||||
return Lambda.array(db.User.getUsers_NoContracts());
|
||||
case "5":
|
||||
return Lambda.array(db.User.getUsers_NoMembership());
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
package controller;
|
||||
import sugoi.form.Form;
|
||||
|
||||
/**
|
||||
* Place controller
|
||||
*/
|
||||
class Place extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@tpl('place/view.mtt')
|
||||
function doView(place:db.Place) {
|
||||
view.place = place;
|
||||
|
||||
//build adress for google maps
|
||||
var addr = "";
|
||||
if (place.address1 != null) addr += place.address1;
|
||||
if (place.address2 != null) addr += ", " + place.address2;
|
||||
if (place.zipCode != null) addr += " " + place.zipCode;
|
||||
if (place.city != null) addr += " " + place.city;
|
||||
|
||||
view.addr = view.escapeJS(addr);
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doEdit(p:db.Place) {
|
||||
|
||||
var currentAddress = p.getAddress();
|
||||
|
||||
var f = sugoi.form.Form.fromSpod(p);
|
||||
|
||||
if (f.isValid()) {
|
||||
|
||||
f.toSpod(p);
|
||||
|
||||
if(currentAddress!=p.getAddress() || p.lat==null){
|
||||
try{
|
||||
service.PlaceService.geocode(p);
|
||||
}catch(e:Dynamic){
|
||||
App.current.session.addMessage(t._("Oops, we're unable to find where is located this address. This place will not be shown on the map.")+'<br/>$e',true);
|
||||
}
|
||||
}
|
||||
|
||||
p.amap = app.user.amap;
|
||||
p.update();
|
||||
throw Ok('/contractAdmin',t._("this place has been updated"));
|
||||
}
|
||||
|
||||
view.form = f;
|
||||
view.title = t._("Edit a place");
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
public function doInsert() {
|
||||
|
||||
var d = new db.Place();
|
||||
var f = sugoi.form.Form.fromSpod(d);
|
||||
|
||||
if (f.isValid()) {
|
||||
f.toSpod(d);
|
||||
d.amap = app.user.amap;
|
||||
d.insert();
|
||||
throw Ok('/contractAdmin',t._("The place has been registred") );
|
||||
}
|
||||
|
||||
view.form = f;
|
||||
view.title = t._("Register a new delivery place");
|
||||
}
|
||||
|
||||
public function doDelete(p:db.Place) {
|
||||
if (!app.user.isAmapManager()) throw "forbidden";
|
||||
if (checkToken()) {
|
||||
|
||||
if (db.Distribution.manager.search($placeId == p.id).length > 0)
|
||||
throw Error('/contractAdmin', t._("You can't delete this place because one or more distributions are linked to this place.") );
|
||||
|
||||
p.lock();
|
||||
p.delete();
|
||||
throw Ok("/contractAdmin", t._("Place deleted") );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Executable
+39
@@ -0,0 +1,39 @@
|
||||
package controller;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut<francois.barbut@gmail.com>
|
||||
*/
|
||||
class Plugin extends sugoi.BaseController
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
#if plugins
|
||||
|
||||
//cagette-hosted
|
||||
public function doHosted(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new hosted.controller.Main());
|
||||
}
|
||||
|
||||
//cagette-pro
|
||||
public function doPro(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new pro.controller.Main());
|
||||
}
|
||||
|
||||
//cagette-connector
|
||||
public function doConnector(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new connector.controller.Main());
|
||||
}
|
||||
|
||||
//cagette-wholesale-order
|
||||
public function doWho(d:haxe.web.Dispatch) {
|
||||
d.dispatch(new who.controller.Main());
|
||||
}
|
||||
|
||||
#end
|
||||
|
||||
}
|
||||
Executable
+339
@@ -0,0 +1,339 @@
|
||||
package controller;
|
||||
import sugoi.form.Form;
|
||||
import Common;
|
||||
import sugoi.form.ListData.FormData;
|
||||
import sugoi.form.elements.FloatInput;
|
||||
import sugoi.form.elements.FloatSelect;
|
||||
import sugoi.form.elements.IntSelect;
|
||||
using Std;
|
||||
|
||||
class Product extends Controller
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
view.nav = ["contractadmin","products"];
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doEdit(d:db.Product) {
|
||||
|
||||
if (!app.user.canManageContract(d.contract)) throw t._("Forbidden access");
|
||||
|
||||
var f = sugoi.form.Form.fromSpod(d);
|
||||
|
||||
//stock mgmt ?
|
||||
if (!d.contract.hasStockManagement()) f.removeElementByName('stock');
|
||||
|
||||
//VAT selector
|
||||
f.removeElement( f.getElement('vat') );
|
||||
var data :FormData<Float> = [];
|
||||
for (k in app.user.amap.vatRates.keys()) {
|
||||
data.push( { label:k, value:app.user.amap.vatRates[k] } );
|
||||
}
|
||||
f.addElement( new FloatSelect("vat", "TVA", data, d.vat ) );
|
||||
|
||||
f.removeElementByName("contractId");
|
||||
|
||||
//Product Taxonomy:
|
||||
//view.taxo = db.TxpProduct.manager.all();
|
||||
//f.addElement(new form.TxpProduct("txpProduct", "taxo",null,false) );
|
||||
var txId = d.txpProduct == null ? "" : Std.string(d.txpProduct.id);
|
||||
var html = '<div id="pInput"></div><script language="javascript">_.getProductInput("pInput","${d.name}","$txId","${f.name}");</script>';
|
||||
f.addElement(new sugoi.form.elements.Html("html",html, 'Nom'),1);
|
||||
|
||||
if (f.isValid()) {
|
||||
|
||||
f.toSpod(d);
|
||||
app.event(EditProduct(d));
|
||||
d.update();
|
||||
throw Ok('/contractAdmin/products/'+d.contract.id, t._("The product has been updated"));
|
||||
}else{
|
||||
app.event(PreEditProduct(d));
|
||||
}
|
||||
|
||||
view.form = f;
|
||||
view.title = t._("Modify a product");
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
public function doInsert(contract:db.Contract ) {
|
||||
|
||||
if (!app.user.isContractManager(contract)) throw Error("/", t._("Forbidden action"));
|
||||
|
||||
var d = new db.Product();
|
||||
var f = sugoi.form.Form.fromSpod(d);
|
||||
|
||||
f.removeElementByName("contractId");
|
||||
|
||||
//stock mgmt ?
|
||||
if (!contract.hasStockManagement()) f.removeElementByName('stock');
|
||||
|
||||
//vat selector
|
||||
f.removeElement( f.getElement('vat') );
|
||||
var data = [];
|
||||
for (k in app.user.amap.vatRates.keys()) {
|
||||
data.push( { value:app.user.amap.vatRates[k], label:k } );
|
||||
}
|
||||
f.addElement( new FloatSelect("vat", "TVA", data, d.vat ) );
|
||||
|
||||
var formName = f.name;
|
||||
var html = '<div id="pInput"></div><script language="javascript">_.getProductInput("pInput","",null,"$formName");</script>';
|
||||
f.addElement(new sugoi.form.elements.Html("html",html, 'Nom'),1);
|
||||
|
||||
if (f.isValid()) {
|
||||
f.toSpod(d);
|
||||
d.contract = contract;
|
||||
app.event(NewProduct(d));
|
||||
d.insert();
|
||||
throw Ok('/contractAdmin/products/'+d.contract.id, t._("The product has been saved"));
|
||||
}else{
|
||||
app.event(PreNewProduct(contract));
|
||||
}
|
||||
|
||||
view.form = f;
|
||||
view.title = t._("Key-in a new product");
|
||||
}
|
||||
|
||||
public function doDelete(p:db.Product) {
|
||||
|
||||
if (!app.user.canManageContract(p.contract)) throw t._("Forbidden access");
|
||||
|
||||
if (checkToken()) {
|
||||
|
||||
app.event(DeleteProduct(p));
|
||||
|
||||
var orders = db.UserContract.manager.search($productId == p.id, false);
|
||||
if (orders.length > 0) {
|
||||
throw Error("/contractAdmin", t._("Not possible to delete this product because some orders are referencing it"));
|
||||
}
|
||||
var cid = p.contract.id;
|
||||
p.lock();
|
||||
p.delete();
|
||||
|
||||
throw Ok("/contractAdmin/products/"+cid, t._("Product deleted"));
|
||||
}
|
||||
throw Error("/contractAdmin", t._("Token error"));
|
||||
}
|
||||
|
||||
|
||||
@tpl('product/import.mtt')
|
||||
function doImport(c:db.Contract, ?args: { confirm:Bool } ) {
|
||||
|
||||
if (!app.user.canManageContract(c)) throw t._("Forbidden access");
|
||||
|
||||
var csv = new sugoi.tools.Csv();
|
||||
csv.step = 1;
|
||||
var request = sugoi.tools.Utils.getMultipart(1024 * 1024 * 4);
|
||||
csv.setHeaders( ["productName","price","ref","desc","qt","unit","organic","floatQt","vat","stock"] );
|
||||
view.contract = c;
|
||||
|
||||
// get the uploaded file content
|
||||
if (request.get("file") != null) {
|
||||
//convert to utf-8 if needed
|
||||
var csvData = request.get("file");
|
||||
try{
|
||||
if (!haxe.Utf8.validate(csvData)){
|
||||
csvData = haxe.Utf8.encode(csvData);
|
||||
}
|
||||
}catch (e:Dynamic){ }
|
||||
var datas = csv.importDatasAsMap(csvData);
|
||||
|
||||
app.session.data.csvImportedData = datas;
|
||||
|
||||
csv.step = 2;
|
||||
view.csv = csv;
|
||||
}
|
||||
|
||||
if (args != null && args.confirm) {
|
||||
var i : Iterable<Map<String,String>> = cast app.session.data.csvImportedData;
|
||||
var fv = new sugoi.form.filters.FloatFilter();
|
||||
|
||||
for (p in i) {
|
||||
|
||||
if (p["productName"] != null){
|
||||
|
||||
var product = new db.Product();
|
||||
product.name = p["productName"];
|
||||
product.price = fv.filterString(p["price"]);
|
||||
product.ref = p["ref"];
|
||||
product.desc = p["desc"];
|
||||
product.vat = fv.filterString(p["vat"]);
|
||||
product.qt = fv.filterString(p["qt"]);
|
||||
if(p["unit"]!=null){
|
||||
product.unitType = switch(p["unit"].toLowerCase()){
|
||||
case "kg" : Kilogram;
|
||||
case "g" : Gram;
|
||||
case "l" : Litre;
|
||||
case "cl" : Centilitre;
|
||||
case "litre" : Litre;
|
||||
default : Piece;
|
||||
}
|
||||
}
|
||||
if (p["stock"] != null) product.stock = fv.filterString(p["stock"]);
|
||||
product.organic = p["organic"] != null;
|
||||
product.hasFloatQt = p["floatQt"] != null;
|
||||
|
||||
product.contract = c;
|
||||
product.insert();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
view.numImported = app.session.data.csvImportedData.length;
|
||||
app.session.data.csvImportedData = null;
|
||||
|
||||
csv.step = 3;
|
||||
}
|
||||
|
||||
if (csv.step == 1) {
|
||||
//reset import when back to import page
|
||||
app.session.data.csvImportedData = null;
|
||||
}
|
||||
|
||||
view.step = csv.step;
|
||||
}
|
||||
|
||||
@tpl("product/categorize.mtt")
|
||||
public function doCategorize(contract:db.Contract) {
|
||||
|
||||
|
||||
if (!app.user.canManageContract(contract)) throw t._("Forbidden access");
|
||||
|
||||
if (db.CategoryGroup.get(app.user.amap).length == 0) throw Error("/contractAdmin", t._("You must first define categories before you can assign a category to a product"));
|
||||
|
||||
//var form = new sugoi.form.Form("cat");
|
||||
//
|
||||
//for ( g in db.CategoryGroup.get(app.user.amap)) {
|
||||
//var data = [];
|
||||
//for ( c in g.getCategories()) {
|
||||
//data.push({key:Std.string(c.id),value:c.name});
|
||||
//}
|
||||
//form.addElement(new sugoi.form.elements.Selectbox("cats"+g.id,g.name,data));
|
||||
//}
|
||||
//
|
||||
//view.form = form;
|
||||
view.c = contract;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* init du Tagger
|
||||
* @param contract
|
||||
*/
|
||||
public function doCategorizeInit(contract:db.Contract) {
|
||||
|
||||
if (!app.user.canManageContract(contract)) throw t._("Forbidden access");
|
||||
|
||||
var data : TaggerInfos = {
|
||||
products:[],
|
||||
categories:[]
|
||||
}
|
||||
|
||||
for (p in contract.getProducts()) {
|
||||
|
||||
data.products.push({product:p.infos(),categories:Lambda.array(Lambda.map(p.getCategories(),function(x) return x.id))});
|
||||
}
|
||||
|
||||
for (cg in db.CategoryGroup.get(app.user.amap)) {
|
||||
|
||||
var x = { id:cg.id, categoryGroupName:cg.name, color:App.current.view.intToHex(db.CategoryGroup.COLORS[cg.color]),tags:[] };
|
||||
|
||||
for (t in cg.getCategories()) {
|
||||
x.tags.push({id:t.id,name:t.name});
|
||||
}
|
||||
data.categories.push(x);
|
||||
|
||||
}
|
||||
|
||||
Sys.print(haxe.Json.stringify(data));
|
||||
}
|
||||
|
||||
public function doCategorizeSubmit(contract:db.Contract) {
|
||||
|
||||
if (!app.user.canManageContract(contract)) throw t._("Forbidden access");
|
||||
|
||||
var data : TaggerInfos = haxe.Json.parse(app.params.get("data"));
|
||||
|
||||
db.ProductCategory.manager.unsafeDelete("delete from ProductCategory where productId in (" + Lambda.map(contract.getProducts(), function(t) return t.id).join(",")+")");
|
||||
|
||||
for (p in data.products) {
|
||||
for (t in p.categories) {
|
||||
var x = new db.ProductCategory();
|
||||
x.category = db.Category.manager.get(t, false);
|
||||
x.product = db.Product.manager.get(p.product.id,false);
|
||||
x.insert();
|
||||
}
|
||||
}
|
||||
|
||||
Sys.print(t._("Modifications saved"));
|
||||
}
|
||||
|
||||
|
||||
@tpl('product/addimage.mtt')
|
||||
function doAddImage(product:db.Product) {
|
||||
|
||||
if (!app.user.canManageContract(product.contract)) throw t._("Forbidden access");
|
||||
|
||||
view.c = product.contract;
|
||||
view.image = product.image;
|
||||
|
||||
var request = sugoi.tools.Utils.getMultipart(1024 * 1024 * 12); //12Mb
|
||||
|
||||
if (request.exists("image")) {
|
||||
|
||||
//Image
|
||||
var image = request.get("image");
|
||||
if (image != null && image.length > 0) {
|
||||
var img : sugoi.db.File = null;
|
||||
if ( Sys.systemName() == "Windows") {
|
||||
img = sugoi.db.File.create(request.get("image"), request.get("image_filename"));
|
||||
}else {
|
||||
img = sugoi.tools.UploadedImage.resizeAndStore(request.get("image"), request.get("image_filename"), 400, 400);
|
||||
}
|
||||
|
||||
product.lock();
|
||||
|
||||
if (product.image != null) {
|
||||
//efface ancienne
|
||||
product.image.lock();
|
||||
product.image.delete();
|
||||
}
|
||||
|
||||
product.image = img;
|
||||
product.update();
|
||||
throw Ok('/product/addImage/'+product.id,'Image mise à jour');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@tpl('product/compose.mtt')
|
||||
function doCompose(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
function doGetTaxo(){
|
||||
|
||||
var out : TxpDictionnary = {products:new Map(), categories:new Map(), subCategories:new Map()};
|
||||
|
||||
for ( p in db.TxpProduct.manager.all()){
|
||||
out.products.set(p.id, {id:p.id, name:p.name, category:p.category.id, subCategory:p.subCategory.id});
|
||||
}
|
||||
|
||||
for ( c in db.TxpCategory.manager.all()){
|
||||
out.categories.set(c.id, {id:c.id,name:c.name });
|
||||
}
|
||||
|
||||
for ( c in db.TxpSubCategory.manager.all()){
|
||||
out.subCategories.set(c.id, {id:c.id,name:c.name });
|
||||
}
|
||||
|
||||
Sys.print(haxe.Serializer.run(out));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+264
@@ -0,0 +1,264 @@
|
||||
package controller;
|
||||
import Common;
|
||||
import tools.ArrayTool;
|
||||
import service.OrderService;
|
||||
|
||||
class Shop extends Controller
|
||||
{
|
||||
|
||||
var distribs : List<db.Distribution>;
|
||||
var contracts : List<db.Contract>;
|
||||
|
||||
@tpl('shop/default.mtt')
|
||||
public function doDefault(place:db.Place,date:Date) {
|
||||
var products = getProducts(place,date);
|
||||
view.products = products;
|
||||
view.place = place;
|
||||
view.date = date;
|
||||
view.group = place.amap;
|
||||
view.infos = ArrayTool.groupByDate(Lambda.array(distribs), "orderEndDate");
|
||||
|
||||
//message if phone is required
|
||||
if(app.user!=null && app.user.amap.flags.has(db.Amap.AmapFlags.PhoneRequired) && app.user.phone==null){
|
||||
app.session.addMessage(t._("Members of this group should provide a phone number. <a href='/account/edit'>Please click here to update your account</a>."),true);
|
||||
}
|
||||
|
||||
//event for additionnal blocks on home page
|
||||
var e = Blocks([], "shop");
|
||||
app.event(e);
|
||||
view.blocks = e.getParameters()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* prints the full product list and current cart in JSON
|
||||
*/
|
||||
public function doInit(place:db.Place,date:Date) {
|
||||
|
||||
//init order serverside if needed
|
||||
var order :OrderInSession = app.session.data.order;
|
||||
if ( order == null) {
|
||||
app.session.data.order = order = cast {products:[]};
|
||||
}
|
||||
|
||||
var products = [];
|
||||
var categs = new Array<{name:String,pinned:Bool,categs:Array<CategoryInfo>}>();
|
||||
|
||||
if (place.amap.flags.has(db.Amap.AmapFlags.ShopCategoriesFromTaxonomy)){
|
||||
|
||||
//TAXO CATEGORIES
|
||||
products = getProducts(place, date, true);
|
||||
}else{
|
||||
|
||||
//CUSTOM CATEGORIES
|
||||
products = getProducts(place, date, false);
|
||||
}
|
||||
|
||||
categs = place.amap.getCategoryGroups();
|
||||
|
||||
//clean
|
||||
for ( p in order.products){
|
||||
p.product = null;
|
||||
}
|
||||
Sys.print( haxe.Serializer.run( {products:products,categories:categs,order:order} ) );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the available products list
|
||||
*/
|
||||
private function getProducts(place,date,?categsFromTaxo=false):Array<ProductInfo> {
|
||||
|
||||
contracts = db.Contract.getActiveContracts(app.getCurrentGroup());
|
||||
|
||||
for (c in Lambda.array(contracts)) {
|
||||
//only varying orders
|
||||
if (c.type != db.Contract.TYPE_VARORDER) {
|
||||
contracts.remove(c);
|
||||
}
|
||||
|
||||
if (!c.isVisibleInShop()) {
|
||||
contracts.remove(c);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
view.contracts = contracts;
|
||||
|
||||
var now = Date.now();
|
||||
var cids = Lambda.map(contracts, function(c) return c.id);
|
||||
var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
||||
var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
|
||||
|
||||
//distribs open to orders, and where distribDate is in the date given as parameter
|
||||
distribs = db.Distribution.manager.search(($contractId in cids) && $orderStartDate <= now && $orderEndDate >= now && $date > d1 && $end < d2 && $place == place, false);
|
||||
var products = [];
|
||||
for ( d in distribs){
|
||||
for (p in d.contract.getProducts(true)){
|
||||
products.push( p.infos(categsFromTaxo,null,d) );
|
||||
}
|
||||
}
|
||||
return products;
|
||||
|
||||
/*var cids = Lambda.map(distribs, function(d) return d.contract.id);
|
||||
var products = db.Product.manager.search(($contractId in cids) && $active==true, { orderBy:name }, false);
|
||||
|
||||
return Lambda.array(Lambda.map(products, function(p) return p.infos(categsFromTaxo)));*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Overlay window loaded by Ajax for product Infos
|
||||
*/
|
||||
@tpl('shop/productInfo.mtt')
|
||||
public function doProductInfo(p:db.Product,?args:{distribution:db.Distribution}) {
|
||||
var d = args!=null && args.distribution!=null ? args.distribution : null;
|
||||
view.p = p.infos(null,null,d);
|
||||
view.product = p;
|
||||
view.vendor = p.contract.vendor;
|
||||
}
|
||||
|
||||
/**
|
||||
* receive cart
|
||||
*/
|
||||
public function doSubmit() {
|
||||
|
||||
var order : OrderInSession = haxe.Json.parse(app.params.get("data"));
|
||||
app.session.data.order = order;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* add a product to the cart
|
||||
*/
|
||||
public function doAdd(productId:Int, quantity:Int) {
|
||||
|
||||
var order : OrderInSession = app.session.data.order;
|
||||
if ( order == null) order = cast { products:[] };
|
||||
order.products.push( { productId:productId, quantity:quantity } );
|
||||
Sys.print( haxe.Json.stringify( {success:true} ) );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* remove a product from cart
|
||||
*/
|
||||
public function doRemove(pid:Int) {
|
||||
|
||||
var order:OrderInSession = app.session.data.order;
|
||||
if ( order == null) return;
|
||||
|
||||
for ( p in order.products.copy()) {
|
||||
if (p.productId == pid) {
|
||||
order.products.remove(p);
|
||||
}
|
||||
}
|
||||
|
||||
Sys.print( haxe.Json.stringify( { success:true } ) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* validate the order
|
||||
*/
|
||||
@tpl('shop/needLogin.mtt')
|
||||
public function doValidate(place:db.Place, date:Date){
|
||||
|
||||
//login is needed : display a loginbox
|
||||
if (app.user == null) {
|
||||
view.redirect = "/shop/validate/" + place.id + "/" + date.toString().substr(0, 10);
|
||||
view.group = place.amap;
|
||||
view.register = true;
|
||||
view.message = t._("In order to confirm your order, You need to authenticate.");
|
||||
return;
|
||||
}
|
||||
|
||||
//add the user to this group if needed
|
||||
if (place.amap.regOption == db.Amap.RegOption.Open && db.UserAmap.get(app.user, place.amap) == null){
|
||||
app.user.makeMemberOf(place.amap);
|
||||
}
|
||||
|
||||
var order : OrderInSession = app.session.data.order;
|
||||
if (order == null || order.products == null || order.products.length == 0) {
|
||||
throw Error("/", t._("Your order is empty") );
|
||||
}
|
||||
|
||||
if (place == null) throw "place cannot be empty";
|
||||
if (date == null) throw "date cannot be empty";
|
||||
|
||||
var products = getProducts(place, date);
|
||||
|
||||
var errors = [];
|
||||
order.total = 0.0;
|
||||
|
||||
//cleaning
|
||||
for (o in order.products.copy()) {
|
||||
|
||||
var p = db.Product.manager.get(o.productId, false);
|
||||
|
||||
//check that the products are from this group (we never know...)
|
||||
if (p.contract.amap.id != app.user.amap.id){
|
||||
app.session.data.order = null;
|
||||
throw Error("/", t._("This cart is invalid") );
|
||||
}
|
||||
|
||||
//check if the product is available
|
||||
if (Lambda.find(products, function(x) return x.id == o.productId) == null) {
|
||||
errors.push( t._("This distribution does not supply the product <b>::pname::</b>",{pname:p.name}) );
|
||||
order.products.remove(o);
|
||||
continue;
|
||||
}else{
|
||||
o.product = p;
|
||||
}
|
||||
|
||||
//find distrib
|
||||
var d = Lambda.find(distribs, function(d) return d.contract.id == p.contract.id);
|
||||
if ( d == null ){
|
||||
errors.push( t._("This distribution does not supply the product <b>::pname::</b>",{pname:p.name}) );
|
||||
order.products.remove(o);
|
||||
continue;
|
||||
}else{
|
||||
o.distributionId = d.id;
|
||||
}
|
||||
|
||||
//moderate order according available stocks
|
||||
if (p.stock != null && p.contract.hasStockManagement() ) {
|
||||
if (p.stock - o.quantity < 0) {
|
||||
var canceled = o.quantity - p.stock;
|
||||
o.quantity -= canceled;
|
||||
errors.push(t._("Order of ::pname:: reduced to ::oquantity:: to match remaining stock", {pname:p.name, oquantity:o.quantity}));
|
||||
}
|
||||
}
|
||||
|
||||
order.total += p.getPrice() * o.quantity;
|
||||
}
|
||||
|
||||
order.userId = app.user.id;
|
||||
|
||||
if (errors.length > 0) {
|
||||
app.session.addMessage(errors.join("<br/>"), true);
|
||||
}
|
||||
|
||||
//store cart in session
|
||||
app.session.data.order = order;
|
||||
|
||||
//debug
|
||||
for ( p in order.products){
|
||||
if (p.distributionId == null){
|
||||
App.current.logError(place.amap.name + " : panier sans distrib Id : " + Std.string(order) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (app.user.amap.hasPayments()){
|
||||
//Go to payments page
|
||||
throw Redirect("/transaction/pay/");
|
||||
}else{
|
||||
//no payments, confirm direclty
|
||||
OrderService.confirmSessionOrder(order);
|
||||
throw Ok("/contract", t._("Your order has been confirmed") );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
package controller;
|
||||
|
||||
class Stats extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@tpl("stats/default.mtt")
|
||||
function doDefault() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
package controller;
|
||||
import db.Operation.OperationType;
|
||||
import Common;
|
||||
import service.OrderService;
|
||||
using Lambda;
|
||||
|
||||
/**
|
||||
* Transction controller
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Transaction extends controller.Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* A manager inserts manually a payment
|
||||
*/
|
||||
@tpl('form.mtt')
|
||||
public function doInsertPayment(user:db.User){
|
||||
|
||||
if (!app.user.isContractManager()) throw Error("/", t._("Action forbidden"));
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var op = new db.Operation();
|
||||
op.user = user;
|
||||
op.date = Date.now();
|
||||
|
||||
var f = new sugoi.form.Form("payement");
|
||||
f.addElement(new sugoi.form.elements.StringInput("name", t._("Label||label or name for a payment"), null, true));
|
||||
f.addElement(new sugoi.form.elements.FloatInput("amount", t._("Amount"), null, true));
|
||||
f.addElement(new sugoi.form.elements.DatePicker("date", t._("Date"), Date.now(), true));
|
||||
var paymentTypes = service.PaymentService.getPaymentTypesForManualEntry(app.user.amap);
|
||||
f.addElement(new sugoi.form.elements.StringSelect("Mtype", t._("Payment type"), paymentTypes, null, true));
|
||||
|
||||
//related operation
|
||||
var unpaid = db.Operation.manager.search($user == user && $group == app.user.amap && $type != Payment ,{limit:20,orderBy:-date});
|
||||
var data = unpaid.map(function(x) return {label:x.name, value:x.id}).array();
|
||||
f.addElement(new sugoi.form.elements.IntSelect("unpaid", t._("As a payment for :"), data, null, false));
|
||||
|
||||
if (f.isValid()){
|
||||
f.toSpod(op);
|
||||
op.type = db.Operation.OperationType.Payment;
|
||||
var data : db.Operation.PaymentInfos = {type:f.getValueOf("Mtype")};
|
||||
op.data = data;
|
||||
op.group = app.user.amap;
|
||||
op.user = user;
|
||||
|
||||
if (f.getValueOf("unpaid") != null){
|
||||
var t2 = db.Operation.manager.get(f.getValueOf("unpaid"));
|
||||
op.relation = t2;
|
||||
if (t2.amount + op.amount == 0) {
|
||||
op.pending = false;
|
||||
t2.lock();
|
||||
t2.pending = false;
|
||||
t2.update();
|
||||
}
|
||||
}
|
||||
|
||||
op.insert();
|
||||
|
||||
service.PaymentService.updateUserBalance(user, app.user.amap);
|
||||
|
||||
throw Ok("/member/payments/" + user.id, t._("Payment recorded") );
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Record a payment for ::user::",{user:user.getCoupleName()}) ;
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
|
||||
@tpl('form.mtt')
|
||||
public function doEdit(op:db.Operation){
|
||||
|
||||
if (!app.user.canAccessMembership() || op.group.id != app.user.amap.id ) {
|
||||
throw Error("/member/payments/" + op.user.id, t._("Action forbidden"));
|
||||
}
|
||||
|
||||
if (op.getPaymentType() == "lemonway-ec") throw Error("/member/payments/"+op.user.id, t._("Editing a credit card payment is not allowed"));
|
||||
|
||||
op.lock();
|
||||
|
||||
var f = new sugoi.form.Form("payement");
|
||||
f.addElement(new sugoi.form.elements.StringInput("name", t._("Label||label or name for a payment"), op.name, true));
|
||||
f.addElement(new sugoi.form.elements.FloatInput("amount", t._("Amount"), op.amount, true));
|
||||
f.addElement(new sugoi.form.elements.DatePicker("date", t._("Date"), op.date, true));
|
||||
//f.addElement(new sugoi.form.elements.DatePicker("pending", t._("Confirmed"), !op.pending, true));
|
||||
//related operation
|
||||
var unpaid = db.Operation.manager.search( $user == op.user && $group == op.group && $type != Payment ,{limit:20,orderBy:-date});
|
||||
var data = unpaid.map(function(x) return {label:x.name, value:x.id}).array();
|
||||
if (op.relation != null) data.push({label:op.relation.name,value:op.relation.id});
|
||||
f.addElement(new sugoi.form.elements.IntSelect("unpaid", t._("As a payment for :"), data, op.relation!=null ? op.relation.id : null, false));
|
||||
|
||||
|
||||
if (f.isValid()){
|
||||
f.toSpod(op);
|
||||
op.pending = false;
|
||||
|
||||
if (f.getValueOf("unpaid") != null){
|
||||
var t2 = db.Operation.manager.get(f.getValueOf("unpaid"));
|
||||
op.relation = t2;
|
||||
if (t2.amount + op.amount == 0) {
|
||||
op.pending = false;
|
||||
t2.lock();
|
||||
t2.pending = false;
|
||||
t2.update();
|
||||
}
|
||||
}
|
||||
|
||||
op.update();
|
||||
throw Ok("/member/payments/" + op.user.id, t._("Operation updated"));
|
||||
}
|
||||
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an operation
|
||||
*/
|
||||
public function doDelete(op:db.Operation){
|
||||
if (!app.user.canAccessMembership() || op.group.id != app.user.amap.id ) throw Error("/member/payments/" + op.user.id, t._("Action forbidden"));
|
||||
//cannot delete a bank card payment op
|
||||
if (op.getPaymentType() == "lemonway-ec"){
|
||||
throw Error("/member/payments/" + op.user.id, t._("Deleting a credit card payment is not allowed"));
|
||||
}
|
||||
//only an admin can delete an order op
|
||||
if((op.type == db.Operation.OperationType.VOrder || op.type == db.Operation.OperationType.COrder) && !app.user.isAdmin()){
|
||||
throw Error("/member/payments/" + op.user.id, t._("Action forbidden"));
|
||||
}
|
||||
if (checkToken()){
|
||||
op.delete();
|
||||
throw Ok("/member/payments/" + op.user.id, t._("Operation deleted"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* payement entry page
|
||||
* @param distribKey
|
||||
*/
|
||||
@tpl("transaction/pay.mtt")
|
||||
public function doPay() {
|
||||
|
||||
view.category = 'home';
|
||||
|
||||
var order : OrderInSession = app.session.data.order;
|
||||
if (order == null) throw Redirect("/");
|
||||
if (order.products.length == 0) throw Error("/", t._("Your cart is empty"));
|
||||
|
||||
view.amount = order.total;
|
||||
view.paymentTypes = service.PaymentService.getAllowedPaymentTypes(app.user.amap);
|
||||
view.allowMoneyPotWithNegativeBalance = app.user.amap.allowMoneyPotWithNegativeBalance;
|
||||
view.futurebalance = db.UserAmap.get(app.user, app.user.amap).balance - order.total;
|
||||
}
|
||||
|
||||
/**
|
||||
* pay by check
|
||||
*/
|
||||
@tpl("transaction/check.mtt")
|
||||
public function doCheck(){
|
||||
|
||||
//order in session
|
||||
var tmpOrder : OrderInSession = app.session.data.order;
|
||||
if (tmpOrder == null) throw Redirect("/contract");
|
||||
if (tmpOrder.products.length == 0) throw Error("/", t._("Your cart is empty"));
|
||||
|
||||
//get a code
|
||||
var d = db.Distribution.manager.get(tmpOrder.products[0].distributionId, false);
|
||||
var code = payment.Check.getCode(d.date, d.place, app.user);
|
||||
|
||||
view.code = code;
|
||||
view.amount = tmpOrder.total;
|
||||
|
||||
//if (checkToken()){
|
||||
|
||||
//record order
|
||||
var orders = OrderService.confirmSessionOrder(tmpOrder);
|
||||
var ops = db.Operation.onOrderConfirm(orders);
|
||||
var ordersGrouped = tools.ObjectListTool.groupOrdersByKey(orders);
|
||||
|
||||
if (Lambda.array(ordersGrouped).length == 1){
|
||||
//all orders are for the same multidistrib
|
||||
var name = t._("Check for the order of ::date::", {date:view.hDate(d.date)}) + " ("+code+")";
|
||||
db.Operation.makePaymentOperation(app.user,app.user.amap, payment.Check.TYPE, tmpOrder.total, name, ops[0] );
|
||||
}else{
|
||||
//orders are for multiple distribs : create one payment
|
||||
db.Operation.makePaymentOperation(app.user,app.user.amap,payment.Check.TYPE, tmpOrder.total, t._("Check") + " ("+code+")" );
|
||||
}
|
||||
|
||||
|
||||
//throw Ok("/contract", t._("Your payment by check has been saved. It will be validated by a coordinator at the delivery."));
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* pay by transfer
|
||||
*/
|
||||
@tpl("transaction/transfer.mtt")
|
||||
public function doTransfer(){
|
||||
|
||||
//order in session
|
||||
var tmpOrder : OrderInSession = app.session.data.order;
|
||||
if (tmpOrder == null) throw Redirect("/contract");
|
||||
if (tmpOrder.products.length == 0) throw Error("/", t._("Your cart is empty"));
|
||||
|
||||
//get a code
|
||||
var d = db.Distribution.manager.get(tmpOrder.products[0].distributionId, false);
|
||||
var code = payment.Check.getCode(d.date, d.place, app.user);
|
||||
|
||||
view.code = code;
|
||||
view.amount = tmpOrder.total;
|
||||
|
||||
//if (checkToken()){
|
||||
|
||||
//record order
|
||||
var orders = OrderService.confirmSessionOrder(tmpOrder);
|
||||
var ops = db.Operation.onOrderConfirm(orders);
|
||||
var ordersGrouped = tools.ObjectListTool.groupOrdersByKey(orders);
|
||||
|
||||
if (Lambda.array(ordersGrouped).length == 1){
|
||||
//one multidistrib
|
||||
var name = t._("Transfer for the order of ::date::", {date:view.hDate(d.date)}) + " ("+code+")";
|
||||
db.Operation.makePaymentOperation(app.user,app.user.amap,payment.Transfer.TYPE, tmpOrder.total, name, ops[0] );
|
||||
}else{
|
||||
//many distribs
|
||||
db.Operation.makePaymentOperation(app.user,app.user.amap,payment.Transfer.TYPE, tmpOrder.total, t._("Bank transfer")+" ("+code+")" );
|
||||
}
|
||||
|
||||
|
||||
//throw Ok("/contract", t._("Your payment by transfer has been saved. It will be validated by a coordinator."));
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* pay by cash
|
||||
*/
|
||||
@tpl("transaction/cash.mtt")
|
||||
public function doCash(){
|
||||
|
||||
//order in session
|
||||
var tmpOrder : OrderInSession = app.session.data.order;
|
||||
if (tmpOrder == null) throw Redirect("/contract");
|
||||
if (tmpOrder.products.length == 0) throw Error("/", t._("Your cart is empty"));
|
||||
|
||||
view.amount = tmpOrder.total;
|
||||
var d = db.Distribution.manager.get(tmpOrder.products[0].distributionId, false);
|
||||
|
||||
//if (checkToken()){
|
||||
|
||||
//record order
|
||||
var orders = OrderService.confirmSessionOrder(tmpOrder);
|
||||
var ops = db.Operation.onOrderConfirm(orders);
|
||||
var ordersGrouped = tools.ObjectListTool.groupOrdersByKey(orders);
|
||||
|
||||
if (Lambda.array(ordersGrouped).length == 1){
|
||||
//same multidistrib
|
||||
var name = t._("Cash for the order of ::date::", {date:view.hDate(d.date)});
|
||||
db.Operation.makePaymentOperation(app.user,app.user.amap,payment.Cash.TYPE, tmpOrder.total, name , ops[0] );
|
||||
}else{
|
||||
//various distribs
|
||||
db.Operation.makePaymentOperation(app.user, app.user.amap, payment.Cash.TYPE, tmpOrder.total, t._("Cash payment"));
|
||||
}
|
||||
|
||||
|
||||
//throw Ok("/contract", t._("Your order is validated, you commited to pay in cash at the delivery."));
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the money pot
|
||||
*/
|
||||
@tpl("transaction/moneypot.mtt")
|
||||
public function doMoneypot(){
|
||||
|
||||
//order in session
|
||||
var tmpOrder : OrderInSession = app.session.data.order;
|
||||
if (tmpOrder == null) throw Redirect("/contract");
|
||||
if (tmpOrder.products.length == 0) throw Error("/", t._("Your cart is empty"));
|
||||
var futureBalance = db.UserAmap.get(app.user, app.user.amap).balance - tmpOrder.total;
|
||||
if (!app.user.amap.allowMoneyPotWithNegativeBalance && futureBalance < 0) {
|
||||
throw Error("/transaction/pay", t._("You do not have sufficient funds to pay this order with your money pot."));
|
||||
}
|
||||
|
||||
//record order
|
||||
var orders = OrderService.confirmSessionOrder(tmpOrder);
|
||||
var ops = db.Operation.onOrderConfirm(orders);
|
||||
|
||||
view.amount = tmpOrder.total;
|
||||
view.balance = db.UserAmap.get(app.user, app.user.amap).balance;
|
||||
|
||||
}
|
||||
}
|
||||
Executable
+255
@@ -0,0 +1,255 @@
|
||||
package controller;
|
||||
import haxe.crypto.Md5;
|
||||
import sugoi.form.elements.Input;
|
||||
import sugoi.form.Form;
|
||||
import sugoi.form.elements.IntInput;
|
||||
import sugoi.form.elements.StringInput;
|
||||
import sugoi.form.validators.EmailValidator;
|
||||
import ufront.mail.*;
|
||||
|
||||
class User extends Controller
|
||||
{
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@tpl("user/default.mtt")
|
||||
function doDefault() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@tpl("user/login.mtt")
|
||||
function doLogin() {
|
||||
|
||||
if (App.current.user != null) {
|
||||
throw Redirect('/');
|
||||
}
|
||||
|
||||
//if its needed to redirect after login
|
||||
if (app.params.exists("redirect")){
|
||||
view.redirect = app.params.exists("redirect");
|
||||
}else{
|
||||
view.redirect = "/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Choose which group to connect to.
|
||||
*/
|
||||
@logged
|
||||
@tpl("user/choose.mtt")
|
||||
function doChoose(?args: { amap:db.Amap } ) {
|
||||
|
||||
if (app.user == null) throw t._("You are not connected");
|
||||
|
||||
var amaps = db.UserAmap.manager.search($user == app.user, false);
|
||||
|
||||
if (amaps.length == 1 && !app.params.exists("show")) {
|
||||
//qu'une amap
|
||||
app.session.data.amapId = amaps.first().amap.id;
|
||||
throw Redirect('/');
|
||||
}else{
|
||||
view.noGroup = true; //force template to not display current group
|
||||
}
|
||||
|
||||
if (args!=null && args.amap!=null) {
|
||||
//select a group
|
||||
var which = app.session.data==null ? 0 : app.session.data.whichUser ;
|
||||
app.session.data.order = null;
|
||||
app.session.data.newGroup = null;
|
||||
app.session.data.amapId = args.amap.id;
|
||||
app.session.data.whichUser = which;
|
||||
throw Redirect('/');
|
||||
}
|
||||
|
||||
view.amaps = amaps;
|
||||
view.wl = db.WaitingList.manager.search($user == app.user, false);
|
||||
|
||||
|
||||
#if plugins
|
||||
view.pros = pro.db.PUserCompany.getCompanies(app.user);
|
||||
#end
|
||||
}
|
||||
|
||||
function doLogout() {
|
||||
App.current.session.delete();
|
||||
throw Redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for password renewal by mail
|
||||
* when password is forgotten
|
||||
*/
|
||||
@tpl("user/forgottenPassword.mtt")
|
||||
function doForgottenPassword(?key:String, ?u:db.User){
|
||||
|
||||
//STEP 1
|
||||
var step = 1;
|
||||
var error : String = null;
|
||||
var url = "/user/forgottenPassword";
|
||||
|
||||
//ask for mail
|
||||
var askmailform = new Form("askemail");
|
||||
askmailform.addElement(new StringInput("email", t._("Please key-in your E-Mail address"),null,true));
|
||||
|
||||
//change pass form
|
||||
var chpassform = new Form("chpass");
|
||||
|
||||
var pass1 = new StringInput("pass1", t._("Your new password"),null,true);
|
||||
pass1.password = true;
|
||||
chpassform.addElement(pass1);
|
||||
|
||||
var pass2 = new StringInput("pass2", t._("Again your new password"),null,true);
|
||||
pass2.password = true;
|
||||
chpassform.addElement(pass2);
|
||||
|
||||
var uid = new IntInput("uid","uid", u == null?null:u.id);
|
||||
uid.inputType = ITHidden;
|
||||
chpassform.addElement(uid);
|
||||
|
||||
if (askmailform.isValid()) {
|
||||
//STEP 2
|
||||
//send password renewal email
|
||||
step = 2;
|
||||
|
||||
var email :String = askmailform.getValueOf("email");
|
||||
var user = db.User.manager.select(email == $email, false);
|
||||
//could be user 2
|
||||
if(user==null) user = db.User.manager.select(email == $email2, false);
|
||||
|
||||
//user not found
|
||||
if (user == null) throw Error(url, t._("This E-mail is not linked to a known account"));
|
||||
|
||||
//create token
|
||||
var token = haxe.crypto.Md5.encode("chp"+Std.random(1000000000));
|
||||
sugoi.db.Cache.set(token, user.id, 60 * 60 * 24 * 30);
|
||||
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
|
||||
m.setRecipient(email, user.name);
|
||||
m.setSubject( "["+App.config.NAME+"] : "+t._("Password change"));
|
||||
m.setHtmlBody( app.processTemplate('mail/forgottenPassword.mtt', { user:user, link:'http://' + App.config.HOST + '/user/forgottenPassword/'+token+"/"+user.id }) );
|
||||
App.sendMail(m);
|
||||
}
|
||||
|
||||
if (key != null && u!=null) {
|
||||
//check key and propose to change pass
|
||||
step = 3;
|
||||
|
||||
if ( u.id == sugoi.db.Cache.get(key) ) {
|
||||
view.form = chpassform;
|
||||
}else {
|
||||
error = t._("Invalid request");
|
||||
}
|
||||
}
|
||||
|
||||
if (chpassform.isValid()) {
|
||||
//change pass
|
||||
step = 4;
|
||||
|
||||
if ( chpassform.getValueOf("pass1") == chpassform.getValueOf("pass2")) {
|
||||
|
||||
var uid = Std.parseInt( chpassform.getValueOf("uid") );
|
||||
var user = db.User.manager.get(uid, true);
|
||||
var pass = chpassform.getValueOf("pass1");
|
||||
user.setPass(pass);
|
||||
user.update();
|
||||
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
|
||||
m.setRecipient(user.email, user.name);
|
||||
if(user.email2!=null) m.setRecipient(user.email2, user.name);
|
||||
m.setSubject( "["+App.config.NAME+"] : "+t._("New password confirmed"));
|
||||
var emails = [user.email];
|
||||
if(user.email2!=null) emails.push(user.email2);
|
||||
var params = {
|
||||
user:user,
|
||||
emails:emails.join(", "),
|
||||
password:pass,
|
||||
NAME:App.config.NAME
|
||||
}
|
||||
m.setHtmlBody( app.processTemplate('mail/newPasswordConfirmed.mtt', params) );
|
||||
App.sendMail(m);
|
||||
|
||||
}else {
|
||||
error = t._("You must key-in two times the same password");
|
||||
}
|
||||
}
|
||||
|
||||
if (step == 1) {
|
||||
view.form = askmailform;
|
||||
}
|
||||
|
||||
view.step = step;
|
||||
view.error = error;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* generate a custom key for transactionnal emails, valid during the current day
|
||||
*/
|
||||
//function getKey(m:db.User) {
|
||||
//return haxe.crypto.Md5.encode(App.config.get("key")+m.email+(Date.now().getDate())).substr(0,12);
|
||||
//}
|
||||
|
||||
|
||||
@logged
|
||||
@tpl("form.mtt")
|
||||
function doDefinePassword(?key:String, ?u:db.User){
|
||||
|
||||
if (app.user.isFullyRegistred()) throw Error("/", t._("You already have a password"));
|
||||
|
||||
var form = new Form("definepass");
|
||||
var pass1 = new StringInput("pass1", t._("Your new password"));
|
||||
var pass2 = new StringInput("pass2", t._("Again your new password"));
|
||||
pass1.password = true;
|
||||
pass2.password = true;
|
||||
form.addElement(pass1);
|
||||
form.addElement(pass2);
|
||||
|
||||
if (form.isValid()) {
|
||||
|
||||
if ( form.getValueOf("pass1") == form.getValueOf("pass2")) {
|
||||
|
||||
app.user.lock();
|
||||
app.user.setPass(form.getValueOf("pass1"));
|
||||
app.user.update();
|
||||
throw Ok('/', t._("Congratulations, your account is now protected by a password."));
|
||||
|
||||
}else {
|
||||
form.addError( t._("You must key-in two times the same password"));
|
||||
}
|
||||
}
|
||||
view.form = form;
|
||||
view.title = t._("Create a password for your account");
|
||||
}
|
||||
|
||||
/**
|
||||
* landing page when coming from an invitation
|
||||
* @param k
|
||||
*/
|
||||
public function doValidate(k:String ) {
|
||||
|
||||
var uid = Std.parseInt(sugoi.db.Cache.get("validation" + k));
|
||||
if (uid == null || uid==0) throw Error('/user/login', t._("Your invitation is invalid or expired ($k)"));
|
||||
var user = db.User.manager.get(uid, true);
|
||||
|
||||
db.User.login(user, user.email);
|
||||
|
||||
var groups = user.getAmaps();
|
||||
if(groups.length>0) app.session.data.amapId = groups.first().id;
|
||||
|
||||
sugoi.db.Cache.destroy("validation" + k);
|
||||
|
||||
throw Ok("/user/definePassword", t._("Congratulations ::userName::, your account is validated!", {userName:user.getName()}));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package controller;
|
||||
import db.Operation.OperationType;
|
||||
using Lambda;
|
||||
|
||||
/**
|
||||
* Distribution validation
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Validate extends controller.Controller
|
||||
{
|
||||
public var date : Date;
|
||||
public var user : db.User;
|
||||
public var place : db.Place;
|
||||
|
||||
@tpl('validate/user.mtt')
|
||||
public function doDefault(){
|
||||
view.member = user;
|
||||
|
||||
if (!app.user.amap.hasShopMode()){
|
||||
//get last operations and check balance
|
||||
view.operations = db.Operation.getLastOperations(this.user,place.amap,10);
|
||||
view.balance = db.UserAmap.get(this.user, place.amap).balance;
|
||||
}
|
||||
|
||||
var b = db.Basket.get(user, place, date);
|
||||
view.orders = service.OrderService.prepare(b.getOrders());
|
||||
view.place = place;
|
||||
view.date = date;
|
||||
view.basket = b;
|
||||
|
||||
checkToken();
|
||||
}
|
||||
|
||||
public function doDeleteOp(op:db.Operation){
|
||||
if (checkToken()){
|
||||
|
||||
op.lock();
|
||||
op.delete();
|
||||
|
||||
service.PaymentService.updateUserBalance(user, app.user.amap);
|
||||
|
||||
throw Ok("/validate/"+date+"/"+place.id+"/"+user.id, t._("Operation deleted"));
|
||||
}
|
||||
}
|
||||
|
||||
public function doValidateOp(op:db.Operation){
|
||||
if (checkToken()){
|
||||
|
||||
op.lock();
|
||||
op.pending = false;
|
||||
op.update();
|
||||
|
||||
service.PaymentService.updateUserBalance(user, app.user.amap);
|
||||
|
||||
throw Ok("/validate/"+date+"/"+place.id+"/"+user.id, t._("Operation validated"));
|
||||
}
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
public function doAddRefund(){
|
||||
|
||||
if (!app.user.isContractManager()) throw t._("Forbidden access");
|
||||
|
||||
var o = new db.Operation();
|
||||
o.user = user;
|
||||
o.date = Date.now();
|
||||
|
||||
var b = db.Basket.get(user, place, date);
|
||||
var op = b.getOrderOperation(false);
|
||||
if(op==null) throw "unable to find related order operation";
|
||||
|
||||
var f = new sugoi.form.Form(t._("payment"));
|
||||
f.addElement(new sugoi.form.elements.StringInput("name", t._("Label"), t._("Refund"), true));
|
||||
f.addElement(new sugoi.form.elements.FloatInput("amount", t._("Amount"), null, true));
|
||||
f.addElement(new sugoi.form.elements.DatePicker("date", "Date", Date.now(), true));
|
||||
var paymentTypes = service.PaymentService.getPaymentTypesForManualEntry(app.user.amap);
|
||||
f.addElement(new sugoi.form.elements.StringSelect("Mtype", t._("Payment type"), paymentTypes, null, true));
|
||||
|
||||
|
||||
if (f.isValid()){
|
||||
f.toSpod(o);
|
||||
o.type = db.Operation.OperationType.Payment;
|
||||
var data : db.Operation.PaymentInfos = {type:f.getValueOf("Mtype")};
|
||||
o.data = data;
|
||||
o.group = app.user.amap;
|
||||
o.user = user;
|
||||
o.relation = op;
|
||||
o.amount = 0-Math.abs(o.amount);
|
||||
o.insert();
|
||||
|
||||
App.current.event(NewOperation(o));
|
||||
|
||||
service.PaymentService.updateUserBalance(user, app.user.amap);
|
||||
|
||||
throw Ok("/validate/"+date+"/"+place.id+"/"+user.id, t._("Refund saved"));
|
||||
}
|
||||
|
||||
view.title = t._("Key-in a refund for ::user::",{user:user.getCoupleName()});
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
public function doAddPayment(){
|
||||
|
||||
if (!app.user.isContractManager()) throw Error("/",t._("Forbidden access"));
|
||||
|
||||
var o = new db.Operation();
|
||||
o.user = user;
|
||||
o.date = Date.now();
|
||||
|
||||
var f = new sugoi.form.Form("payment");
|
||||
f.addElement(new sugoi.form.elements.StringInput("name", t._("Label"), t._("Additional payment"), true));
|
||||
f.addElement(new sugoi.form.elements.FloatInput("amount", t._("Amount"), null, true));
|
||||
f.addElement(new sugoi.form.elements.DatePicker("date", t._("Date"), Date.now(), true));
|
||||
var paymentTypes = service.PaymentService.getPaymentTypesForManualEntry(app.user.amap);
|
||||
f.addElement(new sugoi.form.elements.StringSelect("Mtype", t._("Payment type"), paymentTypes, null, true));
|
||||
|
||||
var b = db.Basket.get(user, place, date);
|
||||
var op = b.getOrderOperation(false);
|
||||
if(op==null) throw "unable to find related order operation";
|
||||
|
||||
if (f.isValid()){
|
||||
f.toSpod(o);
|
||||
o.type = db.Operation.OperationType.Payment;
|
||||
var data : db.Operation.PaymentInfos = {type:f.getValueOf("Mtype")};
|
||||
o.data = data;
|
||||
o.group = app.user.amap;
|
||||
o.user = user;
|
||||
o.relation = op;
|
||||
o.insert();
|
||||
|
||||
service.PaymentService.updateUserBalance(user, app.user.amap);
|
||||
|
||||
throw Ok("/validate/"+date+"/"+place.id+"/"+user.id, t._("Payment saved"));
|
||||
}
|
||||
|
||||
view.title = t._("Key-in a payment for ::user::",{user:user.getCoupleName()});
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
public function doValidate(){
|
||||
|
||||
if (checkToken()){
|
||||
|
||||
var basket = db.Basket.get(user, place, date);
|
||||
service.PaymentService.validateBasket(basket);
|
||||
|
||||
throw Ok("/distribution/validate/"+date+"/"+place.id, t._("Order validated"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+124
@@ -0,0 +1,124 @@
|
||||
package controller;
|
||||
import db.UserContract;
|
||||
import sugoi.form.elements.Selectbox;
|
||||
import sugoi.form.Form;
|
||||
import neko.Web;
|
||||
import sugoi.tools.Utils;
|
||||
|
||||
|
||||
class Vendor extends Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
|
||||
if (!app.user.isContractManager()) throw t._("Forbidden access");
|
||||
|
||||
}
|
||||
|
||||
@logged
|
||||
@tpl('vendor/default.mtt')
|
||||
function doDefault() {
|
||||
var browse:Int->Int->List<Dynamic>;
|
||||
|
||||
//default display
|
||||
browse = function(index:Int, limit:Int) {
|
||||
return db.Vendor.manager.search($id > index && $amap==app.user.amap, { limit:limit, orderBy:-id }, false);
|
||||
}
|
||||
|
||||
var count = db.Vendor.manager.count($amap==app.user.amap);
|
||||
var rb = new sugoi.tools.ResultsBrowser(count, 10, browse);
|
||||
view.vendors = rb;
|
||||
}
|
||||
|
||||
|
||||
@tpl("vendor/view.mtt")
|
||||
function doView(vendor:db.Vendor) {
|
||||
view.vendor = vendor;
|
||||
}
|
||||
|
||||
@tpl('form.mtt')
|
||||
function doEdit(vendor:db.Vendor) {
|
||||
|
||||
var form = sugoi.form.Form.fromSpod(vendor);
|
||||
form.removeElement( form.getElement("amapId") );
|
||||
|
||||
if (form.isValid()) {
|
||||
form.toSpod(vendor); //update model
|
||||
vendor.amap = app.user.amap;
|
||||
vendor.update();
|
||||
throw Ok('/contractAdmin', t._("This supplier has been updated"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
public function doInsert() {
|
||||
|
||||
|
||||
var m = new db.Vendor();
|
||||
var form = sugoi.form.Form.fromSpod(m);
|
||||
form.removeElement(form.getElement("amapId"));
|
||||
|
||||
if (form.isValid()) {
|
||||
form.toSpod(m); //update model
|
||||
m.amap = app.user.amap;
|
||||
m.insert();
|
||||
|
||||
throw Ok('/contractAdmin/', t._("This supplier has been saved"));
|
||||
}
|
||||
|
||||
view.form = form;
|
||||
}
|
||||
|
||||
public function doDelete(v:db.Vendor) {
|
||||
if (!app.user.isAmapManager()) throw t._("Forbidden action");
|
||||
if (checkToken()) {
|
||||
|
||||
if (db.Contract.manager.search($vendorId == v.id).length > 0) throw Error('/contractAdmin', t._("You cannot delete this supplier because some contracts (current or old) are referencing this supplier."));
|
||||
|
||||
v.lock();
|
||||
v.delete();
|
||||
throw Ok("/contractAdmin", t._("Supplier deleted"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@tpl('vendor/addimage.mtt')
|
||||
function doAddImage(v:db.Vendor) {
|
||||
|
||||
view.vendor = v;
|
||||
view.image = v.image;
|
||||
|
||||
var request = sugoi.tools.Utils.getMultipart(1024 * 1024 * 12); //12Mb
|
||||
|
||||
if (request.exists("image")) {
|
||||
|
||||
//Image
|
||||
var image = request.get("image");
|
||||
if (image != null && image.length > 0) {
|
||||
var img : sugoi.db.File = null;
|
||||
if ( Sys.systemName() == "Windows") {
|
||||
img = sugoi.db.File.create(request.get("image"), request.get("image_filename"));
|
||||
}else {
|
||||
img = sugoi.tools.UploadedImage.resizeAndStore(request.get("image"), request.get("image_filename"), 400, 400);
|
||||
}
|
||||
|
||||
v.lock();
|
||||
|
||||
if (v.image != null) {
|
||||
//efface ancienne
|
||||
v.image.lock();
|
||||
v.image.delete();
|
||||
}
|
||||
|
||||
v.image = img;
|
||||
v.update();
|
||||
throw Ok('/contractAdmin/', t._("Image updated"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+223
@@ -0,0 +1,223 @@
|
||||
package controller.admin;
|
||||
import haxe.web.Dispatch;
|
||||
import Common;
|
||||
|
||||
class Admin extends Controller {
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
view.category = 'admin';
|
||||
|
||||
//trigger a "Nav" event
|
||||
var nav = new Array<Link>();
|
||||
var e = Nav(nav,"admin");
|
||||
app.event(e);
|
||||
view.nav = e.getParameters()[0];
|
||||
|
||||
}
|
||||
|
||||
@tpl("admin/default.mtt")
|
||||
function doDefault() {
|
||||
view.now = Date.now();
|
||||
}
|
||||
|
||||
@tpl("admin/emails.mtt")
|
||||
function doEmails() {
|
||||
var browse = function(index:Int, limit:Int) {
|
||||
return sugoi.db.BufferedMail.manager.search($sdate==null,{limit:[index,limit],orderBy:-cdate},false);
|
||||
}
|
||||
|
||||
var count = sugoi.db.BufferedMail.manager.count($sdate==null);
|
||||
view.browser = new sugoi.tools.ResultsBrowser(count,10,browse);
|
||||
view.num = count;
|
||||
|
||||
}
|
||||
|
||||
@tpl("form.mtt")
|
||||
function doSmtp() {
|
||||
|
||||
var f = new sugoi.form.Form("emails");
|
||||
var data = [
|
||||
{label:"SMTP",value:"smtp"},
|
||||
{label:"Mandrill API",value:"mandrill"},
|
||||
];
|
||||
|
||||
var mailer = sugoi.db.Variable.get("mailer")==null ? "smtp" : sugoi.db.Variable.get("mailer");
|
||||
var host = sugoi.db.Variable.get("smtp_host")==null ? App.config.get("smtp_host") : sugoi.db.Variable.get("smtp_host");
|
||||
var port = sugoi.db.Variable.get("smtp_port")==null ? App.config.get("smtp_port") : sugoi.db.Variable.get("smtp_port");
|
||||
var user = sugoi.db.Variable.get("smtp_user")==null ? App.config.get("smtp_user") : sugoi.db.Variable.get("smtp_user");
|
||||
var pass = sugoi.db.Variable.get("smtp_pass")==null ? App.config.get("smtp_pass") : sugoi.db.Variable.get("smtp_pass");
|
||||
|
||||
|
||||
f.addElement(new sugoi.form.elements.StringSelect("mailer", "Mailer", data, mailer ));
|
||||
f.addElement(new sugoi.form.elements.StringInput("smtp_host", "host", host));
|
||||
f.addElement(new sugoi.form.elements.StringInput("smtp_port", "port", port));
|
||||
f.addElement(new sugoi.form.elements.StringInput("smtp_user", "user", user));
|
||||
f.addElement(new sugoi.form.elements.StringInput("smtp_pass", "pass", pass));
|
||||
|
||||
|
||||
if (f.isValid()){
|
||||
for ( k in ["mailer","smtp_host","smtp_port","smtp_user","smtp_pass"]){
|
||||
sugoi.db.Variable.set(k, f.getValueOf(k));
|
||||
}
|
||||
throw Ok("/admin/emails", t._("Configuration updated") );
|
||||
|
||||
}
|
||||
|
||||
view.title = t._("Email service configuration");
|
||||
view.form = f;
|
||||
}
|
||||
|
||||
function doPlugins(d:Dispatch) {
|
||||
d.dispatch(new controller.admin.Plugins());
|
||||
}
|
||||
|
||||
|
||||
@tpl("admin/taxo.mtt")
|
||||
function doTaxo(){
|
||||
|
||||
view.categ = db.TxpCategory.manager.all();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display errors logged in DB
|
||||
*/
|
||||
@tpl("admin/errors.mtt")
|
||||
function doErrors( args:{?user: Int, ?like: String, ?empty:Bool} ) {
|
||||
view.now = Date.now();
|
||||
|
||||
view.u = args.user!=null ? db.User.manager.get(args.user,false) : null;
|
||||
view.like = args.like!=null ? args.like : "";
|
||||
|
||||
var sql = "";
|
||||
if( args.user!=null ) sql += " AND uid="+args.user;
|
||||
//if( args.like!=null && args.like != "" ) sql += " AND error like "+sys.db.Manager.cnx.quote("%"+args.like+"%");
|
||||
if (args.empty) {
|
||||
sys.db.Manager.cnx.request("truncate table Error");
|
||||
}
|
||||
|
||||
var errorsStats = sys.db.Manager.cnx.request("select count(id) as c, DATE_FORMAT(date,'%y-%m-%d') as day from Error where date > NOW()- INTERVAL 1 MONTH "+sql+" group by day order by day").results();
|
||||
view.errorsStats = errorsStats;
|
||||
|
||||
view.browser = new sugoi.tools.ResultsBrowser(
|
||||
sugoi.db.Error.manager.unsafeCount("SELECT count(*) FROM Error WHERE 1 "+sql),
|
||||
20,
|
||||
function(start, limit) { return sugoi.db.Error.manager.unsafeObjects("SELECT * FROM Error WHERE 1 "+sql+" ORDER BY date DESC LIMIT "+start+","+limit,false); }
|
||||
);
|
||||
}
|
||||
|
||||
@tpl("admin/graph.mtt")
|
||||
function doGraph(?key:String,?year:Int,?month:Int){
|
||||
|
||||
|
||||
var from = new Date(year,month,1,0,0,0);
|
||||
var to = new Date(year,month+1,0,23,59,59);
|
||||
|
||||
if(app.params.exists("recompute")){
|
||||
|
||||
switch(key){
|
||||
case "basket":
|
||||
for( d in 1...to.getDate()){
|
||||
var _from = new Date(year,month,d,0,0,0);
|
||||
var _to = new Date(year,month,d,23,59,59);
|
||||
var value = db.Basket.manager.count($cdate>=_from && $cdate<=_to);
|
||||
var g = db.Graph.record(key,value, _from );
|
||||
// trace(value,_from,g);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var data = db.Graph.getRange(key,from,to);
|
||||
view.data = data;
|
||||
view.from = from;
|
||||
view.to = to;
|
||||
view.key = key;
|
||||
|
||||
var averageValue = 0.0;
|
||||
var total = 0.0;
|
||||
for( d in data) total += d.value;
|
||||
averageValue = total/data.length;
|
||||
view.total = total;
|
||||
view.averageValue = averageValue;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function doFixDistribValidation() {
|
||||
|
||||
Sys.println("===== Liste des distributions ayant été re-validées ====<br>");
|
||||
|
||||
//Get the current group
|
||||
var group = app.user.amap;
|
||||
//Get all the contracts for this given group
|
||||
var contractIds = Lambda.map(group.getContracts(),function(x) return x.id);
|
||||
//Get all the validated distributions for this given group
|
||||
var validatedDistribs = db.Distribution.manager.search( ($contractId in contractIds) && $validated == true, {orderBy:date}, false);
|
||||
for (distrib in validatedDistribs){
|
||||
|
||||
service.PaymentService.validateDistribution(distrib);
|
||||
Sys.println(distrib.toString() + "<br>");
|
||||
|
||||
}
|
||||
|
||||
Sys.println("===== Fin de la liste ====");
|
||||
|
||||
}
|
||||
|
||||
function doCheckDistribValidation() {
|
||||
|
||||
Sys.println("===== Liste des distributions validées ayant des opérations/paiements non validés ====<br>");
|
||||
|
||||
//Get the current group
|
||||
var group = app.user.amap;
|
||||
//Get all the contracts for this given group
|
||||
var contractIds = Lambda.map(group.getContracts(),function(x) return x.id);
|
||||
//Get all the validated distributions for this given group
|
||||
var validatedDistribs = db.Distribution.manager.search( ($contractId in contractIds) && $validated == true, {orderBy:date}, false);
|
||||
for (distrib in validatedDistribs){
|
||||
|
||||
for (user in distrib.getUsers()){
|
||||
|
||||
var basket = db.Basket.get(user, distrib.place, distrib.date);
|
||||
if (basket == null || basket.isValidated()) continue;
|
||||
|
||||
for (order in basket.getOrders()){
|
||||
if (!order.paid) {
|
||||
Sys.println(order.distribution.toString() + "<br>");
|
||||
Sys.println(order.toString() + "<br>");
|
||||
}
|
||||
}
|
||||
|
||||
var operation = basket.getOrderOperation(false);
|
||||
if (operation != null){
|
||||
|
||||
if (operation.pending) {
|
||||
Sys.println(distrib.toString() + "<br>");
|
||||
Sys.println(operation.toString() + "<br>");
|
||||
}
|
||||
|
||||
for ( payment in basket.getPayments()){
|
||||
|
||||
if (payment.pending){
|
||||
Sys.println(distrib.toString() + "<br>");
|
||||
Sys.println(payment.toString() + "<br>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Sys.println("===== Fin de la liste ====");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
package controller.admin;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut<francois.barbut@gmail.com>
|
||||
*/
|
||||
class Plugins extends controller.Controller
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
@tpl("admin/plugins/default.mtt")
|
||||
public function doDefault() {
|
||||
view.plugins = App.current.plugins;
|
||||
}
|
||||
|
||||
|
||||
public function doInstall(plugin:String) {
|
||||
|
||||
/*var p = App.current.getPlugin(plugin);
|
||||
if (p == null) throw Error("/admin/plugins","Le plugin '"+plugin+"' introuvable");
|
||||
|
||||
if(p.isInstalled()) throw Ok("/admin/plugins","Le plugin '"+plugin+"' est déjà installé");
|
||||
p.install();
|
||||
throw Ok("/admin/plugins","Le plugin '"+plugin+"' est correctement installé");*/
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package controller.api;
|
||||
import haxe.Json;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Groups API
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Group extends Controller
|
||||
{
|
||||
/**
|
||||
* JSON map datas
|
||||
*
|
||||
* Request by zone : http://localhost/api/group/map?minLat=42.8115217450979&maxLat=51.04139389812637=&minLng=-18.369140624999996&maxLng=23.13720703125
|
||||
* Request by location : http://localhost/api/group/map?lat=48.85&lng=2.32
|
||||
* Request by address : http://localhost/api/group/map?address=105%20avenue%20d%27ivry%20Paris
|
||||
*/
|
||||
public function doMap(args:{?minLat:Float, ?maxLat:Float, ?minLng:Float, ?maxLng:Float, ?lat:Float, ?lng:Float, ?address:String}) {
|
||||
|
||||
var out = new Array<GroupOnMap>();
|
||||
var places = new List<db.Place>();
|
||||
if (args.minLat != null && args.maxLat != null && args.minLng != null && args.maxLng != null){
|
||||
|
||||
//Request by zone
|
||||
#if plugins
|
||||
var sql = "select p.* from Place p, Hosting h where h.id=p.amapId and h.visible=1 and ";
|
||||
sql += 'p.lat > ${args.minLat} and p.lat < ${args.maxLat} and p.lng > ${args.minLng} and p.lng < ${args.maxLng}';
|
||||
#else
|
||||
var sql = "select p.* from Place p where ";
|
||||
sql += 'p.lat > ${args.minLat} and p.lat < ${args.maxLat} and p.lng > ${args.minLng} and p.lng < ${args.maxLng}';
|
||||
#end
|
||||
places = db.Place.manager.unsafeObjects(sql, false);
|
||||
|
||||
}else if (args.lat!=null && args.lng!=null){
|
||||
|
||||
//Request by location
|
||||
places = findGroupByDist(args.lat, args.lng);
|
||||
|
||||
}else{
|
||||
//Request by address
|
||||
if (args.address == null) throw "Please provide parameters";
|
||||
|
||||
var geocode = new sugoi.apis.google.GeoCode(App.config.get("google_geocoding_key"));
|
||||
var loc = geocode.geocode(args.address)[0].geometry.location;
|
||||
|
||||
args.lat = loc.lat;
|
||||
args.lng = loc.lng;
|
||||
|
||||
places = findGroupByDist(args.lat, args.lng);
|
||||
}
|
||||
|
||||
for ( p in places){
|
||||
out.push({
|
||||
id : p.amap.id,
|
||||
name : p.amap.name,
|
||||
image : p.amap.image==null ? null : view.file(p.amap.image),
|
||||
place : p.getInfos()
|
||||
});
|
||||
}
|
||||
|
||||
Sys.print(haxe.Json.stringify({success:true,groups:out}));
|
||||
}
|
||||
|
||||
/**
|
||||
* ~~ Pythagore rulez ~~
|
||||
*/
|
||||
function findGroupByDist(lat:Float, lng:Float,?limit=5){
|
||||
#if plugins
|
||||
var sql = 'select p.*,SQRT( POW(p.lat-$lat,2) + POW(p.lng-$lng,2) ) as dist from Place p, Hosting h ';
|
||||
sql += "where h.id=p.amapId and h.visible=1 and p.lat is not null ";
|
||||
sql += 'order by dist asc LIMIT $limit';
|
||||
#else
|
||||
var sql = 'select p.*,SQRT( POW(p.lat-$lat,2) + POW(p.lng-$lng,2) ) as dist from Place p ';
|
||||
sql += "where p.lat is not null ";
|
||||
sql += 'order by dist asc LIMIT $limit';
|
||||
#end
|
||||
return db.Place.manager.unsafeObjects(sql, false);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package controller.api;
|
||||
import haxe.Json;
|
||||
import tink.core.Error;
|
||||
import service.OrderService;
|
||||
|
||||
/**
|
||||
* Public order API
|
||||
*/
|
||||
class Order extends Controller
|
||||
{
|
||||
/**
|
||||
* get orders of a user from a contractId (constant contract) or a distributionId (varying contract)
|
||||
*/
|
||||
public function doGet(userId:Int){
|
||||
|
||||
checkIsLogged();
|
||||
|
||||
//params
|
||||
var p = app.params;
|
||||
var distributionId = Std.parseInt(p.get("distributionId"));
|
||||
var contractId = Std.parseInt(p.get("contractId"));
|
||||
if (distributionId == null && contractId == null) throw "You should provide a contractId or a distributionId";
|
||||
var user = db.User.manager.get(userId, false);
|
||||
if (user == null) throw 'user #$userId doesn\'t exists';
|
||||
var c : db.Contract = null;
|
||||
var d : db.Distribution = null;
|
||||
if (distributionId == null) {
|
||||
c = db.Contract.manager.get(contractId, false);
|
||||
}else{
|
||||
d = db.Distribution.manager.get(distributionId, false);
|
||||
c = d.contract;
|
||||
}
|
||||
|
||||
//rights
|
||||
if (!app.user.canManageContract(c)) throw new Error(t._("You do not have the authorization to manage this contract"));
|
||||
if (d != null && d.validated) throw new Error(t._("This delivery has been already validated"));
|
||||
if (c.type == db.Contract.TYPE_VARORDER && d == null ) throw "this contract is a 'varying order contract', please provide a distributionId";
|
||||
|
||||
//get datas
|
||||
var pids = tools.ObjectListTool.getIds(c.getProducts(false));
|
||||
var orders;
|
||||
if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
orders = db.UserContract.manager.search($user == user && $distributionId==d.id && ($productId in pids), true);
|
||||
}else {
|
||||
orders = db.UserContract.manager.search($user == user && ($productId in pids), true);
|
||||
}
|
||||
var orders = OrderService.prepare(orders);
|
||||
|
||||
Sys.print(tink.Json.stringify({success:true,orders:orders}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update orders of a user ( from react OrderBox component )
|
||||
* @param userId
|
||||
*/
|
||||
public function doUpdate(userId:Int){
|
||||
|
||||
checkIsLogged();
|
||||
|
||||
//GET params
|
||||
var p = app.params;
|
||||
var distributionId = Std.parseInt(p.get("distributionId"));
|
||||
var contractId = Std.parseInt(p.get("contractId"));
|
||||
|
||||
//POST payload
|
||||
var data = new Array<{id:Int,productId:Int,qt:Float,paid:Bool,invertSharedOrder:Bool,userId2:Int}>();
|
||||
data = haxe.Json.parse( StringTools.urlDecode(sugoi.Web.getPostData()) ).orders;
|
||||
|
||||
if (distributionId == null && contractId == null) throw "You should provide a contractId or a distributionId";
|
||||
var user = db.User.manager.get(userId, false);
|
||||
if (user == null) throw 'user #$userId doesn\'t exists';
|
||||
|
||||
var c : db.Contract = null;
|
||||
var d : db.Distribution = null;
|
||||
if (distributionId == null) {
|
||||
c = db.Contract.manager.get(contractId, false);
|
||||
}else{
|
||||
d = db.Distribution.manager.get(distributionId, false);
|
||||
c = d.contract;
|
||||
}
|
||||
var pids = tools.ObjectListTool.getIds(c.getProducts(false));
|
||||
|
||||
//rights & checks
|
||||
//fbarbut 2018-11-13 : too many problems when people try to edit the order of someone who left the group...
|
||||
//if (!user.isMemberOf(c.amap)) throw new Error(t._("::user:: is not member of this group", {user:user.name}));
|
||||
if (!app.user.canManageContract(c)) throw new Error(t._("You do not have the authorization to manage this contract"));
|
||||
if (d != null && d.validated) throw new Error(t._("This delivery has been already validated"));
|
||||
if (c.type == db.Contract.TYPE_VARORDER && d == null ) throw "this contract is a 'varying order contract', please provide a distributionId";
|
||||
|
||||
/*
|
||||
* record orders
|
||||
**/
|
||||
|
||||
//find existing orders
|
||||
var exOrders = null;
|
||||
if (c.type == db.Contract.TYPE_VARORDER) {
|
||||
exOrders = db.UserContract.manager.search($user == user && $distributionId==d.id && ($productId in pids), true);
|
||||
}else {
|
||||
exOrders = db.UserContract.manager.search($user == user && ($productId in pids), true);
|
||||
}
|
||||
|
||||
var orders = [];
|
||||
for (o in data) {
|
||||
|
||||
//get product
|
||||
var product = db.Product.manager.get(o.productId, false);
|
||||
if (product.contract.id != c.id) throw "product " + o.productId + " is not in contract " + c.id;
|
||||
|
||||
//find existing order
|
||||
var uo = Lambda.find(exOrders, function(uo) return uo.id == o.id);
|
||||
|
||||
//user2 + invert
|
||||
var user2 : db.User = null;
|
||||
var invert = false;
|
||||
if ( o.userId2 != null ) {
|
||||
user2 = db.User.manager.get(o.userId2,false);
|
||||
if (user2 == null) throw t._("Unable to find user #::num::",{num:o.userId2});
|
||||
if (!user2.isMemberOf(product.contract.amap)) throw t._("::user:: is not part of this group",{user:user2});
|
||||
if (user.id == user2.id) throw t._("Both selected accounts must be different ones");
|
||||
|
||||
invert = o.invertSharedOrder;
|
||||
}
|
||||
|
||||
//record order
|
||||
if (uo != null) {
|
||||
//existing record
|
||||
var o = OrderService.edit(uo, o.qt, o.paid , user2, invert);
|
||||
if (o != null) orders.push(o);
|
||||
}else {
|
||||
//new record
|
||||
var o = OrderService.make(user, o.qt , product, d == null ? null : d.id, o.paid , user2, invert);
|
||||
if (o != null) orders.push(o);
|
||||
}
|
||||
}
|
||||
|
||||
app.event(MakeOrder(orders));
|
||||
db.Operation.onOrderConfirm(orders);
|
||||
|
||||
Sys.print(Json.stringify({success:true, orders:data}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package controller.api;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Product API
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Product extends Controller
|
||||
{
|
||||
|
||||
public function doGet(args:{?contractId:db.Contract}) {
|
||||
|
||||
if(args==null || args.contractId==null) throw "invalid params";
|
||||
|
||||
var out = {products:new Array<ProductInfo>()};
|
||||
for( p in args.contractId.getProducts(false)) out.products.push(p.infos(false,false));
|
||||
Sys.print(tink.Json.stringify(out));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
package controller.api;
|
||||
import haxe.Json;
|
||||
import tink.core.Error;
|
||||
import Common;
|
||||
import db.Amap;
|
||||
import tools.ArrayTool;
|
||||
using tools.ObjectListTool;
|
||||
using Lambda;
|
||||
|
||||
class Shop extends Controller
|
||||
{
|
||||
/**
|
||||
* @doc https://app.swaggerhub.com/apis/Cagette.net/Cagette.net/0.9.2#/shop/get_shop_categories
|
||||
*/
|
||||
public function doCategories(args:{date:String, place:db.Place}){
|
||||
|
||||
var out = new Array<CategoryInfo>();
|
||||
var group = args.place.amap;
|
||||
|
||||
if (group.flags.has(ShopCategoriesFromTaxonomy)){
|
||||
|
||||
//TAXO CATEGORIES
|
||||
var taxoCategs = db.TxpCategory.manager.all(false);
|
||||
for (txp in taxoCategs){
|
||||
|
||||
var c : CategoryInfo = {id:txp.id, name:txp.name, subcategories:[]};
|
||||
for (sc in txp.getSubCategories()){
|
||||
c.subcategories.push({id:sc.id,name:sc.name});
|
||||
}
|
||||
out.push(c);
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
//CUSTOM CATEGORIES
|
||||
var catGroups = db.CategoryGroup.get(group);
|
||||
for (cat in catGroups){
|
||||
|
||||
var c : CategoryInfo = {id:cat.id, name:cat.name, subcategories:[]};
|
||||
for ( sc in cat.getCategories() ){
|
||||
c.subcategories.push({id:sc.id,name:sc.name});
|
||||
}
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
Sys.print(Json.stringify({success:true,categories:out}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @doc https://app.swaggerhub.com/apis/Cagette.net/Cagette.net/0.9.2#/shop/get_shop_products
|
||||
*/
|
||||
public function doProducts(args:{date:String, place:db.Place, ?category:Int, ?subcategory:Int}){
|
||||
|
||||
if ( args == null || (args.category == null && args.subcategory == null)) throw "You should provide a category Id or a subcategory Id";
|
||||
//need some optimization : populating all thses objects eats memory, and we need only the ids !
|
||||
var products = getProducts(args.place, Date.fromString(args.date), args.place.amap.flags.has(ShopCategoriesFromTaxonomy));
|
||||
var pids = products.getIds();
|
||||
var categsFromTaxo = args.place.amap.flags.has(ShopCategoriesFromTaxonomy);
|
||||
var catName = "undefined category";
|
||||
|
||||
if( categsFromTaxo ){
|
||||
|
||||
/**
|
||||
* Use Taxonomy :
|
||||
* - Category is TxpCatgory
|
||||
* - Subcategory us TxpSubCategory
|
||||
* - Products are linked to TxpProduct which belongs to a TxpCatgory and a TxpSubCategory
|
||||
*/
|
||||
var sql = "";
|
||||
|
||||
if (args.subcategory != null){
|
||||
|
||||
sql = 'SELECT p.* FROM Product p, TxpProduct tp, TxpSubCategory sc
|
||||
WHERE p.txpProductId = tp.id
|
||||
AND tp.subCategoryId = sc.id
|
||||
AND sc.id = ${args.subcategory}
|
||||
AND p.id IN ( ${pids.join(",")} )';
|
||||
|
||||
var cat = db.TxpSubCategory.manager.get(args.subcategory, false);
|
||||
if (cat == null) throw 'unknown subcategory #' + args.subcategory;
|
||||
catName = cat.name;
|
||||
|
||||
}else if (args.category != null){
|
||||
|
||||
sql = 'SELECT p.* FROM Product p, TxpProduct tp, TxpCategory c
|
||||
WHERE p.txpProductId = tp.id
|
||||
AND tp.categoryId = c.id
|
||||
AND c.id = ${args.category}
|
||||
AND p.id IN ( ${pids.join(",")} )';
|
||||
|
||||
var cat = db.TxpCategory.manager.get(args.subcategory, false);
|
||||
if (cat == null) throw 'unknown category #' + args.category;
|
||||
catName = cat.name;
|
||||
}
|
||||
|
||||
products = db.Product.manager.unsafeObjects(sql,false).array();
|
||||
|
||||
}else{
|
||||
|
||||
/**
|
||||
* Use custom categories :
|
||||
* - Category is CategoryGroup
|
||||
* - Subcategory is Category
|
||||
* - Products are tagged with ProductCategory
|
||||
*/
|
||||
var sql = "";
|
||||
|
||||
if (args.subcategory != null){
|
||||
|
||||
sql = 'SELECT p.* FROM Product p, ProductCategory pc, Category c
|
||||
WHERE pc.productId = p.id
|
||||
AND pc.categoryId = c.id
|
||||
AND c.id = ${args.subcategory}
|
||||
AND p.id IN ( ${pids.join(",")} )';
|
||||
|
||||
var cat = db.Category.manager.get(args.subcategory, false);
|
||||
if (cat == null) throw 'unknown subcategory #' + args.subcategory;
|
||||
catName = cat.name;
|
||||
|
||||
}else if (args.category != null){
|
||||
|
||||
sql = 'SELECT p.* FROM Product p, ProductCategory pc, Category c, CategoryGroup cg
|
||||
WHERE pc.productId = p.id
|
||||
AND pc.categoryId = c.id
|
||||
AND c.categoryGroupId = cg.id
|
||||
AND cg.id = ${args.category}
|
||||
AND p.id IN ( ${pids.join(",")} )';
|
||||
|
||||
var cat = db.CategoryGroup.manager.get(args.category, false);
|
||||
if (cat == null) throw 'unknown category #' + args.category;
|
||||
catName = cat.name;
|
||||
}
|
||||
|
||||
products = db.Product.manager.unsafeObjects(sql,false).array();
|
||||
}
|
||||
|
||||
//to productInfos
|
||||
var products : Array<ProductInfo> = products.map( function(p) return p.infos(categsFromTaxo,true) ).array();
|
||||
|
||||
if (args.category != null){
|
||||
Sys.print(Json.stringify( {success:true, products:products, category:catName} ));
|
||||
}else{
|
||||
Sys.print(Json.stringify( {success:true, products:products, subcategory:catName} ));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function doInit(args:{place:db.Place, date:String}){
|
||||
|
||||
var out = {place:args.place.getInfos(), orderEndDates: new Array<{date:String,contracts:Array<String>}>() };
|
||||
|
||||
//order end dates
|
||||
var contracts = db.Contract.getActiveContracts(args.place.amap);
|
||||
|
||||
for (c in Lambda.array(contracts)) {
|
||||
if (c.type != db.Contract.TYPE_VARORDER) contracts.remove(c);//only varying orders
|
||||
if (!c.isVisibleInShop()) contracts.remove(c);
|
||||
}
|
||||
|
||||
var date = Date.fromString(args.date);
|
||||
var now = Date.now();
|
||||
var cids = Lambda.map(contracts, function(c) return c.id);
|
||||
var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
||||
var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
|
||||
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && $orderStartDate <= now && $orderEndDate >= now && $date > d1 && $end < d2 && $place == args.place, false);
|
||||
var distribByDate = ArrayTool.groupByDate(Lambda.array(distribs), "orderEndDate");
|
||||
out.orderEndDates = [];
|
||||
for ( k in distribByDate.keys() ) {
|
||||
out.orderEndDates.push( {date:k , contracts: distribByDate.get(k).map( function(x) return x.contract.name)} );
|
||||
}
|
||||
|
||||
|
||||
Sys.print(Json.stringify( out ));
|
||||
}
|
||||
|
||||
private function getProductInfos(place:db.Place, date, ?categsFromTaxo = false):Array<ProductInfo>{
|
||||
var products = getProducts(place, date, categsFromTaxo);
|
||||
return Lambda.array(Lambda.map(products, function(p) return p.infos(categsFromTaxo)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available products list
|
||||
*/
|
||||
private function getProducts(place:db.Place,date,?categsFromTaxo=false):Array<db.Product> {
|
||||
|
||||
var contracts = db.Contract.getActiveContracts(place.amap);
|
||||
|
||||
for (c in Lambda.array(contracts)) {
|
||||
if (c.type != db.Contract.TYPE_VARORDER) contracts.remove(c);//only varying orders
|
||||
if (!c.isVisibleInShop()) contracts.remove(c);
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
var cids = Lambda.map(contracts, function(c) return c.id);
|
||||
var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
|
||||
var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
|
||||
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && $orderStartDate <= now && $orderEndDate >= now && $date > d1 && $end < d2 && $place == place, false);
|
||||
|
||||
var cids = Lambda.map(distribs, function(d) return d.contract.id);
|
||||
return Lambda.array(db.Product.manager.search(($contractId in cids) && $active==true, { orderBy:name }, false));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package controller.api;
|
||||
import haxe.Json;
|
||||
import tink.core.Error;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Public user API
|
||||
*/
|
||||
class User extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Login
|
||||
*/
|
||||
public function doLogin(){
|
||||
|
||||
//cleaning
|
||||
var email = StringTools.trim(App.current.params.get("email")).toLowerCase();
|
||||
var pass = StringTools.trim(App.current.params.get("password"));
|
||||
|
||||
service.UserService.login(email, pass);
|
||||
|
||||
Sys.print(Json.stringify({success:true}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register
|
||||
*/
|
||||
public function doRegister(){
|
||||
|
||||
//cleaning
|
||||
var p = app.params;
|
||||
var email = StringTools.trim(p.get("email")).toLowerCase();
|
||||
var pass = StringTools.trim(p.get("password"));
|
||||
var firstName = StringTools.trim(p.get("firstName"));
|
||||
var lastName = StringTools.trim(p.get("lastName")).toUpperCase();
|
||||
var phone = p.exists("phone") ? StringTools.trim(p.get("phone")) : null;
|
||||
|
||||
service.UserService.register(firstName, lastName, email, phone, pass);
|
||||
|
||||
Sys.print(Json.stringify({success:true}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get users of current group
|
||||
*/
|
||||
@logged
|
||||
function doGetFromGroup(){
|
||||
|
||||
if(!app.user.canAccessMembership()) throw new tink.core.Error(403,"Access forbidden");
|
||||
|
||||
var members:Array<UserInfo> = service.UserService.getFromGroup(app.user.amap).map(function(m) return m.infos() );
|
||||
Sys.print(tink.Json.stringify({users:members}));
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+373
@@ -0,0 +1,373 @@
|
||||
package db;
|
||||
import sugoi.form.ListData.FormData;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
enum AmapFlags {
|
||||
HasMembership; //membership management
|
||||
ShopMode; //shop mode / standard mode
|
||||
HasPayments; //manage payments and user balance
|
||||
ComputeMargin; //compute margin instead of percentage
|
||||
CagetteNetwork; //register in cagette.net groups directory
|
||||
ShopCategoriesFromTaxonomy; //the custom categories are not used anymore, use product taxonomy instead
|
||||
HidePhone; //Hide manager phone on group public page
|
||||
PhoneRequired; //phone number of members is required for this group
|
||||
|
||||
}
|
||||
|
||||
//user registration options
|
||||
enum RegOption{
|
||||
Closed;
|
||||
WaitingList;
|
||||
Open;
|
||||
Full;
|
||||
}
|
||||
|
||||
enum GroupType{
|
||||
Amap; //CSA / GASAP / AMAP
|
||||
GroupedOrders; //groupements d'achat
|
||||
ProducerDrive; //drive de producteurs
|
||||
FarmShop; //vente à la ferme
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AMAP
|
||||
*/
|
||||
class Amap extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<64>;
|
||||
|
||||
@formPopulate("getMembersFormElementData")
|
||||
@:relation(userId)
|
||||
public var contact : SNull<User>;
|
||||
|
||||
public var txtIntro:SNull<SText>; //introduction de l'amap
|
||||
public var txtHome:SNull<SText>; //texte accueil adhérents
|
||||
public var txtDistrib:SNull<SText>; //sur liste d'emargement
|
||||
|
||||
public var extUrl : SNull<SString<64>>; //lien sur logo du groupe
|
||||
|
||||
public var membershipRenewalDate : SNull<SDate>;
|
||||
@hideInForms public var membershipPrice : SNull<STinyInt>;
|
||||
|
||||
@hideInForms
|
||||
public var vatRates : SData<Map<String,Float>>;
|
||||
|
||||
public var flags:SFlags<AmapFlags>;
|
||||
public var groupType:SNull<SEnum<GroupType>>;
|
||||
|
||||
@hideInForms @:relation(imageId)
|
||||
public var image : SNull<sugoi.db.File>;
|
||||
|
||||
@hideInForms public var cdate : SDateTime;
|
||||
@hideInForms @:relation(placeId) public var mainPlace : SNull<db.Place>;
|
||||
|
||||
public var regOption : SEnum<RegOption>;
|
||||
|
||||
@hideInForms public var currency:SString<12>; //name or symbol.
|
||||
@hideInForms public var currencyCode:SString<3>; //https://fr.wikipedia.org/wiki/ISO_4217
|
||||
|
||||
//payments
|
||||
@hideInForms public var allowedPaymentsType:SNull<SData<Array<String>>>;
|
||||
@hideInForms public var checkOrder:SNull<SString<64>>;
|
||||
@hideInForms public var IBAN:SNull<SString<40>>;
|
||||
@hideInForms public var allowMoneyPotWithNegativeBalance:SNull<SBool>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
flags = cast 0;
|
||||
flags.set(CagetteNetwork);
|
||||
flags.set(ShopMode);
|
||||
vatRates = ["5,5%" => 5.5, "20%" => 20];
|
||||
cdate = Date.now();
|
||||
regOption = Open;
|
||||
currency = "€";
|
||||
currencyCode = "EUR";
|
||||
checkOrder = "";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* find the most common delivery place
|
||||
*/
|
||||
public function getMainPlace() {
|
||||
|
||||
if (mainPlace != null && Std.random(100) != 0) {
|
||||
return mainPlace;
|
||||
}else {
|
||||
this.lock();
|
||||
|
||||
var places = getPlaces();
|
||||
|
||||
//just 1 place
|
||||
if (places.length == 1) {
|
||||
this.mainPlace = places.first();
|
||||
this.update();
|
||||
return this.mainPlace;
|
||||
}
|
||||
|
||||
//no places !
|
||||
if (places.length == 0) return null;
|
||||
|
||||
var pids = Lambda.map(places, function(x) return x.id);
|
||||
|
||||
var res = sys.db.Manager.cnx.request("select placeId,count(placeId) as top from Distribution where placeId IN ("+pids.join(",")+") group by placeId order by top desc").results();
|
||||
var res = res.first();
|
||||
var pid :Int = null;
|
||||
|
||||
if (res == null){
|
||||
pid = this.getPlaces().first().id;
|
||||
}else{
|
||||
pid = Std.parseInt(res.placeId);
|
||||
}
|
||||
|
||||
if (pid != 0 && pid != null) {
|
||||
var p = db.Place.manager.get(pid, false);
|
||||
this.mainPlace = p;
|
||||
this.update();
|
||||
return p;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods to get flags in templates
|
||||
*/
|
||||
|
||||
public function hasMembership():Bool {
|
||||
return flags != null && flags.has(HasMembership);
|
||||
}
|
||||
|
||||
public function hasShopMode() {
|
||||
return flags.has(ShopMode);
|
||||
}
|
||||
|
||||
public function canExposePhone() {
|
||||
return !flags.has(HidePhone);
|
||||
}
|
||||
|
||||
public function hasPayments(){
|
||||
return flags != null && flags.has(HasPayments);
|
||||
}
|
||||
|
||||
public function hasTaxonomy(){
|
||||
return flags != null && flags.has(ShopCategoriesFromTaxonomy);
|
||||
}
|
||||
|
||||
public function hasPhoneRequired(){
|
||||
return flags != null && flags.has(PhoneRequired);
|
||||
}
|
||||
|
||||
public function getCategoryGroups() {
|
||||
|
||||
//if (flags.has(ShopCategoriesFromTaxonomy)){
|
||||
//return Lambda.array( cast db.TxpCategory.manager.all(false) );
|
||||
//}else{
|
||||
//return Lambda.array( db.CategoryGroup.get(this) );
|
||||
//}
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var categs = new Array<{id:Int,name:String,color:String,pinned:Bool,categs:Array<CategoryInfo>}>();
|
||||
|
||||
if (this.flags.has(db.Amap.AmapFlags.ShopCategoriesFromTaxonomy)){
|
||||
|
||||
//TAXO CATEGORIES
|
||||
var taxoCategs = db.TxpCategory.manager.all(false);
|
||||
var c : Array<CategoryInfo> = Lambda.array(Lambda.map( taxoCategs, function(c){return {id:c.id, name:c.name, subcategories:null}; }));
|
||||
|
||||
categs.push({
|
||||
id:0,
|
||||
name: t._("Product type"),
|
||||
pinned:false,
|
||||
color:"#583816",
|
||||
categs: c
|
||||
});
|
||||
|
||||
}else{
|
||||
|
||||
//CUSTOM CATEGORIES
|
||||
var catGroups = db.CategoryGroup.get(this);
|
||||
for ( cg in catGroups){
|
||||
var color = App.current.view.intToHex(db.CategoryGroup.COLORS[cg.color]);
|
||||
categs.push({
|
||||
id:cg.id,
|
||||
name:cg.name,
|
||||
pinned:cg.pinned,
|
||||
color:color,
|
||||
categs: Lambda.array(Lambda.map( cg.getCategories(), function(c) return c.infos()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return categs;
|
||||
|
||||
}
|
||||
|
||||
|
||||
//public function canAddMember():Bool {
|
||||
// return isAboOk(true);
|
||||
//}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des contrats actifs
|
||||
* @param large=false
|
||||
*/
|
||||
public function getActiveContracts(?large=false) {
|
||||
return Contract.getActiveContracts(this, large, false);
|
||||
}
|
||||
|
||||
public function getContracts() {
|
||||
return Contract.manager.search($amap == this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* récupere les produits des contracts actifs
|
||||
*/
|
||||
public function getProducts() {
|
||||
var contracts = db.Contract.getActiveContracts(App.current.user.amap,false,false);
|
||||
return Product.manager.search( $contractId in Lambda.map(contracts, function(c) return c.id),{orderBy:name}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* get next multi-deliveries
|
||||
*/
|
||||
public function getDeliveries(?limit=3){
|
||||
var out = new Map<String,db.Distribution>();
|
||||
for ( c in getActiveContracts()){
|
||||
for ( d in c.getDistribs(true,3)){
|
||||
out.set(d.getKey(), d);
|
||||
}
|
||||
}
|
||||
|
||||
var out = Lambda.array(out);
|
||||
out.sort(function(a, b){
|
||||
return Math.round(a.date.getTime() / 1000) - Math.round(b.date.getTime() / 1000);
|
||||
});
|
||||
return out.slice(0,limit);
|
||||
}
|
||||
|
||||
public function getPlaces() {
|
||||
return Place.manager.search($amap == this, false);
|
||||
}
|
||||
|
||||
public function getVendors() {
|
||||
return Vendor.manager.search($amap == this, false);
|
||||
}
|
||||
|
||||
public function getMembers() {
|
||||
return User.manager.unsafeObjects("Select u.* from User u,UserAmap ua where u.id=ua.userId and ua.amapId="+this.id+" order by u.lastName", false);
|
||||
}
|
||||
|
||||
public function getMembersNum():Int{
|
||||
return UserAmap.manager.count($amapId == this.id);
|
||||
}
|
||||
|
||||
public function getMembersFormElementData():FormData<Int> {
|
||||
var m = getMembers();
|
||||
var out = [];
|
||||
for (mm in m) {
|
||||
|
||||
out.push({label:mm.getCoupleName() , value:mm.id});
|
||||
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
override public function toString() {
|
||||
if (name != '' && name != null) {
|
||||
return name;
|
||||
}else {
|
||||
return 'group#' + id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pour avoir le nom de la periode de cotisation pour une date donnée
|
||||
*/
|
||||
public function getPeriodName(?d:Date):String {
|
||||
if (d == null) d = Date.now();
|
||||
var year = getMembershipYear(d);
|
||||
return getPeriodNameFromYear(year);
|
||||
}
|
||||
|
||||
/**
|
||||
* Si la date de renouvellement est en janvier ou février, on note la cotisation avec l'année en cours,
|
||||
* sinon c'est "à cheval" donc on note la cotis avec l'année la plus ancienne (ex:2014 pour une cotis 2014-2015)
|
||||
*/
|
||||
public function getMembershipYear(?d:Date):Int {
|
||||
if (d == null) d = Date.now();
|
||||
var year = d.getFullYear();
|
||||
var n = membershipRenewalDate;
|
||||
if (n == null) n = Date.now();
|
||||
var renewalDate = new Date(year, n.getMonth(), n.getDate(), 0, 0, 0);
|
||||
|
||||
//if (membershipRenewalDate.getMonth() <= 1) {
|
||||
|
||||
if (d.getTime() < renewalDate.getTime()) {
|
||||
return year-1;
|
||||
}else {
|
||||
return year;
|
||||
}
|
||||
|
||||
//}else {
|
||||
//return year - 1;
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* à partir d'une année de cotis enregistrée, afficher le nom de la periode
|
||||
* @param y
|
||||
*/
|
||||
public function getPeriodNameFromYear(y:Int):String {
|
||||
if (membershipRenewalDate!=null && membershipRenewalDate.getMonth() <= 1) {
|
||||
return Std.string(y);
|
||||
}else {
|
||||
return Std.string(y) + "-" + Std.string(y+1);
|
||||
}
|
||||
}
|
||||
|
||||
override public function insert(){
|
||||
|
||||
if (txtHome == null){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
txtHome = t._("Welcome in the group of ::name::!\n You can look at the delivery planning or make a new order.",{name:this.name});
|
||||
}
|
||||
|
||||
App.current.event(NewGroup(this,App.current.user));
|
||||
|
||||
super.insert();
|
||||
}
|
||||
|
||||
public function getCurrency():String{
|
||||
|
||||
if (currency == ""){
|
||||
lock();
|
||||
currency = "€";
|
||||
currencyCode = "EUR";
|
||||
update();
|
||||
}
|
||||
|
||||
return currency;
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Group name"),
|
||||
"txtIntro" => t._("Short description"),
|
||||
"txtHome" => t._("Homepage text"),
|
||||
"txtDistrib" => t._("Text for distribution lists"),
|
||||
"extUrl" => t._("Group website URL"),
|
||||
"membershipRenewalDate" => t._("Membership renewal date"),
|
||||
"flags" => t._("Options"),
|
||||
"groupType" => t._("Group type"),
|
||||
"regOption" => t._("Registration setting"),
|
||||
"contact" => t._("Main contact"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
/**
|
||||
* Basket : represents the orders of a user for specific date + place
|
||||
*/
|
||||
//@:index(userId,placeId,ddate,unique)
|
||||
class Basket extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var cdate : SDateTime; //date when the order has been placed
|
||||
public var num : SInt; //order number
|
||||
|
||||
//TODO : link baskets to a multidistrib ID.
|
||||
|
||||
//2018-07-21 fbarbut : we cannot use keys like this, because some distribution's place or date may change after orders are made.
|
||||
//@:relation(userId) public var user : db.User;
|
||||
//@:relation(placeId) public var place : db.Place;
|
||||
//public var ddate : SDate; //date of the delivery
|
||||
|
||||
public static var CACHE = new Map<String,db.Basket>();
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
cdate = Date.now();
|
||||
}
|
||||
|
||||
public static function emptyCache(){
|
||||
CACHE = new Map<String,db.Basket>();
|
||||
}
|
||||
|
||||
public static function get(user:db.User, place:db.Place, date:Date, ?lock = false):db.Basket{
|
||||
|
||||
date = tools.DateTool.setHourMinute(date, 0, 0);
|
||||
|
||||
//caching
|
||||
// var k = user.id + "-" + place.id + "-" + date.toString().substr(0, 10);
|
||||
// var b = CACHE.get(k);
|
||||
var b = null;
|
||||
// if (b == null){
|
||||
var md = MultiDistrib.get(date, place,db.Contract.TYPE_VARORDER);
|
||||
var orders = md.getUserOrders(user);
|
||||
|
||||
for( o in orders){
|
||||
if(o.basket!=null) {
|
||||
b = o.basket;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// CACHE.set(k, b);
|
||||
// }
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Basket or create it if it doesn't exists.
|
||||
* Also link existing orders to this basket
|
||||
* @param user
|
||||
* @param place
|
||||
* @param date
|
||||
*/
|
||||
public static function getOrCreate(user, place, date){
|
||||
var b = get(user, place, date, true);
|
||||
|
||||
date = tools.DateTool.setHourMinute(date, 0, 0);
|
||||
|
||||
if (b == null){
|
||||
|
||||
//compute basket number
|
||||
var md = MultiDistrib.get(date, place,db.Contract.TYPE_VARORDER);
|
||||
|
||||
b = new Basket();
|
||||
b.num = md.getUsers().length + 1;
|
||||
//TODO : should be more safe to do something like "b.num = MAX(num)+1 FROM Basket"
|
||||
b.insert();
|
||||
|
||||
//try to find orders and link them to the basket
|
||||
var dids = tools.ObjectListTool.getIds(md.distributions);
|
||||
for ( o in db.UserContract.manager.search( ($distributionId in dids) && ($user == user), true)){
|
||||
o.basket = b;
|
||||
o.update();
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get basket's orders
|
||||
*/
|
||||
public function getOrders(){
|
||||
return db.UserContract.manager.search($basket == this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of operations which paid this basket
|
||||
* @return
|
||||
*/
|
||||
public function getPayments():Iterable<db.Operation>{
|
||||
|
||||
var op = getOrderOperation(false);
|
||||
if (op == null){
|
||||
return [];
|
||||
}else{
|
||||
return op.getRelatedPayments();
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrderOperation(?onlyPending=true):db.Operation{
|
||||
|
||||
var order = getOrders().first();
|
||||
if(order==null) return null;
|
||||
|
||||
var key = db.Distribution.makeKey(order.distribution.date, order.distribution.place);
|
||||
return db.Operation.findVOrderTransactionFor(key, order.user, order.distribution.place.amap, onlyPending);
|
||||
|
||||
}
|
||||
|
||||
public function isValidated(){
|
||||
|
||||
var ordersPaid = Lambda.count(getOrders(), function(o) return !o.paid) == 0;
|
||||
var op = getOrderOperation(false);
|
||||
var orderOperationNotPending = op!=null ? op.pending == false : true;
|
||||
var paymentOperationsNotPending = Lambda.count(getPayments(), function(p) return p.pending) == 0;
|
||||
|
||||
return ordersPaid && orderOperationNotPending && paymentOperationsNotPending;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
class Category extends sys.db.Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name :SString<128>;
|
||||
|
||||
@:relation(categoryGroupId) public var categoryGroup:db.CategoryGroup;
|
||||
|
||||
/**
|
||||
* get category color in hexa
|
||||
*/
|
||||
public function getColor():String {
|
||||
return App.current.view.intToHex(db.CategoryGroup.COLORS[categoryGroup.color]);
|
||||
}
|
||||
|
||||
public function infos():CategoryInfo{
|
||||
return {id:id, name:name, /*parent:categoryGroup.id, /*pinned:categoryGroup.pinned*/};
|
||||
}
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
|
||||
|
||||
class CategoryGroup extends sys.db.Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
public var color : STinyInt; //color id
|
||||
public var pinned : SBool; //if true, the products tagged with these categories will be pinned on top of the shop.
|
||||
|
||||
@:relation(amapId) public var amap:db.Amap;
|
||||
|
||||
@:skip public static var COLORS = [
|
||||
0x7BAD1C, //vert clair
|
||||
0x007700, //vert foncé
|
||||
0x583816, //marron
|
||||
0xD97801, //orange carotte
|
||||
0xB1933D, //sable
|
||||
0xC91F25, //rouge
|
||||
0x6F0D2E, //vin
|
||||
0x616161, //gris
|
||||
];
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
pinned = false;
|
||||
color = 7;
|
||||
}
|
||||
|
||||
public function getCategories() {
|
||||
return db.Category.manager.search($categoryGroup == this, false);
|
||||
}
|
||||
|
||||
public static function get(amap:db.Amap):List<CategoryGroup> {
|
||||
return manager.search($amap == amap, false);
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Category group name"),
|
||||
"pinned" => t._("Pinned on top"),
|
||||
"color" => t._("Color"),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
package db;
|
||||
import sugoi.form.ListData.FormData;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
enum ContractFlags {
|
||||
UsersCanOrder; //adhérents peuvent saisir eux meme la commande en ligne
|
||||
StockManagement; //gestion des commandes
|
||||
PercentageOnOrders; //calcul d'une commission supplémentaire
|
||||
|
||||
//LogisticMgmt; //gestion logistique
|
||||
//SubGroups; //sous groupes pour commandes groupées
|
||||
//InviteFriends; //peut inviter des amis à participer à la commande
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract
|
||||
*
|
||||
* un contrat réunissant pluseiurs produits d'un meme fournisseur
|
||||
* qui sont livrés au meme endroit et meme moment;
|
||||
*
|
||||
*/
|
||||
class Contract extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<64>;
|
||||
|
||||
//responsable
|
||||
@formPopulate("populate") @:relation(userId) public var contact : SNull<User>;
|
||||
@formPopulate("populateVendor") @:relation(vendorId) public var vendor : Vendor;
|
||||
|
||||
public var startDate:SDate;
|
||||
public var endDate :SDate;
|
||||
|
||||
public var description:SNull<SText>;
|
||||
|
||||
@:relation(amapId) public var amap:Amap;
|
||||
public var distributorNum:STinyInt;
|
||||
public var flags : SFlags<ContractFlags>;
|
||||
|
||||
public var percentageValue : SNull<SInt>; //fees percentage
|
||||
public var percentageName : SNull<SString<64>>; //fee name
|
||||
|
||||
public var type : SInt;
|
||||
@:skip public static var TYPE_CONSTORDERS = 0; //CSA contract
|
||||
@:skip public static var TYPE_VARORDER = 1; //varying orders contract
|
||||
|
||||
@:skip var cache_hasActiveDistribs : Bool;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
flags = cast 0;
|
||||
distributorNum = 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The products can be ordered currently ?
|
||||
*
|
||||
* @deprecated it depends on distributions
|
||||
*/
|
||||
public function isUserOrderAvailable():Bool {
|
||||
|
||||
if (type == TYPE_CONSTORDERS ) {
|
||||
return isVisibleInShop();
|
||||
}else {
|
||||
|
||||
//if ( cache_hasActiveDistribs != null ) return cache_hasActiveDistribs;
|
||||
|
||||
//for varying orders, we need to know if there are some available deliveries
|
||||
var n = Date.now();
|
||||
var d = db.Distribution.manager.count( $orderStartDate <= n && $orderEndDate >= n && $contractId==this.id);
|
||||
|
||||
//tmp : add the "old" deliveries which have a null orderStartDate
|
||||
//d += db.Distribution.manager.count( $orderStartDate == null && $date > n && $contractId == this.id );
|
||||
|
||||
//cache_hasActiveDistribs = d > 0;
|
||||
//return cache_hasActiveDistribs && isVisibleInShop();
|
||||
return d>0 && isVisibleInShop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The products can be displayed in a shop ?
|
||||
*/
|
||||
public function isVisibleInShop():Bool {
|
||||
|
||||
//yes if the contract is active and the 'UsersCanOrder' flag is checked
|
||||
var n = Date.now().getTime();
|
||||
return flags.has(UsersCanOrder) && n < this.endDate.getTime() && n > this.startDate.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* is currently open to orders
|
||||
*/
|
||||
public function hasRunningOrders(){
|
||||
var now = Date.now();
|
||||
var n = now.getTime();
|
||||
|
||||
var contractOpen = flags.has(UsersCanOrder) && n < this.endDate.getTime() && n > this.startDate.getTime();
|
||||
var d = db.Distribution.manager.count( $orderStartDate <= now && $orderEndDate > now && $contractId==this.id);
|
||||
|
||||
return contractOpen && d > 0;
|
||||
}
|
||||
|
||||
|
||||
public function hasPercentageOnOrders():Bool {
|
||||
return flags.has(PercentageOnOrders) && percentageValue!=null && percentageValue!=0;
|
||||
}
|
||||
|
||||
public function hasStockManagement():Bool {
|
||||
return flags.has(StockManagement);
|
||||
}
|
||||
|
||||
/**
|
||||
* computes a 'percentage' fee or a 'margin' fee
|
||||
* depending on the group settings
|
||||
*
|
||||
* @param basePrice
|
||||
*/
|
||||
public function computeFees(basePrice:Float) {
|
||||
if (!hasPercentageOnOrders()) return 0.0;
|
||||
|
||||
if (amap.flags.has(ComputeMargin)) {
|
||||
//commercial margin
|
||||
return (basePrice / ((100 - percentageValue) / 100)) - basePrice;
|
||||
|
||||
}else {
|
||||
//add a percentage
|
||||
return percentageValue / 100 * basePrice;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param amap
|
||||
* @param large = false Si true, montre les contrats terminés depuis moins d'un mois
|
||||
* @param lock = false
|
||||
*/
|
||||
public static function getActiveContracts(amap:Amap,?large = false, ?lock = false) {
|
||||
var now = Date.now();
|
||||
var end = Date.now();
|
||||
|
||||
if (large) {
|
||||
end = DateTools.delta(end , -1000.0 * 60 * 60 * 24 * 30);
|
||||
return db.Contract.manager.search($amap == amap && $endDate > end,{orderBy:-vendorId}, lock);
|
||||
}else {
|
||||
return db.Contract.manager.search($amap == amap && $endDate > now && $startDate < now,{orderBy:-vendorId}, lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get products in this contract
|
||||
* @param onlyActive = true
|
||||
* @return
|
||||
*/
|
||||
public function getProducts(?onlyActive = true):List<Product> {
|
||||
if (onlyActive) {
|
||||
return Product.manager.search($contract==this && $active==true,{orderBy:name},false);
|
||||
}else {
|
||||
return Product.manager.search($contract==this,{orderBy:name},false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a few products to display
|
||||
* @param limit = 6
|
||||
*/
|
||||
public function getProductsPreview(?limit = 6){
|
||||
return Product.manager.search($contract==this && $active==true,{limit:limit,orderBy:-id},false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get users who have orders in this contract ( including user2 )
|
||||
* @return Array<db.User>
|
||||
*/
|
||||
public function getUsers():Array<db.User> {
|
||||
var pids = getProducts().map(function(x) return x.id);
|
||||
var ucs = UserContract.manager.search($productId in pids, false);
|
||||
var ucs2 = [];
|
||||
for( uc in ucs) {
|
||||
ucs2.push(uc.user);
|
||||
if(uc.user2!=null) ucs2.push(uc.user2);
|
||||
}
|
||||
|
||||
//comme un user peut avoir plusieurs produits au sein d'un contrat, il faut dédupliquer cette liste
|
||||
var out = new Map<Int,db.User>();
|
||||
for (u in ucs2) {
|
||||
out.set(u.id, u);
|
||||
}
|
||||
|
||||
return Lambda.array(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all orders of this contract
|
||||
* @param d A delivery is needed for varying orders contract
|
||||
* @return
|
||||
*/
|
||||
public function getOrders(?d:db.Distribution):Array<db.UserContract> {
|
||||
if (type == TYPE_VARORDER && d == null) throw "This type of contract must have a delivery";
|
||||
|
||||
//get product ids, some of the products may have been disabled but we keep the order
|
||||
var pids = getProducts(false).map(function(x) return x.id);
|
||||
var ucs = new List<db.UserContract>();
|
||||
if (type == TYPE_VARORDER) {
|
||||
ucs = UserContract.manager.search( ($productId in pids) && $distribution==d,{orderBy:userId}, false);
|
||||
}else {
|
||||
ucs = UserContract.manager.search( ($productId in pids) ,{orderBy:userId}, false);
|
||||
}
|
||||
return Lambda.array(ucs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get orders for a user.
|
||||
*
|
||||
* @param d
|
||||
* @return
|
||||
*/
|
||||
public function getUserOrders(u:db.User,?d:db.Distribution):Array<db.UserContract> {
|
||||
if (type == TYPE_VARORDER && d == null) throw "This type of contract must have a delivery";
|
||||
|
||||
var pids = getProducts(false).map(function(x) return x.id);
|
||||
var ucs = new List<db.UserContract>();
|
||||
if (d != null && d.contract.type==db.Contract.TYPE_VARORDER) {
|
||||
ucs = UserContract.manager.search( ($productId in pids) && $distribution==d && ($user==u || $user2==u ), false);
|
||||
}else {
|
||||
ucs = UserContract.manager.search( ($productId in pids) && ($user==u || $user2==u ),false);
|
||||
}
|
||||
return Lambda.array(ucs);
|
||||
}
|
||||
|
||||
public function getDistribs(excludeOld = true,?limit=999):List<Distribution> {
|
||||
if (excludeOld) {
|
||||
//still include deliveries which just expired in last 24h
|
||||
return Distribution.manager.search($end > DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24) && $contract == this, { orderBy:date,limit:limit } );
|
||||
}else{
|
||||
return Distribution.manager.search( $contract == this, { orderBy:date,limit:limit } );
|
||||
}
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
return name+" du "+this.startDate.toString().substr(0,10)+" au "+this.endDate.toString().substr(0,10);
|
||||
}
|
||||
|
||||
public function populate() {
|
||||
return App.current.user.amap.getMembersFormElementData();
|
||||
}
|
||||
|
||||
/**
|
||||
* get a vendor list as form data
|
||||
* @return
|
||||
*/
|
||||
public function populateVendor():FormData<Int>{
|
||||
var vendors = Vendor.manager.search($amap == App.current.user.amap, false);
|
||||
var out = [];
|
||||
for (v in vendors) {
|
||||
out.push({label:v.name, value:v.id });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Contract name"),
|
||||
"startDate" => t._("Start date"),
|
||||
"endDate" => t._("End date"),
|
||||
"description" => t._("Description"),
|
||||
"distributorNum" => t._("Number of required distributors (0 to 4)"),
|
||||
"flags" => t._("Options"),
|
||||
"percentageValue" => t._("Fees percentage"),
|
||||
"percentageName" => t._("Fees label"),
|
||||
"contact" => t._("Contact"),
|
||||
"vendor" => t._("Farmer"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
package db;
|
||||
import sugoi.form.ListData;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
/**
|
||||
* Distrib
|
||||
*/
|
||||
class Distribution extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
|
||||
@:relation(contractId)
|
||||
public var contract : Contract;
|
||||
|
||||
@formPopulate("placePopulate")
|
||||
@:relation(placeId)
|
||||
public var place : Place;
|
||||
|
||||
//when orders are open
|
||||
@hideInForms public var orderStartDate : SNull<SDateTime>;
|
||||
@hideInForms public var orderEndDate : SNull<SDateTime>;
|
||||
|
||||
//start and end date for delivery
|
||||
public var date : SDateTime;
|
||||
public var end : SDateTime;
|
||||
|
||||
@:relation(distributionCycleId) public var distributionCycle : SNull<DistributionCycle>;
|
||||
|
||||
@formPopulate("populate") @:relation(distributor1Id) public var distributor1 : SNull<db.User>;
|
||||
@formPopulate("populate") @:relation(distributor2Id) public var distributor2 : SNull<db.User>;
|
||||
@formPopulate("populate") @:relation(distributor3Id) public var distributor3 : SNull<db.User>;
|
||||
@formPopulate("populate") @:relation(distributor4Id) public var distributor4 : SNull<db.User>;
|
||||
|
||||
@hideInForms public var validated :SBool;
|
||||
|
||||
public static var DISTRIBUTION_VALIDATION_LIMIT = 10;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
date = Date.now();
|
||||
end = DateTools.delta(date, 1000 * 60 * 90);
|
||||
validated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get group members list as form data
|
||||
*/
|
||||
public function populate():FormData<Int> {
|
||||
if(App.current.user!=null && App.current.user.getAmap()!=null){
|
||||
return App.current.user.getAmap().getMembersFormElementData();
|
||||
}else{
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* get groups places as form data
|
||||
* @return
|
||||
*/
|
||||
public function placePopulate():FormData<Int> {
|
||||
var out = [];
|
||||
var places = new List();
|
||||
if(this.contract!=null){
|
||||
//edit form
|
||||
places = db.Place.manager.search($amapId == this.contract.amap.id, false);
|
||||
}else{
|
||||
//insert form
|
||||
places = db.Place.manager.search($amapId == App.current.user.amap.id, false);
|
||||
}
|
||||
|
||||
for (p in places) out.push( { label:p.name,value:p.id} );
|
||||
return out;
|
||||
}
|
||||
|
||||
public function hasEnoughDistributors() {
|
||||
var n = contract.distributorNum;
|
||||
|
||||
var d = 0;
|
||||
if (distributor1 != null) d++;
|
||||
if (distributor2 != null) d++;
|
||||
if (distributor3 != null) d++;
|
||||
if (distributor4 != null) d++;
|
||||
|
||||
return (d >= n) ;
|
||||
}
|
||||
|
||||
public function isDistributor(u:User) {
|
||||
if (u == null) return false;
|
||||
return (distributor1!=null && u.id == distributor1.id) ||
|
||||
(distributor2!=null && u.id == distributor2.id) ||
|
||||
(distributor3!=null && u.id == distributor3.id) ||
|
||||
(distributor4!=null && u.id == distributor4.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* String to identify this distribution (debug use only)
|
||||
*/
|
||||
override public function toString() {
|
||||
return "#" + id + " Delivery " + date.toString() + " of " + contract.name;
|
||||
}
|
||||
|
||||
public function getOrders() {
|
||||
|
||||
if ( this.contract.type == Contract.TYPE_CONSTORDERS){
|
||||
var pids = db.Product.manager.search($contract == this.contract, false);
|
||||
var pids = Lambda.map(pids, function(x) return x.id);
|
||||
return UserContract.manager.search( ($productId in pids), false);
|
||||
}else{
|
||||
return UserContract.manager.search($distribution == this, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserOrders(user:db.User){
|
||||
if ( this.contract.type == Contract.TYPE_CONSTORDERS){
|
||||
var pids = db.Product.manager.search($contract == this.contract, false);
|
||||
var pids = Lambda.map(pids, function(x) return x.id);
|
||||
return UserContract.manager.search( (($productId in pids) && ($user==user || $user2==user) ), false);
|
||||
}else{
|
||||
return UserContract.manager.search($distribution == this && $user==user, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getUsers():Iterable<db.User>{
|
||||
|
||||
return tools.ObjectListTool.deduplicate( Lambda.map(getOrders(), function(x) return x.user ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get TTC turnover for this distribution
|
||||
*/
|
||||
public function getTurnOver(){
|
||||
|
||||
var sql = "select SUM(quantity * productPrice) from UserContract where productId IN (" + tools.ObjectListTool.getIds(contract.getProducts()).join(",") +") ";
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
sql += " and distributionId=" + this.id;
|
||||
}
|
||||
|
||||
return sys.db.Manager.cnx.request(sql).getFloatResult(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HT turnover for this distribution
|
||||
*/
|
||||
public function getHTTurnOver(){
|
||||
|
||||
var pids = tools.ObjectListTool.getIds(contract.getProducts(false));
|
||||
|
||||
var sql = "select SUM(uc.quantity * (p.price/(1+p.vat/100)) ) from UserContract uc, Product p ";
|
||||
sql += "where uc.productId IN (" + pids.join(",") +") ";
|
||||
sql += "and p.id=uc.productId ";
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
sql += " and uc.distributionId=" + this.id;
|
||||
}
|
||||
|
||||
return sys.db.Manager.cnx.request(sql).getFloatResult(0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function canOrderNow() {
|
||||
|
||||
if (orderEndDate == null) {
|
||||
return this.contract.isUserOrderAvailable();
|
||||
}else {
|
||||
var n = Date.now().getTime();
|
||||
var f = this.contract.flags.has(UsersCanOrder);
|
||||
|
||||
return f && n < orderEndDate.getTime() && n > orderStartDate.getTime();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next multi-devliveries
|
||||
* ( deliveries including more than one vendors )
|
||||
*/
|
||||
/*public static function getNextMultiDeliveries(){
|
||||
|
||||
var out = new Map<String,{place:Place,startDate:Date,endDate:Date,active:Bool,products:Array<ProductInfo>}>();
|
||||
return Lambda.array(manager.search($orderStartDate <= Date.now() && $orderEndDate >= Date.now() && $contract==contract,false));
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
var contracts = Contract.getActiveContracts(App.current.user.amap);
|
||||
var cids = Lambda.map(contracts, function(p) return p.id);
|
||||
|
||||
//available deliveries + some of the next deliveries
|
||||
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && $orderEndDate >= now, { orderBy:date }, false);
|
||||
var inOneMonth = DateTools.delta(now, 1000.0 * 60 * 60 * 24 * 30);
|
||||
for (d in distribs) {
|
||||
|
||||
var o = out.get(d.getKey());
|
||||
if (o == null) o = {place:d.place, startDate:d.date,active:null, endDate:d.end, products:[]};
|
||||
for ( p in d.contract.getProductsPreview(8)){
|
||||
if (o.products.length >= 8) break;
|
||||
o.products.push( p.infos() );
|
||||
}
|
||||
|
||||
if (d.orderStartDate.getTime() <= now.getTime() ){
|
||||
//order currently open
|
||||
o.active = true;
|
||||
}else if (d.orderStartDate.getTime() <= inOneMonth.getTime() ){
|
||||
//open soon
|
||||
o.active = false;
|
||||
}else{
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
out.set(d.getKey(), o);
|
||||
}
|
||||
return Lambda.array(out);
|
||||
}*/
|
||||
|
||||
override public function update(){
|
||||
this.end = new Date(this.date.getFullYear(), this.date.getMonth(), this.date.getDate(), this.end.getHours(), this.end.getMinutes(), 0);
|
||||
super.update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get open to orders deliveries
|
||||
* @param contract
|
||||
*/
|
||||
public static function getOpenToOrdersDeliveries(contract:db.Contract){
|
||||
|
||||
return Lambda.array(manager.search($orderStartDate <= Date.now() && $orderEndDate >= Date.now() && $contract==contract,{orderBy:date},false));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a string like $placeId-$date.
|
||||
*
|
||||
* It's an ID representing all the distributions happening on that day at that place.
|
||||
*/
|
||||
public function getKey():String{
|
||||
return db.Distribution.makeKey(this.date, this.place);
|
||||
}
|
||||
|
||||
public static function makeKey(date, place){
|
||||
return date.toString().substr(0, 10) +"|"+Std.string(place.id);
|
||||
}
|
||||
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"date" => t._("Date"),
|
||||
"endDate" => t._("End hour"),
|
||||
"place" => t._("Place"),
|
||||
"distributor1" => t._("Distributor #1"),
|
||||
"distributor2" => t._("Distributor #2"),
|
||||
"distributor3" => t._("Distributor #3"),
|
||||
"distributor4" => t._("Distributor #4"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
using tools.DateTool;
|
||||
|
||||
enum CycleType {
|
||||
Weekly;
|
||||
Monthly;
|
||||
BiWeekly;
|
||||
TriWeekly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribution cycle
|
||||
*/
|
||||
class DistributionCycle extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
@:relation(contractId) public var contract : Contract;
|
||||
public var cycleType:SEnum<CycleType>;
|
||||
public var startDate : SDate; //debut
|
||||
public var endDate : SDate; //fin de la recurrence
|
||||
public var startHour : SDateTime;
|
||||
public var endHour : SDateTime;
|
||||
public var daysBeforeOrderStart:SNull<STinyInt>;
|
||||
public var daysBeforeOrderEnd:SNull<STinyInt>;
|
||||
public var openingHour:SNull<SDate>;
|
||||
public var closingHour:SNull<SDate>;
|
||||
@formPopulate("placePopulate") @:relation(placeId) public var place : Place;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"cycleType" => t._("Fréquence"),
|
||||
"startDate" => t._("Date de début"),
|
||||
"endDate" => t._("Date de fin"),
|
||||
"daysBeforeOrderStart" => t._("Ouverture de commande (nbre de jours avant distribution)"),
|
||||
"daysBeforeOrderEnd" => t._("Fermeture de commande (nbre de jours avant distribution)"),
|
||||
"place" => t._("Place"),
|
||||
];
|
||||
}
|
||||
|
||||
public function placePopulate():Array<{label:String,value:Int}> {
|
||||
var out = [];
|
||||
if ( App.current.user == null || App.current.user.amap == null ) return out;
|
||||
var places = db.Place.manager.search($amapId == App.current.user.amap.id, false);
|
||||
for (p in places) out.push( { label:p.name,value :p.id } );
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
|
||||
/**
|
||||
* Tool to Graph daily values
|
||||
*/
|
||||
@:id(key,date)
|
||||
class Graph extends Object{
|
||||
|
||||
public var key:SString<128>;
|
||||
public var date:SDate;
|
||||
public var value:SFloat;
|
||||
|
||||
/**
|
||||
* record a value
|
||||
*/
|
||||
public static function record(key:String,value:Float,?date:Date){
|
||||
if(date==null) date = Date.now();
|
||||
date = new Date(date.getFullYear(),date.getMonth(),date.getDate(),0,0,0);
|
||||
var o = manager.select($key==key && $date==date,true);
|
||||
if(o == null){
|
||||
o = new Graph();
|
||||
o.date = date;
|
||||
o.key = key;
|
||||
o.value = value;
|
||||
o.insert();
|
||||
}else{
|
||||
o.value = value;
|
||||
o.update();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of records in a time frame
|
||||
*/
|
||||
public static function getRange(key:String,from:Date,to:Date):Array<db.Graph>{
|
||||
return Lambda.array(manager.search($key==key && $date>=from && $date<=to, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
@:id(userId,amapId,year)
|
||||
class Membership extends Object
|
||||
{
|
||||
@:relation(amapId)
|
||||
public var amap : Amap;
|
||||
|
||||
@:relation(userId)
|
||||
public var user : db.User;
|
||||
|
||||
//année de cotisation (année la plus ancienne si a cheval sur deux années : 2014-2015 -> 2014)
|
||||
public var year : Int;
|
||||
public var date : SNull<SDate>;
|
||||
|
||||
public static function get(user:User, amap:Amap,year:Int, ?lock = false) {
|
||||
return manager.select($user == user && $amap == amap && $year == year, lock);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import tink.core.Noise;
|
||||
import tink.core.Outcome;
|
||||
import sugoi.mail.IMailer;
|
||||
|
||||
|
||||
/**
|
||||
* Message sent from the message Section
|
||||
*/
|
||||
class Message extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
@:relation(amapId) public var amap : SNull<Amap>;
|
||||
@:relation(senderId) public var sender : SNull<User>;
|
||||
|
||||
public var recipientListId : SNull<SString<12>>;
|
||||
public var recipients : SNull<SData<Array<String>>>;
|
||||
|
||||
public var title : SString<128>;
|
||||
public var body : SText;
|
||||
public var date : SDateTime;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
import tink.core.Error;
|
||||
|
||||
enum OperationType{
|
||||
VOrder; //order on a varying order contract
|
||||
COrder;//order on a constant order contract
|
||||
Payment;
|
||||
Membership;
|
||||
}
|
||||
|
||||
typedef PaymentInfos = {type:String, ?remoteOpId:Int, ?netAmount:Float};
|
||||
typedef VOrderInfos = {basketId:Int};
|
||||
typedef COrderInfos = {contractId:Int};
|
||||
|
||||
|
||||
/**
|
||||
* Payment operation
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Operation extends sys.db.Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
public var amount : SFloat;
|
||||
public var date : SDateTime;
|
||||
public var type : SEnum<OperationType>;
|
||||
public var data : SData<Dynamic>;
|
||||
@hideInForms @:relation(relationId) public var relation : SNull<db.Operation>; //linked to another operation : ie a payment pays an order
|
||||
|
||||
@formPopulate("populate") @:relation(userId) public var user : db.User;
|
||||
@hideInForms @:relation(groupId) public var group : db.Amap;
|
||||
|
||||
public var pending : SBool; //a pending payment means the payment has not been confirmed, a pending order means the ordre can still change before closing.
|
||||
|
||||
public function getTypeIndex(){
|
||||
var e : OperationType = type;
|
||||
return e.getIndex();
|
||||
}
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
pending = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* if operation is a payment, give the payment type
|
||||
*/
|
||||
public function getPaymentType():String{
|
||||
switch(type){
|
||||
case Payment:
|
||||
var x : PaymentInfos = this.data;
|
||||
if (data == null){
|
||||
return null;
|
||||
}else{
|
||||
return x.type;
|
||||
}
|
||||
default : return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get translated payment type name
|
||||
*/
|
||||
public function getPaymentTypeName(){
|
||||
var t = getPaymentType();
|
||||
if (t == null) return null;
|
||||
for ( pt in service.PaymentService.getAllPaymentTypes()){
|
||||
if (pt.type == t) return pt.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get payments linked to this order transaction
|
||||
*/
|
||||
public function getRelatedPayments(){
|
||||
return db.Operation.manager.search($relation == this, false);
|
||||
}
|
||||
|
||||
public function getOrderInfos(){
|
||||
return switch(type){
|
||||
case COrder, VOrder : this.data;
|
||||
default : null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPaymentInfos():PaymentInfos{
|
||||
return switch(type){
|
||||
case Payment : this.data;
|
||||
default : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function countOperations(user:db.User, group:db.Amap):Int{
|
||||
return manager.count($user == user && $group == group);
|
||||
}
|
||||
|
||||
/**
|
||||
* get all user operations
|
||||
* @param user -
|
||||
* @param group -
|
||||
* @param reverse=false -
|
||||
*/
|
||||
public static function getOperations(user:db.User, group:db.Amap,?reverse=false ){
|
||||
if(reverse) {
|
||||
return manager.search($user == user && $group == group,{orderBy:-date},false);
|
||||
}
|
||||
return manager.search($user == user && $group == group,{orderBy:date},false);
|
||||
}
|
||||
|
||||
public static function getOperationsWithIndex(user:db.User, group:db.Amap,index:Int,limit:Int,?reverse=false ){
|
||||
if(reverse) {
|
||||
return manager.search($user == user && $group == group, { limit:[index,limit], orderBy:-date }, false);
|
||||
}
|
||||
return manager.search($user == user && $group == group, { limit:[index,limit], orderBy:date },false);
|
||||
}
|
||||
|
||||
/*public static function getOrder_Operations(user:db.User, group:db.Amap,?limit=50 ){
|
||||
//return manager.search($user == user && $group == group && $type!=Payment,{orderBy:date},false);
|
||||
//return manager.search($user == user && $group == group && $relation==null,{orderBy:date},false);
|
||||
return manager.search($user == user && $group == group,{orderBy:date,limit:limit},false);
|
||||
}*/
|
||||
|
||||
public static function getPaymentOperations(user:db.User, group:db.Amap,?limit=50){
|
||||
return manager.search($user == user && $group == group && $type == Payment, {orderBy:date,limit:limit},false);
|
||||
}
|
||||
|
||||
public static function getLastOperations(user:db.User, group:db.Amap, ?limit = 50){
|
||||
|
||||
var c = manager.count($user == user && $group == group);
|
||||
c -= limit;
|
||||
if (c < 0) c = 0;
|
||||
return manager.search($user == user && $group == group,{orderBy:date,limit:[c,limit]},false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new transaction
|
||||
* @param orders
|
||||
*/
|
||||
public static function makeOrderOperation(orders: Array<db.UserContract>, ?basket:db.Basket){
|
||||
|
||||
if (orders == null) throw "orders are null";
|
||||
if (orders.length == 0) throw "no orders";
|
||||
if (orders[0].user == null ) throw "no user in order";
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var _amount = 0.0;
|
||||
for ( o in orders ){
|
||||
var t = o.quantity * o.productPrice;
|
||||
_amount += t + t * (o.feesRate / 100);
|
||||
}
|
||||
|
||||
var contract = orders[0].product.contract;
|
||||
|
||||
var op = new db.Operation();
|
||||
var user = orders[0].user;
|
||||
var group = orders[0].product.contract.amap;
|
||||
|
||||
if (contract.type == db.Contract.TYPE_CONSTORDERS){
|
||||
//Constant orders
|
||||
var dNum = contract.getDistribs(false).length;
|
||||
op.name = "" + contract.name + " (" + contract.vendor.name+") " + dNum + " " + t._("deliveries");
|
||||
op.amount = dNum * (0 - _amount);
|
||||
op.date = Date.now();
|
||||
op.type = COrder;
|
||||
var data : COrderInfos = {contractId:contract.id};
|
||||
op.data = data;
|
||||
op.user = user;
|
||||
op.group = group;
|
||||
op.pending = true;
|
||||
|
||||
}else{
|
||||
|
||||
if (basket == null) throw "varying contract orders should have a basket";
|
||||
|
||||
//varying orders
|
||||
var date = App.current.view.dDate(orders[0].distribution.date);
|
||||
op.name = t._("Order for ::date::",{date:date});
|
||||
op.amount = 0 - _amount;
|
||||
op.date = Date.now();
|
||||
op.type = VOrder;
|
||||
var data : VOrderInfos = {basketId:basket.id};
|
||||
op.data = data;
|
||||
op.user = user;
|
||||
op.group = group;
|
||||
op.pending = true;
|
||||
}
|
||||
|
||||
op.insert();
|
||||
|
||||
service.PaymentService.updateUserBalance(op.user, op.group);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* update an order operation
|
||||
*/
|
||||
public static function updateOrderOperation(op:db.Operation, orders: Array<db.UserContract>, ?basket:db.Basket){
|
||||
|
||||
op.lock();
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var _amount = 0.0;
|
||||
for ( o in orders ){
|
||||
var a = o.quantity * o.productPrice;
|
||||
_amount += a + a * (o.feesRate / 100);
|
||||
}
|
||||
|
||||
var contract = orders[0].product.contract;
|
||||
if (contract.type == db.Contract.TYPE_CONSTORDERS){
|
||||
//Constant orders
|
||||
var dNum = contract.getDistribs(false).length;
|
||||
op.name = "" + contract.name + " (" + contract.vendor.name+") "+ dNum + " " + t._("deliveries");
|
||||
op.amount = dNum * (0 - _amount);
|
||||
}else{
|
||||
|
||||
if (basket == null) throw "varying contract orders should have a basket";
|
||||
op.amount = 0 - _amount;
|
||||
}
|
||||
|
||||
//op.date = Date.now(); //leave original date
|
||||
op.update();
|
||||
service.PaymentService.updateUserBalance(op.user, op.group);
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a new payment operation
|
||||
* @param type
|
||||
* @param amount
|
||||
* @param name
|
||||
* @param relation
|
||||
*/
|
||||
public static function makePaymentOperation(user:db.User,group:db.Amap,type:String, amount:Float, name:String, ?relation:db.Operation ){
|
||||
|
||||
var t = new db.Operation();
|
||||
t.amount = Math.abs(amount);
|
||||
t.date = Date.now();
|
||||
t.name = name;
|
||||
t.group = group;
|
||||
t.pending = true;
|
||||
t.user = user;
|
||||
t.type = Payment;
|
||||
var data : PaymentInfos = {type:type};
|
||||
t.data = data;
|
||||
if(relation!=null) t.relation = relation;
|
||||
t.insert();
|
||||
|
||||
service.PaymentService.updateUserBalance(user, group);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* when updating a (varying) order , we need to update the existing pending transaction
|
||||
*/
|
||||
public static function findVOrderTransactionFor(dkey:String, user:db.User, group:db.Amap,?onlyPending=true):db.Operation{
|
||||
|
||||
//throw 'find $dkey for user ${user.id} in group ${group.id} , onlyPending:$onlyPending';
|
||||
|
||||
var date = dkey.split("|")[0];
|
||||
var placeId = Std.parseInt(dkey.split("|")[1]);
|
||||
var transactions = new List();
|
||||
if (onlyPending){
|
||||
transactions = manager.search($user == user && $group == group && $pending == true && $type==VOrder , {orderBy:-date}, true);
|
||||
}else{
|
||||
transactions = manager.search($user == user && $group == group && $type==VOrder , {orderBy:-date}, true);
|
||||
}
|
||||
|
||||
//throw transactions;
|
||||
|
||||
var place = db.Place.manager.get(placeId,false);
|
||||
var date = Date.fromString(date);
|
||||
var basket = db.Basket.get(user, place, date);
|
||||
if(basket==null) throw new Error('No basket found for user #'+user.id+', place #'+place.id+', date '+date);
|
||||
|
||||
for ( t in transactions){
|
||||
switch(t.type){
|
||||
case VOrder :
|
||||
var data : VOrderInfos = t.data;
|
||||
if ( data == null) continue;
|
||||
if (data.basketId == basket.id) return t;
|
||||
default :
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* when updating a constant order, we need to update the existing operation.
|
||||
*/
|
||||
public static function findCOrderTransactionFor(contract:db.Contract, user:db.User):db.Operation{
|
||||
|
||||
if (contract.type != db.Contract.TYPE_CONSTORDERS) throw "contract type should be TYPE_CONSTORDERS";
|
||||
|
||||
var transactions = manager.search($user == user && $group == contract.amap && $amount<=0 && $type==COrder, {orderBy:date,limit:100}, true);
|
||||
|
||||
for ( t in transactions){
|
||||
|
||||
switch(t.type){
|
||||
|
||||
case COrder :
|
||||
|
||||
//var id = Lambda.find(orders, function(x) return db.UserContract.manager.get(x, false) != null);
|
||||
//if (id == null) {
|
||||
////all orders in this transaction dont exists anymore
|
||||
//t.delete();
|
||||
//continue;
|
||||
//}else{
|
||||
//for ( i in orders){
|
||||
//var order = db.UserContract.manager.get(i);
|
||||
//if (order == null) continue;
|
||||
//if (order.product.contract.id == contract.id) return t;
|
||||
//}
|
||||
//}
|
||||
var data : COrderInfos = t.data;
|
||||
if (data == null) continue;
|
||||
if (data.contractId == contract.id) return t;
|
||||
|
||||
|
||||
default :
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/update the needed order operations and returns the related operations
|
||||
* @param orders
|
||||
*/
|
||||
public static function onOrderConfirm(orders:Array<db.UserContract>):Array<db.Operation>{
|
||||
|
||||
if (orders.length == 0) return null;
|
||||
if (orders[0] == null) return null;
|
||||
|
||||
var out = [];
|
||||
var user = orders[0].user;
|
||||
var group = orders[0].product.contract.amap;
|
||||
|
||||
//should not go further if group has not activated payements
|
||||
if (user==null || !group.hasPayments()) return null;
|
||||
|
||||
//we consider that ALL orders are from the same contract type : varying or constant
|
||||
if (orders[0].product.contract.type == db.Contract.TYPE_VARORDER ){
|
||||
|
||||
// varying contract :
|
||||
//manage separatly orders which occur at different dates
|
||||
var ordersGroup = tools.ObjectListTool.groupOrdersByKey(orders);
|
||||
|
||||
for ( orders in ordersGroup){
|
||||
|
||||
//find basket
|
||||
var basket = null;
|
||||
for ( o in orders) {
|
||||
if (o.basket != null) {
|
||||
basket = o.basket;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//get all orders for the same multidistrib, in order to update related operation.
|
||||
var k = orders[0].distribution.getKey();
|
||||
var allOrders = db.UserContract.getUserOrdersByMultiDistrib(k, user, group);
|
||||
|
||||
//existing transaction
|
||||
var existing = db.Operation.findVOrderTransactionFor( k , user, group, false);
|
||||
|
||||
var op;
|
||||
if (existing != null){
|
||||
op = db.Operation.updateOrderOperation(existing,allOrders,basket);
|
||||
}else{
|
||||
op = db.Operation.makeOrderOperation(allOrders,basket);
|
||||
}
|
||||
out.push(op);
|
||||
|
||||
//delete order and payment operations if sum of orders qt is 0
|
||||
/*var sum = 0.0;
|
||||
for ( o in allOrders) sum += o.quantity;
|
||||
if ( sum == 0 ) {
|
||||
existing.delete();
|
||||
op.delete();
|
||||
}*/
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
// constant contract
|
||||
// create/update a transaction computed like $distribNumber * $price.
|
||||
var contract = orders[0].product.contract;
|
||||
|
||||
var existing = db.Operation.findCOrderTransactionFor( contract , user);
|
||||
if (existing != null){
|
||||
out.push( db.Operation.updateOrderOperation(existing, contract.getUserOrders(user) ) );
|
||||
}else{
|
||||
out.push( db.Operation.makeOrderOperation( contract.getUserOrders(user) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
public function populate(){
|
||||
return App.current.user.getAmap().getMembersFormElementData();
|
||||
}
|
||||
|
||||
/*public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Text"),
|
||||
"date" => t._("Date"),
|
||||
"endDate" => t._("End date"),
|
||||
"place" => t._("Place"),
|
||||
"distributor1" => t._("Distributor #1"),
|
||||
"distributor2" => t._("Distributor #2"),
|
||||
"distributor3" => t._("Distributor #3"),
|
||||
"distributor4" => t._("Distributor #4"),
|
||||
];
|
||||
}*/
|
||||
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
class Place extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<64>;
|
||||
public var address1:SNull<SString<64>>;
|
||||
public var address2:SNull<SString<64>>;
|
||||
public var zipCode:SString<32>;
|
||||
public var city:SString<64>;
|
||||
public var country:SNull<SString<64>>;
|
||||
|
||||
//latitude/longitude
|
||||
public var lat:SNull<SFloat>;
|
||||
public var lng:SNull<SFloat>;
|
||||
|
||||
@hideInForms @:relation(amapId) public var amap : Amap;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
country = "France";
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
if (name == null) {
|
||||
return "place";
|
||||
}else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public function getFullAddress(){
|
||||
var str = new StringBuf();
|
||||
str.add(name+", \n");
|
||||
if (address1 != null) str.add(address1 + ", \n");
|
||||
if (address2 != null) str.add(address2 + ", \n");
|
||||
if (zipCode != null) str.add(zipCode);
|
||||
if (city != null) str.add(" - "+city);
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* get adress without 'name' field.
|
||||
*/
|
||||
public function getAddress(){
|
||||
var str = new StringBuf();
|
||||
if (address1 != null) str.add(address1 + ", \n");
|
||||
if (address2 != null) str.add(address2 + ", \n");
|
||||
if (zipCode != null) str.add(zipCode);
|
||||
if (city != null) str.add(" - "+city);
|
||||
if(country != null) str.add(", "+country);
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static function getLabels():Map<String,String>{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Name"),
|
||||
"address1" => t._("Address 1"),
|
||||
"address2" => t._("Address 2"),
|
||||
"zipCode" => t._("Zip code"),
|
||||
"city" => t._("City"),
|
||||
"country" => t._("Country"),
|
||||
"lat" => t._("Latitude"),
|
||||
"lng" => t._("Longitude"),
|
||||
];
|
||||
}
|
||||
|
||||
public function getInfos():PlaceInfos{
|
||||
return {
|
||||
id:id,
|
||||
name:name,
|
||||
address1:address1,
|
||||
address2:address2,
|
||||
zipCode:zipCode,
|
||||
city:city,
|
||||
latitude : lat,
|
||||
longitude: lng
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+206
@@ -0,0 +1,206 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Product
|
||||
*/
|
||||
class Product extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
public var ref : SNull<SString<32>>; //référence produit
|
||||
|
||||
@:relation(contractId)
|
||||
public var contract : Contract;
|
||||
|
||||
//prix TTC
|
||||
public var price : SFloat;
|
||||
public var vat : SFloat;
|
||||
|
||||
public var desc : SNull<SText>;
|
||||
public var stock : SNull<SFloat>; //if qantity can be float, stock should be float
|
||||
|
||||
public var unitType : SNull<SEnum<Unit>>; // Kg / L / g / units
|
||||
public var qt : SNull<SFloat>;
|
||||
|
||||
public var organic : SBool;
|
||||
public var variablePrice : Bool; //price can vary depending on weighting of the product
|
||||
public var multiWeight : Bool; //product cannot be cumulated in one order record
|
||||
|
||||
//https://docs.google.com/document/d/1IqHN8THT6zbKrLdHDClKZLWgKWeL0xw6cYOiFofw04I/edit
|
||||
@hideInForms public var wholesale : Bool; //this product is a wholesale product (crate,bag,pallet)
|
||||
@hideInForms public var retail : Bool; //this products is a fraction of a wholesale product
|
||||
@hideInForms public var bulk : Bool; //(vrac) warn the customer this product is not packaged
|
||||
public var hasFloatQt:SBool; //this product can be ordered in "float" quantity
|
||||
|
||||
@hideInForms @:relation(imageId) public var image : SNull<sugoi.db.File>;
|
||||
@:relation(txpProductId) public var txpProduct : SNull<db.TxpProduct>; //taxonomy
|
||||
|
||||
|
||||
public var active : SBool; //if false, product disabled, not visible on front office
|
||||
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
//type = 0;
|
||||
organic = false;
|
||||
hasFloatQt = false;
|
||||
active = true;
|
||||
variablePrice = false;
|
||||
multiWeight = false;
|
||||
wholesale = false;
|
||||
retail = false;
|
||||
bulk = false;
|
||||
vat = 5.5;
|
||||
unitType = Unit.Piece;
|
||||
qt = 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product image URL
|
||||
*/
|
||||
public function getImage() {
|
||||
if (image == null) {
|
||||
if (txpProduct != null){
|
||||
return "/img/taxo/cat" + txpProduct.category.id + ".png";
|
||||
}else{
|
||||
return "/img/unknown.png";
|
||||
}
|
||||
}else {
|
||||
return App.current.view.file(image);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
||||
if (unitType != null && qt != null && qt != 0){
|
||||
return name +" " + qt + " " + App.current.view.unit(unitType);
|
||||
}else{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* get price including margins
|
||||
*/
|
||||
public function getPrice():Float{
|
||||
return price + contract.computeFees(price);
|
||||
}
|
||||
|
||||
/**
|
||||
get product infos as an anonymous object
|
||||
@param CategFromTaxo=false
|
||||
@param populateCategories=tru
|
||||
@return
|
||||
**/
|
||||
public function infos(?CategFromTaxo=false,?populateCategories=true,?distribution:db.Distribution):ProductInfo {
|
||||
var o :ProductInfo = {
|
||||
id : id,
|
||||
ref : ref,
|
||||
name : name,
|
||||
image : getImage(),
|
||||
contractId : contract.id,
|
||||
price : getPrice(),
|
||||
vat : vat,
|
||||
vatValue: (vat != 0 && vat != null) ? ( this.price - (this.price / (vat/100+1)) ) : null,
|
||||
contractTax : contract.percentageValue,
|
||||
contractTaxName : contract.percentageName,
|
||||
desc : App.current.view.nl2br(desc),
|
||||
categories : null,
|
||||
subcategories:null,
|
||||
orderable : this.contract.isUserOrderAvailable(),
|
||||
stock : contract.hasStockManagement() ? this.stock : null,
|
||||
hasFloatQt : hasFloatQt,
|
||||
qt:qt,
|
||||
unitType:unitType,
|
||||
organic:organic,
|
||||
variablePrice:variablePrice,
|
||||
wholesale:wholesale,
|
||||
active: active,
|
||||
distributionId : distribution==null ? null : distribution.id,
|
||||
}
|
||||
|
||||
if(populateCategories){
|
||||
if (CategFromTaxo){
|
||||
o.categories = [txpProduct == null?null:txpProduct.category.id];
|
||||
o.subcategories = [txpProduct == null?null:txpProduct.subCategory.id];
|
||||
}else{
|
||||
o.categories = Lambda.array(Lambda.map(getCategories(), function(c) return c.id));
|
||||
o.subcategories = o.categories;
|
||||
}
|
||||
}
|
||||
|
||||
App.current.event(ProductInfosEvent(o,distribution));
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* customs categs
|
||||
*/
|
||||
public function getCategories() {
|
||||
//"Types de produits" categGroup first
|
||||
//var pc = db.ProductCategory.manager.search($productId == id, {orderBy:categoryId}, false);
|
||||
return Lambda.map(db.ProductCategory.manager.search($productId == id,{orderBy:categoryId},false), function(x) return x.category);
|
||||
}
|
||||
|
||||
/**
|
||||
* general categs
|
||||
*/
|
||||
public function getFullCategorization(){
|
||||
if (txpProduct == null) return [];
|
||||
return txpProduct.getFullCategorization();
|
||||
}
|
||||
|
||||
public static function getByRef(c:db.Contract, ref:String){
|
||||
var pids = tools.ObjectListTool.getIds(c.getProducts(false));
|
||||
return db.Product.manager.select($ref == ref && $id in pids, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix values that will make mysql 5.7 scream
|
||||
*/
|
||||
function check(){
|
||||
if(this.vat==null) this.vat=0;
|
||||
if(this.name.length>128) this.name = this.name.substr(0,128);
|
||||
}
|
||||
|
||||
override public function update(){
|
||||
check();
|
||||
super.update();
|
||||
}
|
||||
|
||||
override public function insert(){
|
||||
check();
|
||||
super.insert();
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Product name"),
|
||||
"ref" => t._("Product ID"),
|
||||
"price" => t._("Price"),
|
||||
"desc" => t._("Description"),
|
||||
"stock" => t._("Stock"),
|
||||
"unitType" => t._("Base unit"),
|
||||
"qt" => t._("Quantity"),
|
||||
"hasFloatQt" => t._("Allow fractional quantities"),
|
||||
"active" => t._("Available"),
|
||||
"organic" => t._("Organic agriculture"),
|
||||
"vat" => t._("VAT Rate"),
|
||||
"variablePrice" => t._("Variable price based on weight"),
|
||||
"multiWeight" => t._("Multi-weighing"),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
|
||||
@:id(productId,categoryId)
|
||||
class ProductCategory extends sys.db.Object
|
||||
{
|
||||
|
||||
@:relation(productId)
|
||||
public var product : db.Product;
|
||||
|
||||
@:relation(categoryId)
|
||||
public var category : db.Category;
|
||||
|
||||
public static function getOrCreate(product, category){
|
||||
|
||||
var x = db.ProductCategory.manager.select($product==product && $category==category,true);
|
||||
if(x==null){
|
||||
x = new db.ProductCategory();
|
||||
x.product = product;
|
||||
x.category = category;
|
||||
x.insert();
|
||||
}
|
||||
return x;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TxpCategory extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
|
||||
public function getSubCategories(){
|
||||
|
||||
return db.TxpSubCategory.manager.search($category == this, false);
|
||||
|
||||
}
|
||||
|
||||
override public function toString(){
|
||||
return '#$id-$name';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TxpProduct extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
@:relation(categoryId) public var category : db.TxpCategory;
|
||||
@:relation(subCategoryId) public var subCategory : db.TxpSubCategory;
|
||||
|
||||
override public function toString(){
|
||||
return '#$id-$name';
|
||||
}
|
||||
|
||||
public function getFullCategorization():Array<String>{
|
||||
return [category.name, subCategory.name, name];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TxpSubCategory extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
@:relation(categoryId) public var category:db.TxpCategory;
|
||||
|
||||
|
||||
public function getProducts(){
|
||||
|
||||
return db.TxpProduct.manager.search($subCategory == this, false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
override public function toString(){
|
||||
return '#$id-$name';
|
||||
}
|
||||
}
|
||||
Executable
+574
@@ -0,0 +1,574 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import db.UserAmap;
|
||||
import Common;
|
||||
|
||||
enum UserFlags {
|
||||
HasEmailNotif4h; //send notifications by mail 4h before
|
||||
HasEmailNotif24h; //send notifications by mail 24h before
|
||||
HasEmailNotifOuverture; //send notifications by mail on command open
|
||||
//Tuto; //enable tutorials
|
||||
}
|
||||
|
||||
/**
|
||||
* Site-wide right
|
||||
*/
|
||||
enum RightSite {
|
||||
Admin;
|
||||
}
|
||||
|
||||
@:index(email,unique)
|
||||
class User extends Object {
|
||||
|
||||
public var id : SId;
|
||||
public var lang : SString<2>;
|
||||
@:skip public var name(get, set) : String;
|
||||
public var pass : STinyText;
|
||||
public var rights : SFlags<RightSite>;
|
||||
|
||||
public var firstName:SString<32>;
|
||||
public var lastName:SString<32>;
|
||||
public var email : SString<64>;
|
||||
public var phone:SNull<SString<19>>;
|
||||
|
||||
public var firstName2:SNull<SString<32>>;
|
||||
public var lastName2:SNull<SString<32>>;
|
||||
public var email2 : SNull<SString<64>>;
|
||||
public var phone2:SNull<SString<19>>;
|
||||
|
||||
public var address1:SNull<SString<64>>;
|
||||
public var address2:SNull<SString<64>>;
|
||||
public var zipCode:SNull<SString<32>>;
|
||||
public var city:SNull<SString<25>>;
|
||||
|
||||
@:skip public var amap(get_amap, null) : Amap;
|
||||
|
||||
public var cdate : SDate; //creation
|
||||
public var ldate : SNull<SDateTime>; //derniere connexion
|
||||
|
||||
public var flags : SFlags<UserFlags>;
|
||||
|
||||
@hideInForms public var tutoState : SNull<SData<{name:String,step:Int}>>; //tutorial state
|
||||
|
||||
public var apiKey : SNull<SString<128>>; //private API key
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
|
||||
//default values
|
||||
cdate = Date.now();
|
||||
rights = sys.db.Types.SFlags.ofInt(0);
|
||||
flags = sys.db.Types.SFlags.ofInt(0);
|
||||
flags.set(HasEmailNotif24h);
|
||||
flags.set(HasEmailNotifOuverture);
|
||||
lang = "fr";
|
||||
pass = "";
|
||||
|
||||
}
|
||||
|
||||
public override function toString() {
|
||||
return getName()+" ["+id+"]";
|
||||
}
|
||||
|
||||
public function isAdmin() {
|
||||
return rights.has(Admin) || id==1;
|
||||
}
|
||||
|
||||
public static function login(user:db.User, email:String) {
|
||||
|
||||
user.lock();
|
||||
user.ldate = Date.now();
|
||||
user.update();
|
||||
App.current.session.setUser(user);
|
||||
if (App.current.session.data == null) App.current.session.data = {};
|
||||
//Who's connected, user1 or user2 ?
|
||||
App.current.session.data.whichUser = (email == user.email) ? 0 : 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* is this user the manager of the current group
|
||||
*/
|
||||
public function isAmapManager() {
|
||||
var a = getAmap();
|
||||
if (a == null) return false;
|
||||
var ua = getUserAmap(a);
|
||||
if (ua == null) return false;
|
||||
return ua.hasRight(Right.GroupAdmin);
|
||||
}
|
||||
|
||||
function getUserAmap(amap:db.Amap):db.UserAmap {
|
||||
return db.UserAmap.get(this, amap);
|
||||
}
|
||||
|
||||
public function isFullyRegistred(){
|
||||
return pass != null && pass != "";
|
||||
}
|
||||
|
||||
public function makeMemberOf(group:db.Amap){
|
||||
var ua = db.UserAmap.get(this, group);
|
||||
if (ua == null) {
|
||||
ua = new db.UserAmap();
|
||||
ua.user = this;
|
||||
ua.amap = group;
|
||||
ua.insert();
|
||||
}
|
||||
return ua;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Est ce que ce membre a la gestion de ce contrat
|
||||
* si null, est ce qu'il a la gestion d'un des contrat, n'importe lequel (utilse pour afficher 'gestion contrat' dans la nav )
|
||||
* @param contract
|
||||
*/
|
||||
public function isContractManager(?contract:db.Contract ) {
|
||||
if (isAdmin()) return true;
|
||||
if (contract != null) {
|
||||
return canManageContract(contract);
|
||||
}else {
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.rights == null) return false;
|
||||
for (r in ua.rights) {
|
||||
switch(r) {
|
||||
case Right.ContractAdmin(cid):
|
||||
return true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function canManageAllContracts(){
|
||||
if (isAdmin()) return true;
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.rights == null) return false;
|
||||
for (r in ua.rights) {
|
||||
switch(r) {
|
||||
case Right.ContractAdmin(cid):
|
||||
if(cid==null) return true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canAccessMessages():Bool {
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.hasRight(Right.Messages)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canAccessMembership():Bool {
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.hasRight(Right.Membership)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canManageContract(c:db.Contract):Bool {
|
||||
var ua = getUserAmap(c.amap);
|
||||
if (ua == null) return false;
|
||||
if (ua.hasRight(Right.ContractAdmin())) return true;
|
||||
if (ua.hasRight(Right.ContractAdmin(c.id))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getContractManager(?lock=false) {
|
||||
return Contract.manager.search($amap == amap && $contact == this, false);
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return get_name();
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return lastName + " " + firstName;
|
||||
}
|
||||
|
||||
public function getCoupleName() {
|
||||
var n = lastName + " " + firstName;
|
||||
if (lastName2 != null) {
|
||||
n = n + " / " + lastName2 + " " + firstName2;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public function set_name(name:String) {
|
||||
var name = name.split(' ');
|
||||
firstName = name[0];
|
||||
lastName = name[1];
|
||||
return firstName+" "+lastName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a user password
|
||||
* @param p
|
||||
*/
|
||||
public function setPass(p:String) {
|
||||
if (p == null){
|
||||
this.pass = "";
|
||||
}else{
|
||||
this.pass = haxe.crypto.Md5.encode( App.config.get('key') + StringTools.trim(p));
|
||||
}
|
||||
return this.pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les commandes actuelles du user
|
||||
* @param _amap force une amap
|
||||
* @param lock=false
|
||||
*/
|
||||
public function getOrders(?_amap:db.Amap,?lock = false):List<UserContract> {
|
||||
var a = _amap == null ? getAmap() : _amap;
|
||||
var c = a.getActiveContracts(true);
|
||||
var cids = Lambda.map(c,function(m) return m.id);
|
||||
var pids = Lambda.map(db.Product.manager.search($contractId in cids,false), function(x) return x.id);
|
||||
var out = UserContract.manager.search(($userId == id || $userId2 == id) && $productId in pids, lock);
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie les commandes à partir d'une liste de contrats
|
||||
*/
|
||||
public function getOrdersFromContracts(c:Iterable<db.Contract>):List<db.UserContract> {
|
||||
var cids = Lambda.map(c,function(m) return m.id);
|
||||
var pids = Lambda.map(db.Product.manager.search($contractId in cids,false), function(x) return x.id);
|
||||
return UserContract.manager.search(($userId == id || $userId2 == id) && $productId in pids, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie les commandes de contrat variables à partir d'une distribution
|
||||
*/
|
||||
public function getOrdersFromDistrib(d:db.Distribution):List<db.UserContract> {
|
||||
var pids = Lambda.map(db.Product.manager.search($contractId == d.contract.id, false), function(x) return x.id);
|
||||
return UserContract.manager.search(($userId == id || $userId2 == id) && $distributionId==d.id && $productId in pids , false);
|
||||
}
|
||||
|
||||
public function get_amap():Amap {
|
||||
return getAmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie l'amap selectionnée par le user en cours
|
||||
*/
|
||||
public function getAmap() {
|
||||
|
||||
if (App.current.user != null && id != App.current.user.id) throw "This function is valid only for the current user";
|
||||
if (App.current.session == null) return null;
|
||||
if (App.current.session.data == null ) return null;
|
||||
var a = App.current.session.data.amapId;
|
||||
if (a == null) {
|
||||
return null;
|
||||
}else {
|
||||
return Amap.manager.get(a,false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get groups this user belongs to
|
||||
*/
|
||||
public function getAmaps():List<db.Amap> {
|
||||
return Lambda.map(UserAmap.manager.search($user == this, false), function(o) return o.amap);
|
||||
}
|
||||
|
||||
public function isMemberOf(amap:Amap) {
|
||||
return UserAmap.manager.select($user == this && $amapId == amap.id, false) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie la liste des contrats dans lequel l'adherent a des commandes
|
||||
* @param lock=false
|
||||
* @return
|
||||
*/
|
||||
public function getContracts(?lock=false):Array<Contract> {
|
||||
var out = [];
|
||||
var ucs = getOrders(lock);
|
||||
for (uc in ucs) {
|
||||
if (!Lambda.has(out, uc.product.contract)) {
|
||||
out.push(uc.product.contract);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge fields of 2 users, then delete the second (u2)
|
||||
* Be carefull : check before calling this function that u2 can be safely deleted !
|
||||
*/
|
||||
public function merge(u2:db.User) {
|
||||
|
||||
this.lock();
|
||||
u2.lock();
|
||||
|
||||
var m = function(a, b) {
|
||||
return a == null || a=="" ? b : a;
|
||||
}
|
||||
|
||||
this.address1 = m(this.address1, u2.address1);
|
||||
this.address2 = m(this.address2, u2.address2);
|
||||
this.zipCode = m(this.zipCode, u2.zipCode);
|
||||
this.city = m(this.city, u2.city);
|
||||
|
||||
//find how to merge the 2 names in each account
|
||||
if (this.email == u2.email) {
|
||||
|
||||
this.firstName = m(this.firstName, u2.firstName);
|
||||
this.lastName = m(this.lastName, u2.lastName);
|
||||
this.phone = m(this.phone, u2.phone);
|
||||
|
||||
} else if (this.email == u2.email2) {
|
||||
|
||||
this.firstName = m(this.firstName, u2.firstName2);
|
||||
this.lastName = m(this.lastName, u2.lastName2);
|
||||
this.phone = m(this.phone, u2.phone2);
|
||||
}
|
||||
|
||||
if (this.email2 == u2.email) {
|
||||
|
||||
this.firstName2 = m(this.firstName2, u2.firstName);
|
||||
this.lastName2 = m(this.lastName2, u2.lastName);
|
||||
this.phone2 = m(this.phone2, u2.phone);
|
||||
|
||||
} else if (this.email2 == u2.email2) {
|
||||
|
||||
this.firstName2 = m(this.firstName2, u2.firstName2);
|
||||
this.lastName2 = m(this.lastName2, u2.lastName2);
|
||||
this.phone2 = m(this.phone2, u2.phone2);
|
||||
|
||||
}
|
||||
|
||||
u2.delete();
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
public static function getOrCreate(firstName:String, lastName:String, email:String):db.User{
|
||||
var u = db.User.manager.select($email == email || $email2 == email, true);
|
||||
if (u == null){
|
||||
u = new db.User();
|
||||
u.firstName = firstName;
|
||||
u.lastName = lastName;
|
||||
u.email = email;
|
||||
u.insert();
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for similar users in the DB ( same firstName+lastName or same email )
|
||||
*/
|
||||
public static function __getSimilar(firstName:String, lastName:String, email:String,?firstName2:String, ?lastName2:String, ?email2:String):List<db.User> {
|
||||
var out = new Array();
|
||||
out = Lambda.array(User.manager.search($firstName.like(firstName) && $lastName.like(lastName), false));
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($firstName2.like(firstName) && $lastName2.like(lastName), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email), false)));
|
||||
|
||||
//recherche pour le deuxieme user
|
||||
if (lastName2 != "" && lastName2 != null && firstName2 != "" && firstName2 != null) {
|
||||
out = out.concat(Lambda.array(User.manager.search($firstName.like(firstName2) && $lastName.like(lastName2), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($firstName2.like(firstName2) && $lastName2.like(lastName2), false)));
|
||||
}
|
||||
if (email2 != null && email2 != "") {
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email2), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email2), false)));
|
||||
}
|
||||
|
||||
//dedouble
|
||||
var x = new Map<Int,db.User>();
|
||||
for ( oo in out) {
|
||||
x.set(oo.id, oo);
|
||||
}
|
||||
return Lambda.list(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for similar users in the DB ( same email )
|
||||
*/
|
||||
public static function getSameEmail(email:String, ?email2:String){
|
||||
|
||||
var out = new Array();
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email), false)));
|
||||
if (email2 != null && email2 != "") {
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email2), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email2), false)));
|
||||
}
|
||||
return Lambda.list(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users with no contracts
|
||||
**/
|
||||
public static function getUsers_NoContracts(?index:Int,?limit:Int):List<db.User> {
|
||||
var productsIds = App.current.user.getAmap().getProducts().map(function(x) return x.id);
|
||||
var uc = UserContract.manager.search($productId in productsIds, false);
|
||||
var uc2 = uc.map(function(x) return x.user.id); //liste des userId avec un contrat dans cette amap
|
||||
|
||||
// J. Le Clerc - BUGFIX#1 Ne pas oublier les contrats alternés
|
||||
for (u in uc) {
|
||||
if (u.user2 != null) {
|
||||
uc2.add(u.user2.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (uc2.length > 0){
|
||||
//les gens qui sont dans cette amap et qui n'ont pas de contrat de cette amap
|
||||
var ua = db.UserAmap.manager.unsafeObjects("select * from UserAmap where amapId=" + App.current.user.getAmap().id +" and userId NOT IN(" + uc2.join(",") + ")", false);
|
||||
return Lambda.map(ua, function(x) return x.user);
|
||||
}else{
|
||||
return App.current.user.amap.getMembers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* User with contracts
|
||||
*/
|
||||
public static function getUsers_Contracts(?index:Int,?limit:Int):List<db.User> {
|
||||
var productsIds = App.current.user.getAmap().getProducts().map(function(x) return x.id);
|
||||
if (productsIds.length == 0) return new List();
|
||||
return db.User.manager.unsafeObjects("select u.* from User u, UserContract uc where uc.productId IN(" + productsIds.join(",") + ") AND (uc.userId=u.id OR uc.userId2=u.id) group by u.id ORDER BY u.lastName", false);
|
||||
}
|
||||
|
||||
|
||||
public static function getUsers_NoMembership(?index:Int,?limit:Int):List<db.User> {
|
||||
var ua = new List();
|
||||
if (index == null && limit == null) {
|
||||
ua = db.UserAmap.manager.search($amap == App.current.user.amap, false);
|
||||
}else {
|
||||
ua = db.UserAmap.manager.search($amap == App.current.user.amap,{limit:[index,limit]}, false);
|
||||
}
|
||||
|
||||
for (u in Lambda.array(ua)) {
|
||||
if (u.hasValidMembership()) ua.remove(u);
|
||||
}
|
||||
|
||||
return Lambda.map(ua, function(x) return x.user);
|
||||
}
|
||||
|
||||
public static function getUsers_NewUsers(?index:Int, ?limit:Int):List<db.User> {
|
||||
|
||||
var uas = db.UserAmap.manager.search($amap == App.current.user.amap, false);
|
||||
var ids = Lambda.map(uas, function(x) return x.user.id);
|
||||
if (index == null && limit == null) {
|
||||
return db.User.manager.search($pass == "" && ($id in ids), {orderBy:lastName} ,false);
|
||||
}else {
|
||||
return db.User.manager.search($pass == "" && ($id in ids), {limit:[index, limit] ,orderBy:lastName} , false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function sendInvitation(group:db.Amap) {
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if (isFullyRegistred()) throw t._("This user cannot receive an invitation");
|
||||
|
||||
/*var group : db.Amap = null;
|
||||
|
||||
if (App.current.user == null) {
|
||||
group = this.getAmaps().first();
|
||||
}else {
|
||||
//prend l'amap du user connecté qui a lancé l'invite.
|
||||
group = App.current.user.amap;
|
||||
}*/
|
||||
|
||||
//store token
|
||||
var k = sugoi.db.Session.generateId();
|
||||
sugoi.db.Cache.set("validation" + k, this.id, 60 * 60 * 24 * 30); //expire in 1 month
|
||||
|
||||
var e = new sugoi.mail.Mail();
|
||||
if (group != null){
|
||||
e.setSubject(t._("Invitation")+" "+group.name);
|
||||
}else{
|
||||
e.setSubject(t._("Invitation Cagette.net"));
|
||||
}
|
||||
|
||||
e.addRecipient(this.email,this.getName());
|
||||
e.setSender(App.config.get("default_email"),t._("Cagette.net"));
|
||||
|
||||
var html = App.current.processTemplate("mail/invitation.mtt", {
|
||||
email:email,
|
||||
email2:email2,
|
||||
groupName:(group == null?null:group.name),
|
||||
name:firstName,
|
||||
k:k
|
||||
} );
|
||||
e.setHtmlBody(html);
|
||||
|
||||
App.sendMail(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* cleaning before saving
|
||||
*/
|
||||
override public function insert() {
|
||||
clean();
|
||||
super.insert();
|
||||
}
|
||||
|
||||
override public function update() {
|
||||
clean();
|
||||
super.update();
|
||||
}
|
||||
|
||||
function clean() {
|
||||
|
||||
//emails
|
||||
this.email = this.email.toLowerCase();
|
||||
if (this.email2 != null) this.email2 = this.email2.toLowerCase();
|
||||
|
||||
//lastname
|
||||
if (this.lastName != null) this.lastName = this.lastName.toUpperCase();
|
||||
if (this.lastName2 != null) this.lastName2 = this.lastName2.toUpperCase();
|
||||
|
||||
if(pass==null) pass="";
|
||||
}
|
||||
|
||||
public function infos():UserInfo{
|
||||
return {
|
||||
id:id,
|
||||
name : getName(),
|
||||
email : email
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get form labels
|
||||
*/
|
||||
public static function getLabels():Map<String,String>{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"firstName" => t._("First name"),
|
||||
"lastName" => t._("Last name"),
|
||||
"email" => t._("Email"),
|
||||
"phone" => t._("Phone"),
|
||||
"firstName2"=> t._("Partner first name"),
|
||||
"lastName2" => t._("Partner last name"),
|
||||
"email2" => t._("Partner email"),
|
||||
"phone2" => t._("Partner phone"),
|
||||
"lang" => t._("Language"),
|
||||
"address1" => t._("Address 1"),
|
||||
"address2" => t._("Address 2"),
|
||||
"zipCode" => t._("Zip code"),
|
||||
"city" => t._("City"),
|
||||
"rights" => t._("Rights"),
|
||||
"cdate" => t._("Registration date"),
|
||||
"flags" => t._("Options"),
|
||||
"pass" => t._("Password"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+128
@@ -0,0 +1,128 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
enum Right{
|
||||
GroupAdmin; //can manage whole group
|
||||
ContractAdmin(?cid:Int); //can manage one or all contracts
|
||||
Membership; //can manage group members
|
||||
Messages; //can send messages
|
||||
}
|
||||
|
||||
/**
|
||||
* A user which is member of a group
|
||||
*/
|
||||
@:id(userId,amapId)
|
||||
class UserAmap extends Object
|
||||
{
|
||||
@:relation(amapId) public var amap : db.Amap;
|
||||
@:relation(userId) public var user : db.User;
|
||||
public var rights : SNull<SData<Array<Right>>>;
|
||||
public var balance : SFloat; //account balance in group currency
|
||||
static var CACHE = new Map<String,db.UserAmap>();
|
||||
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
balance = 0;
|
||||
}
|
||||
|
||||
public static function get(user:User, amap:Amap, ?lock = false) {
|
||||
if (user == null || amap == null) return null;
|
||||
//SPOD doesnt cache elements with double primary key, so lets do it manually
|
||||
var c = CACHE.get(user.id + "-" + amap.id);
|
||||
if (c == null) {
|
||||
c = manager.select($user == user && $amap == amap, true/*lock*/);
|
||||
CACHE.set(user.id + "-" + amap.id,c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public static function getOrCreate(user:db.User, group:db.Amap){
|
||||
var ua = get(user, group);
|
||||
if ( ua == null){
|
||||
ua = new UserAmap();
|
||||
ua.user = user;
|
||||
ua.amap = group;
|
||||
ua.insert();
|
||||
}
|
||||
return ua;
|
||||
}
|
||||
|
||||
/**
|
||||
* give right and update DB
|
||||
*/
|
||||
public function giveRight(r:Right) {
|
||||
|
||||
if (hasRight(r)) return;
|
||||
if (rights == null) rights = [];
|
||||
lock();
|
||||
rights.push(r);
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* remove right and update DB
|
||||
*/
|
||||
public function removeRight(r:Right) {
|
||||
if (rights == null) return;
|
||||
var newrights = [];
|
||||
for (right in rights.copy()) {
|
||||
if ( !Type.enumEq(right, r) ) {
|
||||
newrights.push(right);
|
||||
}
|
||||
}
|
||||
rights = newrights;
|
||||
update();
|
||||
}
|
||||
|
||||
public function hasRight(r:Right):Bool {
|
||||
if (this.user.isAdmin()) return true;
|
||||
if (rights == null) return false;
|
||||
for ( right in rights) {
|
||||
if ( Type.enumEq(r,right) ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRightName(r:Right):String {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return switch(r) {
|
||||
case Right.GroupAdmin : t._("Administrator");
|
||||
case Right.Messages : t._("Messaging");
|
||||
case Right.Membership : t._("Members management");
|
||||
case Right.ContractAdmin(cid) :
|
||||
if (cid == null) {
|
||||
t._("Management of all contracts");
|
||||
}else {
|
||||
var c = db.Contract.manager.get(cid);
|
||||
if(c==null) {
|
||||
t._("Deleted contract");
|
||||
}else{
|
||||
t._("::name:: contract management",{name:c.name});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function hasValidMembership():Bool {
|
||||
|
||||
if (amap.membershipRenewalDate == null) return false;
|
||||
var cotis = db.Membership.get(this.user, this.amap, this.amap.getMembershipYear());
|
||||
return cotis != null;
|
||||
}
|
||||
|
||||
override public function insert(){
|
||||
App.current.event(NewMember(this.user,this.amap));
|
||||
super.insert();
|
||||
}
|
||||
|
||||
public function getPaymentOperations(){
|
||||
return db.Operation.getPaymentOperations(user, amap);
|
||||
}
|
||||
|
||||
public function getLastOperations(limit){
|
||||
return db.Operation.getLastOperations(user, amap, limit);
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+200
@@ -0,0 +1,200 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* a product order
|
||||
*/
|
||||
class UserContract extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
|
||||
@formPopulate("populate") @:relation(userId)
|
||||
public var user : User;
|
||||
|
||||
//shared order
|
||||
@formPopulate("populate") @:relation(userId2)
|
||||
public var user2 : SNull<User>;
|
||||
|
||||
public var quantity : SFloat;
|
||||
|
||||
@formPopulate("populateProducts") @:relation(productId)
|
||||
public var product : Product;
|
||||
|
||||
//store price (1 unit price) and fees (percentage not amount ) rate when the order is done
|
||||
public var productPrice : SFloat;
|
||||
public var feesRate : SInt; //fees in percentage
|
||||
|
||||
public var paid : SBool;
|
||||
|
||||
//if not null : varying orders
|
||||
@:relation(distributionId)
|
||||
public var distribution:SNull<db.Distribution>;
|
||||
|
||||
|
||||
@:relation(basketId)
|
||||
public var basket:SNull<db.Basket>;
|
||||
|
||||
public var date : SDateTime;
|
||||
public var flags : SFlags<OrderFlags>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
quantity = 1;
|
||||
paid = false;
|
||||
date = Date.now();
|
||||
flags = cast 0;
|
||||
feesRate = 0;
|
||||
}
|
||||
|
||||
public function populate() {
|
||||
return App.current.user.getAmap().getMembersFormElementData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For shared alternated orders in AMAP contracts
|
||||
* @param distrib
|
||||
* @return false -> user , true -> user2
|
||||
*/
|
||||
public function getWhosTurn(distrib:Distribution) {
|
||||
if (distrib == null) throw "distribution is null";
|
||||
if (user2 == null) throw "this contract is not shared";
|
||||
|
||||
//compter le nbre de distrib pour ce contrat
|
||||
var c = Distribution.manager.count( $contract == product.contract && $date >= product.contract.startDate && $date <= distrib.date);
|
||||
var r = c % 2 == 0;
|
||||
if (flags.has(InvertSharedOrder)){
|
||||
return !r;
|
||||
}else{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
override public function toString() {
|
||||
if(product==null) return quantity +"x produit inconnu";
|
||||
return quantity + "x" + product.name;
|
||||
}
|
||||
|
||||
public function hasInvertSharedOrder():Bool{
|
||||
return flags.has(InvertSharedOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* On peut modifier si ça na pas deja été payé + commande encore ouvertes
|
||||
*/
|
||||
public function canModify():Bool {
|
||||
|
||||
var can = false;
|
||||
if (this.product.contract.type == db.Contract.TYPE_VARORDER) {
|
||||
|
||||
if (this.distribution.orderStartDate == null) {
|
||||
can = true;
|
||||
}else {
|
||||
var n = Date.now().getTime();
|
||||
can = n > this.distribution.orderStartDate.getTime() && n < this.distribution.orderEndDate.getTime();
|
||||
}
|
||||
}else {
|
||||
can = this.product.contract.isUserOrderAvailable();
|
||||
}
|
||||
|
||||
return can && !this.paid;
|
||||
}
|
||||
|
||||
/**
|
||||
* get users orders for a distribution
|
||||
*/
|
||||
public static function getOrders(contract:db.Contract, ?distribution:db.Distribution, ?csv = false):Array<UserOrder>{
|
||||
var view = App.current.view;
|
||||
var orders = new Array<db.UserContract>();
|
||||
if (contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
orders = contract.getOrders(distribution);
|
||||
}else {
|
||||
orders = contract.getOrders();
|
||||
}
|
||||
|
||||
var orders = service.OrderService.prepare(orders);
|
||||
|
||||
//CSV export
|
||||
if (csv) {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var data = new Array<Dynamic>();
|
||||
|
||||
for (o in orders) {
|
||||
data.push( {
|
||||
"name":o.userName,
|
||||
"productName":o.productName,
|
||||
"price":view.formatNum(o.productPrice),
|
||||
"quantity":view.formatNum(o.quantity),
|
||||
"fees":view.formatNum(o.fees),
|
||||
"total":view.formatNum(o.total),
|
||||
"paid":o.paid
|
||||
});
|
||||
}
|
||||
|
||||
var exportName = "";
|
||||
if (distribution != null){
|
||||
exportName = contract.amap.name + " - " + t._("Delivery ::contractName:: ", {contractName:contract.name}) + distribution.date.toString().substr(0, 10);
|
||||
}else{
|
||||
exportName = contract.amap.name + " - " + contract.name;
|
||||
}
|
||||
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(data, ["name", "productName", "price", "quantity", "fees", "total", "paid"], exportName+" - " + t._("Per member"));
|
||||
return null;
|
||||
}else{
|
||||
return orders;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the orders (varying orders) of a user for a multidistrib ( distribs with same day + same place )
|
||||
*
|
||||
* @param distribKey "$date|$placeId"
|
||||
*/
|
||||
public static function getUserOrdersByMultiDistrib(distribKey:String, user:db.User,group:db.Amap):Array<db.UserContract>{
|
||||
//var contracts = db.Contract.getActiveContracts(group);
|
||||
var contracts = db.Contract.manager.search($amap == group, false); //should be able to edit a contract which is closed
|
||||
for ( c in Lambda.array(contracts)){
|
||||
if (c.type == db.Contract.TYPE_CONSTORDERS){
|
||||
contracts.remove(c); //only varying orders
|
||||
}
|
||||
}
|
||||
|
||||
var cids = Lambda.map(contracts, function(x) return x.id);
|
||||
var start = Date.fromString(distribKey.split("|")[0] + " 00:00:00");
|
||||
var end = Date.fromString(distribKey.split("|")[0] + " 23:59:00");
|
||||
var ds = db.Distribution.manager.search($date > start && $date < end && ($contractId in cids), false);
|
||||
var out = [];
|
||||
for (d in ds) {
|
||||
out = out.concat(Lambda.array(user.getOrdersFromDistrib(d)));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function getTotalPrice(tmpOrder:OrderInSession){
|
||||
var t = 0.0;
|
||||
for ( o in tmpOrder.products){
|
||||
var p = db.Product.manager.get(o.productId, false);
|
||||
t += o.quantity * p.getPrice();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function check(){
|
||||
if(quantity==null) quantity == 1;
|
||||
}
|
||||
|
||||
override function update(){
|
||||
check();
|
||||
super.update();
|
||||
}
|
||||
|
||||
override function insert(){
|
||||
check();
|
||||
super.insert();
|
||||
}
|
||||
}
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
/**
|
||||
* Vendor (producteur)
|
||||
*/
|
||||
class Vendor extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
|
||||
public var email : STinyText;
|
||||
public var phone:SNull<SString<19>>;
|
||||
|
||||
public var address1:SNull<SString<64>>;
|
||||
public var address2:SNull<SString<64>>;
|
||||
public var zipCode:SString<32>;
|
||||
public var city:SString<25>;
|
||||
|
||||
public var desc : SNull<SText>;
|
||||
|
||||
public var linkText:SNull<SString<256>>;
|
||||
public var linkUrl:SNull<SString<256>>;
|
||||
|
||||
@hideInForms @:relation(imageId) public var image : SNull<sugoi.db.File>;
|
||||
|
||||
@:relation(amapId) public var amap : SNull<Amap>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
name = t._("Supplier");
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public function getActiveContracts(){
|
||||
var now = Date.now();
|
||||
return db.Contract.manager.search($vendor == this && $startDate < now && $endDate > now ,{orderBy:-startDate}, false);
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Supplier name"),
|
||||
"desc" => t._("Description"),
|
||||
"email" => t._("Email"),
|
||||
"phone" => t._("Phone"),
|
||||
"address1" => t._("Address 1"),
|
||||
"address2" => t._("Address 2"),
|
||||
"zipCode" => t._("Zip code"),
|
||||
"city" => t._("City"),
|
||||
"linkText" => t._("Link text"),
|
||||
"linkUrl" => t._("Link URL"),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
|
||||
@:id(userId,amapId)
|
||||
class WaitingList extends Object
|
||||
{
|
||||
@:relation(amapId) public var group : Amap;
|
||||
@:relation(userId) public var user : db.User;
|
||||
public var date : SDateTime;
|
||||
public var message : SText;
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
message = "";
|
||||
date = Date.now();
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
package form;
|
||||
import sugoi.form.elements.RadioGroup;
|
||||
import Common;
|
||||
|
||||
class ColorRadioGroup extends RadioGroup
|
||||
{
|
||||
|
||||
public function new(name:String, label:String,selected:String)
|
||||
{
|
||||
var data = [];
|
||||
var i = 0;
|
||||
for (c in db.CategoryGroup.COLORS) {
|
||||
data.push( { value:Std.string(c), label:Std.string(i) } );
|
||||
i++;
|
||||
}
|
||||
|
||||
super(name, label, data, selected, "1", false, true);
|
||||
}
|
||||
|
||||
override public function render():String
|
||||
{
|
||||
var s = "";
|
||||
var n = parentForm.name + "_" +name;
|
||||
|
||||
var c = 0;
|
||||
if (data != null)
|
||||
{
|
||||
for (row in data)
|
||||
{
|
||||
|
||||
var radio = "<input type=\"radio\" name=\""+n+"\" id=\""+n+c+"\" value=\"" + row.label + "\" " + (row.label == Std.string(value) ? "checked":"") +" />\n";
|
||||
|
||||
var img = "<div style='margin-right:16px;width:32px;height:32px;background:"+App.current.view.intToHex(Std.parseInt(row.value))+";'></div>";
|
||||
|
||||
s += "<label for=\"" + n+c + "\" class='checkbox' style='display: inline-block;'>"+radio + " "+img+" </label>";
|
||||
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
package form;
|
||||
import db.Product;
|
||||
import sugoi.form.elements.RadioGroup;
|
||||
import db.Product;
|
||||
import Common;
|
||||
/**
|
||||
* list des types de produits avec image
|
||||
* @author
|
||||
*/
|
||||
class ProductTypeRadioGroup extends RadioGroup
|
||||
{
|
||||
|
||||
public function new(name:String, label:String,selected:String)
|
||||
{
|
||||
var data = [];
|
||||
var i = 0;
|
||||
for (e in Type.getEnumConstructs(ProductType)) {
|
||||
data.push( { key:Std.string(i), value:Std.string(e) } );
|
||||
i++;
|
||||
}
|
||||
|
||||
super(name, label, data, selected, "1", false, true);
|
||||
}
|
||||
|
||||
override public function render():String
|
||||
{
|
||||
var s = "";
|
||||
var n = parentForm.name + "_" +name;
|
||||
|
||||
var c = 0;
|
||||
if (data != null)
|
||||
{
|
||||
for (row in data)
|
||||
{
|
||||
|
||||
|
||||
var radio = "<input type=\"radio\" name=\""+n+"\" id=\""+n+c+"\" value=\"" + row.key + "\" " + (row.key == Std.string(value) ? "checked":"") +" />\n";
|
||||
var e = Type.createEnumIndex(ProductType, Std.parseInt(row.key));
|
||||
var img = "<img src='/img/"+Std.string(e).toLowerCase().substring(2)+".png' />";
|
||||
|
||||
s += "<label for=\"" + n+c + "\" class='checkbox' style='display: inline-block;'>"+radio + " "+img+" </label>";
|
||||
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package form;
|
||||
import Common;
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class UnitQuantity extends sugoi.form.elements.FloatInput
|
||||
{
|
||||
|
||||
var unit : Unit;
|
||||
|
||||
public function new(name, label, value, ?required=false,unit){
|
||||
super(name, label, value, required);
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
override function render(){
|
||||
|
||||
var r = super.render();
|
||||
return '
|
||||
<div class="input-group">
|
||||
'+r+'
|
||||
<div class="input-group-addon">'+App.current.view.unit(unit)+'</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package payment;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Cash extends payment.Payment
|
||||
{
|
||||
|
||||
public static var TYPE = "cash";
|
||||
|
||||
public function new()
|
||||
{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
this.type = TYPE;
|
||||
this.icon = '<i class="fa fa-credit-card" aria-hidden="true"></i>';
|
||||
this.name = t._("Cash");
|
||||
//this.desc = t._("Pay by cash at product distribution");
|
||||
this.link = "/transaction/cash";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package payment;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Check extends payment.Payment
|
||||
{
|
||||
public static var TYPE = "check";
|
||||
|
||||
public function new()
|
||||
{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
this.type = TYPE;
|
||||
this.icon = '<i class="fa fa-credit-card" aria-hidden="true"></i>';
|
||||
this.name = t._("Check");
|
||||
this.link = "/transaction/check";
|
||||
}
|
||||
|
||||
public static function getCode(date:Date,place:db.Place,user:db.User){
|
||||
return date.toString().substr(0, 10) + "-" + place.id + "-" + user.id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package payment;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class MoneyPot extends payment.Payment
|
||||
{
|
||||
|
||||
public static var TYPE = "moneypot";
|
||||
|
||||
public function new()
|
||||
{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
this.type = TYPE;
|
||||
this.icon = '<i class="glyphicon glyphicon-piggy-bank" aria-hidden="true"></i>';
|
||||
this.name = t._("Money pot");
|
||||
//this.desc = t._("Pay by cash at product distribution");
|
||||
this.link = "/transaction/moneypot";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package payment;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Payment
|
||||
{
|
||||
|
||||
public var type:String;
|
||||
public var icon:String;
|
||||
public var name:String; //translated name
|
||||
public var link:String;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package payment;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Transfer extends payment.Payment
|
||||
{
|
||||
|
||||
public static var TYPE = "transfer";
|
||||
|
||||
public function new()
|
||||
{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
this.type = TYPE;
|
||||
this.icon = '<i class="fa fa-credit-card" aria-hidden="true"></i>';
|
||||
this.name = t._("Bank transfer");
|
||||
this.link = "/transaction/transfer";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package plugin;
|
||||
import Common;
|
||||
import sugoi.plugin.*;
|
||||
|
||||
/**
|
||||
* Tutorials internal plugin
|
||||
*
|
||||
* Its listening to events to know if
|
||||
* the user can go to the next step of his tutorial
|
||||
*
|
||||
* User's tutorial state is stored in user.tutoState.
|
||||
* The JS widget is triggered in view.init()
|
||||
*
|
||||
*
|
||||
*/
|
||||
class Tutorial extends PlugIn implements IPlugIn
|
||||
{
|
||||
public function new() {
|
||||
super();
|
||||
App.current.eventDispatcher.add(onEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* catch events
|
||||
*/
|
||||
public function onEvent(e:Event) {
|
||||
//no need to continue if tutos are disabled
|
||||
if ( App.current.user==null || App.current.user.tutoState==null ) return;
|
||||
|
||||
switch(e) {
|
||||
|
||||
//a page is displayed
|
||||
case Page(uri):
|
||||
|
||||
var ts = App.current.user.tutoState;
|
||||
if (ts == null) return;
|
||||
var tuto = TutoDatas.get(ts.name);
|
||||
var step = tuto.steps[ts.step];
|
||||
if (step == null ) return;
|
||||
|
||||
//skip steps if action is "next"
|
||||
while (step.action.equals(TANext)) {
|
||||
if (ts.step + 1 >= tuto.steps.length) break;
|
||||
ts.step++;
|
||||
step = tuto.steps[ts.step];
|
||||
}
|
||||
|
||||
//trace( "tuto active, listening to step="+ts.step );
|
||||
switch(step.action) {
|
||||
case TAPage(_uri):
|
||||
|
||||
//trace(""+_uri+"="+uri+" ?");
|
||||
if (match(_uri, uri)) {
|
||||
|
||||
//trace("ok");
|
||||
var u = App.current.user;
|
||||
u.lock();
|
||||
|
||||
if ( ts.step+1 >= tuto.steps.length) {
|
||||
//tuto finished
|
||||
u.tutoState = null;
|
||||
}else {
|
||||
//next step
|
||||
u.tutoState.step = ts.step+1;
|
||||
}
|
||||
|
||||
u.update();
|
||||
}
|
||||
default:
|
||||
|
||||
}
|
||||
|
||||
|
||||
default :
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* to know if the current uri matches with the tuto step uri
|
||||
*/
|
||||
public static function match(pattern:String, uri:String):Bool {
|
||||
|
||||
if (pattern.indexOf("*") > -1) {
|
||||
|
||||
//the url contains a wildcard
|
||||
|
||||
// ~/http:\/\/(\w+).com/ match urls like http://anything.com
|
||||
var s = pattern;
|
||||
s = StringTools.replace(s, "/", "\\/"); //escape antislashes
|
||||
s = StringTools.replace(s, "*", "(\\w+)");
|
||||
var e = new EReg(s,"");
|
||||
|
||||
return e.match(uri);
|
||||
|
||||
}else {
|
||||
|
||||
return pattern == uri;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function all() {
|
||||
TutoDatas.get("intro");//just to init translation
|
||||
return TutoDatas.TUTOS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
package service;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Distribution Service
|
||||
* @author web-wizard
|
||||
*/
|
||||
class DistributionService
|
||||
{
|
||||
/**
|
||||
* It will update the name of the operation with the new number of distributions
|
||||
* as well as the total amount
|
||||
* @param contract -
|
||||
*/
|
||||
public static function updateAmapContractOperations(contract:db.Contract) {
|
||||
|
||||
//Update all operations for this amap contract when payments are enabled
|
||||
if (contract.type == db.Contract.TYPE_CONSTORDERS && contract.amap.hasPayments()) {
|
||||
//Get all the users who have orders for this contract
|
||||
var users = contract.getUsers();
|
||||
for ( user in users ){
|
||||
|
||||
//Get the one operation for this amap contract and user
|
||||
var operation = db.Operation.findCOrderTransactionFor(contract, user);
|
||||
|
||||
if (operation != null)
|
||||
{
|
||||
//Get all the orders for this contract and user
|
||||
var orders = contract.getUserOrders(user);
|
||||
//Update this operation with the new number of distributions, this will affect the name of the operation
|
||||
//as well as the total amount to pay
|
||||
db.Operation.updateOrderOperation(operation, orders);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* checks if dates are correct and if that there is no other distribution in the same time range
|
||||
* and for the same contract and place
|
||||
* @param d
|
||||
*/
|
||||
public static function checkDistrib(d:db.Distribution) {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var view = App.current.view;
|
||||
|
||||
var c = d.contract;
|
||||
|
||||
var distribs1;
|
||||
var distribs2;
|
||||
var distribs3;
|
||||
//We are checking that there is no existing distribution with an overlapping time frame for the same place and contract
|
||||
if (d.id == null) { //We need to check there the id as $id != null doesn't work in the manager.search
|
||||
//Looking for existing distributions with a time range overlapping the start of the about to be created distribution
|
||||
distribs1 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.date && $end >= d.date, false);
|
||||
//Looking for existing distributions with a time range overlapping the end of the about to be created distribution
|
||||
distribs2 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.end && $end >= d.end, false);
|
||||
//Looking for existing distributions with a time range included in the time range of the about to be created distribution
|
||||
distribs3 = db.Distribution.manager.search($contract == c && $place == d.place && $date >= d.date && $end <= d.end, false);
|
||||
}
|
||||
else {
|
||||
//Looking for existing distributions with a time range overlapping the start of the about to be created distribution
|
||||
distribs1 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.date && $end >= d.date && $id != d.id, false);
|
||||
//Looking for existing distributions with a time range overlapping the end of the about to be created distribution
|
||||
distribs2 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.end && $end >= d.end && $id != d.id, false);
|
||||
//Looking for existing distributions with a time range included in the time range of the about to be created distribution
|
||||
distribs3 = db.Distribution.manager.search($contract == c && $place == d.place && $date >= d.date && $end <= d.end && $id != d.id, false);
|
||||
}
|
||||
|
||||
if (distribs1.length != 0 || distribs2.length != 0 || distribs3.length != 0) {
|
||||
throw new tink.core.Error(t._("There is already a distribution at this place overlapping with the time range you've selected."));
|
||||
}
|
||||
|
||||
if (d.date.getTime() > c.endDate.getTime()) throw new tink.core.Error(t._("The date of the delivery must be prior to the end of the contract (::contractEndDate::)", {contractEndDate:view.hDate(c.endDate)}));
|
||||
if (d.date.getTime() < c.startDate.getTime()) throw new tink.core.Error(t._("The date of the delivery must be after the begining of the contract (::contractBeginDate::)", {contractBeginDate:view.hDate(c.startDate)}));
|
||||
|
||||
if (c.type == db.Contract.TYPE_VARORDER ) {
|
||||
if (d.date.getTime() < d.orderEndDate.getTime() ) throw new tink.core.Error(t._("The distribution start date must be set after the orders end date."));
|
||||
if (d.orderStartDate.getTime() > d.orderEndDate.getTime() ) throw new tink.core.Error(t._("The orders end date must be set after the orders start date !"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new distribution and prevents distribution overlapping and other checks
|
||||
* @param contract -
|
||||
* @param date -
|
||||
* @param end -
|
||||
* @param placeId -
|
||||
* @param distributor1Id -
|
||||
* @param distributor2Id -
|
||||
* @param distributor3Id -
|
||||
* @param distributor4Id -
|
||||
* @param orderStartDate -
|
||||
* @param orderEndDate -
|
||||
* @param distributionCycle -
|
||||
* @param dispatchEvent=true -
|
||||
* @return db.Distribution
|
||||
*/
|
||||
public static function create(contract:db.Contract,date:Date,end:Date,placeId:Int,
|
||||
?distributor1Id:Int,?distributor2Id:Int,?distributor3Id:Int,?distributor4Id:Int,
|
||||
?orderStartDate:Date,?orderEndDate:Date,?distributionCycle:db.DistributionCycle,?dispatchEvent=true):db.Distribution {
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.contract = contract;
|
||||
d.date = date;
|
||||
d.place = db.Place.manager.get(placeId);
|
||||
d.distributionCycle = distributionCycle;
|
||||
if(distributor1Id != null) d.distributor1 = db.User.manager.get(distributor1Id);
|
||||
if(distributor2Id != null) d.distributor2 = db.User.manager.get(distributor2Id);
|
||||
if(distributor3Id != null) d.distributor3 = db.User.manager.get(distributor3Id);
|
||||
if(distributor4Id != null) d.distributor4 = db.User.manager.get(distributor4Id);
|
||||
if(contract.type==db.Contract.TYPE_VARORDER){
|
||||
d.orderStartDate = orderStartDate;
|
||||
d.orderEndDate = orderEndDate;
|
||||
}
|
||||
|
||||
if (end == null) {
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 60);
|
||||
}
|
||||
else {
|
||||
d.end = new Date(d.date.getFullYear(), d.date.getMonth(), d.date.getDate(), end.getHours(), end.getMinutes(), 0);
|
||||
}
|
||||
|
||||
DistributionService.checkDistrib(d);
|
||||
|
||||
if(distributionCycle == null && dispatchEvent) {
|
||||
var e :Event = NewDistrib(d);
|
||||
App.current.event(e);
|
||||
}
|
||||
|
||||
if (d.date == null){
|
||||
return d;
|
||||
} else {
|
||||
d.insert();
|
||||
|
||||
//In case this is a distrib for an amap contract with payments enabled, it will update all the operations
|
||||
//names and amounts with the new number of distribs
|
||||
updateAmapContractOperations(d.contract);
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies an existing distribution and prevents distribution overlapping and other checks
|
||||
* @param d -
|
||||
* @param date -
|
||||
* @param end -
|
||||
* @param placeId -
|
||||
* @param distributor1Id -
|
||||
* @param distributor2Id -
|
||||
* @param distributor3Id -
|
||||
* @param distributor4Id -
|
||||
* @param orderStartDate -
|
||||
* @param orderEndDate -
|
||||
* @return db.Distribution
|
||||
*/
|
||||
public static function edit(d:db.Distribution,date:Date,end:Date,placeId:Int,
|
||||
distributor1Id:Int,distributor2Id:Int,distributor3Id:Int,distributor4Id:Int,
|
||||
orderStartDate:Date,orderEndDate:Date,?dispatchEvent=true):db.Distribution {
|
||||
|
||||
//We prevent others from modifying it
|
||||
d.lock();
|
||||
|
||||
d.date = date;
|
||||
d.place = db.Place.manager.get(placeId);
|
||||
d.distributor1 = db.User.manager.get(distributor1Id);
|
||||
d.distributor2 = db.User.manager.get(distributor2Id);
|
||||
d.distributor3 = db.User.manager.get(distributor3Id);
|
||||
d.distributor4 = db.User.manager.get(distributor4Id);
|
||||
if(d.contract.type==db.Contract.TYPE_VARORDER){
|
||||
d.orderStartDate = orderStartDate;
|
||||
d.orderEndDate = orderEndDate;
|
||||
}
|
||||
|
||||
if (end == null) {
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 60);
|
||||
}
|
||||
else {
|
||||
d.end = new Date(d.date.getFullYear(), d.date.getMonth(), d.date.getDate(), end.getHours(), end.getMinutes(), 0);
|
||||
}
|
||||
|
||||
DistributionService.checkDistrib(d);
|
||||
|
||||
if(dispatchEvent) App.current.event(EditDistrib(d));
|
||||
|
||||
if (d.date == null){
|
||||
return d;
|
||||
} else {
|
||||
d.update();
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether there are orders with non zero quantity for non amap contract
|
||||
* @param d -
|
||||
* @return Bool
|
||||
*/
|
||||
public static function canDelete(d:db.Distribution):Bool{
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_CONSTORDERS) return true;
|
||||
|
||||
var quantity = 0.0;
|
||||
for ( order in d.getOrders() ){
|
||||
quantity += order.quantity;
|
||||
}
|
||||
return quantity == 0.0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a distribution
|
||||
* @param d -
|
||||
* @param dispatchEvent=true -
|
||||
*/
|
||||
public static function delete(d:db.Distribution,?dispatchEvent=true) {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( !canDelete(d) ) {
|
||||
throw new tink.core.Error(t._("Deletion non possible: some orders are saved for this delivery."));
|
||||
}
|
||||
|
||||
var contract = d.contract;
|
||||
d.lock();
|
||||
if (dispatchEvent) {
|
||||
App.current.event(DeleteDistrib(d));
|
||||
}
|
||||
d.delete();
|
||||
//In case this is a distrib for an amap contract with payments enabled, it will update all the operations
|
||||
//names and amounts with the new number of distribs
|
||||
updateAmapContractOperations(contract);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the correct start and end dates
|
||||
* @param dc -
|
||||
* @param datePointer -
|
||||
*/
|
||||
public static function getDates(dc:db.DistributionCycle, datePointer:Date) {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var startDate = new Date(datePointer.getFullYear(),datePointer.getMonth(),datePointer.getDate(),dc.startHour.getHours(),dc.startHour.getMinutes(),0);
|
||||
var orderStartDate = null;
|
||||
var orderEndDate = null;
|
||||
if (dc.contract.type == db.Contract.TYPE_VARORDER){
|
||||
|
||||
if (dc.daysBeforeOrderEnd == null || dc.daysBeforeOrderStart == null) throw new tink.core.Error(t._("daysBeforeOrderEnd or daysBeforeOrderStart is null"));
|
||||
|
||||
var a = DateTools.delta(startDate, -1.0 * dc.daysBeforeOrderStart * 1000 * 60 * 60 * 24);
|
||||
var h : Date = dc.openingHour;
|
||||
orderStartDate = new Date(a.getFullYear(), a.getMonth(), a.getDate(), h.getHours(), h.getMinutes(), 0);
|
||||
|
||||
var a = DateTools.delta(startDate, -1.0 * dc.daysBeforeOrderEnd * 1000 * 60 * 60 * 24);
|
||||
var h : Date = dc.closingHour;
|
||||
orderEndDate = new Date(a.getFullYear(), a.getMonth(), a.getDate(), h.getHours(), h.getMinutes(), 0);
|
||||
}
|
||||
return { date: startDate, orderStartDate: orderStartDate, orderEndDate: orderEndDate };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all the distributions from the first date
|
||||
* @param dc -
|
||||
*/
|
||||
public static function createCycleDistribs(dc:db.DistributionCycle) {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
//switch end date to 23:59 to avoid the last distribution to be skipped
|
||||
dc.endDate = tools.DateTool.setHourMinute(dc.endDate,23,59);
|
||||
|
||||
if (dc.id == null) throw new tink.core.Error(t._("this distributionCycle has not been recorded"));
|
||||
|
||||
//iterations
|
||||
//For first distrib
|
||||
var datePointer = new Date(dc.startDate.getFullYear(), dc.startDate.getMonth(), dc.startDate.getDate(), 12, 0, 0);
|
||||
//why hour=12 ? because if we set hour to 0, it switch to 23 (-1) or 1 (+1) on daylight saving time switch dates, thus changing the day!!
|
||||
var firstDistribDate = new Date(datePointer.getFullYear(),datePointer.getMonth(),datePointer.getDate(),dc.startHour.getHours(),dc.startHour.getMinutes(),0);
|
||||
for(i in 0...100) {
|
||||
|
||||
if(i != 0){ //All distribs except the first one
|
||||
var oneDay = 1000 * 60 * 60 * 24.0;
|
||||
switch(dc.cycleType) {
|
||||
case Weekly :
|
||||
datePointer = DateTools.delta(datePointer, oneDay * 7.0);
|
||||
App.log("on ajoute "+(oneDay * 7.0)+"millisec pour ajouter 7 jours");
|
||||
App.log('pointer : $datePointer');
|
||||
|
||||
case BiWeekly :
|
||||
datePointer = DateTools.delta(datePointer, oneDay * 14.0);
|
||||
|
||||
case TriWeekly :
|
||||
datePointer = DateTools.delta(datePointer, oneDay * 21.0);
|
||||
|
||||
case Monthly :
|
||||
var n = tools.DateTool.getWhichNthDayOfMonth(firstDistribDate);
|
||||
var dayOfWeek = firstDistribDate.getDay();
|
||||
var nextMonth = new Date(datePointer.getFullYear(), datePointer.getMonth() + 1, 1, 0, 0, 0);
|
||||
datePointer = tools.DateTool.getNthDayOfMonth(nextMonth.getFullYear(), nextMonth.getMonth(), dayOfWeek, n);
|
||||
if (datePointer.getMonth() != nextMonth.getMonth()) {
|
||||
datePointer = tools.DateTool.getNthDayOfMonth(nextMonth.getFullYear(), nextMonth.getMonth(), dayOfWeek, n - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//stop if cycle end is reached
|
||||
if (datePointer.getTime() > dc.endDate.getTime()) {
|
||||
break;
|
||||
}
|
||||
|
||||
var dates = getDates(dc, datePointer);
|
||||
|
||||
service.DistributionService.create(dc.contract,dates.date,
|
||||
new Date(datePointer.getFullYear(),datePointer.getMonth(),datePointer.getDate(),dc.endHour.getHours(),dc.endHour.getMinutes(),0),
|
||||
dc.place.id,null,null,null,null,dates.orderStartDate,dates.orderEndDate,dc);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all distributions which are part of this cycle
|
||||
* @param cycle -
|
||||
*/
|
||||
public static function deleteCycleDistribs(cycle:db.DistributionCycle){
|
||||
|
||||
cycle.lock();
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var view = App.current.view;
|
||||
|
||||
var children = db.Distribution.manager.search($distributionCycle == cycle, true);
|
||||
var messages = [];
|
||||
if(children.length != 0) {
|
||||
|
||||
var contract = Lambda.array(children)[0].contract;
|
||||
for ( d in children ){
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_VARORDER && !canDelete(d) ){
|
||||
messages.push(t._("The delivery of the ::delivDate:: could not be deleted because it has orders.", {delivDate:view.hDate(d.date)}));
|
||||
}else{
|
||||
d.delete();
|
||||
}
|
||||
}
|
||||
|
||||
//In case this is a distrib cycle for an amap contract with payments enabled, it will update all the operations
|
||||
//names and amounts with the new number of distribs
|
||||
updateAmapContractOperations(contract);
|
||||
|
||||
}
|
||||
cycle.delete();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new distribution cycle and prevents distribution overlapping and other checks
|
||||
* @param contract -
|
||||
* @param cycleType -
|
||||
* @param startDate -
|
||||
* @param endDate -
|
||||
* @param startHour -
|
||||
* @param endHour -
|
||||
* @param daysBeforeOrderStart -
|
||||
* @param daysBeforeOrderEnd -
|
||||
* @param openingHour -
|
||||
* @param closingHour -
|
||||
* @param placeId -
|
||||
* @param dispatchEvent=true -
|
||||
* @return db.DistributionCycle
|
||||
*/
|
||||
public static function createCycle(contract:db.Contract,cycleType:db.DistributionCycle.CycleType,startDate:Date,endDate:Date,
|
||||
startHour:Date,endHour:Date,daysBeforeOrderStart:Null<Int>,daysBeforeOrderEnd:Null<Int>,openingHour:Null<Date>,closingHour:Null<Date>,
|
||||
placeId:Int,?dispatchEvent=true):db.DistributionCycle {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var view = App.current.view;
|
||||
|
||||
var dc = new db.DistributionCycle();
|
||||
dc.contract = contract;
|
||||
dc.cycleType = cycleType;
|
||||
dc.startDate = startDate;
|
||||
dc.endDate = endDate;
|
||||
dc.startHour = startHour;
|
||||
dc.endHour = endHour;
|
||||
dc.place = db.Place.manager.get(placeId);
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
dc.daysBeforeOrderStart = daysBeforeOrderStart;
|
||||
dc.daysBeforeOrderEnd = daysBeforeOrderEnd;
|
||||
dc.openingHour = openingHour;
|
||||
dc.closingHour = closingHour;
|
||||
}
|
||||
|
||||
if (dc.endDate.getTime() > contract.endDate.getTime()) {
|
||||
throw new tink.core.Error(t._("The date of the delivery must be prior to the end of the contract (::contractEndDate::)", {contractEndDate:view.hDate(contract.endDate)}));
|
||||
}
|
||||
if (dc.startDate.getTime() < contract.startDate.getTime()) {
|
||||
throw new tink.core.Error(t._("The date of the delivery must be after the begining of the contract (::contractBeginDate::)", {contractBeginDate:view.hDate(contract.startDate)}));
|
||||
}
|
||||
|
||||
if(dispatchEvent){
|
||||
App.current.event(NewDistribCycle(dc));
|
||||
}
|
||||
|
||||
dc.insert();
|
||||
createCycleDistribs(dc);
|
||||
|
||||
return dc;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package service;
|
||||
using Lambda;
|
||||
using tools.ObjectListTool;
|
||||
/**
|
||||
* Service for managing groups
|
||||
* @author fbarbut
|
||||
*/
|
||||
class GroupService
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* copy groups.
|
||||
* @param g
|
||||
*/
|
||||
public static function duplicateGroup(g:db.Amap){
|
||||
|
||||
var d = new db.Amap();
|
||||
d.name = g.name+" (copy)";
|
||||
d.contact = g.contact;
|
||||
d.txtIntro = g.txtIntro;
|
||||
d.txtHome = g.txtHome;
|
||||
d.txtDistrib = g.txtDistrib;
|
||||
d.extUrl = g.extUrl;
|
||||
d.membershipRenewalDate = g.membershipRenewalDate;
|
||||
d.membershipPrice = g.membershipPrice;
|
||||
d.vatRates = g.vatRates;
|
||||
d.flags = g.flags;
|
||||
d.groupType = g.groupType;
|
||||
d.image = g.image;
|
||||
d.regOption = g.regOption;
|
||||
d.currency = g.currency;
|
||||
d.currencyCode = g.currencyCode;
|
||||
d.allowedPaymentsType = g.allowedPaymentsType;
|
||||
d.checkOrder = g.checkOrder;
|
||||
d.IBAN = g.IBAN;
|
||||
d.insert();
|
||||
|
||||
//put me in the group
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static function duplicateCategories(from:db.Amap,to:db.Amap){
|
||||
|
||||
}
|
||||
|
||||
static function duplicateContract(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Get users with rights in this group
|
||||
**/
|
||||
public static function getGroupMembersWithRights(group:db.Amap,?rights:Array<db.UserAmap.Right>):Array<db.User>{
|
||||
|
||||
var membersWithAnyRights = db.UserAmap.manager.search($rights!=null && $amap==group,false).array();
|
||||
if(rights==null){
|
||||
return Lambda.map(membersWithAnyRights,function(ua) return ua.user).array();
|
||||
}else{
|
||||
var members = [];
|
||||
for( m in membersWithAnyRights){
|
||||
for(r in rights){
|
||||
if(m.hasRight(r)){
|
||||
members.push(m.user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return members.deduplicate();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
package service;
|
||||
import Common;
|
||||
import tink.core.Error;
|
||||
|
||||
/**
|
||||
* Order Service
|
||||
* @author web-wizard,fbarbut
|
||||
*/
|
||||
class OrderService
|
||||
{
|
||||
|
||||
static function canHaveFloatQt(product:db.Product):Bool{
|
||||
return product.hasFloatQt || product.wholesale || product.variablePrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a product Order
|
||||
*
|
||||
* @param quantity
|
||||
* @param productId
|
||||
*/
|
||||
public static function make(user:db.User, quantity:Float, product:db.Product, ?distribId:Int, ?paid:Bool, ?user2:db.User, ?invert:Bool):db.UserContract {
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if(product.contract.type==db.Contract.TYPE_VARORDER && distribId==null) throw "You have to provide a distribId";
|
||||
if(quantity==null) throw "Quantity is null";
|
||||
|
||||
//quantity
|
||||
if ( !canHaveFloatQt(product) ){
|
||||
if( !tools.FloatTool.isInt(quantity) ) {
|
||||
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer",{product:product.name}));
|
||||
}
|
||||
}
|
||||
|
||||
//multiweight : make one row per product
|
||||
if (product.multiWeight && quantity > 1.0){
|
||||
if (product.multiWeight && quantity != Math.abs(quantity)) throw t._("multi-weighing products should be ordered only with integer quantities");
|
||||
|
||||
var o = null;
|
||||
for ( i in 0...Math.round(quantity)){
|
||||
o = make(user, 1, product, distribId, paid, user2, invert);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
//checks
|
||||
if (quantity <= 0) return null;
|
||||
|
||||
//check for previous orders on the same distrib
|
||||
var prevOrders = new List<db.UserContract>();
|
||||
if (distribId == null) {
|
||||
prevOrders = db.UserContract.manager.search($product==product && $user==user, true);
|
||||
}else {
|
||||
prevOrders = db.UserContract.manager.search($product==product && $user==user && $distributionId==distribId, true);
|
||||
}
|
||||
|
||||
//Create order object
|
||||
var o = new db.UserContract();
|
||||
o.product = product;
|
||||
o.quantity = quantity;
|
||||
o.productPrice = product.price;
|
||||
if (product.contract.hasPercentageOnOrders()) {
|
||||
o.feesRate = product.contract.percentageValue;
|
||||
}
|
||||
o.user = user;
|
||||
if (user2 != null) {
|
||||
o.user2 = user2;
|
||||
if (invert != null) o.flags.set(InvertSharedOrder);
|
||||
}
|
||||
if (paid != null) o.paid = paid;
|
||||
if (distribId != null) o.distribution = db.Distribution.manager.get(distribId);
|
||||
|
||||
//cumulate quantities if there is a similar previous order
|
||||
if (prevOrders.length > 0 && !product.multiWeight) {
|
||||
for (prevOrder in prevOrders) {
|
||||
//if (!prevOrder.paid) {
|
||||
o.quantity += prevOrder.quantity;
|
||||
prevOrder.delete();
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
//create a basket object
|
||||
if (distribId != null){
|
||||
var dist = o.distribution;
|
||||
var basket = db.Basket.getOrCreate(user, dist.place, dist.date);
|
||||
o.basket = basket;
|
||||
}
|
||||
|
||||
o.insert();
|
||||
|
||||
//Stocks
|
||||
if (o.product.stock != null) {
|
||||
var c = o.product.contract;
|
||||
if (c.hasStockManagement()) {
|
||||
//trace("stock for "+quantity+" x "+product.name);
|
||||
if (o.product.stock == 0) {
|
||||
if (App.current.session != null) {
|
||||
App.current.session.addMessage(t._("There is no more '::productName::' in stock, we removed it from your order", {productName:o.product.name}), true);
|
||||
}
|
||||
o.quantity -= quantity;
|
||||
if ( o.quantity <= 0 ) {
|
||||
o.delete();
|
||||
return null;
|
||||
}
|
||||
}else if (o.product.stock - quantity < 0) {
|
||||
var canceled = quantity - o.product.stock;
|
||||
o.quantity -= canceled;
|
||||
o.update();
|
||||
|
||||
if (App.current.session != null) {
|
||||
var msg = t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore", {productName:o.product.name, oQuantity:o.quantity});
|
||||
App.current.session.addMessage(msg, true);
|
||||
}
|
||||
o.product.lock();
|
||||
o.product.stock = 0;
|
||||
o.product.update();
|
||||
App.current.event(StockMove({product:o.product, move:0 - (quantity - canceled) }));
|
||||
|
||||
}else {
|
||||
o.product.lock();
|
||||
o.product.stock -= quantity;
|
||||
o.product.update();
|
||||
App.current.event(StockMove({product:o.product, move:0 - quantity}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Edit an existing order (quantity)
|
||||
*/
|
||||
public static function edit(order:db.UserContract, newquantity:Float, ?paid:Bool , ?user2:db.User,?invert:Bool) {
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
order.lock();
|
||||
|
||||
//quantity
|
||||
if (newquantity == null) newquantity = 0;
|
||||
if ( !canHaveFloatQt(order.product) ){
|
||||
if( !tools.FloatTool.isInt(newquantity) ) {
|
||||
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer",{product:order.product.name}));
|
||||
}
|
||||
}
|
||||
|
||||
//paid
|
||||
if (paid != null) {
|
||||
order.paid = paid;
|
||||
}else {
|
||||
if (order.quantity < newquantity) order.paid = false;
|
||||
}
|
||||
|
||||
//shared order
|
||||
if (user2 != null){
|
||||
order.user2 = user2;
|
||||
if (invert == true) order.flags.set(InvertSharedOrder);
|
||||
if (invert == false) order.flags.unset(InvertSharedOrder);
|
||||
}else{
|
||||
order.user2 = null;
|
||||
order.flags.unset(InvertSharedOrder);
|
||||
}
|
||||
|
||||
//stocks
|
||||
var e : Event = null;
|
||||
if (order.product.stock != null) {
|
||||
var c = order.product.contract;
|
||||
|
||||
if (c.hasStockManagement()) {
|
||||
|
||||
if (newquantity < order.quantity) {
|
||||
|
||||
//on commande moins que prévu : incrément de stock
|
||||
order.product.lock();
|
||||
order.product.stock += (order.quantity-newquantity);
|
||||
e = StockMove({product:order.product, move:0 - (order.quantity-newquantity) });
|
||||
|
||||
}else {
|
||||
|
||||
//on commande plus que prévu : décrément de stock
|
||||
var addedquantity = newquantity - order.quantity;
|
||||
|
||||
if (order.product.stock - addedquantity < 0) {
|
||||
|
||||
//stock is not enough, reduce order
|
||||
newquantity = order.quantity + order.product.stock;
|
||||
if( App.current.session!=null) App.current.session.addMessage(t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore", {productName:order.product.name, oQuantity:newquantity}), true);
|
||||
|
||||
e = StockMove({product:order.product, move: 0 - order.product.stock });
|
||||
|
||||
order.product.lock();
|
||||
order.product.stock = 0;
|
||||
|
||||
}else{
|
||||
|
||||
//stock is big enough
|
||||
order.product.lock();
|
||||
order.product.stock -= addedquantity;
|
||||
|
||||
e = StockMove({ product:order.product, move: 0 - addedquantity });
|
||||
}
|
||||
}
|
||||
order.product.update();
|
||||
}
|
||||
}
|
||||
|
||||
//update order
|
||||
if (newquantity == 0) {
|
||||
order.quantity = 0;
|
||||
order.paid = true;
|
||||
order.update();
|
||||
}else {
|
||||
order.quantity = newquantity;
|
||||
order.update();
|
||||
}
|
||||
|
||||
App.current.event(e);
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an order
|
||||
*/
|
||||
public static function delete(order:db.UserContract) {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if(order==null) throw new Error(t._("This order has already been deleted."));
|
||||
|
||||
order.lock();
|
||||
|
||||
if (order.quantity == 0) {
|
||||
|
||||
var contract = order.product.contract;
|
||||
var user = order.user;
|
||||
|
||||
//Amap Contract
|
||||
if ( contract.type == db.Contract.TYPE_CONSTORDERS ) {
|
||||
|
||||
order.delete();
|
||||
|
||||
if( contract.amap.hasPayments() ){
|
||||
var orders = contract.getUserOrders(user);
|
||||
if( orders.length == 0 ){
|
||||
var operation = db.Operation.findCOrderTransactionFor(contract, user);
|
||||
if(operation!=null) operation.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
else { //Variable orders contract
|
||||
|
||||
//Get the basket for this user
|
||||
var place = order.distribution.place;
|
||||
var basket = db.Basket.get(user, place, order.distribution.date);
|
||||
|
||||
if( contract.amap.hasPayments() ){
|
||||
var orders = basket.getOrders();
|
||||
//Check if it is the last order, if yes then delete the related operation
|
||||
if( orders.length == 1 && orders.first().id==order.id ){
|
||||
var operation = db.Operation.findVOrderTransactionFor(order.distribution.getKey(), user, place.amap);
|
||||
if(operation!=null) operation.delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
order.delete();
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(t._("Deletion not possible: quantity is not zero."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a simple dataset, ready to be displayed
|
||||
*/
|
||||
public static function prepare(orders:Iterable<db.UserContract>):Array<UserOrder> {
|
||||
var out = new Array<UserOrder>();
|
||||
var orders = Lambda.array(orders);
|
||||
var view = App.current.view;
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
for (o in orders) {
|
||||
|
||||
var x : UserOrder = cast { };
|
||||
x.id = o.id;
|
||||
x.userId = o.user.id;
|
||||
x.userName = o.user.getCoupleName();
|
||||
x.userEmail = o.user.email;
|
||||
|
||||
//shared order
|
||||
if (o.user2 != null){
|
||||
x.userId2 = o.user2.id;
|
||||
x.userName2 = o.user2.getCoupleName();
|
||||
x.userEmail2 = o.user2.email;
|
||||
}
|
||||
|
||||
//deprecated
|
||||
x.productId = o.product.id;
|
||||
x.productRef = o.product.ref;
|
||||
x.productQt = o.product.qt;
|
||||
x.productUnit = o.product.unitType;
|
||||
x.productPrice = o.productPrice;
|
||||
x.productImage = o.product.getImage();
|
||||
x.productHasFloatQt = o.product.hasFloatQt;
|
||||
x.productHasVariablePrice = o.product.variablePrice;
|
||||
//new way
|
||||
x.product = o.product.infos();
|
||||
x.product.price = o.productPrice;//do not use current price, but price of the order
|
||||
|
||||
|
||||
x.quantity = o.quantity;
|
||||
|
||||
//smartQt
|
||||
if (x.quantity == 0.0){
|
||||
x.smartQt = t._("Canceled");
|
||||
}else if(x.productHasFloatQt || x.productHasVariablePrice || o.product.wholesale){
|
||||
x.smartQt = view.smartQt(x.quantity, x.productQt, x.productUnit);
|
||||
}else{
|
||||
x.smartQt = Std.string(x.quantity);
|
||||
}
|
||||
|
||||
//product name.
|
||||
if ( x.productHasVariablePrice || x.productQt==null || x.productUnit==null ){
|
||||
x.productName = o.product.name;
|
||||
}else{
|
||||
x.productName = o.product.name + " " + view.formatNum(x.productQt) +" "+ view.unit(x.productUnit,x.productQt>1);
|
||||
}
|
||||
|
||||
x.subTotal = o.quantity * o.productPrice;
|
||||
|
||||
var c = o.product.contract;
|
||||
|
||||
if ( o.feesRate!=0 ) {
|
||||
|
||||
x.fees = x.subTotal * (o.feesRate/100);
|
||||
x.percentageName = c.percentageName;
|
||||
x.percentageValue = o.feesRate;
|
||||
x.total = x.subTotal + x.fees;
|
||||
|
||||
}else {
|
||||
x.total = x.subTotal;
|
||||
}
|
||||
|
||||
//flags
|
||||
x.paid = o.paid;
|
||||
x.invertSharedOrder = o.flags.has(InvertSharedOrder);
|
||||
x.contractId = c.id;
|
||||
x.contractName = c.name;
|
||||
x.canModify = o.canModify();
|
||||
|
||||
out.push(x);
|
||||
}
|
||||
|
||||
return sort(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms an order : create real orders from tmp orders in session
|
||||
* @param order
|
||||
*/
|
||||
public static function confirmSessionOrder(tmpOrder:OrderInSession){
|
||||
var orders = [];
|
||||
var user = db.User.manager.get(tmpOrder.userId);
|
||||
for (o in tmpOrder.products){
|
||||
o.product = db.Product.manager.get(o.productId);
|
||||
orders.push( make(user, o.quantity, o.product, o.distributionId) );
|
||||
}
|
||||
|
||||
App.current.event(MakeOrder(orders));
|
||||
App.current.session.data.order = null;
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an order-by-products report to the coordinator
|
||||
*/
|
||||
public static function sendOrdersByProductReport(d:db.Distribution){
|
||||
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.addRecipient(d.contract.contact.email , d.contract.contact.getName());
|
||||
m.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
m.setSubject('[${d.contract.amap.name}] Distribution du ${App.current.view.dDate(d.date)} (${d.contract.name})');
|
||||
var orders = service.ReportService.getOrdersByProduct(d);
|
||||
|
||||
var html = App.current.processTemplate("mail/ordersByProduct.mtt", {
|
||||
contract:d.contract,
|
||||
distribution:d,
|
||||
orders:orders,
|
||||
formatNum:App.current.view.formatNum,
|
||||
currency:App.current.view.currency,
|
||||
dDate:App.current.view.dDate,
|
||||
hHour:App.current.view.hHour,
|
||||
group:d.contract.amap
|
||||
} );
|
||||
|
||||
m.setHtmlBody(html);
|
||||
App.sendMail(m);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Order summary for a member
|
||||
* WARNING : its for one distrib, not for a whole basket !
|
||||
*/
|
||||
public static function sendOrderSummaryToMembers(d:db.Distribution){
|
||||
|
||||
var title = '[${d.contract.amap.name}] Votre commande pour le ${App.current.view.dDate(d.date)} (${d.contract.name})';
|
||||
|
||||
for( user in d.getUsers() ){
|
||||
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.addRecipient(user.email , user.getName(),user.id);
|
||||
if(user.email2!=null) m.addRecipient(user.email2 , user.getName(),user.id);
|
||||
m.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
m.setSubject(title);
|
||||
var orders = prepare(d.contract.getUserOrders(user,d));
|
||||
|
||||
var html = App.current.processTemplate("mail/orderSummaryForMember.mtt", {
|
||||
contract:d.contract,
|
||||
distribution:d,
|
||||
orders:orders,
|
||||
formatNum:App.current.view.formatNum,
|
||||
currency:App.current.view.currency,
|
||||
dDate:App.current.view.dDate,
|
||||
hHour:App.current.view.hHour,
|
||||
group:d.contract.amap
|
||||
} );
|
||||
|
||||
m.setHtmlBody(html);
|
||||
App.sendMail(m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function sort(orders:Array<UserOrder>){
|
||||
|
||||
//order by lastname (+lastname2 if exists), then contract
|
||||
orders.sort(function(a, b) {
|
||||
|
||||
if (a.userName + a.userId + a.userName2 + a.userId2 + a.contractId > b.userName + b.userId + b.userName2 + b.userId2 + b.contractId ) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
if (a.userName + a.userId + a.userName2 + a.userId2 + a.contractId < b.userName + b.userId + b.userName2 + b.userId2 + b.contractId ) {
|
||||
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package service;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Payment Service
|
||||
* @author web-wizard
|
||||
*/
|
||||
class PaymentService
|
||||
{
|
||||
/**
|
||||
* Get all available payment types, including one from plugins
|
||||
*/
|
||||
public static function getAllPaymentTypes(){
|
||||
var types = [
|
||||
new payment.Cash(),
|
||||
new payment.Check(),
|
||||
new payment.Transfer(),
|
||||
new payment.MoneyPot(),
|
||||
];
|
||||
|
||||
var e = App.current.event(GetPaymentTypes({types:types}));
|
||||
return switch(e){
|
||||
case GetPaymentTypes(d): d.types;
|
||||
default : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function getAllowedPaymentTypes(group:db.Amap):Array<payment.Payment>{
|
||||
var out :Array<payment.Payment> = [];
|
||||
|
||||
//populate with activated payment types.
|
||||
var all = getAllPaymentTypes();
|
||||
if ( group.allowedPaymentsType == null ) return [];
|
||||
for ( t in group.allowedPaymentsType){
|
||||
|
||||
var found = Lambda.find(all, function(a) return a.type == t);
|
||||
if (found != null) out.push(found);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function getPaymentTypesForManualEntry(group:db.Amap){
|
||||
|
||||
var out = [];
|
||||
var paymentTypes = [];
|
||||
var allowedPaymentTypes = service.PaymentService.getAllowedPaymentTypes(group);
|
||||
if ( !Lambda.exists(allowedPaymentTypes, function(obj) return obj.type == "moneypot" ) ) {
|
||||
paymentTypes = allowedPaymentTypes;
|
||||
}
|
||||
else {
|
||||
paymentTypes = service.PaymentService.getAllPaymentTypes();
|
||||
}
|
||||
for ( t in paymentTypes ){
|
||||
if(t.type != "moneypot") out.push({label:t.name,value:t.type});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto validate a distribution.
|
||||
* This is called by the hourly cron
|
||||
*
|
||||
* @param distrib
|
||||
*/
|
||||
public static function validateDistribution(distrib:db.Distribution) {
|
||||
|
||||
for ( user in distrib.getUsers()){
|
||||
var basket = db.Basket.get(user, distrib.place, distrib.date);
|
||||
validateBasket(basket);
|
||||
}
|
||||
//finally validate distrib
|
||||
distrib.lock();
|
||||
distrib.validated = true;
|
||||
distrib.update();
|
||||
}
|
||||
|
||||
public static function unvalidateDistribution(distrib:db.Distribution) {
|
||||
|
||||
for ( user in distrib.getUsers()){
|
||||
var basket = db.Basket.get(user, distrib.place, distrib.date);
|
||||
unvalidateBasket(basket);
|
||||
}
|
||||
//finally validate distrib
|
||||
distrib.lock();
|
||||
distrib.validated = false;
|
||||
distrib.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto validate a basket
|
||||
*
|
||||
* @param basket
|
||||
*/
|
||||
public static function validateBasket(basket:db.Basket) {
|
||||
|
||||
if (basket == null || basket.isValidated()) return false;
|
||||
|
||||
//mark orders as paid
|
||||
var orders = basket.getOrders();
|
||||
for ( order in orders ){
|
||||
|
||||
order.lock();
|
||||
order.paid = true;
|
||||
order.update();
|
||||
}
|
||||
|
||||
//validate order operation and payments
|
||||
var operation = basket.getOrderOperation(false);
|
||||
if (operation != null){
|
||||
|
||||
operation.lock();
|
||||
operation.pending = false;
|
||||
operation.update();
|
||||
|
||||
for ( payment in basket.getPayments()){
|
||||
|
||||
if ( payment.pending){
|
||||
payment.lock();
|
||||
payment.pending = false;
|
||||
payment.update();
|
||||
}
|
||||
}
|
||||
|
||||
var o = orders.first();
|
||||
updateUserBalance(o.user, o.distribution.place.amap);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function unvalidateBasket(basket:db.Basket) {
|
||||
|
||||
if (basket == null || !basket.isValidated()) return false;
|
||||
|
||||
//mark orders as paid
|
||||
var orders = basket.getOrders();
|
||||
for ( order in orders ){
|
||||
|
||||
order.lock();
|
||||
order.paid = false;
|
||||
order.update();
|
||||
}
|
||||
|
||||
//validate order operation and payments
|
||||
var operation = basket.getOrderOperation(false);
|
||||
if (operation != null){
|
||||
|
||||
operation.lock();
|
||||
operation.pending = true;
|
||||
operation.update();
|
||||
|
||||
for ( payment in basket.getPayments()){
|
||||
|
||||
if (!payment.pending){
|
||||
payment.lock();
|
||||
payment.pending = true;
|
||||
payment.update();
|
||||
}
|
||||
}
|
||||
|
||||
var o = orders.first();
|
||||
updateUserBalance(o.user, o.distribution.place.amap);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* update user balance
|
||||
*/
|
||||
public static function updateUserBalance(user:db.User,group:db.Amap){
|
||||
|
||||
var ua = db.UserAmap.getOrCreate(user, group);
|
||||
var b = sys.db.Manager.cnx.request('SELECT SUM(amount) FROM Operation WHERE userId=${user.id} and groupId=${group.id} and !(type=2 and pending=1)').getFloatResult(0);
|
||||
b = Math.round(b * 100) / 100;
|
||||
ua.balance = b;
|
||||
ua.update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package service;
|
||||
|
||||
class PlaceService{
|
||||
|
||||
/**
|
||||
* Geocode a place with Google Geocode API
|
||||
*/
|
||||
public static function geocode(p:db.Place):{lat:Float,lng:Float}{
|
||||
var apiKey = App.config.get("google_geocoding_key");
|
||||
if(apiKey==null) return null;
|
||||
|
||||
var gc = new sugoi.apis.google.GeoCode(apiKey);
|
||||
var address = p.getAddress();
|
||||
//var comp = "administrative_area:" + p.city + "|postal_code:" + p.zipCode + "|country:FR";
|
||||
//Sys.print(address+"<br/>"+comp+"<br/>");
|
||||
|
||||
var geo = gc.geocode( address , null);
|
||||
var coords = geo[0].geometry.location;
|
||||
|
||||
p.lock();
|
||||
p.lat = coords.lat;
|
||||
p.lng = coords.lng;
|
||||
p.update();
|
||||
|
||||
return {lat:p.lat,lng:p.lng};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package service;
|
||||
|
||||
class ProductService{
|
||||
|
||||
|
||||
/**
|
||||
* Batch disable products
|
||||
*/
|
||||
public static function batchDisableProducts(productIds:Array<Int>){
|
||||
|
||||
var data = {pids:productIds,enable:false};
|
||||
var contract = db.Product.manager.get(productIds[0], true).contract;
|
||||
var products = contract.getProducts(false);
|
||||
|
||||
App.current.event( BatchEnableProducts(data) );
|
||||
|
||||
for ( pid in data.pids){
|
||||
|
||||
var p = db.Product.manager.get(pid, true);
|
||||
|
||||
if ( Lambda.find(products,function(p) return p.id==pid)==null ) throw 'product $pid is not in this contract !';
|
||||
|
||||
p.active = false;
|
||||
p.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Batch enable products
|
||||
*/
|
||||
public static function batchEnableProducts(productIds:Array<Int>){
|
||||
|
||||
var data = {pids:productIds,enable:true};
|
||||
var contract = db.Product.manager.get(productIds[0], true).contract;
|
||||
var products = contract.getProducts(false);
|
||||
|
||||
App.current.event( BatchEnableProducts(data) );
|
||||
|
||||
for ( pid in data.pids){
|
||||
|
||||
var p = db.Product.manager.get(pid, true);
|
||||
|
||||
if ( Lambda.find(products,function(p) return p.id==pid)==null ) throw 'product $pid is not in this contract !';
|
||||
|
||||
p.active = true;
|
||||
p.update();
|
||||
}
|
||||
}
|
||||
|
||||
inline public static function getHTPrice(ttcPrice:Float,vatRate:Float):Float{
|
||||
return ttcPrice / (1 + vatRate / 100);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package service;
|
||||
import Common;
|
||||
|
||||
class ReportService{
|
||||
|
||||
/**
|
||||
Get orders grouped by products.
|
||||
*/
|
||||
public static function getOrdersByProduct( distribution:db.Distribution, ?csv = false):Array<OrderByProduct>{
|
||||
var view = App.current.view;
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var where = "";
|
||||
|
||||
var exportName = t._("Delivery ::contractName:: of the ", {contractName:distribution.contract.name}) + distribution.date.toString().substr(0, 10);
|
||||
where += ' and p.contractId = ${distribution.contract.id}';
|
||||
if (distribution.contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
where += ' and up.distributionId = ${distribution.id}';
|
||||
}
|
||||
|
||||
//Product price will be an average if price changed
|
||||
var sql = 'select
|
||||
SUM(quantity) as quantity,
|
||||
MAX(p.id) as pid,
|
||||
p.name as pname,
|
||||
AVG(up.productPrice) as price,
|
||||
AVG(p.vat) as vat,
|
||||
p.ref as ref,
|
||||
SUM(quantity*up.productPrice) as totalTTC
|
||||
from UserContract up, Product p
|
||||
where up.productId = p.id
|
||||
$where
|
||||
group by ref,pname,price
|
||||
order by pname asc;';
|
||||
|
||||
var res = sys.db.Manager.cnx.request(sql).results();
|
||||
var orders = [];
|
||||
|
||||
//populate with full product names
|
||||
for ( r in res){
|
||||
|
||||
var o : OrderByProduct = {
|
||||
quantity:1.0 * r.quantity,
|
||||
smartQt:"",
|
||||
pid:r.pid,
|
||||
pname:r.pname,
|
||||
ref:r.ref,
|
||||
priceHT: service.ProductService.getHTPrice(r.price,r.vat),
|
||||
priceTTC: r.price,
|
||||
vat:r.vat,
|
||||
totalTTC : r.totalTTC,
|
||||
totalHT : service.ProductService.getHTPrice( r.totalTTC ,r.vat),
|
||||
weightOrVolume:"",
|
||||
};
|
||||
|
||||
//smartQt
|
||||
var p = db.Product.manager.get(r.pid, false);
|
||||
if( p.hasFloatQt || p.variablePrice ){
|
||||
o.smartQt = view.smartQt(o.quantity, p.qt, p.unitType);
|
||||
}else{
|
||||
o.smartQt = Std.string(o.quantity);
|
||||
}
|
||||
o.weightOrVolume = view.smartQt(o.quantity, p.qt, p.unitType);
|
||||
|
||||
if ( /*p.hasFloatQt || p.variablePrice ||*/ p.qt==null || p.unitType==null){
|
||||
o.pname = p.name;
|
||||
}else{
|
||||
o.pname = p.name + " " + view.formatNum(p.qt) +" " + view.unit(p.unitType, o.quantity > 1);
|
||||
}
|
||||
|
||||
//special case : if product is multiweight, we should count the records number ( and not SUM quantities )
|
||||
if (p.multiWeight){
|
||||
sql = 'select
|
||||
COUNT(up.id) as quantity
|
||||
from UserContract up, Product p
|
||||
where up.productId = p.id and up.quantity > 0 and p.id=${p.id}
|
||||
$where';
|
||||
var count = sys.db.Manager.cnx.request(sql).getIntResult(0);
|
||||
o.smartQt = ""+count;
|
||||
}
|
||||
|
||||
orders.push(o);
|
||||
}
|
||||
|
||||
if (csv) {
|
||||
var data = new Array<Dynamic>();
|
||||
for (o in orders) {
|
||||
data.push({
|
||||
"quantity":view.formatNum(o.quantity),
|
||||
"pname":o.pname,
|
||||
"ref":o.ref,
|
||||
"priceHT":view.formatNum(o.priceHT),
|
||||
"priceTTC":view.formatNum(o.priceTTC),
|
||||
"totalHT":view.formatNum(o.totalHT),
|
||||
"totalTTC":view.formatNum(o.totalTTC),
|
||||
});
|
||||
}
|
||||
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(data, ["quantity", "pname","ref", "priceHT","priceTTC","totalHT","totalTTC"],"Export-"+exportName+"-par produits");
|
||||
return null;
|
||||
}else{
|
||||
return orders;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getTurnoverFromOrdersByProducts(ordersByProduct:Array<OrderByProduct>):{turnoverHT:Float,turnoverTTC:Float}{
|
||||
var out = {turnoverHT:0.0,turnoverTTC:0.0};
|
||||
for( o in ordersByProduct){
|
||||
out.turnoverHT += o.totalHT;
|
||||
out.turnoverTTC += o.totalTTC;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package service;
|
||||
import tink.core.Error;
|
||||
|
||||
/**
|
||||
* User Service
|
||||
* @author fbarbut
|
||||
*/
|
||||
class UserService
|
||||
{
|
||||
|
||||
var user : db.User;
|
||||
|
||||
public function new(u:db.User)
|
||||
{
|
||||
this.user = u;
|
||||
}
|
||||
|
||||
/**
|
||||
* User login service
|
||||
* @param email
|
||||
* @param password
|
||||
*/
|
||||
public static function login(email:String, password:String){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
//user exists ?
|
||||
var user = db.User.manager.select( $email == email || $email2 == email , true);
|
||||
if (user == null) throw new Error(404,t._("There is no account with this email"));
|
||||
|
||||
//new account
|
||||
if (!user.isFullyRegistred()) {
|
||||
var group = user.getAmaps().first();
|
||||
user.sendInvitation(group);
|
||||
var text = t._("Your account have not been validated yet. We sent an e-mail to ::email:: to finalize your subscription!",{email:user.email});
|
||||
throw new Error(403,text);
|
||||
}
|
||||
|
||||
var pass = haxe.crypto.Md5.encode( App.config.get('key') + password );
|
||||
|
||||
if (user.pass != pass) {
|
||||
throw new Error(403,t._("Invalid password"));
|
||||
}
|
||||
|
||||
db.User.login(user, email);
|
||||
|
||||
//register the user to the current group if needed
|
||||
var group = App.current.getCurrentGroup();
|
||||
if (group != null && group.regOption == db.Amap.RegOption.Open && db.UserAmap.get(user, group) == null){
|
||||
user.makeMemberOf(group);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Full registration by a user himself
|
||||
**/
|
||||
public static function register(firstName:String, lastName:String, email:String, phone:String, pass:String){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if (!sugoi.form.validators.EmailValidator.check(email)){
|
||||
throw new Error(500,t._("Invalid email address"));
|
||||
}
|
||||
|
||||
if ( db.User.getSameEmail(email).length > 0 ) {
|
||||
throw new Error(409,t._("We already have an account with this email address"));
|
||||
}
|
||||
|
||||
var user = new db.User();
|
||||
user.email = email;
|
||||
user.firstName = firstName;
|
||||
user.lastName = lastName;
|
||||
user.phone = phone;
|
||||
user.setPass(pass);
|
||||
user.insert();
|
||||
|
||||
var group = App.current.getCurrentGroup();
|
||||
if (group != null && group.regOption == db.Amap.RegOption.Open){
|
||||
user.makeMemberOf(group);
|
||||
}
|
||||
|
||||
db.User.login(user, email);
|
||||
}
|
||||
|
||||
/**
|
||||
Soft registration :
|
||||
- Somebody creates/import a new user ,
|
||||
- or pre-registration in a waiting list
|
||||
**/
|
||||
public static function softRegistration(firstName:String, lastName:String, email:String){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if (!sugoi.form.validators.EmailValidator.check(email)){
|
||||
throw new Error(500,t._("Invalid email address"));
|
||||
}
|
||||
|
||||
if ( db.User.getSameEmail(email).length > 0 ) {
|
||||
throw new Error(409,t._("We already have an account with this email address"));
|
||||
}
|
||||
|
||||
var user = new db.User();
|
||||
user.email = email;
|
||||
user.firstName = firstName;
|
||||
user.lastName = lastName;
|
||||
user.insert();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* get users belonging to a group
|
||||
* @param group -
|
||||
* @return Array<db.User>
|
||||
*/
|
||||
public static function getFromGroup(group:db.Amap):Array<db.User>{
|
||||
return Lambda.array( group.getMembers() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package service;
|
||||
import tink.core.Error;
|
||||
import db.UserAmap.Right;
|
||||
|
||||
class WaitingListService{
|
||||
|
||||
|
||||
public static function registerToWl(user:db.User,group:db.Amap,message:String){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
canRegister(user,group);
|
||||
|
||||
var w = new db.WaitingList();
|
||||
w.user = user;
|
||||
w.group = group;
|
||||
w.message = message;
|
||||
w.insert();
|
||||
|
||||
//emails
|
||||
var html = t._("<p><b>::name::</b> suscribed to the waiting list of <b>::group::</b> on ::date::</p>",{
|
||||
group:group.name,
|
||||
name:user.name,
|
||||
date:App.current.view.hDate(Date.now())
|
||||
});
|
||||
if(message!=null && message!=""){
|
||||
html += t._("<p>He/she left this message :<br/>\"::message::\"</p>",{message:message});
|
||||
}
|
||||
|
||||
for( u in service.GroupService.getGroupMembersWithRights(group,[Right.GroupAdmin,Right.Membership]) ){
|
||||
|
||||
App.quickMail(
|
||||
u.email,
|
||||
t._("[::group::] ::name:: suscribed to the waiting list.",{group:group.name,name:user.name}),
|
||||
html,
|
||||
group
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function canRegister(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if ( db.WaitingList.manager.select($amapId == group.id && $user == user) != null) {
|
||||
throw new Error(t._("You are already in the waiting list of this group"));
|
||||
}
|
||||
if ( db.UserAmap.manager.select($amapId == group.id && $user == user) != null) {
|
||||
throw new Error(t._("You are already member of this group."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
the user cancels his request
|
||||
**/
|
||||
public static function removeFromWl(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( user == null) {
|
||||
throw new Error(t._("You should be logged in."));
|
||||
}
|
||||
|
||||
var wl = db.WaitingList.manager.select($amapId == group.id && $user == user,true);
|
||||
|
||||
if ( wl == null) {
|
||||
throw new Error(t._("You are not in the waiting list of this group"));
|
||||
}
|
||||
wl.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
an admin cancels a request
|
||||
**/
|
||||
public static function cancelRequest(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( user == null) throw "user is null";
|
||||
var wl = db.WaitingList.manager.select($amapId == group.id && $user == user,true);
|
||||
if ( wl == null) throw "this user is not in waiting list";
|
||||
|
||||
//email the requester
|
||||
App.quickMail(
|
||||
wl.user.email,
|
||||
t._("[::group::] Membership request refused.",{group:group.name}),
|
||||
t._("Your membership request for <b>::group::</b> has been refused.",{group:group.name})
|
||||
);
|
||||
|
||||
//email others admin
|
||||
for( u in service.GroupService.getGroupMembersWithRights(group,[Right.GroupAdmin,Right.Membership]) ){
|
||||
if(u.id==App.current.user.id) continue;
|
||||
App.quickMail(
|
||||
u.email,
|
||||
t._("[::group::] ::name:: membership request has been refused by ::admin::.",{group:group.name, name:user.name, admin:App.current.user.name}),
|
||||
t._("<p><b>::name::</b> was registred to the waiting list.</p><p><b>::admin::</b> has refused his/her request.</p>",{name:user.name, admin:App.current.user.name}),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
wl.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
an admin approves a request
|
||||
**/
|
||||
public static function approveRequest(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( user == null) throw "user is null";
|
||||
var wl = db.WaitingList.manager.select($amapId == group.id && $user == user,true);
|
||||
if ( wl == null) throw "this user is not in waiting list";
|
||||
|
||||
if (db.UserAmap.get(user, group, false) == null){
|
||||
var ua = new db.UserAmap();
|
||||
ua.amap = wl.group;
|
||||
ua.user = wl.user;
|
||||
ua.insert();
|
||||
}
|
||||
|
||||
wl.delete();
|
||||
|
||||
//email the requester
|
||||
App.quickMail(
|
||||
wl.user.email,
|
||||
t._("[::group::] Membership request accepted.",{group:group.name}),
|
||||
t._("<p>Your membership request for <b>::group::</b> has been accepted !</p><p>You're now a member of the group.</p>",{group:group.name}),
|
||||
group
|
||||
);
|
||||
|
||||
//email others admin
|
||||
for( u in service.GroupService.getGroupMembersWithRights(group,[Right.GroupAdmin,Right.Membership]) ){
|
||||
if(u.id==App.current.user.id) continue;
|
||||
App.quickMail(
|
||||
u.email,
|
||||
t._("[::group::] ::name:: membership request has been accepted by ::admin::.",{group:group.name, name:user.name, admin:App.current.user.name}),
|
||||
t._("<p><b>::name::</b> was registred to the waiting list.</p><p><b>::admin::</b> has accepted his/her request.</p>",{name:user.name, admin:App.current.user.name}),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
wl.delete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package test;
|
||||
|
||||
/**
|
||||
* Test distribution creation
|
||||
*
|
||||
* @author web-wizard
|
||||
*/
|
||||
class TestDistributions extends haxe.unit.TestCase
|
||||
{
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
override function setup(){
|
||||
TestSuite.initDB();
|
||||
TestSuite.initDatas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we can add a distribution outside existing time range for a specific contract and place
|
||||
* Check that we can't add a distribution overlapping with existing distribution for a specific contract and place
|
||||
*/
|
||||
function testOverlapping() {
|
||||
var existingDistrib = TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE;
|
||||
var e1 = null;
|
||||
try {
|
||||
var distrib1 = service.DistributionService.create(existingDistrib.contract,new Date(2018, 5, 1, 18, 0, 0),new Date(2018, 5, 1, 18, 30, 0),
|
||||
existingDistrib.place.id,null,null,null,null,new Date(2018, 4, 1, 18, 0, 0),new Date(2018, 4, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error) {
|
||||
e1 = x;
|
||||
}
|
||||
assertEquals(e1, null);
|
||||
|
||||
//existingDistrib.date <= distrib2.date && existingDistrib.end >= distrib2.date
|
||||
var e2 = null;
|
||||
try {
|
||||
var distrib2 = service.DistributionService.create(existingDistrib.contract,new Date(2017, 5, 1, 19, 30, 0),new Date(2017, 5, 1, 20, 30, 0),
|
||||
existingDistrib.place.id,null,null,null,null,new Date(2017, 4, 1, 18, 0, 0),new Date(2017, 4, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error) {
|
||||
e2 = x;
|
||||
}
|
||||
assertEquals(e2.message, "There is already a distribution at this place overlapping with the time range you've selected.");
|
||||
|
||||
//existingDistrib.date <= distrib3.end && existingDistrib.end >= distrib3.end
|
||||
var e3 = null;
|
||||
try{
|
||||
var distrib3 = service.DistributionService.create(existingDistrib.contract,new Date(2017, 5, 1, 17, 30, 0),new Date(2017, 5, 1, 19, 30, 0),
|
||||
existingDistrib.place.id,null,null,null,null,new Date(2017, 4, 1, 18, 0, 0),new Date(2017, 4, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e3 = x;
|
||||
}
|
||||
assertEquals(e3.message, "There is already a distribution at this place overlapping with the time range you've selected.");
|
||||
|
||||
//existingDistrib.date >= distrib4.date && existingDistrib.end <= distrib4.end
|
||||
var e4 = null;
|
||||
try{
|
||||
var distrib4 = service.DistributionService.create(existingDistrib.contract,new Date(2017, 5, 1, 17, 30, 0),new Date(2017, 5, 1, 21, 30, 0),
|
||||
existingDistrib.place.id,null,null,null,null,new Date(2017, 4, 1, 18, 0, 0),new Date(2017, 4, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e4 = x;
|
||||
}
|
||||
assertEquals(e4.message, "There is already a distribution at this place overlapping with the time range you've selected.");
|
||||
|
||||
//existingDistrib.date > distrib5.end
|
||||
var e5 = null;
|
||||
try{
|
||||
var distrib5 = service.DistributionService.create(existingDistrib.contract,new Date(2017, 5, 1, 17, 30, 0),new Date(2017, 5, 1, 18, 59, 0),
|
||||
existingDistrib.place.id,null,null,null,null,new Date(2017, 4, 1, 18, 0, 0),new Date(2017, 4, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e5 = x;
|
||||
}
|
||||
assertEquals(e5, null);
|
||||
|
||||
//existingDistrib.date > distrib6.end
|
||||
var e6 = null;
|
||||
try{
|
||||
var distrib6 = service.DistributionService.create(existingDistrib.contract,new Date(2017, 3, 1, 17, 30, 0),new Date(2017, 3, 1, 18, 59, 0),
|
||||
existingDistrib.place.id,null,null,null,null,new Date(2017, 2, 1, 18, 0, 0),new Date(2017, 3, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e6 = x;
|
||||
}
|
||||
assertEquals(e6.message, "The distribution start date must be set after the orders end date.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that we can add a distribution outside existing time range for a specific contract and place
|
||||
* Check that we can't add a distribution overlapping with existing distribution for a specific contract and place
|
||||
*/
|
||||
function testEdit() {
|
||||
|
||||
//TO DO
|
||||
assertEquals(true, true);
|
||||
}
|
||||
|
||||
function testCreateCycle() {
|
||||
TestSuite.CONTRAT_LEGUMES.startDate = new Date(2018, 0, 1, 0, 0, 0);
|
||||
TestSuite.CONTRAT_LEGUMES.endDate = new Date(2019, 11, 31, 23, 59, 0);
|
||||
TestSuite.CONTRAT_LEGUMES.update();
|
||||
|
||||
var weeklyDistribCycle = service.DistributionService.createCycle(TestSuite.CONTRAT_LEGUMES,Weekly,new Date(2018, 11, 24, 0, 0, 0),
|
||||
new Date(2019, 0, 24, 0, 0, 0),new Date(2018, 5, 4, 13, 0, 0),new Date(2018, 5, 4, 14, 0, 0),10,2,
|
||||
new Date(2018, 5, 4, 8, 0, 0),new Date(2018, 5, 4, 23, 0, 0),TestSuite.PLACE_DU_VILLAGE.id);
|
||||
|
||||
var weeklyDistribs = Lambda.array(db.Distribution.manager.search($distributionCycle == weeklyDistribCycle, false));
|
||||
assertEquals(weeklyDistribs.length, 5);
|
||||
assertEquals(weeklyDistribs[0].date.toString(), new Date(2018, 11, 24, 13, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[0].end.toString(), new Date(2018, 11, 24, 14, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[1].date.toString(), new Date(2018, 11, 31, 13, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[1].end.toString(), new Date(2018, 11, 31, 14, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[2].date.toString(), new Date(2019, 0, 7, 13, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[2].end.toString(), new Date(2019, 0, 7, 14, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[3].date.toString(), new Date(2019, 0, 14, 13, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[3].end.toString(), new Date(2019, 0, 14, 14, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[4].date.toString(), new Date(2019, 0, 21, 13, 0, 0).toString());
|
||||
assertEquals(weeklyDistribs[4].end.toString(), new Date(2019, 0, 21, 14, 0, 0).toString());
|
||||
service.DistributionService.deleteCycleDistribs(weeklyDistribCycle);
|
||||
|
||||
var monthlyDistribCycle = service.DistributionService.createCycle(TestSuite.CONTRAT_LEGUMES,Monthly,new Date(2018, 9, 30, 0, 0, 0),
|
||||
new Date(2019, 2, 31, 0, 0, 0),new Date(2018, 5, 4, 13, 0, 0),new Date(2018, 5, 4, 14, 0, 0),10,2,
|
||||
new Date(2018, 5, 4, 8, 0, 0),new Date(2018, 5, 4, 23, 0, 0),TestSuite.PLACE_DU_VILLAGE.id);
|
||||
|
||||
var monthlyDistribs = Lambda.array(db.Distribution.manager.search($distributionCycle == monthlyDistribCycle, false));
|
||||
assertEquals(monthlyDistribs.length, 6);
|
||||
assertEquals(monthlyDistribs[0].date.toString(), new Date(2018, 9, 30, 13, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[0].end.toString(), new Date(2018, 9, 30, 14, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[1].date.toString(), new Date(2018, 10, 27, 13, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[1].end.toString(), new Date(2018, 10, 27, 14, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[2].date.toString(), new Date(2018, 11, 25, 13, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[2].end.toString(), new Date(2018, 11, 25, 14, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[3].date.toString(), new Date(2019, 0, 29, 13, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[3].end.toString(), new Date(2019, 0, 29, 14, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[4].date.toString(), new Date(2019, 1, 26, 13, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[4].end.toString(), new Date(2019, 1, 26, 14, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[5].date.toString(), new Date(2019, 2, 26, 13, 0, 0).toString());
|
||||
assertEquals(monthlyDistribs[5].end.toString(), new Date(2019, 2, 26, 14, 0, 0).toString());
|
||||
service.DistributionService.deleteCycleDistribs(monthlyDistribCycle);
|
||||
|
||||
var biweeklyDistribCycle = service.DistributionService.createCycle(TestSuite.CONTRAT_LEGUMES,BiWeekly,new Date(2018, 9, 30, 0, 0, 0),
|
||||
new Date(2019, 0, 31, 0, 0, 0),new Date(2018, 5, 4, 13, 0, 0),new Date(2018, 5, 4, 14, 0, 0),10,2,
|
||||
new Date(2018, 5, 4, 8, 0, 0),new Date(2018, 5, 4, 23, 0, 0),TestSuite.PLACE_DU_VILLAGE.id);
|
||||
|
||||
var biweeklyDistribs = Lambda.array(db.Distribution.manager.search($distributionCycle == biweeklyDistribCycle, false));
|
||||
assertEquals(biweeklyDistribs.length, 7);
|
||||
assertEquals(biweeklyDistribs[0].date.toString(), new Date(2018, 9, 30, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[0].end.toString(), new Date(2018, 9, 30, 14, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[1].date.toString(), new Date(2018, 10, 13, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[1].end.toString(), new Date(2018, 10, 13, 14, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[2].date.toString(), new Date(2018, 10, 27, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[2].end.toString(), new Date(2018, 10, 27, 14, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[3].date.toString(), new Date(2018, 11, 11, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[3].end.toString(), new Date(2018, 11, 11, 14, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[4].date.toString(), new Date(2018, 11, 25, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[4].end.toString(), new Date(2018, 11, 25, 14, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[5].date.toString(), new Date(2019, 0, 8, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[5].end.toString(), new Date(2019, 0, 8, 14, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[6].date.toString(), new Date(2019, 0, 22, 13, 0, 0).toString());
|
||||
assertEquals(biweeklyDistribs[6].end.toString(), new Date(2019, 0, 22, 14, 0, 0).toString());
|
||||
service.DistributionService.deleteCycleDistribs(biweeklyDistribCycle);
|
||||
|
||||
var triweeklyDistribCycle = service.DistributionService.createCycle(TestSuite.CONTRAT_LEGUMES,TriWeekly,new Date(2018, 9, 30, 0, 0, 0),
|
||||
new Date(2019, 0, 31, 0, 0, 0),new Date(2018, 5, 4, 13, 0, 0),new Date(2018, 5, 4, 14, 0, 0),10,2,
|
||||
new Date(2018, 5, 4, 8, 0, 0),new Date(2018, 5, 4, 23, 0, 0),TestSuite.PLACE_DU_VILLAGE.id);
|
||||
|
||||
var triweeklyDistribs = Lambda.array(db.Distribution.manager.search($distributionCycle == triweeklyDistribCycle, false));
|
||||
assertEquals(triweeklyDistribs.length, 5);
|
||||
assertEquals(triweeklyDistribs[0].date.toString(), new Date(2018, 9, 30, 13, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[0].end.toString(), new Date(2018, 9, 30, 14, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[1].date.toString(), new Date(2018, 10, 20, 13, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[1].end.toString(), new Date(2018, 10, 20, 14, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[2].date.toString(), new Date(2018, 11, 11, 13, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[2].end.toString(), new Date(2018, 11, 11, 14, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[3].date.toString(), new Date(2019, 0, 1, 13, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[3].end.toString(), new Date(2019, 0, 1, 14, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[4].date.toString(), new Date(2019, 0, 22, 13, 0, 0).toString());
|
||||
assertEquals(triweeklyDistribs[4].end.toString(), new Date(2019, 0, 22, 14, 0, 0).toString());
|
||||
service.DistributionService.deleteCycleDistribs(triweeklyDistribCycle);
|
||||
}
|
||||
|
||||
function testDelete() {
|
||||
//A variable contract with a distribution that has orders
|
||||
var ordersDistrib = TestSuite.DISTRIB_LEGUMES_RUE_SAUCISSE;
|
||||
var ordersDistribId = ordersDistrib.id;
|
||||
var chicken = TestSuite.CHICKEN;
|
||||
var order = service.OrderService.make(TestSuite.FRANCOIS, 1, chicken, ordersDistrib.id);
|
||||
|
||||
var e = null;
|
||||
try{
|
||||
service.DistributionService.delete(ordersDistrib);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e = x;
|
||||
}
|
||||
assertEquals(e.message, "Deletion non possible: some orders are saved for this delivery.");
|
||||
assertTrue(db.Distribution.manager.get(ordersDistribId) != null);
|
||||
|
||||
//A variable contract with a distribution that has no orders
|
||||
var noOrdersDistrib = TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE;
|
||||
var noOrdersDistribId = noOrdersDistrib.id;
|
||||
|
||||
var e = null;
|
||||
try{
|
||||
service.DistributionService.delete(noOrdersDistrib);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e = x;
|
||||
}
|
||||
assertEquals(e, null);
|
||||
assertEquals(db.Distribution.manager.get(noOrdersDistribId), null);
|
||||
|
||||
//An Amap contract with a distribution that has orders
|
||||
var amapDistrib = TestSuite.DISTRIB_CONTRAT_AMAP;
|
||||
var amapDistribId = amapDistrib.id;
|
||||
var panier = TestSuite.PANIER_AMAP_LEGUMES;
|
||||
var amapOrder = service.OrderService.make(TestSuite.FRANCOIS, 1, panier, amapDistrib.id);
|
||||
|
||||
var e = null;
|
||||
try{
|
||||
service.DistributionService.delete(amapDistrib);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e = x;
|
||||
}
|
||||
assertEquals(e, null);
|
||||
assertEquals(db.Distribution.manager.get(amapDistribId), null);
|
||||
|
||||
}
|
||||
|
||||
function testUpdateAmapContractOperations(){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
//Take an amap contract with payments enabled
|
||||
//Take 2 users and make orders for each
|
||||
var amapDistrib = TestSuite.DISTRIB_CONTRAT_AMAP;
|
||||
var contract = amapDistrib.contract;
|
||||
var panier = TestSuite.PANIER_AMAP_LEGUMES;
|
||||
var francoisOrder = service.OrderService.make(TestSuite.FRANCOIS, 1, panier, amapDistrib.id);
|
||||
db.Operation.onOrderConfirm([francoisOrder]);
|
||||
var sebOrder = service.OrderService.make(TestSuite.SEB, 3, panier, amapDistrib.id);
|
||||
db.Operation.onOrderConfirm([sebOrder]);
|
||||
|
||||
//Check initial operation names and amounts
|
||||
var francoisOperation = db.Operation.findCOrderTransactionFor(contract, TestSuite.FRANCOIS);
|
||||
assertEquals(francoisOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 1 deliveries");
|
||||
assertEquals(francoisOperation.amount, -13);
|
||||
var sebOperation = db.Operation.findCOrderTransactionFor(contract, TestSuite.SEB);
|
||||
assertEquals(sebOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 1 deliveries");
|
||||
assertEquals(sebOperation.amount, -39);
|
||||
|
||||
//Add a distrib
|
||||
var distrib = null;
|
||||
var e = null;
|
||||
try{
|
||||
distrib = service.DistributionService.create(contract,new Date(2018, 5, 1, 18, 0, 0),new Date(2018, 5, 1, 18, 30, 0),
|
||||
amapDistrib.place.id,null,null,null,null,new Date(2018, 4, 1, 18, 0, 0),new Date(2018, 4, 30, 18, 30, 0));
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e = x;
|
||||
}
|
||||
//Check names and amounts are modified accordingly
|
||||
assertEquals(francoisOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 2 deliveries");
|
||||
assertEquals(francoisOperation.amount, -26);
|
||||
var sebOperation = db.Operation.findCOrderTransactionFor(contract, TestSuite.SEB);
|
||||
assertEquals(sebOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 2 deliveries");
|
||||
assertEquals(sebOperation.amount, -78);
|
||||
|
||||
//Add a distrib cycle
|
||||
var weeklyDistribCycle = service.DistributionService.createCycle(contract,Weekly,new Date(2018, 11, 24, 0, 0, 0),
|
||||
new Date(2019, 0, 24, 0, 0, 0),new Date(2018, 5, 4, 13, 0, 0),new Date(2018, 5, 4, 14, 0, 0),10,2,
|
||||
new Date(2018, 5, 4, 8, 0, 0),new Date(2018, 5, 4, 23, 0, 0),TestSuite.PLACE_DU_VILLAGE.id);
|
||||
//Check names and amounts are modified accordingly
|
||||
assertEquals(francoisOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 7 deliveries");
|
||||
assertEquals(francoisOperation.amount, -91);
|
||||
var sebOperation = db.Operation.findCOrderTransactionFor(contract, TestSuite.SEB);
|
||||
assertEquals(sebOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 7 deliveries");
|
||||
assertEquals(sebOperation.amount, -273);
|
||||
|
||||
//Delete the distrib cycle
|
||||
service.DistributionService.deleteCycleDistribs(weeklyDistribCycle);
|
||||
//Check names and amounts are modified accordingly
|
||||
assertEquals(francoisOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 2 deliveries");
|
||||
assertEquals(francoisOperation.amount, -26);
|
||||
var sebOperation = db.Operation.findCOrderTransactionFor(contract, TestSuite.SEB);
|
||||
assertEquals(sebOperation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 2 deliveries");
|
||||
assertEquals(sebOperation.amount, -78);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
package test;
|
||||
import Common;
|
||||
import service.OrderService;
|
||||
/**
|
||||
* Test order making, updating and deleting
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TestOrders extends haxe.unit.TestCase
|
||||
{
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
}
|
||||
|
||||
var c : db.Contract;
|
||||
var p : db.Product;
|
||||
var bob : db.User;
|
||||
|
||||
/**
|
||||
* get a contract + a user + a product + empty orders
|
||||
*/
|
||||
override function setup(){
|
||||
|
||||
TestSuite.initDB();
|
||||
TestSuite.initDatas();
|
||||
|
||||
db.Basket.emptyCache();
|
||||
|
||||
c = TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE.contract;
|
||||
|
||||
p = TestSuite.STRAWBERRIES;
|
||||
p.lock();
|
||||
p.stock = 8;
|
||||
p.update();
|
||||
|
||||
bob = db.User.manager.get(1);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Test Basket creation and numbering
|
||||
**/
|
||||
public function testBasket(){
|
||||
|
||||
var d = TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE;
|
||||
|
||||
var o = OrderService.make(TestSuite.FRANCOIS, 3, TestSuite.STRAWBERRIES, d.id);
|
||||
assertEquals(1, o.basket.num);
|
||||
|
||||
var o = OrderService.make(TestSuite.SEB, 1, TestSuite.STRAWBERRIES, d.id);
|
||||
assertEquals(2, o.basket.num);
|
||||
|
||||
//order again, should keep existing basket number
|
||||
var o = OrderService.make(TestSuite.FRANCOIS, 1, TestSuite.APPLES, d.id);
|
||||
assertEquals(1, o.basket.num);
|
||||
|
||||
//check bug of 2018-07 : changing the date and place of the distribution leads to lost basket (because basket were indexed on user-date-place)
|
||||
d.lock();
|
||||
d.date = new Date(2028,1,1,0,0,0);
|
||||
|
||||
var place = new db.Place();
|
||||
place.name = "Chez Momo";
|
||||
place.zipCode = "54";
|
||||
place.amap = d.contract.amap;
|
||||
place.insert();
|
||||
|
||||
d.place = place;
|
||||
d.update();
|
||||
|
||||
//Seb's basket is still 2
|
||||
var basket = db.Basket.get(TestSuite.SEB,place,d.date);
|
||||
assertEquals(2, basket.num);
|
||||
|
||||
var o = OrderService.make(TestSuite.SEB, 1, TestSuite.APPLES, d.id);
|
||||
assertEquals(2, o.basket.num);
|
||||
|
||||
var o2 = OrderService.edit(o,5,true,null,false);
|
||||
assertEquals(2, o2.basket.num);
|
||||
|
||||
//order to a different distrib in same contract should start a new numbering
|
||||
var d2 = service.DistributionService.create(d.contract,new Date(2026,6,6,0,0,0),new Date(2026,6,6,1,0,0),place.id,null,null,null,null,new Date(2026,6,4,0,0,0),new Date(2026,6,5,0,0,0));
|
||||
var o = OrderService.make(TestSuite.SEB, 12, TestSuite.APPLES, d2.id);
|
||||
assertEquals(1, o.basket.num);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* make orders & stock management
|
||||
*/
|
||||
public function testStocks(){
|
||||
|
||||
var stock = p.stock;
|
||||
|
||||
assertTrue(c.type == db.Contract.TYPE_VARORDER);
|
||||
assertTrue(c.flags.has(db.Contract.ContractFlags.StockManagement));
|
||||
assertTrue(stock == 8);
|
||||
|
||||
//bob orders 3 strawberries, stock fall to 2
|
||||
//order is update to 6 berries
|
||||
App.current.eventDispatcher.addOnce(function(e:Event){
|
||||
switch(e){
|
||||
case StockMove(e):
|
||||
assertTrue(e.move==-3);
|
||||
assertTrue(e.product==p);
|
||||
default:
|
||||
}
|
||||
});
|
||||
var o = OrderService.make(bob, 3, p, TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE.id);
|
||||
assertTrue(p.stock == 5);
|
||||
assertTrue(o.quantity == 3);
|
||||
|
||||
//bob orders 6 more. stock fall to 0, order is reduced to 5
|
||||
//quantity is not 9 but 8
|
||||
App.current.eventDispatcher.addOnce(function(e:Event){
|
||||
switch(e){
|
||||
case StockMove(e):
|
||||
assertTrue(e.move==-5);
|
||||
assertTrue(e.product==p);
|
||||
default:
|
||||
}
|
||||
});
|
||||
var o = OrderService.make(bob, 6, p, TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE.id);
|
||||
assertTrue(p.stock == 0);
|
||||
assertTrue(o.quantity == 8);
|
||||
|
||||
//bob orders again but cant order anything
|
||||
var o = OrderService.make(bob, 3, p, TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE.id);
|
||||
assertTrue(p.stock == 0);
|
||||
assertTrue(o.quantity == 8);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* test edit orders and stock management
|
||||
*/
|
||||
function testOrderEdit(){
|
||||
|
||||
var o = db.UserContract.manager.select( $user == bob && $product == p, true);
|
||||
|
||||
//no order, stock at 8
|
||||
assertEquals(p.stock , 8);
|
||||
assertEquals(o , null);
|
||||
|
||||
//bob orders 3 strawberries
|
||||
var o = OrderService.make(bob, 3, p , TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE.id);
|
||||
assertEquals(o.product.name, p.name);
|
||||
assertEquals(o.quantity, 3);
|
||||
assertEquals(p.stock , 5);
|
||||
|
||||
//order edit, order 6 berries
|
||||
App.current.eventDispatcher.addOnce(function(e:Event){
|
||||
switch(e){
|
||||
case StockMove(e):
|
||||
assertTrue(e.move==-3);
|
||||
assertTrue(e.product==p);
|
||||
default:
|
||||
}
|
||||
});
|
||||
var o = OrderService.edit(o, 6);
|
||||
assertTrue(p.stock == 2);
|
||||
assertTrue(o.quantity == 6);
|
||||
|
||||
//order edit, order 9 berries. ( 3 more, but stock fall to 0, reduced to 2 )
|
||||
App.current.eventDispatcher.addOnce(function(e:Event){
|
||||
switch(e){
|
||||
case StockMove(e):
|
||||
assertEquals( -2.0 , e.move );
|
||||
assertEquals( p , e.product);
|
||||
default:
|
||||
}
|
||||
});
|
||||
var o = OrderService.edit(o, 9);
|
||||
assertEquals(0.0 , p.stock);
|
||||
assertEquals(8.0 , o.quantity);
|
||||
|
||||
//order more, but stock at 0
|
||||
var o = OrderService.edit(o, 12);
|
||||
assertEquals(0.0 , p.stock);
|
||||
assertEquals(8.0 , o.quantity);
|
||||
|
||||
//order less
|
||||
var o = OrderService.edit(o, 6);
|
||||
assertEquals(2.0 , p.stock);
|
||||
assertEquals(6.0 , o.quantity);
|
||||
|
||||
|
||||
//floatQt : ordering float quantities should throw an exception
|
||||
var err = null;
|
||||
try{
|
||||
var o = OrderService.edit(o, 6.4);
|
||||
}catch(e:tink.core.Error){
|
||||
err = e.message;
|
||||
}
|
||||
assertTrue( err!=null );
|
||||
}
|
||||
|
||||
/**
|
||||
* test orders with multiweight product
|
||||
*/
|
||||
function testOrderWithMultiWeightProduct(){
|
||||
|
||||
var chicken = TestSuite.CHICKEN;
|
||||
var distrib = db.Distribution.manager.select($contract == chicken.contract, false);
|
||||
|
||||
var order = OrderService.make(bob, 1, chicken, distrib.id);
|
||||
assertEquals(1.0, order.quantity);
|
||||
assertEquals(chicken.id, order.product.id);
|
||||
assertEquals(chicken.price, order.productPrice);
|
||||
|
||||
//order 2 more, should not aggregate because multiWeight is true
|
||||
var order2 = OrderService.make(bob, 2, chicken, distrib.id);
|
||||
|
||||
assertTrue(order2.id != order.id);
|
||||
|
||||
//we should get 3 different orders
|
||||
var orders = distrib.getOrders();
|
||||
|
||||
//trace(OrderService.prepare(orders));
|
||||
|
||||
assertEquals(3, orders.length);
|
||||
for ( o in orders){
|
||||
assertEquals(o.user.id, bob.id);
|
||||
assertEquals(o.product.id, chicken.id);
|
||||
assertEquals(1.0, o.quantity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author fbarbut
|
||||
* @date 2018-01-26
|
||||
* order a product, edit and set qt to zero, order again.
|
||||
* the same record should be re-used ( if not multiweight )
|
||||
*/
|
||||
function testMakeOrderAndZeroQuantity(){
|
||||
var fraises = TestSuite.STRAWBERRIES;
|
||||
var distrib = db.Distribution.manager.select($contract == fraises.contract, false);
|
||||
|
||||
var order = OrderService.make(bob, 1, fraises, distrib.id);
|
||||
|
||||
order = OrderService.edit(order, 0);
|
||||
|
||||
assertEquals(0.0, order.quantity);
|
||||
|
||||
var order2 = OrderService.make(bob, 1, fraises, distrib.id);
|
||||
|
||||
var bobOrders = [];
|
||||
for ( o in distrib.getOrders()) if (o.user.id == bob.id) bobOrders.push(o);
|
||||
|
||||
assertFalse(fraises.multiWeight);
|
||||
assertEquals(1, bobOrders.length);
|
||||
assertEquals(1.0, bobOrders[0].quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test order deletion and operation deletion in various contexts
|
||||
@author jbarbic
|
||||
*/
|
||||
function testDelete(){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
//[Test case] Should throw an error when trying to delete order and that the quantity is not zero
|
||||
var amapDistrib = TestSuite.DISTRIB_CONTRAT_AMAP;
|
||||
var amapContract = amapDistrib.contract;
|
||||
var order = OrderService.make(TestSuite.FRANCOIS, 1, TestSuite.PANIER_AMAP_LEGUMES, amapDistrib.id);
|
||||
var orderId = order.id;
|
||||
db.Operation.onOrderConfirm([order]);
|
||||
var e1 = null;
|
||||
try {
|
||||
service.OrderService.delete(order);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e1 = x;
|
||||
}
|
||||
assertEquals(e1.message, "Deletion not possible: quantity is not zero.");
|
||||
assertTrue(db.UserContract.manager.get(orderId) != null);
|
||||
|
||||
//[Test case] Amap contract and quantity zero with payments disabled
|
||||
//Check that order is deleted
|
||||
order = OrderService.edit(order, 0);
|
||||
var e2 = null;
|
||||
try {
|
||||
OrderService.delete(order);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e2 = x;
|
||||
}
|
||||
assertEquals(e2, null);
|
||||
assertEquals(db.UserContract.manager.get(orderId), null);
|
||||
|
||||
//[Test case] Amap contract and quantity zero with payments enabled and 2 orders
|
||||
//Check that first order is deleted but operation amount is at 0
|
||||
//Check that operation is deleted only at the second order deletion
|
||||
var order1 = OrderService.make(TestSuite.FRANCOIS, 1, TestSuite.PANIER_AMAP_LEGUMES, amapDistrib.id);
|
||||
db.Operation.onOrderConfirm([order1]);
|
||||
var order1Id = order1.id;
|
||||
order1 = OrderService.edit(order1, 0);
|
||||
db.Operation.onOrderConfirm([order1]);
|
||||
var order2 = OrderService.make(TestSuite.FRANCOIS, 1, TestSuite.PANIER_AMAP_LEGUMES, amapDistrib.id);
|
||||
db.Operation.onOrderConfirm([order2]);
|
||||
var order2Id = order2.id;
|
||||
order2 = OrderService.edit(order2, 0);
|
||||
db.Operation.onOrderConfirm([order2]);
|
||||
var operation = db.Operation.findCOrderTransactionFor(amapContract, TestSuite.FRANCOIS);
|
||||
var operationId = operation.id;
|
||||
var e3 = null;
|
||||
try {
|
||||
service.OrderService.delete(order1);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e3 = x;
|
||||
}
|
||||
assertEquals(e3, null);
|
||||
assertEquals(db.UserContract.manager.get(order1Id), null);
|
||||
assertTrue(db.Operation.manager.get(operationId) != null);
|
||||
assertEquals(operation.name, "Contrat AMAP Légumes (La ferme de la Galinette) 1 deliveries");
|
||||
assertEquals(operation.amount, 0);
|
||||
var e4 = null;
|
||||
try {
|
||||
service.OrderService.delete(order2);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e4 = x;
|
||||
}
|
||||
assertEquals(null, e4);
|
||||
assertEquals(null, db.UserContract.manager.get(order2Id));
|
||||
assertEquals(null, db.Operation.manager.get(operationId));
|
||||
|
||||
//[Test case] Var Order contract and quantity zero with payments disabled
|
||||
//Check that order is deleted
|
||||
var variableDistrib = TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE;
|
||||
|
||||
var g = variableDistrib.contract.amap;
|
||||
g.lock();
|
||||
g.flags.unset(HasPayments);
|
||||
g.update();
|
||||
|
||||
var order = OrderService.make(TestSuite.FRANCOIS, 2, TestSuite.STRAWBERRIES, variableDistrib.id);
|
||||
var orderId = order.id;
|
||||
db.Operation.onOrderConfirm([order]);
|
||||
order = OrderService.edit(order, 0);
|
||||
db.Operation.onOrderConfirm([order]);
|
||||
var e1 = null;
|
||||
try {
|
||||
service.OrderService.delete(order);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e1 = x;
|
||||
}
|
||||
assertEquals(false,variableDistrib.contract.amap.hasPayments() );
|
||||
assertEquals(null, e1);
|
||||
assertEquals(null, db.UserContract.manager.get(orderId));
|
||||
|
||||
//[Test case] Var Order contract and quantity zero with payments enabled and 2 orders
|
||||
//Check that first order is deleted
|
||||
//Check that operation is deleted only at the second order deletion
|
||||
variableDistrib = TestSuite.DISTRIB_FRUITS_PLACE_DU_VILLAGE;
|
||||
var variableContract = variableDistrib.contract;
|
||||
var g = variableDistrib.contract.amap;
|
||||
g.lock();
|
||||
g.flags.set(HasPayments);
|
||||
g.update();
|
||||
assertTrue(variableContract.amap.hasPayments());
|
||||
|
||||
var order1 = OrderService.make(TestSuite.FRANCOIS, 2, TestSuite.STRAWBERRIES, variableDistrib.id);
|
||||
db.Operation.onOrderConfirm([order1]);
|
||||
var order1Id = order1.id;
|
||||
|
||||
var order2 = OrderService.make(TestSuite.FRANCOIS, 3, TestSuite.APPLES, variableDistrib.id);
|
||||
db.Operation.onOrderConfirm([order2]);
|
||||
var order2Id = order2.id;
|
||||
|
||||
order1 = OrderService.edit(order1, 0);
|
||||
db.Operation.onOrderConfirm([order1]);
|
||||
|
||||
order2 = OrderService.edit(order2, 0);
|
||||
db.Operation.onOrderConfirm([order2]);
|
||||
|
||||
assertEquals(2, variableContract.getUserOrders(TestSuite.FRANCOIS,variableDistrib).length); //François has 2 orders
|
||||
var basket = db.Basket.get(TestSuite.FRANCOIS,variableDistrib.place,variableDistrib.date);
|
||||
assertEquals(2, basket.getOrders().length);
|
||||
|
||||
var operation1 = db.Operation.findVOrderTransactionFor(order1.distribution.getKey(), TestSuite.FRANCOIS, variableContract.amap);
|
||||
var operation1Id = operation1.id;
|
||||
var operation2 = db.Operation.findVOrderTransactionFor(order2.distribution.getKey(), TestSuite.FRANCOIS, variableContract.amap);
|
||||
var operation2Id = operation2.id;
|
||||
assertEquals(operation1Id,operation2Id);
|
||||
var e2 = null;
|
||||
try {
|
||||
//delete strawberries order
|
||||
service.OrderService.delete(order1);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e2 = x;
|
||||
}
|
||||
assertEquals(null, e2);
|
||||
assertEquals(null, db.UserContract.manager.get(order1Id) ); //order 1 is deleted
|
||||
assertTrue( db.Operation.manager.get(operation1Id) != null); //operation should be here
|
||||
assertEquals(0.0,operation1.amount);
|
||||
assertEquals(1 , basket.getOrders().length);
|
||||
var e3 = null;
|
||||
try {
|
||||
//delete apple order
|
||||
service.OrderService.delete(order2);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e3 = x;
|
||||
}
|
||||
assertEquals(null, e3);
|
||||
assertEquals(null, db.UserContract.manager.get(order2Id)); //order 2 is deleted
|
||||
assertEquals(null, db.Operation.manager.get(operation1Id)); //operation should be deleted
|
||||
assertEquals(0 , basket.getOrders().length);
|
||||
|
||||
//[Test case] 2 VarOrderContracts and quantity zero with payments enabled and 1 order each
|
||||
//Check that first order is deleted but operation amount is at 0
|
||||
//Check that operation is deleted only at the second order deletion
|
||||
var variableDistrib1 = TestSuite.DISTRIB_LEGUMES_RUE_SAUCISSE;
|
||||
var order1 = OrderService.make(TestSuite.FRANCOIS, 2, TestSuite.COURGETTES, variableDistrib1.id);
|
||||
assertTrue(order1.basket!=null);
|
||||
// trace("WE GOT A BASKET "+order1.basket.id);
|
||||
// trace("... THEN RE-GET BASKET user "+TestSuite.FRANCOIS.id+" place "+variableDistrib1.place.id+" date "+variableDistrib1.date);
|
||||
var basket = db.Basket.get(TestSuite.FRANCOIS,variableDistrib1.place,variableDistrib1.date);
|
||||
assertTrue(basket!=null);
|
||||
assertEquals(1, basket.getOrders().length);
|
||||
db.Operation.onOrderConfirm([order1]);
|
||||
var order1Id = order1.id;
|
||||
|
||||
var variableDistrib2 = TestSuite.DISTRIB_PATISSERIES;
|
||||
var order2 = OrderService.make(TestSuite.FRANCOIS, 3, TestSuite.FLAN, variableDistrib2.id);
|
||||
db.Operation.onOrderConfirm([order2]);
|
||||
var order2Id = order2.id;
|
||||
|
||||
order1 = OrderService.edit(order1, 0);
|
||||
db.Operation.onOrderConfirm([order1]);
|
||||
|
||||
order2 = OrderService.edit(order2, 0);
|
||||
db.Operation.onOrderConfirm([order2]);
|
||||
|
||||
//check basket
|
||||
var basket = db.Basket.get(TestSuite.FRANCOIS,variableDistrib1.place,variableDistrib1.date);
|
||||
assertEquals(2, basket.getOrders().length);
|
||||
|
||||
var operation = db.Operation.findVOrderTransactionFor(order1.distribution.getKey(), TestSuite.FRANCOIS, variableDistrib1.contract.amap);
|
||||
var operationId = operation.id;
|
||||
|
||||
|
||||
var e4 = null;
|
||||
try {
|
||||
service.OrderService.delete(order1);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e4 = x;
|
||||
}
|
||||
assertEquals(e4, null);
|
||||
assertEquals(db.UserContract.manager.get(order1Id), null);
|
||||
assertTrue(db.Operation.manager.get(operationId) != null); //op should still be here
|
||||
assertEquals(0.0,operation.amount);//...with amount 0
|
||||
|
||||
var e5 = null;
|
||||
try {
|
||||
service.OrderService.delete(order2);
|
||||
}
|
||||
catch(x:tink.core.Error){
|
||||
e5 = x;
|
||||
}
|
||||
assertEquals(e5, null);
|
||||
assertEquals(db.UserContract.manager.get(order2Id), null);
|
||||
assertEquals(null, db.Operation.manager.get(operationId), null); //op should have been deleted
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package test;
|
||||
|
||||
/**
|
||||
* Test payments
|
||||
*
|
||||
* @author web-wizard
|
||||
*/
|
||||
class TestPayments extends haxe.unit.TestCase
|
||||
{
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
}
|
||||
|
||||
override function setup(){
|
||||
TestSuite.initDB();
|
||||
TestSuite.initDatas();
|
||||
db.Basket.emptyCache();
|
||||
}
|
||||
|
||||
function testValidateDistribution() {
|
||||
|
||||
//Take a contract with payments enabled
|
||||
//Take 2 users and make orders for each
|
||||
var distrib = TestSuite.DISTRIB_LEGUMES_RUE_SAUCISSE;
|
||||
var contract = distrib.contract;
|
||||
var product = TestSuite.COURGETTES;
|
||||
var francoisOrder = service.OrderService.make(TestSuite.FRANCOIS, 1, product, distrib.id);
|
||||
var francoisOrderOperation = db.Operation.onOrderConfirm([francoisOrder]);
|
||||
var sebOrder = service.OrderService.make(TestSuite.SEB, 3, product, distrib.id);
|
||||
var sebOrderOperation = db.Operation.onOrderConfirm([sebOrder]);
|
||||
//They both pay by check
|
||||
var francoisPayment = db.Operation.makePaymentOperation(TestSuite.FRANCOIS,contract.amap, payment.Check.TYPE, product.price, "Payment by check", francoisOrderOperation[0]);
|
||||
var sebPayment = db.Operation.makePaymentOperation(TestSuite.SEB,contract.amap, payment.Check.TYPE, 3 * product.price, "Payment by check", sebOrderOperation[0] );
|
||||
|
||||
//Autovalidate this old distrib and check that all the payments are validated
|
||||
service.PaymentService.validateDistribution(distrib);
|
||||
|
||||
//distrib should be validated
|
||||
assertTrue(contract.amap.hasPayments());
|
||||
assertEquals(true, distrib.validated);
|
||||
|
||||
//orders should be marked as paid
|
||||
assertEquals(true, francoisOrder.paid);
|
||||
assertEquals(true, sebOrder.paid);
|
||||
|
||||
//order operation is not pending
|
||||
var francoisOperation = db.Operation.findVOrderTransactionFor(francoisOrder.distribution.getKey(), TestSuite.FRANCOIS, contract.amap, false);
|
||||
var sebOperation = db.Operation.findVOrderTransactionFor(sebOrder.distribution.getKey(), TestSuite.SEB, contract.amap, false);
|
||||
assertEquals(francoisOperation.pending, false);
|
||||
assertEquals(sebOperation.pending, false);
|
||||
|
||||
//payment operation is not pending
|
||||
assertEquals(francoisPayment.pending, false);
|
||||
assertEquals(sebPayment.pending, false);
|
||||
|
||||
//basket are validated
|
||||
var b = db.Basket.get(TestSuite.SEB,distrib.place,distrib.date);
|
||||
assertEquals(true, b.isValidated());
|
||||
var b = db.Basket.get(TestSuite.FRANCOIS,distrib.place,distrib.date);
|
||||
assertEquals(true, b.isValidated());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package test;
|
||||
import Common;
|
||||
import test.TestSuite;
|
||||
import service.ReportService;
|
||||
import service.OrderService;
|
||||
|
||||
/**
|
||||
* Test order reports
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TestReports extends haxe.unit.TestCase
|
||||
{
|
||||
|
||||
public function new(){
|
||||
|
||||
super();
|
||||
}
|
||||
|
||||
override function setup(){
|
||||
|
||||
TestSuite.initDB();
|
||||
TestSuite.initDatas();
|
||||
|
||||
}
|
||||
|
||||
|
||||
function testOrdersByProduct(){
|
||||
|
||||
//record orders
|
||||
var seb = TestSuite.SEB;
|
||||
var francois = TestSuite.FRANCOIS;
|
||||
var julie = TestSuite.JULIE;
|
||||
|
||||
//distrib de légumes
|
||||
var d = TestSuite.DISTRIB_LEGUMES_RUE_SAUCISSE;
|
||||
var carrots = TestSuite.CARROTS;
|
||||
var courgettes = TestSuite.COURGETTES;
|
||||
var poulet = TestSuite.CHICKEN;
|
||||
|
||||
OrderService.make(seb,4,courgettes,d.id);
|
||||
OrderService.make(seb,1,poulet,d.id);
|
||||
|
||||
OrderService.make(francois,6,courgettes,d.id);
|
||||
OrderService.make(francois,2,poulet,d.id);
|
||||
OrderService.make(francois,3,carrots,d.id);
|
||||
|
||||
OrderService.make(julie,8,carrots,d.id);
|
||||
OrderService.make(julie,3,poulet,d.id);
|
||||
|
||||
//record orders on ANOTHER distrib
|
||||
var d2 = service.DistributionService.create(
|
||||
d.contract,
|
||||
new Date(2018,2,12,0,0,0),
|
||||
new Date(2018,2,12,0,3,0),
|
||||
d.contract.amap.getPlaces().first().id,
|
||||
null,null,null,null,
|
||||
new Date(2018,2,8,0,0,0),
|
||||
new Date(2018,2,11,0,0,0)
|
||||
);
|
||||
OrderService.make(julie,6,carrots,d2.id);
|
||||
OrderService.make(julie,1,poulet,d2.id);
|
||||
|
||||
var orders = ReportService.getOrdersByProduct(d);
|
||||
|
||||
//courgettes x 10
|
||||
var courgettesOrder = Lambda.find(orders, function(o) return o.pid==courgettes.id);
|
||||
assertEquals( 10.0 , courgettesOrder.quantity );
|
||||
assertEquals( 35.0 , courgettesOrder.totalTTC );
|
||||
assertEquals( 33.18 , tools.FloatTool.clean(courgettesOrder.totalHT) );
|
||||
|
||||
//the report stays the same, even if the product has a new price.
|
||||
courgettes.lock();
|
||||
courgettes.price+=4;
|
||||
courgettes.update();
|
||||
var orders = ReportService.getOrdersByProduct(d);
|
||||
var courgettesOrder = Lambda.find(orders, function(o) return o.pid==courgettes.id);
|
||||
assertEquals( 10.0 , courgettesOrder.quantity );
|
||||
assertEquals( 35.0 , courgettesOrder.totalTTC );
|
||||
assertEquals( 33.18 , tools.FloatTool.clean(courgettesOrder.totalHT) );
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* run once at the beginning
|
||||
*/
|
||||
/*override function setup(){
|
||||
|
||||
sys.db.Manager.cnx.request("TRUNCATE TABLE UserContract;");
|
||||
|
||||
var bubar = db.User.manager.get(1);
|
||||
var seb = db.User.manager.get(2);
|
||||
|
||||
//fruits from group 1
|
||||
var fraises = db.Product.manager.get(2);
|
||||
fraises.stock = 30;
|
||||
var pommes = db.Product.manager.get(3);
|
||||
var distrib = fraises.contract.getDistribs().first();
|
||||
|
||||
db.UserContract.make(bubar, 4, fraises, distrib.id);
|
||||
db.UserContract.make(seb, 2, pommes, distrib.id);
|
||||
|
||||
//vegetables from group 2
|
||||
var courgettes = db.Product.manager.get(4);
|
||||
var carottes = db.Product.manager.get(5);
|
||||
var distrib = courgettes.contract.getDistribs().first();
|
||||
|
||||
db.UserContract.make(bubar, 1, carottes ,distrib.id);
|
||||
db.UserContract.make(seb, 5, courgettes ,distrib.id);
|
||||
}*/
|
||||
|
||||
|
||||
/**
|
||||
* test a simple report with just a time frame
|
||||
*/
|
||||
/*public function testTimeFrameReport(){
|
||||
|
||||
var fraises = db.Product.manager.get(2);
|
||||
var pommes = db.Product.manager.get(3);
|
||||
var courgettes = db.Product.manager.get(4);
|
||||
var carottes = db.Product.manager.get(5);
|
||||
|
||||
//check we got the right products
|
||||
assertEquals("Fraises",fraises.name);
|
||||
assertEquals("Pommes",pommes.name);
|
||||
assertEquals("Courgettes",courgettes.name);
|
||||
assertEquals("Carottes", carottes.name);
|
||||
|
||||
var options = { startDate:new Date(2017, 5, 1, 0, 0, 0), endDate:new Date(2017, 5, 31, 0, 0, 0), groups:[], contracts:[] };
|
||||
|
||||
var rep = new pro.OrderReport(options);
|
||||
|
||||
var data = rep.byProduct();
|
||||
|
||||
assertEquals(4,data.length); //should be the 4 products
|
||||
|
||||
for ( d in data){
|
||||
switch(d.pname){
|
||||
case "Fraises": assertEquals(d.qt, 4);
|
||||
case "Pommes": assertEquals(d.qt, 2);
|
||||
case "Carottes": assertEquals(d.qt, 1);
|
||||
case "Courgettes": assertEquals(d.qt, 5);
|
||||
}
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
|
||||
/**
|
||||
* test a report with time frame + group
|
||||
*/
|
||||
/*public function testGroupReport(){
|
||||
|
||||
var options = { startDate:new Date(2017, 5, 1, 0, 0, 0), endDate:new Date(2017, 5, 31, 0, 0, 0), groups:[1], contracts:[] };
|
||||
|
||||
var rep = new pro.OrderReport(options);
|
||||
var data = rep.byProduct();
|
||||
assertEquals(2,data.length); //should be the 2 products
|
||||
|
||||
for ( d in data){
|
||||
switch(d.pname){
|
||||
case "Fraises": assertEquals(d.qt, 4);
|
||||
case "Pommes": assertEquals(d.qt, 2);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
package test;
|
||||
import Common;
|
||||
/**
|
||||
* CAGETTE.NET TEST SUITE
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TestSuite
|
||||
{
|
||||
|
||||
static function main() {
|
||||
|
||||
connectDb();
|
||||
var r = new haxe.unit.TestRunner();
|
||||
|
||||
//Cagette core tests
|
||||
r.add(new test.TestUser());
|
||||
r.add(new test.TestOrders());
|
||||
r.add(new test.TestTools());
|
||||
r.add(new test.TestDistributions());
|
||||
r.add(new test.TestPayments());
|
||||
r.add(new test.TestReports());
|
||||
|
||||
#if plugins
|
||||
//Cagette-pro tests, keep in this order
|
||||
r.add(new pro.test.TestProductService());
|
||||
r.add(new pro.test.TestRemoteCatalog());
|
||||
r.add(new pro.test.TestDistribService());
|
||||
r.add(new pro.test.TestReports());
|
||||
//wholesale-order tests
|
||||
r.add(new who.test.TestWho());
|
||||
#end
|
||||
|
||||
r.run();
|
||||
}
|
||||
|
||||
static function connectDb() {
|
||||
var dbstr = Sys.args()[0];
|
||||
var dbreg = ~/([^:]+):\/\/([^:]+):([^@]*?)@([^:]+)(:[0-9]+)?\/(.*?)$/;
|
||||
if( !dbreg.match(dbstr) )
|
||||
throw "Configuration requires a valid database attribute, format is : mysql://user:password@host:port/dbname";
|
||||
var port = dbreg.matched(5);
|
||||
var dbparams = {
|
||||
user:dbreg.matched(2),
|
||||
pass:dbreg.matched(3),
|
||||
host:dbreg.matched(4),
|
||||
port:port == null ? 3306 : Std.parseInt(port.substr(1)),
|
||||
database:dbreg.matched(6),
|
||||
socket:null
|
||||
};
|
||||
|
||||
sys.db.Manager.cnx = sys.db.Mysql.connect(dbparams);
|
||||
sys.db.Manager.initialize();
|
||||
}
|
||||
|
||||
|
||||
public static function initDB(){
|
||||
//NUKE EVERYTHING BWAAAAH !!
|
||||
sys.db.Manager.cleanup(); //cleanup cache objects
|
||||
sql("DROP DATABASE tests;");
|
||||
sql("CREATE DATABASE tests;");
|
||||
sql("USE tests;");
|
||||
|
||||
var tables : Array<Dynamic> = [
|
||||
|
||||
//cagette
|
||||
db.TxpProduct.manager,
|
||||
db.TxpCategory.manager,
|
||||
db.TxpSubCategory.manager,
|
||||
db.Category.manager,
|
||||
db.CategoryGroup.manager,
|
||||
db.ProductCategory.manager,
|
||||
|
||||
db.Basket.manager,
|
||||
db.UserContract.manager,
|
||||
db.UserAmap.manager,
|
||||
db.Operation.manager,
|
||||
|
||||
db.User.manager,
|
||||
db.Amap.manager,
|
||||
db.Contract.manager,
|
||||
db.Product.manager,
|
||||
db.Vendor.manager,
|
||||
db.Place.manager,
|
||||
db.Distribution.manager,
|
||||
db.DistributionCycle.manager,
|
||||
|
||||
//sugoi tables
|
||||
sugoi.db.Cache.manager,
|
||||
sugoi.db.Error.manager,
|
||||
sugoi.db.File.manager,
|
||||
sugoi.db.Session.manager,
|
||||
sugoi.db.Variable.manager,
|
||||
];
|
||||
|
||||
for(t in tables) createTable(t);
|
||||
|
||||
#if plugins
|
||||
//add Cpro datas : we need those tables even in cagette core tests
|
||||
pro.test.ProTestSuite.initDB();
|
||||
pro.test.ProTestSuite.initDatas();
|
||||
#end
|
||||
}
|
||||
|
||||
public static function createTable( m ){
|
||||
if ( sys.db.TableCreate.exists(m) ){
|
||||
drop(m);
|
||||
}
|
||||
// Sys.println("Creating table "+ m.dbInfos().name);
|
||||
sys.db.TableCreate.create(m);
|
||||
}
|
||||
|
||||
public static function truncate(m){
|
||||
sql("TRUNCATE TABLE "+m.dbInfos().name+";");
|
||||
}
|
||||
|
||||
public static function drop(m){
|
||||
sql("DROP TABLE "+m.dbInfos().name+";");
|
||||
}
|
||||
|
||||
public static function sql(sql){
|
||||
return sys.db.Manager.cnx.request(sql);
|
||||
}
|
||||
|
||||
//shortcut to datas
|
||||
public static var FRANCOIS:db.User = null;
|
||||
public static var SEB:db.User = null;
|
||||
public static var JULIE:db.User = null;
|
||||
|
||||
public static var CHICKEN:db.Product = null;
|
||||
public static var STRAWBERRIES:db.Product = null;
|
||||
public static var APPLES:db.Product = null;
|
||||
public static var AMAP_DU_JARDIN:db.Amap = null;
|
||||
public static var LOCAVORES:db.Amap = null;
|
||||
public static var PANIER_AMAP_LEGUMES:db.Product = null;
|
||||
public static var DISTRIB_CONTRAT_AMAP:db.Distribution = null;
|
||||
public static var DISTRIB_FRUITS_PLACE_DU_VILLAGE:db.Distribution = null;
|
||||
public static var DISTRIB_LEGUMES_RUE_SAUCISSE:db.Distribution = null;
|
||||
public static var CONTRAT_LEGUMES:db.Contract = null;
|
||||
public static var PLACE_DU_VILLAGE:db.Place = null;
|
||||
public static var COURGETTES:db.Product = null;
|
||||
public static var CARROTS:db.Product = null;
|
||||
public static var FLAN:db.Product = null;
|
||||
public static var CROISSANT:db.Product = null;
|
||||
public static var DISTRIB_PATISSERIES:db.Distribution = null;
|
||||
|
||||
public static function initDatas(){
|
||||
|
||||
//USERS
|
||||
|
||||
var f = new db.User();
|
||||
f.firstName = "François";
|
||||
f.lastName = "B";
|
||||
f.email = "francois@alilo.fr";
|
||||
f.insert();
|
||||
|
||||
FRANCOIS = f;
|
||||
|
||||
var u = new db.User();
|
||||
u.firstName = "Seb";
|
||||
u.lastName = "Z";
|
||||
u.email = "sebastien@alilo.fr";
|
||||
u.insert();
|
||||
|
||||
SEB = u;
|
||||
|
||||
var u = new db.User();
|
||||
u.firstName = "Julie";
|
||||
u.lastName = "B";
|
||||
u.email = "julie@alilo.fr";
|
||||
u.insert();
|
||||
|
||||
JULIE = u;
|
||||
|
||||
initApp(u);
|
||||
|
||||
//GROUP "AMAP du Jardin public"
|
||||
var a = new db.Amap();
|
||||
a.name = "AMAP du Jardin public";
|
||||
a.contact = f;
|
||||
a.flags.set(db.Amap.AmapFlags.HasPayments);
|
||||
a.insert();
|
||||
AMAP_DU_JARDIN = a;
|
||||
|
||||
var place = new db.Place();
|
||||
place.name = "Place du village";
|
||||
place.zipCode = "00000";
|
||||
place.city = "St Martin";
|
||||
place.amap = a;
|
||||
place.insert();
|
||||
|
||||
PLACE_DU_VILLAGE = place;
|
||||
|
||||
//VENDOR "Ferme de la galinette"
|
||||
var v = new db.Vendor();
|
||||
v.name = "La ferme de la Galinette";
|
||||
v.email = "galinette@gmail.com";
|
||||
v.zipCode = "00000";
|
||||
v.city = "Bourligheim";
|
||||
v.insert();
|
||||
|
||||
var c = new db.Contract();
|
||||
c.name = "Contrat AMAP Légumes";
|
||||
c.startDate = new Date(2017, 1, 1, 0, 0, 0);
|
||||
c.endDate = new Date(2030, 12, 31, 23, 59, 0);
|
||||
c.vendor = v;
|
||||
c.amap = a;
|
||||
c.type = db.Contract.TYPE_CONSTORDERS;
|
||||
c.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Panier Légumes";
|
||||
p.price = 13;
|
||||
p.contract = c;
|
||||
p.insert();
|
||||
|
||||
PANIER_AMAP_LEGUMES = p;
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.date = new Date(2017, 5, 1, 19, 0, 0);
|
||||
d.end = new Date(2017, 5, 1, 20, 0, 0);
|
||||
d.orderStartDate = new Date(2017, 4, 1, 20, 0, 0);
|
||||
d.orderEndDate = new Date(2017, 4, 30, 20, 0, 0);
|
||||
d.contract = c;
|
||||
d.place = place;
|
||||
d.insert();
|
||||
|
||||
DISTRIB_CONTRAT_AMAP = d;
|
||||
|
||||
//varying contract for strawberries with stock mgmt
|
||||
var c = new db.Contract();
|
||||
c.name = "Commande fruits";
|
||||
c.vendor = v;
|
||||
c.startDate = new Date(2017, 1, 1, 0, 0, 0);
|
||||
c.endDate = new Date(2030, 12, 31, 23, 59, 0);
|
||||
c.flags.set(db.Contract.ContractFlags.StockManagement);
|
||||
c.type = db.Contract.TYPE_VARORDER;
|
||||
c.amap = a;
|
||||
c.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Fraises";
|
||||
p.qt = 1;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 10;
|
||||
p.organic = true;
|
||||
p.contract = c;
|
||||
p.stock = 8;
|
||||
p.insert();
|
||||
|
||||
STRAWBERRIES = p;
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Pommes";
|
||||
p.qt = 1;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 6;
|
||||
p.organic = true;
|
||||
p.contract = c;
|
||||
p.stock = 12;
|
||||
p.insert();
|
||||
|
||||
APPLES = p;
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.date = new Date(2017, 5, 1, 19, 0, 0);
|
||||
d.end = new Date(2017, 5, 1, 20, 0, 0);
|
||||
d.orderStartDate = new Date(2017, 4, 1, 20, 0, 0);
|
||||
d.orderEndDate = new Date(2017, 4, 30, 20, 0, 0);
|
||||
d.contract = c;
|
||||
d.place = place;
|
||||
d.insert();
|
||||
|
||||
DISTRIB_FRUITS_PLACE_DU_VILLAGE = d;
|
||||
|
||||
//second group
|
||||
var a = new db.Amap();
|
||||
a.name = "Les Locavores de la Rue Saucisse";
|
||||
a.contact = f;
|
||||
a.flags.set(db.Amap.AmapFlags.HasPayments);
|
||||
a.insert();
|
||||
LOCAVORES = a;
|
||||
|
||||
var place = new db.Place();
|
||||
place.name = "Rue Saucisse";
|
||||
place.zipCode = "00000";
|
||||
place.city = "St Martin";
|
||||
place.amap = a;
|
||||
place.insert();
|
||||
|
||||
var v = new db.Vendor();
|
||||
v.name = "La ferme de la courgette enragée";
|
||||
v.email = "courgette@gmail.com";
|
||||
v.zipCode = "00000";
|
||||
v.city = "Bourligeac";
|
||||
v.insert();
|
||||
|
||||
var c = new db.Contract();
|
||||
c.name = "Commande Legumes";
|
||||
c.startDate = new Date(2017, 1, 1, 0, 0, 0);
|
||||
c.endDate = new Date(2030, 12, 31, 23, 59, 0);
|
||||
c.vendor = v;
|
||||
c.amap = a;
|
||||
c.type = db.Contract.TYPE_VARORDER;
|
||||
c.insert();
|
||||
|
||||
CONTRAT_LEGUMES = c;
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Courgettes";
|
||||
p.qt = 1;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 3.5;
|
||||
p.vat = 5.5;
|
||||
p.organic = true;
|
||||
p.contract = c;
|
||||
p.insert();
|
||||
|
||||
COURGETTES = p;
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Carottes";
|
||||
p.qt = 1;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 2.8;
|
||||
p.vat = 5.5;
|
||||
p.contract = c;
|
||||
p.insert();
|
||||
|
||||
CARROTS = p;
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Poulet";
|
||||
p.qt = 1.5;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 15;
|
||||
p.vat = 5.5;
|
||||
p.multiWeight = true;
|
||||
p.hasFloatQt = true;
|
||||
p.contract = c;
|
||||
p.insert();
|
||||
|
||||
CHICKEN = p;
|
||||
|
||||
var d = service.DistributionService.create(c,new Date(2017, 5, 1, 19, 0, 0),new Date(2017, 5, 1, 19, 2, 0),place.id,null,null,null,null,new Date(2017, 4, 10, 19, 0, 0),new Date(2017, 4, 20, 19, 0, 0));
|
||||
DISTRIB_LEGUMES_RUE_SAUCISSE = d;
|
||||
|
||||
//PASTRY
|
||||
|
||||
var c = new db.Contract();
|
||||
c.name = "Commande Pâtisseries";
|
||||
c.startDate = new Date(2017, 1, 1, 0, 0, 0);
|
||||
c.endDate = new Date(2017, 12, 31, 23, 59, 0);
|
||||
c.vendor = v;
|
||||
c.amap = a;
|
||||
c.type = db.Contract.TYPE_VARORDER;
|
||||
c.insert();
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Flan";
|
||||
p.qt = 1;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 3.5;
|
||||
p.organic = true;
|
||||
p.contract = c;
|
||||
p.insert();
|
||||
|
||||
FLAN = p;
|
||||
|
||||
var p = new db.Product();
|
||||
p.name = "Croissant";
|
||||
p.qt = 1;
|
||||
p.unitType = Common.Unit.Kilogram;
|
||||
p.price = 2.8;
|
||||
p.contract = c;
|
||||
p.insert();
|
||||
|
||||
CROISSANT = p;
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.date = new Date(2017, 5, 1, 19, 0, 0);
|
||||
d.contract = c;
|
||||
d.place = place;
|
||||
d.insert();
|
||||
|
||||
DISTRIB_PATISSERIES = d;
|
||||
}
|
||||
|
||||
static function initApp(u:db.User){
|
||||
|
||||
//setup App
|
||||
var app = App.current = new App();
|
||||
App.config.DEBUG = true;
|
||||
app.initLang("en");
|
||||
|
||||
app.eventDispatcher = new hxevents.Dispatcher<Event>();
|
||||
app.plugins = [];
|
||||
//internal plugins
|
||||
app.plugins.push(new plugin.Tutorial());
|
||||
|
||||
//optionnal plugins
|
||||
#if plugins
|
||||
//app.plugins.push( new hosted.HostedPlugIn() );
|
||||
app.plugins.push( new pro.ProPlugIn() );
|
||||
app.plugins.push( new connector.ConnectorPlugIn() );
|
||||
//app.plugins.push( new pro.LemonwayEC() );
|
||||
app.plugins.push( new who.WhoPlugIn() );
|
||||
#end
|
||||
|
||||
App.current.user = u;
|
||||
App.current.view = new View();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package test;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Test various tools
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TestTools extends haxe.unit.TestCase
|
||||
{
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
function testDateRanges(){
|
||||
// test last hour range
|
||||
var now = Date.fromString("2018-01-01 00:30:12");
|
||||
var r = tools.DateTool.getLastHourRange(now);
|
||||
assertEquals("2017-12-31 23:00:00",r.from.toString());
|
||||
assertEquals("2018-01-01 00:00:00",r.to.toString());
|
||||
|
||||
// test last minute
|
||||
var now = Date.fromString("2018-01-01 00:30:12");
|
||||
var r = tools.DateTool.getLastMinuteRange(now);
|
||||
assertEquals("2018-01-01 00:29:00",r.from.toString());
|
||||
assertEquals("2018-01-01 00:30:00",r.to.toString());
|
||||
}
|
||||
|
||||
|
||||
function testFloatTool(){
|
||||
|
||||
assertEquals( true , tools.FloatTool.isEqual(10.0,10.000) );
|
||||
assertEquals( true , tools.FloatTool.isEqual(10.0,10) );
|
||||
assertEquals( true , tools.FloatTool.isEqual(10,10) );
|
||||
assertEquals( true , tools.FloatTool.isEqual(10.00000001,10) );
|
||||
assertEquals( false , tools.FloatTool.isEqual(10.02,10) );
|
||||
|
||||
assertEquals( false , tools.FloatTool.isInt(10.08) );
|
||||
assertEquals( true , tools.FloatTool.isInt(10.00) );
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@admin
|
||||
public function doTests() {
|
||||
|
||||
var assertTrue = function(val, ?desc="") {
|
||||
if (val) {
|
||||
Sys.println("OK : <br/>");
|
||||
}else {
|
||||
Sys.println("ERROR : "+desc+"<br/>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//test les fonctions de cotisation
|
||||
|
||||
var amap = db.Amap.manager.get(1);
|
||||
|
||||
amap.membershipRenewalDate = new Date(2015, 0, 1,0,0,0);
|
||||
amap.update();
|
||||
|
||||
assertTrue(amap.getMembershipYear(new Date(2015, 3, 3, 0, 0, 0) ) == 2015);
|
||||
assertTrue(amap.getPeriodName(new Date(2015, 3, 3, 0, 0, 0)) == "2015");
|
||||
|
||||
assertTrue(amap.getMembershipYear(new Date(2014, 8, 8, 0, 0, 0) ) == 2014);
|
||||
assertTrue(amap.getPeriodName(new Date(2014, 8, 8, 0, 0, 0) ) == "2014");
|
||||
|
||||
assertTrue(amap.getMembershipYear(new Date(2013, 11, 12, 0, 0, 0) ) == 2013);
|
||||
assertTrue(amap.getPeriodName(new Date(2013, 11, 12, 0, 0, 0) ) == "2013");
|
||||
|
||||
amap.membershipRenewalDate = new Date(2015, 8, 1,0,0,0);
|
||||
amap.update();
|
||||
|
||||
assertTrue(amap.getMembershipYear(new Date(2015, 3, 3, 0, 0, 0) ) == 2014);
|
||||
assertTrue(amap.getPeriodName(new Date(2015, 3, 3, 0, 0, 0)) == "2014-2015");
|
||||
|
||||
var d = amap.getMembershipYear(new Date(2015, 8, 8, 0, 0, 0) );
|
||||
assertTrue( d == 2015, "le 8 sept 2015, on doit etre en cotis 2015, la c " + d);
|
||||
assertTrue(amap.getPeriodName(new Date(2015, 8, 8, 0, 0, 0)) == "2015-2016");
|
||||
|
||||
var d = new Date(2013, 11, 12, 0, 0, 0) ;
|
||||
assertTrue( amap.getMembershipYear(d) == 2013, "le 12 oct 2013, on doit etre en cotis 2013 , là c " + amap.getMembershipYear(d) );
|
||||
assertTrue(amap.getPeriodName(d) == "2013-2014", "le 12 oct 2013, on doit etre en 2013-2014 , là c " + amap.getPeriodName(d) );
|
||||
|
||||
|
||||
}*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
|
||||
package test;
|
||||
import db.UserAmap;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Test user rights to view contracts
|
||||
*
|
||||
* @author web-wizard
|
||||
*/
|
||||
class TestUser extends haxe.unit.TestCase
|
||||
{
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
}
|
||||
|
||||
var contract : db.Contract;
|
||||
var user : db.User;
|
||||
var group1 : db.Amap;
|
||||
var group2 : db.Amap;
|
||||
var userAmap : db.UserAmap;
|
||||
|
||||
/**
|
||||
* get a contract + a user
|
||||
*/
|
||||
override function setup(){
|
||||
TestSuite.initDB();
|
||||
TestSuite.initDatas();
|
||||
|
||||
contract = db.Contract.manager.get(3);
|
||||
user = db.User.manager.get(1);
|
||||
group1 = db.Amap.manager.get(1);
|
||||
group2 = db.Amap.manager.get(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a user who has admin rights for his/her group can't view a contract from a group
|
||||
he/she doesn't belong to
|
||||
*/
|
||||
function testViewContract(){
|
||||
userAmap = db.UserAmap.getOrCreate(user, group1);
|
||||
userAmap.giveRight(Right.GroupAdmin);
|
||||
assertFalse(user.canManageContract(contract));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package tools;
|
||||
|
||||
/**
|
||||
* Some utility functions for arrays
|
||||
*/
|
||||
class ArrayTool
|
||||
{
|
||||
/**
|
||||
* shuffle (randomize) an array
|
||||
*/
|
||||
//public static function shuffle<T>(arr:Array<T>):Array<T>
|
||||
//{
|
||||
//if (arr!=null) {
|
||||
//for (i in 0...arr.length) {
|
||||
//var j = Std.random(arr.length);
|
||||
//var a = arr[i];
|
||||
//var b = arr[j];
|
||||
//arr[i] = b;
|
||||
//arr[j] = a;
|
||||
//}
|
||||
//}
|
||||
//return arr;
|
||||
//}
|
||||
|
||||
/**
|
||||
* Group a list of objects by date
|
||||
* @param objs List of objects
|
||||
* @param dateParamName Name of the object field which is a date
|
||||
* @return
|
||||
*/
|
||||
public static function groupByDate<T>(objs:Array<T>,dateFieldName:String):Map<String,Array<T>>{
|
||||
|
||||
var out = new Map<String, Array<T> >();
|
||||
for ( o in objs){
|
||||
var d : Date = Reflect.field(o, dateFieldName);
|
||||
var group = out.get(d.toString());
|
||||
if (group == null) group = new Array<T>();
|
||||
group.push(o);
|
||||
out.set(d.toString(), group);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
public static function mapLength<T>(m:Map<T,Dynamic>):Int{
|
||||
var i = 0;
|
||||
for (x in m) i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package tools;
|
||||
|
||||
/**
|
||||
* Date tool
|
||||
* @author fbarbut
|
||||
*/
|
||||
class DateTool
|
||||
{
|
||||
|
||||
public static function now():Date{
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
public static function deltaDays(d:Date,n:Int):Date{
|
||||
return DateTools.delta(d, n * 1000 * 60 * 60 * 24.0);
|
||||
}
|
||||
|
||||
public static function setHourMinute(d:Date, hour:Int, minute:Int):Date{
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), hour, minute, 0);
|
||||
}
|
||||
|
||||
public static function setDateMonth(d:Date, date:Int, month:Int):Date{
|
||||
return new Date(d.getFullYear(), month, date, d.getHours(), d.getMinutes(), 0);
|
||||
}
|
||||
|
||||
|
||||
public static function getLastHourRange(?now:Date){
|
||||
if(now==null) now = Date.now();
|
||||
var HOUR = 1000.0 * 60 * 60;
|
||||
var to = setHourMinute(now,now.getHours(),0);
|
||||
var from = DateTools.delta(to, -HOUR );
|
||||
return {from:from,to:to};
|
||||
}
|
||||
|
||||
public static function getLastMinuteRange(?now:Date){
|
||||
if(now==null) now = Date.now();
|
||||
var MIN = 1000.0 * 60;
|
||||
var to = setHourMinute(now,now.getHours(),now.getMinutes());
|
||||
var from = DateTools.delta(to, -MIN );
|
||||
return {from:from,to:to};
|
||||
}
|
||||
|
||||
public static function getWhichNthDayOfMonth(date:Date):Int {
|
||||
return Math.floor((date.getDate() - 1) / 7) + 1;
|
||||
}
|
||||
|
||||
public static function getNthDayOfMonth(year:Int,month:Int,dayOfWeek:Int,n:Int):Date {
|
||||
//dayOfWeek and getDay() follow the same value system: from 0 (Sunday) to 6 (Saturday)
|
||||
return deltaDays(new Date(year,month,1+7*n, 0, 0, 0), -new Date(year,month,8-(dayOfWeek+1), 0, 0, 0).getDay()-1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package tools;
|
||||
|
||||
|
||||
class FloatTool{
|
||||
|
||||
/**
|
||||
Tries to fix buddy float comparison in Neko...
|
||||
**/
|
||||
public static function isEqual(a:Float,b:Float){
|
||||
a = Math.round(a*10000);
|
||||
b = Math.round(b*10000);
|
||||
return a==b;
|
||||
}
|
||||
|
||||
public static function isInt(f:Float){
|
||||
return isEqual(f , Math.round(f) );
|
||||
}
|
||||
|
||||
//prevent float comparison bug in Neko
|
||||
public static function clean(f:Float){
|
||||
return Math.round(f*100)/100;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package tools;
|
||||
import Common.UserOrder;
|
||||
|
||||
/**
|
||||
* Utility to work on sys.db.Object lists
|
||||
* @author fbarbut
|
||||
*/
|
||||
class ObjectListTool
|
||||
{
|
||||
|
||||
/**
|
||||
* Get a list of IDs from an object list
|
||||
*/
|
||||
public static function getIds( objs:Iterable<sys.db.Object> ):Array<Int>{
|
||||
var out = new Array<Int>();
|
||||
for ( o in objs ) out.push(untyped o.id);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* deduplicate objects on IDs
|
||||
*/
|
||||
public static function deduplicate<T>(objs:Iterable<T>):Array<T>{
|
||||
var out = new Map<Int,T>();
|
||||
for ( u in objs) out.set( untyped u.id, u );
|
||||
return Lambda.array(out);
|
||||
}
|
||||
|
||||
|
||||
public static function toIdMap<T>(objs:Iterable<T>):Map<Int,T>{
|
||||
var out = new Map<Int,T>();
|
||||
for ( u in objs) out.set( untyped u.id, u );
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate user orders. (merge orders on same product from a same user)
|
||||
* @param orders
|
||||
* @return
|
||||
*/
|
||||
public static function deduplicateOrders(orders:Array<UserOrder>):Array<UserOrder>{
|
||||
|
||||
var out = new Map<String,UserOrder>();
|
||||
|
||||
for ( o in orders){
|
||||
|
||||
var key = o.userId + "-" + o.userId2 + "-" + o.productId;
|
||||
var x = out.get(key);
|
||||
if ( x == null){
|
||||
x = o;
|
||||
}else{
|
||||
//null safety
|
||||
if (x.fees == null) x.fees = 0;
|
||||
if (o.fees == null) o.fees = 0;
|
||||
|
||||
//merge
|
||||
x.quantity += o.quantity;
|
||||
x.fees += o.fees;
|
||||
x.subTotal += o.subTotal;
|
||||
x.total += o.total;
|
||||
}
|
||||
|
||||
out.set(key, x);
|
||||
}
|
||||
|
||||
return Lambda.array(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate distributions by key (date+placeId)
|
||||
* @param distribs
|
||||
*/
|
||||
public static function deduplicateDistribsByKey(distribs:Iterable<db.Distribution>){
|
||||
|
||||
var out = new Map<String,db.Distribution>();
|
||||
for ( d in distribs) out.set(d.getKey(), d);
|
||||
return Lambda.array(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group distributions by key (date+placeId)
|
||||
* @param distribs
|
||||
*/
|
||||
public static function groupDistribsByKey(distribs:Iterable<db.Distribution>){
|
||||
|
||||
var out = new Map<String,Array<db.Distribution>>();
|
||||
for ( d in distribs) {
|
||||
|
||||
var v = out.get(d.getKey());
|
||||
if (v == null) v = [];
|
||||
v.push(d);
|
||||
out.set(d.getKey(), v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groupe orders by multidistrib key (date+placeId)
|
||||
*/
|
||||
public static function groupOrdersByKey(ucs:Iterable<db.UserContract>){
|
||||
|
||||
var out = new Map<String,Array<db.UserContract>>();
|
||||
for ( uc in ucs) {
|
||||
var k = uc.distribution.getKey();
|
||||
var v = out.get(k);
|
||||
if (v == null) v = [];
|
||||
v.push(uc);
|
||||
out.set(k, v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group distributions by group and day, order by day
|
||||
*/
|
||||
public static function groupDistributionsByGroupAndDay(dists:Iterable<db.Distribution>){
|
||||
|
||||
var out = new Map<String,Array<db.Distribution>>();
|
||||
for ( d in dists){
|
||||
|
||||
var k = d.date.toString().substr(0, 10) + "-" + d.contract.amap.id;
|
||||
|
||||
var x = out[k];
|
||||
if (x == null) x = [];
|
||||
x.push(d);
|
||||
out[k] = x;
|
||||
|
||||
}
|
||||
|
||||
//sort keys
|
||||
var keys = [];
|
||||
for ( k in out.keys()) keys.push(k);
|
||||
keys.sort(function(a, b){ if (a > b) return 1 else return -1; });
|
||||
|
||||
var out2 = [];
|
||||
for ( k in keys) out2.push(out[k]);
|
||||
|
||||
return out2;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package tools;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class StockTool
|
||||
{
|
||||
|
||||
/**
|
||||
* Stock dispatching between groups.
|
||||
*
|
||||
* i.e we got 10kg of potatoes to dispatch with 3 groups.
|
||||
* I dont want to have 3,3333 kg for each group , but something like [3,3,4]
|
||||
*/
|
||||
public static function dispatch(stock:Int, groups:Int){
|
||||
|
||||
var out = [];
|
||||
|
||||
var modulo = stock % groups;
|
||||
|
||||
stock -= modulo;
|
||||
|
||||
var s = Math.round(stock / groups);
|
||||
|
||||
for ( i in 0...groups) out.push(s);
|
||||
|
||||
for (i in 0...modulo){
|
||||
out[i % out.length]++;
|
||||
}
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user