code from amapei

This commit is contained in:
cagette@ct8
2020-09-26 18:25:18 +00:00
parent 42fe367911
commit 72b4699871
1966 changed files with 220437 additions and 2 deletions
+100
View File
@@ -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;
}
}
+53
View File
@@ -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;
}
}
+367
View File
@@ -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;
}
}
+111
View File
@@ -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());
}
}
+160
View File
@@ -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");
}
}
}
+619
View File
@@ -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"));
}
}
}
+1027
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -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."));
}
}
}
+477
View File
@@ -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/>" );
}
}
+577
View File
@@ -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"));
}
}
+368
View File
@@ -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;
}
}
+273
View File
@@ -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();
}
}
}
+261
View File
@@ -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();
}
}
+741
View File
@@ -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);
}
}
+73
View File
@@ -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");
}
}
}
+211
View File
@@ -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;
}
}
}
+87
View File
@@ -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") );
}
}
}
+39
View File
@@ -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
}
+339
View File
@@ -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));
}
}
+264
View File
@@ -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") );
}
}
}
+18
View File
@@ -0,0 +1,18 @@
package controller;
class Stats extends Controller
{
public function new()
{
super();
}
@tpl("stats/default.mtt")
function doDefault() {
}
}
+294
View File
@@ -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;
}
}
+255
View File
@@ -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()}));
}
}
+154
View File
@@ -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"));
}
}
}
+124
View File
@@ -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"));
}
}
}
}
+223
View File
@@ -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 ====");
}
}
+32
View File
@@ -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é");*/
}
}
+80
View File
@@ -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);
}
}
+142
View File
@@ -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}));
}
}
+20
View File
@@ -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));
}
}
+209
View File
@@ -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));
}
}
+57
View File
@@ -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}));
}
}