code from amapei
This commit is contained in:
Executable
+373
@@ -0,0 +1,373 @@
|
||||
package db;
|
||||
import sugoi.form.ListData.FormData;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
enum AmapFlags {
|
||||
HasMembership; //membership management
|
||||
ShopMode; //shop mode / standard mode
|
||||
HasPayments; //manage payments and user balance
|
||||
ComputeMargin; //compute margin instead of percentage
|
||||
CagetteNetwork; //register in cagette.net groups directory
|
||||
ShopCategoriesFromTaxonomy; //the custom categories are not used anymore, use product taxonomy instead
|
||||
HidePhone; //Hide manager phone on group public page
|
||||
PhoneRequired; //phone number of members is required for this group
|
||||
|
||||
}
|
||||
|
||||
//user registration options
|
||||
enum RegOption{
|
||||
Closed;
|
||||
WaitingList;
|
||||
Open;
|
||||
Full;
|
||||
}
|
||||
|
||||
enum GroupType{
|
||||
Amap; //CSA / GASAP / AMAP
|
||||
GroupedOrders; //groupements d'achat
|
||||
ProducerDrive; //drive de producteurs
|
||||
FarmShop; //vente à la ferme
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AMAP
|
||||
*/
|
||||
class Amap extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<64>;
|
||||
|
||||
@formPopulate("getMembersFormElementData")
|
||||
@:relation(userId)
|
||||
public var contact : SNull<User>;
|
||||
|
||||
public var txtIntro:SNull<SText>; //introduction de l'amap
|
||||
public var txtHome:SNull<SText>; //texte accueil adhérents
|
||||
public var txtDistrib:SNull<SText>; //sur liste d'emargement
|
||||
|
||||
public var extUrl : SNull<SString<64>>; //lien sur logo du groupe
|
||||
|
||||
public var membershipRenewalDate : SNull<SDate>;
|
||||
@hideInForms public var membershipPrice : SNull<STinyInt>;
|
||||
|
||||
@hideInForms
|
||||
public var vatRates : SData<Map<String,Float>>;
|
||||
|
||||
public var flags:SFlags<AmapFlags>;
|
||||
public var groupType:SNull<SEnum<GroupType>>;
|
||||
|
||||
@hideInForms @:relation(imageId)
|
||||
public var image : SNull<sugoi.db.File>;
|
||||
|
||||
@hideInForms public var cdate : SDateTime;
|
||||
@hideInForms @:relation(placeId) public var mainPlace : SNull<db.Place>;
|
||||
|
||||
public var regOption : SEnum<RegOption>;
|
||||
|
||||
@hideInForms public var currency:SString<12>; //name or symbol.
|
||||
@hideInForms public var currencyCode:SString<3>; //https://fr.wikipedia.org/wiki/ISO_4217
|
||||
|
||||
//payments
|
||||
@hideInForms public var allowedPaymentsType:SNull<SData<Array<String>>>;
|
||||
@hideInForms public var checkOrder:SNull<SString<64>>;
|
||||
@hideInForms public var IBAN:SNull<SString<40>>;
|
||||
@hideInForms public var allowMoneyPotWithNegativeBalance:SNull<SBool>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
flags = cast 0;
|
||||
flags.set(CagetteNetwork);
|
||||
flags.set(ShopMode);
|
||||
vatRates = ["5,5%" => 5.5, "20%" => 20];
|
||||
cdate = Date.now();
|
||||
regOption = Open;
|
||||
currency = "€";
|
||||
currencyCode = "EUR";
|
||||
checkOrder = "";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* find the most common delivery place
|
||||
*/
|
||||
public function getMainPlace() {
|
||||
|
||||
if (mainPlace != null && Std.random(100) != 0) {
|
||||
return mainPlace;
|
||||
}else {
|
||||
this.lock();
|
||||
|
||||
var places = getPlaces();
|
||||
|
||||
//just 1 place
|
||||
if (places.length == 1) {
|
||||
this.mainPlace = places.first();
|
||||
this.update();
|
||||
return this.mainPlace;
|
||||
}
|
||||
|
||||
//no places !
|
||||
if (places.length == 0) return null;
|
||||
|
||||
var pids = Lambda.map(places, function(x) return x.id);
|
||||
|
||||
var res = sys.db.Manager.cnx.request("select placeId,count(placeId) as top from Distribution where placeId IN ("+pids.join(",")+") group by placeId order by top desc").results();
|
||||
var res = res.first();
|
||||
var pid :Int = null;
|
||||
|
||||
if (res == null){
|
||||
pid = this.getPlaces().first().id;
|
||||
}else{
|
||||
pid = Std.parseInt(res.placeId);
|
||||
}
|
||||
|
||||
if (pid != 0 && pid != null) {
|
||||
var p = db.Place.manager.get(pid, false);
|
||||
this.mainPlace = p;
|
||||
this.update();
|
||||
return p;
|
||||
}else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods to get flags in templates
|
||||
*/
|
||||
|
||||
public function hasMembership():Bool {
|
||||
return flags != null && flags.has(HasMembership);
|
||||
}
|
||||
|
||||
public function hasShopMode() {
|
||||
return flags.has(ShopMode);
|
||||
}
|
||||
|
||||
public function canExposePhone() {
|
||||
return !flags.has(HidePhone);
|
||||
}
|
||||
|
||||
public function hasPayments(){
|
||||
return flags != null && flags.has(HasPayments);
|
||||
}
|
||||
|
||||
public function hasTaxonomy(){
|
||||
return flags != null && flags.has(ShopCategoriesFromTaxonomy);
|
||||
}
|
||||
|
||||
public function hasPhoneRequired(){
|
||||
return flags != null && flags.has(PhoneRequired);
|
||||
}
|
||||
|
||||
public function getCategoryGroups() {
|
||||
|
||||
//if (flags.has(ShopCategoriesFromTaxonomy)){
|
||||
//return Lambda.array( cast db.TxpCategory.manager.all(false) );
|
||||
//}else{
|
||||
//return Lambda.array( db.CategoryGroup.get(this) );
|
||||
//}
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var categs = new Array<{id:Int,name:String,color:String,pinned:Bool,categs:Array<CategoryInfo>}>();
|
||||
|
||||
if (this.flags.has(db.Amap.AmapFlags.ShopCategoriesFromTaxonomy)){
|
||||
|
||||
//TAXO CATEGORIES
|
||||
var taxoCategs = db.TxpCategory.manager.all(false);
|
||||
var c : Array<CategoryInfo> = Lambda.array(Lambda.map( taxoCategs, function(c){return {id:c.id, name:c.name, subcategories:null}; }));
|
||||
|
||||
categs.push({
|
||||
id:0,
|
||||
name: t._("Product type"),
|
||||
pinned:false,
|
||||
color:"#583816",
|
||||
categs: c
|
||||
});
|
||||
|
||||
}else{
|
||||
|
||||
//CUSTOM CATEGORIES
|
||||
var catGroups = db.CategoryGroup.get(this);
|
||||
for ( cg in catGroups){
|
||||
var color = App.current.view.intToHex(db.CategoryGroup.COLORS[cg.color]);
|
||||
categs.push({
|
||||
id:cg.id,
|
||||
name:cg.name,
|
||||
pinned:cg.pinned,
|
||||
color:color,
|
||||
categs: Lambda.array(Lambda.map( cg.getCategories(), function(c) return c.infos()))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return categs;
|
||||
|
||||
}
|
||||
|
||||
|
||||
//public function canAddMember():Bool {
|
||||
// return isAboOk(true);
|
||||
//}
|
||||
|
||||
/**
|
||||
* Renvoie la liste des contrats actifs
|
||||
* @param large=false
|
||||
*/
|
||||
public function getActiveContracts(?large=false) {
|
||||
return Contract.getActiveContracts(this, large, false);
|
||||
}
|
||||
|
||||
public function getContracts() {
|
||||
return Contract.manager.search($amap == this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* récupere les produits des contracts actifs
|
||||
*/
|
||||
public function getProducts() {
|
||||
var contracts = db.Contract.getActiveContracts(App.current.user.amap,false,false);
|
||||
return Product.manager.search( $contractId in Lambda.map(contracts, function(c) return c.id),{orderBy:name}, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* get next multi-deliveries
|
||||
*/
|
||||
public function getDeliveries(?limit=3){
|
||||
var out = new Map<String,db.Distribution>();
|
||||
for ( c in getActiveContracts()){
|
||||
for ( d in c.getDistribs(true,3)){
|
||||
out.set(d.getKey(), d);
|
||||
}
|
||||
}
|
||||
|
||||
var out = Lambda.array(out);
|
||||
out.sort(function(a, b){
|
||||
return Math.round(a.date.getTime() / 1000) - Math.round(b.date.getTime() / 1000);
|
||||
});
|
||||
return out.slice(0,limit);
|
||||
}
|
||||
|
||||
public function getPlaces() {
|
||||
return Place.manager.search($amap == this, false);
|
||||
}
|
||||
|
||||
public function getVendors() {
|
||||
return Vendor.manager.search($amap == this, false);
|
||||
}
|
||||
|
||||
public function getMembers() {
|
||||
return User.manager.unsafeObjects("Select u.* from User u,UserAmap ua where u.id=ua.userId and ua.amapId="+this.id+" order by u.lastName", false);
|
||||
}
|
||||
|
||||
public function getMembersNum():Int{
|
||||
return UserAmap.manager.count($amapId == this.id);
|
||||
}
|
||||
|
||||
public function getMembersFormElementData():FormData<Int> {
|
||||
var m = getMembers();
|
||||
var out = [];
|
||||
for (mm in m) {
|
||||
|
||||
out.push({label:mm.getCoupleName() , value:mm.id});
|
||||
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
override public function toString() {
|
||||
if (name != '' && name != null) {
|
||||
return name;
|
||||
}else {
|
||||
return 'group#' + id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pour avoir le nom de la periode de cotisation pour une date donnée
|
||||
*/
|
||||
public function getPeriodName(?d:Date):String {
|
||||
if (d == null) d = Date.now();
|
||||
var year = getMembershipYear(d);
|
||||
return getPeriodNameFromYear(year);
|
||||
}
|
||||
|
||||
/**
|
||||
* Si la date de renouvellement est en janvier ou février, on note la cotisation avec l'année en cours,
|
||||
* sinon c'est "à cheval" donc on note la cotis avec l'année la plus ancienne (ex:2014 pour une cotis 2014-2015)
|
||||
*/
|
||||
public function getMembershipYear(?d:Date):Int {
|
||||
if (d == null) d = Date.now();
|
||||
var year = d.getFullYear();
|
||||
var n = membershipRenewalDate;
|
||||
if (n == null) n = Date.now();
|
||||
var renewalDate = new Date(year, n.getMonth(), n.getDate(), 0, 0, 0);
|
||||
|
||||
//if (membershipRenewalDate.getMonth() <= 1) {
|
||||
|
||||
if (d.getTime() < renewalDate.getTime()) {
|
||||
return year-1;
|
||||
}else {
|
||||
return year;
|
||||
}
|
||||
|
||||
//}else {
|
||||
//return year - 1;
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
* à partir d'une année de cotis enregistrée, afficher le nom de la periode
|
||||
* @param y
|
||||
*/
|
||||
public function getPeriodNameFromYear(y:Int):String {
|
||||
if (membershipRenewalDate!=null && membershipRenewalDate.getMonth() <= 1) {
|
||||
return Std.string(y);
|
||||
}else {
|
||||
return Std.string(y) + "-" + Std.string(y+1);
|
||||
}
|
||||
}
|
||||
|
||||
override public function insert(){
|
||||
|
||||
if (txtHome == null){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
txtHome = t._("Welcome in the group of ::name::!\n You can look at the delivery planning or make a new order.",{name:this.name});
|
||||
}
|
||||
|
||||
App.current.event(NewGroup(this,App.current.user));
|
||||
|
||||
super.insert();
|
||||
}
|
||||
|
||||
public function getCurrency():String{
|
||||
|
||||
if (currency == ""){
|
||||
lock();
|
||||
currency = "€";
|
||||
currencyCode = "EUR";
|
||||
update();
|
||||
}
|
||||
|
||||
return currency;
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Group name"),
|
||||
"txtIntro" => t._("Short description"),
|
||||
"txtHome" => t._("Homepage text"),
|
||||
"txtDistrib" => t._("Text for distribution lists"),
|
||||
"extUrl" => t._("Group website URL"),
|
||||
"membershipRenewalDate" => t._("Membership renewal date"),
|
||||
"flags" => t._("Options"),
|
||||
"groupType" => t._("Group type"),
|
||||
"regOption" => t._("Registration setting"),
|
||||
"contact" => t._("Main contact"),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
/**
|
||||
* Basket : represents the orders of a user for specific date + place
|
||||
*/
|
||||
//@:index(userId,placeId,ddate,unique)
|
||||
class Basket extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var cdate : SDateTime; //date when the order has been placed
|
||||
public var num : SInt; //order number
|
||||
|
||||
//TODO : link baskets to a multidistrib ID.
|
||||
|
||||
//2018-07-21 fbarbut : we cannot use keys like this, because some distribution's place or date may change after orders are made.
|
||||
//@:relation(userId) public var user : db.User;
|
||||
//@:relation(placeId) public var place : db.Place;
|
||||
//public var ddate : SDate; //date of the delivery
|
||||
|
||||
public static var CACHE = new Map<String,db.Basket>();
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
cdate = Date.now();
|
||||
}
|
||||
|
||||
public static function emptyCache(){
|
||||
CACHE = new Map<String,db.Basket>();
|
||||
}
|
||||
|
||||
public static function get(user:db.User, place:db.Place, date:Date, ?lock = false):db.Basket{
|
||||
|
||||
date = tools.DateTool.setHourMinute(date, 0, 0);
|
||||
|
||||
//caching
|
||||
// var k = user.id + "-" + place.id + "-" + date.toString().substr(0, 10);
|
||||
// var b = CACHE.get(k);
|
||||
var b = null;
|
||||
// if (b == null){
|
||||
var md = MultiDistrib.get(date, place,db.Contract.TYPE_VARORDER);
|
||||
var orders = md.getUserOrders(user);
|
||||
|
||||
for( o in orders){
|
||||
if(o.basket!=null) {
|
||||
b = o.basket;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// CACHE.set(k, b);
|
||||
// }
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Basket or create it if it doesn't exists.
|
||||
* Also link existing orders to this basket
|
||||
* @param user
|
||||
* @param place
|
||||
* @param date
|
||||
*/
|
||||
public static function getOrCreate(user, place, date){
|
||||
var b = get(user, place, date, true);
|
||||
|
||||
date = tools.DateTool.setHourMinute(date, 0, 0);
|
||||
|
||||
if (b == null){
|
||||
|
||||
//compute basket number
|
||||
var md = MultiDistrib.get(date, place,db.Contract.TYPE_VARORDER);
|
||||
|
||||
b = new Basket();
|
||||
b.num = md.getUsers().length + 1;
|
||||
//TODO : should be more safe to do something like "b.num = MAX(num)+1 FROM Basket"
|
||||
b.insert();
|
||||
|
||||
//try to find orders and link them to the basket
|
||||
var dids = tools.ObjectListTool.getIds(md.distributions);
|
||||
for ( o in db.UserContract.manager.search( ($distributionId in dids) && ($user == user), true)){
|
||||
o.basket = b;
|
||||
o.update();
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get basket's orders
|
||||
*/
|
||||
public function getOrders(){
|
||||
return db.UserContract.manager.search($basket == this, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of operations which paid this basket
|
||||
* @return
|
||||
*/
|
||||
public function getPayments():Iterable<db.Operation>{
|
||||
|
||||
var op = getOrderOperation(false);
|
||||
if (op == null){
|
||||
return [];
|
||||
}else{
|
||||
return op.getRelatedPayments();
|
||||
}
|
||||
}
|
||||
|
||||
public function getOrderOperation(?onlyPending=true):db.Operation{
|
||||
|
||||
var order = getOrders().first();
|
||||
if(order==null) return null;
|
||||
|
||||
var key = db.Distribution.makeKey(order.distribution.date, order.distribution.place);
|
||||
return db.Operation.findVOrderTransactionFor(key, order.user, order.distribution.place.amap, onlyPending);
|
||||
|
||||
}
|
||||
|
||||
public function isValidated(){
|
||||
|
||||
var ordersPaid = Lambda.count(getOrders(), function(o) return !o.paid) == 0;
|
||||
var op = getOrderOperation(false);
|
||||
var orderOperationNotPending = op!=null ? op.pending == false : true;
|
||||
var paymentOperationsNotPending = Lambda.count(getPayments(), function(p) return p.pending) == 0;
|
||||
|
||||
return ordersPaid && orderOperationNotPending && paymentOperationsNotPending;
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
class Category extends sys.db.Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name :SString<128>;
|
||||
|
||||
@:relation(categoryGroupId) public var categoryGroup:db.CategoryGroup;
|
||||
|
||||
/**
|
||||
* get category color in hexa
|
||||
*/
|
||||
public function getColor():String {
|
||||
return App.current.view.intToHex(db.CategoryGroup.COLORS[categoryGroup.color]);
|
||||
}
|
||||
|
||||
public function infos():CategoryInfo{
|
||||
return {id:id, name:name, /*parent:categoryGroup.id, /*pinned:categoryGroup.pinned*/};
|
||||
}
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
|
||||
|
||||
class CategoryGroup extends sys.db.Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
public var color : STinyInt; //color id
|
||||
public var pinned : SBool; //if true, the products tagged with these categories will be pinned on top of the shop.
|
||||
|
||||
@:relation(amapId) public var amap:db.Amap;
|
||||
|
||||
@:skip public static var COLORS = [
|
||||
0x7BAD1C, //vert clair
|
||||
0x007700, //vert foncé
|
||||
0x583816, //marron
|
||||
0xD97801, //orange carotte
|
||||
0xB1933D, //sable
|
||||
0xC91F25, //rouge
|
||||
0x6F0D2E, //vin
|
||||
0x616161, //gris
|
||||
];
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
pinned = false;
|
||||
color = 7;
|
||||
}
|
||||
|
||||
public function getCategories() {
|
||||
return db.Category.manager.search($categoryGroup == this, false);
|
||||
}
|
||||
|
||||
public static function get(amap:db.Amap):List<CategoryGroup> {
|
||||
return manager.search($amap == amap, false);
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Category group name"),
|
||||
"pinned" => t._("Pinned on top"),
|
||||
"color" => t._("Color"),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+285
@@ -0,0 +1,285 @@
|
||||
package db;
|
||||
import sugoi.form.ListData.FormData;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
enum ContractFlags {
|
||||
UsersCanOrder; //adhérents peuvent saisir eux meme la commande en ligne
|
||||
StockManagement; //gestion des commandes
|
||||
PercentageOnOrders; //calcul d'une commission supplémentaire
|
||||
|
||||
//LogisticMgmt; //gestion logistique
|
||||
//SubGroups; //sous groupes pour commandes groupées
|
||||
//InviteFriends; //peut inviter des amis à participer à la commande
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract
|
||||
*
|
||||
* un contrat réunissant pluseiurs produits d'un meme fournisseur
|
||||
* qui sont livrés au meme endroit et meme moment;
|
||||
*
|
||||
*/
|
||||
class Contract extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<64>;
|
||||
|
||||
//responsable
|
||||
@formPopulate("populate") @:relation(userId) public var contact : SNull<User>;
|
||||
@formPopulate("populateVendor") @:relation(vendorId) public var vendor : Vendor;
|
||||
|
||||
public var startDate:SDate;
|
||||
public var endDate :SDate;
|
||||
|
||||
public var description:SNull<SText>;
|
||||
|
||||
@:relation(amapId) public var amap:Amap;
|
||||
public var distributorNum:STinyInt;
|
||||
public var flags : SFlags<ContractFlags>;
|
||||
|
||||
public var percentageValue : SNull<SInt>; //fees percentage
|
||||
public var percentageName : SNull<SString<64>>; //fee name
|
||||
|
||||
public var type : SInt;
|
||||
@:skip public static var TYPE_CONSTORDERS = 0; //CSA contract
|
||||
@:skip public static var TYPE_VARORDER = 1; //varying orders contract
|
||||
|
||||
@:skip var cache_hasActiveDistribs : Bool;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
flags = cast 0;
|
||||
distributorNum = 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The products can be ordered currently ?
|
||||
*
|
||||
* @deprecated it depends on distributions
|
||||
*/
|
||||
public function isUserOrderAvailable():Bool {
|
||||
|
||||
if (type == TYPE_CONSTORDERS ) {
|
||||
return isVisibleInShop();
|
||||
}else {
|
||||
|
||||
//if ( cache_hasActiveDistribs != null ) return cache_hasActiveDistribs;
|
||||
|
||||
//for varying orders, we need to know if there are some available deliveries
|
||||
var n = Date.now();
|
||||
var d = db.Distribution.manager.count( $orderStartDate <= n && $orderEndDate >= n && $contractId==this.id);
|
||||
|
||||
//tmp : add the "old" deliveries which have a null orderStartDate
|
||||
//d += db.Distribution.manager.count( $orderStartDate == null && $date > n && $contractId == this.id );
|
||||
|
||||
//cache_hasActiveDistribs = d > 0;
|
||||
//return cache_hasActiveDistribs && isVisibleInShop();
|
||||
return d>0 && isVisibleInShop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The products can be displayed in a shop ?
|
||||
*/
|
||||
public function isVisibleInShop():Bool {
|
||||
|
||||
//yes if the contract is active and the 'UsersCanOrder' flag is checked
|
||||
var n = Date.now().getTime();
|
||||
return flags.has(UsersCanOrder) && n < this.endDate.getTime() && n > this.startDate.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* is currently open to orders
|
||||
*/
|
||||
public function hasRunningOrders(){
|
||||
var now = Date.now();
|
||||
var n = now.getTime();
|
||||
|
||||
var contractOpen = flags.has(UsersCanOrder) && n < this.endDate.getTime() && n > this.startDate.getTime();
|
||||
var d = db.Distribution.manager.count( $orderStartDate <= now && $orderEndDate > now && $contractId==this.id);
|
||||
|
||||
return contractOpen && d > 0;
|
||||
}
|
||||
|
||||
|
||||
public function hasPercentageOnOrders():Bool {
|
||||
return flags.has(PercentageOnOrders) && percentageValue!=null && percentageValue!=0;
|
||||
}
|
||||
|
||||
public function hasStockManagement():Bool {
|
||||
return flags.has(StockManagement);
|
||||
}
|
||||
|
||||
/**
|
||||
* computes a 'percentage' fee or a 'margin' fee
|
||||
* depending on the group settings
|
||||
*
|
||||
* @param basePrice
|
||||
*/
|
||||
public function computeFees(basePrice:Float) {
|
||||
if (!hasPercentageOnOrders()) return 0.0;
|
||||
|
||||
if (amap.flags.has(ComputeMargin)) {
|
||||
//commercial margin
|
||||
return (basePrice / ((100 - percentageValue) / 100)) - basePrice;
|
||||
|
||||
}else {
|
||||
//add a percentage
|
||||
return percentageValue / 100 * basePrice;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param amap
|
||||
* @param large = false Si true, montre les contrats terminés depuis moins d'un mois
|
||||
* @param lock = false
|
||||
*/
|
||||
public static function getActiveContracts(amap:Amap,?large = false, ?lock = false) {
|
||||
var now = Date.now();
|
||||
var end = Date.now();
|
||||
|
||||
if (large) {
|
||||
end = DateTools.delta(end , -1000.0 * 60 * 60 * 24 * 30);
|
||||
return db.Contract.manager.search($amap == amap && $endDate > end,{orderBy:-vendorId}, lock);
|
||||
}else {
|
||||
return db.Contract.manager.search($amap == amap && $endDate > now && $startDate < now,{orderBy:-vendorId}, lock);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get products in this contract
|
||||
* @param onlyActive = true
|
||||
* @return
|
||||
*/
|
||||
public function getProducts(?onlyActive = true):List<Product> {
|
||||
if (onlyActive) {
|
||||
return Product.manager.search($contract==this && $active==true,{orderBy:name},false);
|
||||
}else {
|
||||
return Product.manager.search($contract==this,{orderBy:name},false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get a few products to display
|
||||
* @param limit = 6
|
||||
*/
|
||||
public function getProductsPreview(?limit = 6){
|
||||
return Product.manager.search($contract==this && $active==true,{limit:limit,orderBy:-id},false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get users who have orders in this contract ( including user2 )
|
||||
* @return Array<db.User>
|
||||
*/
|
||||
public function getUsers():Array<db.User> {
|
||||
var pids = getProducts().map(function(x) return x.id);
|
||||
var ucs = UserContract.manager.search($productId in pids, false);
|
||||
var ucs2 = [];
|
||||
for( uc in ucs) {
|
||||
ucs2.push(uc.user);
|
||||
if(uc.user2!=null) ucs2.push(uc.user2);
|
||||
}
|
||||
|
||||
//comme un user peut avoir plusieurs produits au sein d'un contrat, il faut dédupliquer cette liste
|
||||
var out = new Map<Int,db.User>();
|
||||
for (u in ucs2) {
|
||||
out.set(u.id, u);
|
||||
}
|
||||
|
||||
return Lambda.array(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all orders of this contract
|
||||
* @param d A delivery is needed for varying orders contract
|
||||
* @return
|
||||
*/
|
||||
public function getOrders(?d:db.Distribution):Array<db.UserContract> {
|
||||
if (type == TYPE_VARORDER && d == null) throw "This type of contract must have a delivery";
|
||||
|
||||
//get product ids, some of the products may have been disabled but we keep the order
|
||||
var pids = getProducts(false).map(function(x) return x.id);
|
||||
var ucs = new List<db.UserContract>();
|
||||
if (type == TYPE_VARORDER) {
|
||||
ucs = UserContract.manager.search( ($productId in pids) && $distribution==d,{orderBy:userId}, false);
|
||||
}else {
|
||||
ucs = UserContract.manager.search( ($productId in pids) ,{orderBy:userId}, false);
|
||||
}
|
||||
return Lambda.array(ucs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get orders for a user.
|
||||
*
|
||||
* @param d
|
||||
* @return
|
||||
*/
|
||||
public function getUserOrders(u:db.User,?d:db.Distribution):Array<db.UserContract> {
|
||||
if (type == TYPE_VARORDER && d == null) throw "This type of contract must have a delivery";
|
||||
|
||||
var pids = getProducts(false).map(function(x) return x.id);
|
||||
var ucs = new List<db.UserContract>();
|
||||
if (d != null && d.contract.type==db.Contract.TYPE_VARORDER) {
|
||||
ucs = UserContract.manager.search( ($productId in pids) && $distribution==d && ($user==u || $user2==u ), false);
|
||||
}else {
|
||||
ucs = UserContract.manager.search( ($productId in pids) && ($user==u || $user2==u ),false);
|
||||
}
|
||||
return Lambda.array(ucs);
|
||||
}
|
||||
|
||||
public function getDistribs(excludeOld = true,?limit=999):List<Distribution> {
|
||||
if (excludeOld) {
|
||||
//still include deliveries which just expired in last 24h
|
||||
return Distribution.manager.search($end > DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24) && $contract == this, { orderBy:date,limit:limit } );
|
||||
}else{
|
||||
return Distribution.manager.search( $contract == this, { orderBy:date,limit:limit } );
|
||||
}
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
return name+" du "+this.startDate.toString().substr(0,10)+" au "+this.endDate.toString().substr(0,10);
|
||||
}
|
||||
|
||||
public function populate() {
|
||||
return App.current.user.amap.getMembersFormElementData();
|
||||
}
|
||||
|
||||
/**
|
||||
* get a vendor list as form data
|
||||
* @return
|
||||
*/
|
||||
public function populateVendor():FormData<Int>{
|
||||
var vendors = Vendor.manager.search($amap == App.current.user.amap, false);
|
||||
var out = [];
|
||||
for (v in vendors) {
|
||||
out.push({label:v.name, value:v.id });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Contract name"),
|
||||
"startDate" => t._("Start date"),
|
||||
"endDate" => t._("End date"),
|
||||
"description" => t._("Description"),
|
||||
"distributorNum" => t._("Number of required distributors (0 to 4)"),
|
||||
"flags" => t._("Options"),
|
||||
"percentageValue" => t._("Fees percentage"),
|
||||
"percentageName" => t._("Fees label"),
|
||||
"contact" => t._("Contact"),
|
||||
"vendor" => t._("Farmer"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
package db;
|
||||
import sugoi.form.ListData;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
/**
|
||||
* Distrib
|
||||
*/
|
||||
class Distribution extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
|
||||
@:relation(contractId)
|
||||
public var contract : Contract;
|
||||
|
||||
@formPopulate("placePopulate")
|
||||
@:relation(placeId)
|
||||
public var place : Place;
|
||||
|
||||
//when orders are open
|
||||
@hideInForms public var orderStartDate : SNull<SDateTime>;
|
||||
@hideInForms public var orderEndDate : SNull<SDateTime>;
|
||||
|
||||
//start and end date for delivery
|
||||
public var date : SDateTime;
|
||||
public var end : SDateTime;
|
||||
|
||||
@:relation(distributionCycleId) public var distributionCycle : SNull<DistributionCycle>;
|
||||
|
||||
@formPopulate("populate") @:relation(distributor1Id) public var distributor1 : SNull<db.User>;
|
||||
@formPopulate("populate") @:relation(distributor2Id) public var distributor2 : SNull<db.User>;
|
||||
@formPopulate("populate") @:relation(distributor3Id) public var distributor3 : SNull<db.User>;
|
||||
@formPopulate("populate") @:relation(distributor4Id) public var distributor4 : SNull<db.User>;
|
||||
|
||||
@hideInForms public var validated :SBool;
|
||||
|
||||
public static var DISTRIBUTION_VALIDATION_LIMIT = 10;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
date = Date.now();
|
||||
end = DateTools.delta(date, 1000 * 60 * 90);
|
||||
validated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get group members list as form data
|
||||
*/
|
||||
public function populate():FormData<Int> {
|
||||
if(App.current.user!=null && App.current.user.getAmap()!=null){
|
||||
return App.current.user.getAmap().getMembersFormElementData();
|
||||
}else{
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* get groups places as form data
|
||||
* @return
|
||||
*/
|
||||
public function placePopulate():FormData<Int> {
|
||||
var out = [];
|
||||
var places = new List();
|
||||
if(this.contract!=null){
|
||||
//edit form
|
||||
places = db.Place.manager.search($amapId == this.contract.amap.id, false);
|
||||
}else{
|
||||
//insert form
|
||||
places = db.Place.manager.search($amapId == App.current.user.amap.id, false);
|
||||
}
|
||||
|
||||
for (p in places) out.push( { label:p.name,value:p.id} );
|
||||
return out;
|
||||
}
|
||||
|
||||
public function hasEnoughDistributors() {
|
||||
var n = contract.distributorNum;
|
||||
|
||||
var d = 0;
|
||||
if (distributor1 != null) d++;
|
||||
if (distributor2 != null) d++;
|
||||
if (distributor3 != null) d++;
|
||||
if (distributor4 != null) d++;
|
||||
|
||||
return (d >= n) ;
|
||||
}
|
||||
|
||||
public function isDistributor(u:User) {
|
||||
if (u == null) return false;
|
||||
return (distributor1!=null && u.id == distributor1.id) ||
|
||||
(distributor2!=null && u.id == distributor2.id) ||
|
||||
(distributor3!=null && u.id == distributor3.id) ||
|
||||
(distributor4!=null && u.id == distributor4.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* String to identify this distribution (debug use only)
|
||||
*/
|
||||
override public function toString() {
|
||||
return "#" + id + " Delivery " + date.toString() + " of " + contract.name;
|
||||
}
|
||||
|
||||
public function getOrders() {
|
||||
|
||||
if ( this.contract.type == Contract.TYPE_CONSTORDERS){
|
||||
var pids = db.Product.manager.search($contract == this.contract, false);
|
||||
var pids = Lambda.map(pids, function(x) return x.id);
|
||||
return UserContract.manager.search( ($productId in pids), false);
|
||||
}else{
|
||||
return UserContract.manager.search($distribution == this, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserOrders(user:db.User){
|
||||
if ( this.contract.type == Contract.TYPE_CONSTORDERS){
|
||||
var pids = db.Product.manager.search($contract == this.contract, false);
|
||||
var pids = Lambda.map(pids, function(x) return x.id);
|
||||
return UserContract.manager.search( (($productId in pids) && ($user==user || $user2==user) ), false);
|
||||
}else{
|
||||
return UserContract.manager.search($distribution == this && $user==user, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getUsers():Iterable<db.User>{
|
||||
|
||||
return tools.ObjectListTool.deduplicate( Lambda.map(getOrders(), function(x) return x.user ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get TTC turnover for this distribution
|
||||
*/
|
||||
public function getTurnOver(){
|
||||
|
||||
var sql = "select SUM(quantity * productPrice) from UserContract where productId IN (" + tools.ObjectListTool.getIds(contract.getProducts()).join(",") +") ";
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
sql += " and distributionId=" + this.id;
|
||||
}
|
||||
|
||||
return sys.db.Manager.cnx.request(sql).getFloatResult(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HT turnover for this distribution
|
||||
*/
|
||||
public function getHTTurnOver(){
|
||||
|
||||
var pids = tools.ObjectListTool.getIds(contract.getProducts(false));
|
||||
|
||||
var sql = "select SUM(uc.quantity * (p.price/(1+p.vat/100)) ) from UserContract uc, Product p ";
|
||||
sql += "where uc.productId IN (" + pids.join(",") +") ";
|
||||
sql += "and p.id=uc.productId ";
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
sql += " and uc.distributionId=" + this.id;
|
||||
}
|
||||
|
||||
return sys.db.Manager.cnx.request(sql).getFloatResult(0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function canOrderNow() {
|
||||
|
||||
if (orderEndDate == null) {
|
||||
return this.contract.isUserOrderAvailable();
|
||||
}else {
|
||||
var n = Date.now().getTime();
|
||||
var f = this.contract.flags.has(UsersCanOrder);
|
||||
|
||||
return f && n < orderEndDate.getTime() && n > orderStartDate.getTime();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next multi-devliveries
|
||||
* ( deliveries including more than one vendors )
|
||||
*/
|
||||
/*public static function getNextMultiDeliveries(){
|
||||
|
||||
var out = new Map<String,{place:Place,startDate:Date,endDate:Date,active:Bool,products:Array<ProductInfo>}>();
|
||||
return Lambda.array(manager.search($orderStartDate <= Date.now() && $orderEndDate >= Date.now() && $contract==contract,false));
|
||||
|
||||
var now = Date.now();
|
||||
|
||||
var contracts = Contract.getActiveContracts(App.current.user.amap);
|
||||
var cids = Lambda.map(contracts, function(p) return p.id);
|
||||
|
||||
//available deliveries + some of the next deliveries
|
||||
|
||||
var distribs = db.Distribution.manager.search(($contractId in cids) && $orderEndDate >= now, { orderBy:date }, false);
|
||||
var inOneMonth = DateTools.delta(now, 1000.0 * 60 * 60 * 24 * 30);
|
||||
for (d in distribs) {
|
||||
|
||||
var o = out.get(d.getKey());
|
||||
if (o == null) o = {place:d.place, startDate:d.date,active:null, endDate:d.end, products:[]};
|
||||
for ( p in d.contract.getProductsPreview(8)){
|
||||
if (o.products.length >= 8) break;
|
||||
o.products.push( p.infos() );
|
||||
}
|
||||
|
||||
if (d.orderStartDate.getTime() <= now.getTime() ){
|
||||
//order currently open
|
||||
o.active = true;
|
||||
}else if (d.orderStartDate.getTime() <= inOneMonth.getTime() ){
|
||||
//open soon
|
||||
o.active = false;
|
||||
}else{
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
out.set(d.getKey(), o);
|
||||
}
|
||||
return Lambda.array(out);
|
||||
}*/
|
||||
|
||||
override public function update(){
|
||||
this.end = new Date(this.date.getFullYear(), this.date.getMonth(), this.date.getDate(), this.end.getHours(), this.end.getMinutes(), 0);
|
||||
super.update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get open to orders deliveries
|
||||
* @param contract
|
||||
*/
|
||||
public static function getOpenToOrdersDeliveries(contract:db.Contract){
|
||||
|
||||
return Lambda.array(manager.search($orderStartDate <= Date.now() && $orderEndDate >= Date.now() && $contract==contract,{orderBy:date},false));
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return a string like $placeId-$date.
|
||||
*
|
||||
* It's an ID representing all the distributions happening on that day at that place.
|
||||
*/
|
||||
public function getKey():String{
|
||||
return db.Distribution.makeKey(this.date, this.place);
|
||||
}
|
||||
|
||||
public static function makeKey(date, place){
|
||||
return date.toString().substr(0, 10) +"|"+Std.string(place.id);
|
||||
}
|
||||
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"date" => t._("Date"),
|
||||
"endDate" => t._("End hour"),
|
||||
"place" => t._("Place"),
|
||||
"distributor1" => t._("Distributor #1"),
|
||||
"distributor2" => t._("Distributor #2"),
|
||||
"distributor3" => t._("Distributor #3"),
|
||||
"distributor4" => t._("Distributor #4"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+54
@@ -0,0 +1,54 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
using tools.DateTool;
|
||||
|
||||
enum CycleType {
|
||||
Weekly;
|
||||
Monthly;
|
||||
BiWeekly;
|
||||
TriWeekly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Distribution cycle
|
||||
*/
|
||||
class DistributionCycle extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
@:relation(contractId) public var contract : Contract;
|
||||
public var cycleType:SEnum<CycleType>;
|
||||
public var startDate : SDate; //debut
|
||||
public var endDate : SDate; //fin de la recurrence
|
||||
public var startHour : SDateTime;
|
||||
public var endHour : SDateTime;
|
||||
public var daysBeforeOrderStart:SNull<STinyInt>;
|
||||
public var daysBeforeOrderEnd:SNull<STinyInt>;
|
||||
public var openingHour:SNull<SDate>;
|
||||
public var closingHour:SNull<SDate>;
|
||||
@formPopulate("placePopulate") @:relation(placeId) public var place : Place;
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"cycleType" => t._("Fréquence"),
|
||||
"startDate" => t._("Date de début"),
|
||||
"endDate" => t._("Date de fin"),
|
||||
"daysBeforeOrderStart" => t._("Ouverture de commande (nbre de jours avant distribution)"),
|
||||
"daysBeforeOrderEnd" => t._("Fermeture de commande (nbre de jours avant distribution)"),
|
||||
"place" => t._("Place"),
|
||||
];
|
||||
}
|
||||
|
||||
public function placePopulate():Array<{label:String,value:Int}> {
|
||||
var out = [];
|
||||
if ( App.current.user == null || App.current.user.amap == null ) return out;
|
||||
var places = db.Place.manager.search($amapId == App.current.user.amap.id, false);
|
||||
for (p in places) out.push( { label:p.name,value :p.id } );
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
|
||||
/**
|
||||
* Tool to Graph daily values
|
||||
*/
|
||||
@:id(key,date)
|
||||
class Graph extends Object{
|
||||
|
||||
public var key:SString<128>;
|
||||
public var date:SDate;
|
||||
public var value:SFloat;
|
||||
|
||||
/**
|
||||
* record a value
|
||||
*/
|
||||
public static function record(key:String,value:Float,?date:Date){
|
||||
if(date==null) date = Date.now();
|
||||
date = new Date(date.getFullYear(),date.getMonth(),date.getDate(),0,0,0);
|
||||
var o = manager.select($key==key && $date==date,true);
|
||||
if(o == null){
|
||||
o = new Graph();
|
||||
o.date = date;
|
||||
o.key = key;
|
||||
o.value = value;
|
||||
o.insert();
|
||||
}else{
|
||||
o.value = value;
|
||||
o.update();
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of records in a time frame
|
||||
*/
|
||||
public static function getRange(key:String,from:Date,to:Date):Array<db.Graph>{
|
||||
return Lambda.array(manager.search($key==key && $date>=from && $date<=to, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
@:id(userId,amapId,year)
|
||||
class Membership extends Object
|
||||
{
|
||||
@:relation(amapId)
|
||||
public var amap : Amap;
|
||||
|
||||
@:relation(userId)
|
||||
public var user : db.User;
|
||||
|
||||
//année de cotisation (année la plus ancienne si a cheval sur deux années : 2014-2015 -> 2014)
|
||||
public var year : Int;
|
||||
public var date : SNull<SDate>;
|
||||
|
||||
public static function get(user:User, amap:Amap,year:Int, ?lock = false) {
|
||||
return manager.select($user == user && $amap == amap && $year == year, lock);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import tink.core.Noise;
|
||||
import tink.core.Outcome;
|
||||
import sugoi.mail.IMailer;
|
||||
|
||||
|
||||
/**
|
||||
* Message sent from the message Section
|
||||
*/
|
||||
class Message extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
@:relation(amapId) public var amap : SNull<Amap>;
|
||||
@:relation(senderId) public var sender : SNull<User>;
|
||||
|
||||
public var recipientListId : SNull<SString<12>>;
|
||||
public var recipients : SNull<SData<Array<String>>>;
|
||||
|
||||
public var title : SString<128>;
|
||||
public var body : SText;
|
||||
public var date : SDateTime;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
import tink.core.Error;
|
||||
|
||||
enum OperationType{
|
||||
VOrder; //order on a varying order contract
|
||||
COrder;//order on a constant order contract
|
||||
Payment;
|
||||
Membership;
|
||||
}
|
||||
|
||||
typedef PaymentInfos = {type:String, ?remoteOpId:Int, ?netAmount:Float};
|
||||
typedef VOrderInfos = {basketId:Int};
|
||||
typedef COrderInfos = {contractId:Int};
|
||||
|
||||
|
||||
/**
|
||||
* Payment operation
|
||||
*
|
||||
* @author fbarbut
|
||||
*/
|
||||
class Operation extends sys.db.Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
public var amount : SFloat;
|
||||
public var date : SDateTime;
|
||||
public var type : SEnum<OperationType>;
|
||||
public var data : SData<Dynamic>;
|
||||
@hideInForms @:relation(relationId) public var relation : SNull<db.Operation>; //linked to another operation : ie a payment pays an order
|
||||
|
||||
@formPopulate("populate") @:relation(userId) public var user : db.User;
|
||||
@hideInForms @:relation(groupId) public var group : db.Amap;
|
||||
|
||||
public var pending : SBool; //a pending payment means the payment has not been confirmed, a pending order means the ordre can still change before closing.
|
||||
|
||||
public function getTypeIndex(){
|
||||
var e : OperationType = type;
|
||||
return e.getIndex();
|
||||
}
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
pending = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* if operation is a payment, give the payment type
|
||||
*/
|
||||
public function getPaymentType():String{
|
||||
switch(type){
|
||||
case Payment:
|
||||
var x : PaymentInfos = this.data;
|
||||
if (data == null){
|
||||
return null;
|
||||
}else{
|
||||
return x.type;
|
||||
}
|
||||
default : return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get translated payment type name
|
||||
*/
|
||||
public function getPaymentTypeName(){
|
||||
var t = getPaymentType();
|
||||
if (t == null) return null;
|
||||
for ( pt in service.PaymentService.getAllPaymentTypes()){
|
||||
if (pt.type == t) return pt.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get payments linked to this order transaction
|
||||
*/
|
||||
public function getRelatedPayments(){
|
||||
return db.Operation.manager.search($relation == this, false);
|
||||
}
|
||||
|
||||
public function getOrderInfos(){
|
||||
return switch(type){
|
||||
case COrder, VOrder : this.data;
|
||||
default : null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPaymentInfos():PaymentInfos{
|
||||
return switch(type){
|
||||
case Payment : this.data;
|
||||
default : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function countOperations(user:db.User, group:db.Amap):Int{
|
||||
return manager.count($user == user && $group == group);
|
||||
}
|
||||
|
||||
/**
|
||||
* get all user operations
|
||||
* @param user -
|
||||
* @param group -
|
||||
* @param reverse=false -
|
||||
*/
|
||||
public static function getOperations(user:db.User, group:db.Amap,?reverse=false ){
|
||||
if(reverse) {
|
||||
return manager.search($user == user && $group == group,{orderBy:-date},false);
|
||||
}
|
||||
return manager.search($user == user && $group == group,{orderBy:date},false);
|
||||
}
|
||||
|
||||
public static function getOperationsWithIndex(user:db.User, group:db.Amap,index:Int,limit:Int,?reverse=false ){
|
||||
if(reverse) {
|
||||
return manager.search($user == user && $group == group, { limit:[index,limit], orderBy:-date }, false);
|
||||
}
|
||||
return manager.search($user == user && $group == group, { limit:[index,limit], orderBy:date },false);
|
||||
}
|
||||
|
||||
/*public static function getOrder_Operations(user:db.User, group:db.Amap,?limit=50 ){
|
||||
//return manager.search($user == user && $group == group && $type!=Payment,{orderBy:date},false);
|
||||
//return manager.search($user == user && $group == group && $relation==null,{orderBy:date},false);
|
||||
return manager.search($user == user && $group == group,{orderBy:date,limit:limit},false);
|
||||
}*/
|
||||
|
||||
public static function getPaymentOperations(user:db.User, group:db.Amap,?limit=50){
|
||||
return manager.search($user == user && $group == group && $type == Payment, {orderBy:date,limit:limit},false);
|
||||
}
|
||||
|
||||
public static function getLastOperations(user:db.User, group:db.Amap, ?limit = 50){
|
||||
|
||||
var c = manager.count($user == user && $group == group);
|
||||
c -= limit;
|
||||
if (c < 0) c = 0;
|
||||
return manager.search($user == user && $group == group,{orderBy:date,limit:[c,limit]},false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new transaction
|
||||
* @param orders
|
||||
*/
|
||||
public static function makeOrderOperation(orders: Array<db.UserContract>, ?basket:db.Basket){
|
||||
|
||||
if (orders == null) throw "orders are null";
|
||||
if (orders.length == 0) throw "no orders";
|
||||
if (orders[0].user == null ) throw "no user in order";
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var _amount = 0.0;
|
||||
for ( o in orders ){
|
||||
var t = o.quantity * o.productPrice;
|
||||
_amount += t + t * (o.feesRate / 100);
|
||||
}
|
||||
|
||||
var contract = orders[0].product.contract;
|
||||
|
||||
var op = new db.Operation();
|
||||
var user = orders[0].user;
|
||||
var group = orders[0].product.contract.amap;
|
||||
|
||||
if (contract.type == db.Contract.TYPE_CONSTORDERS){
|
||||
//Constant orders
|
||||
var dNum = contract.getDistribs(false).length;
|
||||
op.name = "" + contract.name + " (" + contract.vendor.name+") " + dNum + " " + t._("deliveries");
|
||||
op.amount = dNum * (0 - _amount);
|
||||
op.date = Date.now();
|
||||
op.type = COrder;
|
||||
var data : COrderInfos = {contractId:contract.id};
|
||||
op.data = data;
|
||||
op.user = user;
|
||||
op.group = group;
|
||||
op.pending = true;
|
||||
|
||||
}else{
|
||||
|
||||
if (basket == null) throw "varying contract orders should have a basket";
|
||||
|
||||
//varying orders
|
||||
var date = App.current.view.dDate(orders[0].distribution.date);
|
||||
op.name = t._("Order for ::date::",{date:date});
|
||||
op.amount = 0 - _amount;
|
||||
op.date = Date.now();
|
||||
op.type = VOrder;
|
||||
var data : VOrderInfos = {basketId:basket.id};
|
||||
op.data = data;
|
||||
op.user = user;
|
||||
op.group = group;
|
||||
op.pending = true;
|
||||
}
|
||||
|
||||
op.insert();
|
||||
|
||||
service.PaymentService.updateUserBalance(op.user, op.group);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* update an order operation
|
||||
*/
|
||||
public static function updateOrderOperation(op:db.Operation, orders: Array<db.UserContract>, ?basket:db.Basket){
|
||||
|
||||
op.lock();
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var _amount = 0.0;
|
||||
for ( o in orders ){
|
||||
var a = o.quantity * o.productPrice;
|
||||
_amount += a + a * (o.feesRate / 100);
|
||||
}
|
||||
|
||||
var contract = orders[0].product.contract;
|
||||
if (contract.type == db.Contract.TYPE_CONSTORDERS){
|
||||
//Constant orders
|
||||
var dNum = contract.getDistribs(false).length;
|
||||
op.name = "" + contract.name + " (" + contract.vendor.name+") "+ dNum + " " + t._("deliveries");
|
||||
op.amount = dNum * (0 - _amount);
|
||||
}else{
|
||||
|
||||
if (basket == null) throw "varying contract orders should have a basket";
|
||||
op.amount = 0 - _amount;
|
||||
}
|
||||
|
||||
//op.date = Date.now(); //leave original date
|
||||
op.update();
|
||||
service.PaymentService.updateUserBalance(op.user, op.group);
|
||||
return op;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a new payment operation
|
||||
* @param type
|
||||
* @param amount
|
||||
* @param name
|
||||
* @param relation
|
||||
*/
|
||||
public static function makePaymentOperation(user:db.User,group:db.Amap,type:String, amount:Float, name:String, ?relation:db.Operation ){
|
||||
|
||||
var t = new db.Operation();
|
||||
t.amount = Math.abs(amount);
|
||||
t.date = Date.now();
|
||||
t.name = name;
|
||||
t.group = group;
|
||||
t.pending = true;
|
||||
t.user = user;
|
||||
t.type = Payment;
|
||||
var data : PaymentInfos = {type:type};
|
||||
t.data = data;
|
||||
if(relation!=null) t.relation = relation;
|
||||
t.insert();
|
||||
|
||||
service.PaymentService.updateUserBalance(user, group);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* when updating a (varying) order , we need to update the existing pending transaction
|
||||
*/
|
||||
public static function findVOrderTransactionFor(dkey:String, user:db.User, group:db.Amap,?onlyPending=true):db.Operation{
|
||||
|
||||
//throw 'find $dkey for user ${user.id} in group ${group.id} , onlyPending:$onlyPending';
|
||||
|
||||
var date = dkey.split("|")[0];
|
||||
var placeId = Std.parseInt(dkey.split("|")[1]);
|
||||
var transactions = new List();
|
||||
if (onlyPending){
|
||||
transactions = manager.search($user == user && $group == group && $pending == true && $type==VOrder , {orderBy:-date}, true);
|
||||
}else{
|
||||
transactions = manager.search($user == user && $group == group && $type==VOrder , {orderBy:-date}, true);
|
||||
}
|
||||
|
||||
//throw transactions;
|
||||
|
||||
var place = db.Place.manager.get(placeId,false);
|
||||
var date = Date.fromString(date);
|
||||
var basket = db.Basket.get(user, place, date);
|
||||
if(basket==null) throw new Error('No basket found for user #'+user.id+', place #'+place.id+', date '+date);
|
||||
|
||||
for ( t in transactions){
|
||||
switch(t.type){
|
||||
case VOrder :
|
||||
var data : VOrderInfos = t.data;
|
||||
if ( data == null) continue;
|
||||
if (data.basketId == basket.id) return t;
|
||||
default :
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* when updating a constant order, we need to update the existing operation.
|
||||
*/
|
||||
public static function findCOrderTransactionFor(contract:db.Contract, user:db.User):db.Operation{
|
||||
|
||||
if (contract.type != db.Contract.TYPE_CONSTORDERS) throw "contract type should be TYPE_CONSTORDERS";
|
||||
|
||||
var transactions = manager.search($user == user && $group == contract.amap && $amount<=0 && $type==COrder, {orderBy:date,limit:100}, true);
|
||||
|
||||
for ( t in transactions){
|
||||
|
||||
switch(t.type){
|
||||
|
||||
case COrder :
|
||||
|
||||
//var id = Lambda.find(orders, function(x) return db.UserContract.manager.get(x, false) != null);
|
||||
//if (id == null) {
|
||||
////all orders in this transaction dont exists anymore
|
||||
//t.delete();
|
||||
//continue;
|
||||
//}else{
|
||||
//for ( i in orders){
|
||||
//var order = db.UserContract.manager.get(i);
|
||||
//if (order == null) continue;
|
||||
//if (order.product.contract.id == contract.id) return t;
|
||||
//}
|
||||
//}
|
||||
var data : COrderInfos = t.data;
|
||||
if (data == null) continue;
|
||||
if (data.contractId == contract.id) return t;
|
||||
|
||||
|
||||
default :
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create/update the needed order operations and returns the related operations
|
||||
* @param orders
|
||||
*/
|
||||
public static function onOrderConfirm(orders:Array<db.UserContract>):Array<db.Operation>{
|
||||
|
||||
if (orders.length == 0) return null;
|
||||
if (orders[0] == null) return null;
|
||||
|
||||
var out = [];
|
||||
var user = orders[0].user;
|
||||
var group = orders[0].product.contract.amap;
|
||||
|
||||
//should not go further if group has not activated payements
|
||||
if (user==null || !group.hasPayments()) return null;
|
||||
|
||||
//we consider that ALL orders are from the same contract type : varying or constant
|
||||
if (orders[0].product.contract.type == db.Contract.TYPE_VARORDER ){
|
||||
|
||||
// varying contract :
|
||||
//manage separatly orders which occur at different dates
|
||||
var ordersGroup = tools.ObjectListTool.groupOrdersByKey(orders);
|
||||
|
||||
for ( orders in ordersGroup){
|
||||
|
||||
//find basket
|
||||
var basket = null;
|
||||
for ( o in orders) {
|
||||
if (o.basket != null) {
|
||||
basket = o.basket;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//get all orders for the same multidistrib, in order to update related operation.
|
||||
var k = orders[0].distribution.getKey();
|
||||
var allOrders = db.UserContract.getUserOrdersByMultiDistrib(k, user, group);
|
||||
|
||||
//existing transaction
|
||||
var existing = db.Operation.findVOrderTransactionFor( k , user, group, false);
|
||||
|
||||
var op;
|
||||
if (existing != null){
|
||||
op = db.Operation.updateOrderOperation(existing,allOrders,basket);
|
||||
}else{
|
||||
op = db.Operation.makeOrderOperation(allOrders,basket);
|
||||
}
|
||||
out.push(op);
|
||||
|
||||
//delete order and payment operations if sum of orders qt is 0
|
||||
/*var sum = 0.0;
|
||||
for ( o in allOrders) sum += o.quantity;
|
||||
if ( sum == 0 ) {
|
||||
existing.delete();
|
||||
op.delete();
|
||||
}*/
|
||||
}
|
||||
|
||||
}else{
|
||||
|
||||
// constant contract
|
||||
// create/update a transaction computed like $distribNumber * $price.
|
||||
var contract = orders[0].product.contract;
|
||||
|
||||
var existing = db.Operation.findCOrderTransactionFor( contract , user);
|
||||
if (existing != null){
|
||||
out.push( db.Operation.updateOrderOperation(existing, contract.getUserOrders(user) ) );
|
||||
}else{
|
||||
out.push( db.Operation.makeOrderOperation( contract.getUserOrders(user) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
public function populate(){
|
||||
return App.current.user.getAmap().getMembersFormElementData();
|
||||
}
|
||||
|
||||
/*public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Text"),
|
||||
"date" => t._("Date"),
|
||||
"endDate" => t._("End date"),
|
||||
"place" => t._("Place"),
|
||||
"distributor1" => t._("Distributor #1"),
|
||||
"distributor2" => t._("Distributor #2"),
|
||||
"distributor3" => t._("Distributor #3"),
|
||||
"distributor4" => t._("Distributor #4"),
|
||||
];
|
||||
}*/
|
||||
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
class Place extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<64>;
|
||||
public var address1:SNull<SString<64>>;
|
||||
public var address2:SNull<SString<64>>;
|
||||
public var zipCode:SString<32>;
|
||||
public var city:SString<64>;
|
||||
public var country:SNull<SString<64>>;
|
||||
|
||||
//latitude/longitude
|
||||
public var lat:SNull<SFloat>;
|
||||
public var lng:SNull<SFloat>;
|
||||
|
||||
@hideInForms @:relation(amapId) public var amap : Amap;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
country = "France";
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
if (name == null) {
|
||||
return "place";
|
||||
}else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public function getFullAddress(){
|
||||
var str = new StringBuf();
|
||||
str.add(name+", \n");
|
||||
if (address1 != null) str.add(address1 + ", \n");
|
||||
if (address2 != null) str.add(address2 + ", \n");
|
||||
if (zipCode != null) str.add(zipCode);
|
||||
if (city != null) str.add(" - "+city);
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* get adress without 'name' field.
|
||||
*/
|
||||
public function getAddress(){
|
||||
var str = new StringBuf();
|
||||
if (address1 != null) str.add(address1 + ", \n");
|
||||
if (address2 != null) str.add(address2 + ", \n");
|
||||
if (zipCode != null) str.add(zipCode);
|
||||
if (city != null) str.add(" - "+city);
|
||||
if(country != null) str.add(", "+country);
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public static function getLabels():Map<String,String>{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Name"),
|
||||
"address1" => t._("Address 1"),
|
||||
"address2" => t._("Address 2"),
|
||||
"zipCode" => t._("Zip code"),
|
||||
"city" => t._("City"),
|
||||
"country" => t._("Country"),
|
||||
"lat" => t._("Latitude"),
|
||||
"lng" => t._("Longitude"),
|
||||
];
|
||||
}
|
||||
|
||||
public function getInfos():PlaceInfos{
|
||||
return {
|
||||
id:id,
|
||||
name:name,
|
||||
address1:address1,
|
||||
address2:address2,
|
||||
zipCode:zipCode,
|
||||
city:city,
|
||||
latitude : lat,
|
||||
longitude: lng
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+206
@@ -0,0 +1,206 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Product
|
||||
*/
|
||||
class Product extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
public var ref : SNull<SString<32>>; //référence produit
|
||||
|
||||
@:relation(contractId)
|
||||
public var contract : Contract;
|
||||
|
||||
//prix TTC
|
||||
public var price : SFloat;
|
||||
public var vat : SFloat;
|
||||
|
||||
public var desc : SNull<SText>;
|
||||
public var stock : SNull<SFloat>; //if qantity can be float, stock should be float
|
||||
|
||||
public var unitType : SNull<SEnum<Unit>>; // Kg / L / g / units
|
||||
public var qt : SNull<SFloat>;
|
||||
|
||||
public var organic : SBool;
|
||||
public var variablePrice : Bool; //price can vary depending on weighting of the product
|
||||
public var multiWeight : Bool; //product cannot be cumulated in one order record
|
||||
|
||||
//https://docs.google.com/document/d/1IqHN8THT6zbKrLdHDClKZLWgKWeL0xw6cYOiFofw04I/edit
|
||||
@hideInForms public var wholesale : Bool; //this product is a wholesale product (crate,bag,pallet)
|
||||
@hideInForms public var retail : Bool; //this products is a fraction of a wholesale product
|
||||
@hideInForms public var bulk : Bool; //(vrac) warn the customer this product is not packaged
|
||||
public var hasFloatQt:SBool; //this product can be ordered in "float" quantity
|
||||
|
||||
@hideInForms @:relation(imageId) public var image : SNull<sugoi.db.File>;
|
||||
@:relation(txpProductId) public var txpProduct : SNull<db.TxpProduct>; //taxonomy
|
||||
|
||||
|
||||
public var active : SBool; //if false, product disabled, not visible on front office
|
||||
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
//type = 0;
|
||||
organic = false;
|
||||
hasFloatQt = false;
|
||||
active = true;
|
||||
variablePrice = false;
|
||||
multiWeight = false;
|
||||
wholesale = false;
|
||||
retail = false;
|
||||
bulk = false;
|
||||
vat = 5.5;
|
||||
unitType = Unit.Piece;
|
||||
qt = 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product image URL
|
||||
*/
|
||||
public function getImage() {
|
||||
if (image == null) {
|
||||
if (txpProduct != null){
|
||||
return "/img/taxo/cat" + txpProduct.category.id + ".png";
|
||||
}else{
|
||||
return "/img/unknown.png";
|
||||
}
|
||||
}else {
|
||||
return App.current.view.file(image);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
||||
if (unitType != null && qt != null && qt != 0){
|
||||
return name +" " + qt + " " + App.current.view.unit(unitType);
|
||||
}else{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* get price including margins
|
||||
*/
|
||||
public function getPrice():Float{
|
||||
return price + contract.computeFees(price);
|
||||
}
|
||||
|
||||
/**
|
||||
get product infos as an anonymous object
|
||||
@param CategFromTaxo=false
|
||||
@param populateCategories=tru
|
||||
@return
|
||||
**/
|
||||
public function infos(?CategFromTaxo=false,?populateCategories=true,?distribution:db.Distribution):ProductInfo {
|
||||
var o :ProductInfo = {
|
||||
id : id,
|
||||
ref : ref,
|
||||
name : name,
|
||||
image : getImage(),
|
||||
contractId : contract.id,
|
||||
price : getPrice(),
|
||||
vat : vat,
|
||||
vatValue: (vat != 0 && vat != null) ? ( this.price - (this.price / (vat/100+1)) ) : null,
|
||||
contractTax : contract.percentageValue,
|
||||
contractTaxName : contract.percentageName,
|
||||
desc : App.current.view.nl2br(desc),
|
||||
categories : null,
|
||||
subcategories:null,
|
||||
orderable : this.contract.isUserOrderAvailable(),
|
||||
stock : contract.hasStockManagement() ? this.stock : null,
|
||||
hasFloatQt : hasFloatQt,
|
||||
qt:qt,
|
||||
unitType:unitType,
|
||||
organic:organic,
|
||||
variablePrice:variablePrice,
|
||||
wholesale:wholesale,
|
||||
active: active,
|
||||
distributionId : distribution==null ? null : distribution.id,
|
||||
}
|
||||
|
||||
if(populateCategories){
|
||||
if (CategFromTaxo){
|
||||
o.categories = [txpProduct == null?null:txpProduct.category.id];
|
||||
o.subcategories = [txpProduct == null?null:txpProduct.subCategory.id];
|
||||
}else{
|
||||
o.categories = Lambda.array(Lambda.map(getCategories(), function(c) return c.id));
|
||||
o.subcategories = o.categories;
|
||||
}
|
||||
}
|
||||
|
||||
App.current.event(ProductInfosEvent(o,distribution));
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* customs categs
|
||||
*/
|
||||
public function getCategories() {
|
||||
//"Types de produits" categGroup first
|
||||
//var pc = db.ProductCategory.manager.search($productId == id, {orderBy:categoryId}, false);
|
||||
return Lambda.map(db.ProductCategory.manager.search($productId == id,{orderBy:categoryId},false), function(x) return x.category);
|
||||
}
|
||||
|
||||
/**
|
||||
* general categs
|
||||
*/
|
||||
public function getFullCategorization(){
|
||||
if (txpProduct == null) return [];
|
||||
return txpProduct.getFullCategorization();
|
||||
}
|
||||
|
||||
public static function getByRef(c:db.Contract, ref:String){
|
||||
var pids = tools.ObjectListTool.getIds(c.getProducts(false));
|
||||
return db.Product.manager.select($ref == ref && $id in pids, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix values that will make mysql 5.7 scream
|
||||
*/
|
||||
function check(){
|
||||
if(this.vat==null) this.vat=0;
|
||||
if(this.name.length>128) this.name = this.name.substr(0,128);
|
||||
}
|
||||
|
||||
override public function update(){
|
||||
check();
|
||||
super.update();
|
||||
}
|
||||
|
||||
override public function insert(){
|
||||
check();
|
||||
super.insert();
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Product name"),
|
||||
"ref" => t._("Product ID"),
|
||||
"price" => t._("Price"),
|
||||
"desc" => t._("Description"),
|
||||
"stock" => t._("Stock"),
|
||||
"unitType" => t._("Base unit"),
|
||||
"qt" => t._("Quantity"),
|
||||
"hasFloatQt" => t._("Allow fractional quantities"),
|
||||
"active" => t._("Available"),
|
||||
"organic" => t._("Organic agriculture"),
|
||||
"vat" => t._("VAT Rate"),
|
||||
"variablePrice" => t._("Variable price based on weight"),
|
||||
"multiWeight" => t._("Multi-weighing"),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
package db;
|
||||
import sys.db.Types;
|
||||
|
||||
@:id(productId,categoryId)
|
||||
class ProductCategory extends sys.db.Object
|
||||
{
|
||||
|
||||
@:relation(productId)
|
||||
public var product : db.Product;
|
||||
|
||||
@:relation(categoryId)
|
||||
public var category : db.Category;
|
||||
|
||||
public static function getOrCreate(product, category){
|
||||
|
||||
var x = db.ProductCategory.manager.select($product==product && $category==category,true);
|
||||
if(x==null){
|
||||
x = new db.ProductCategory();
|
||||
x.product = product;
|
||||
x.category = category;
|
||||
x.insert();
|
||||
}
|
||||
return x;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TxpCategory extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
|
||||
public function getSubCategories(){
|
||||
|
||||
return db.TxpSubCategory.manager.search($category == this, false);
|
||||
|
||||
}
|
||||
|
||||
override public function toString(){
|
||||
return '#$id-$name';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TxpProduct extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
@:relation(categoryId) public var category : db.TxpCategory;
|
||||
@:relation(subCategoryId) public var subCategory : db.TxpSubCategory;
|
||||
|
||||
override public function toString(){
|
||||
return '#$id-$name';
|
||||
}
|
||||
|
||||
public function getFullCategorization():Array<String>{
|
||||
return [category.name, subCategory.name, name];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
/**
|
||||
* ...
|
||||
* @author fbarbut
|
||||
*/
|
||||
class TxpSubCategory extends Object
|
||||
{
|
||||
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
@:relation(categoryId) public var category:db.TxpCategory;
|
||||
|
||||
|
||||
public function getProducts(){
|
||||
|
||||
return db.TxpProduct.manager.search($subCategory == this, false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
override public function toString(){
|
||||
return '#$id-$name';
|
||||
}
|
||||
}
|
||||
Executable
+574
@@ -0,0 +1,574 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import db.UserAmap;
|
||||
import Common;
|
||||
|
||||
enum UserFlags {
|
||||
HasEmailNotif4h; //send notifications by mail 4h before
|
||||
HasEmailNotif24h; //send notifications by mail 24h before
|
||||
HasEmailNotifOuverture; //send notifications by mail on command open
|
||||
//Tuto; //enable tutorials
|
||||
}
|
||||
|
||||
/**
|
||||
* Site-wide right
|
||||
*/
|
||||
enum RightSite {
|
||||
Admin;
|
||||
}
|
||||
|
||||
@:index(email,unique)
|
||||
class User extends Object {
|
||||
|
||||
public var id : SId;
|
||||
public var lang : SString<2>;
|
||||
@:skip public var name(get, set) : String;
|
||||
public var pass : STinyText;
|
||||
public var rights : SFlags<RightSite>;
|
||||
|
||||
public var firstName:SString<32>;
|
||||
public var lastName:SString<32>;
|
||||
public var email : SString<64>;
|
||||
public var phone:SNull<SString<19>>;
|
||||
|
||||
public var firstName2:SNull<SString<32>>;
|
||||
public var lastName2:SNull<SString<32>>;
|
||||
public var email2 : SNull<SString<64>>;
|
||||
public var phone2:SNull<SString<19>>;
|
||||
|
||||
public var address1:SNull<SString<64>>;
|
||||
public var address2:SNull<SString<64>>;
|
||||
public var zipCode:SNull<SString<32>>;
|
||||
public var city:SNull<SString<25>>;
|
||||
|
||||
@:skip public var amap(get_amap, null) : Amap;
|
||||
|
||||
public var cdate : SDate; //creation
|
||||
public var ldate : SNull<SDateTime>; //derniere connexion
|
||||
|
||||
public var flags : SFlags<UserFlags>;
|
||||
|
||||
@hideInForms public var tutoState : SNull<SData<{name:String,step:Int}>>; //tutorial state
|
||||
|
||||
public var apiKey : SNull<SString<128>>; //private API key
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
|
||||
//default values
|
||||
cdate = Date.now();
|
||||
rights = sys.db.Types.SFlags.ofInt(0);
|
||||
flags = sys.db.Types.SFlags.ofInt(0);
|
||||
flags.set(HasEmailNotif24h);
|
||||
flags.set(HasEmailNotifOuverture);
|
||||
lang = "fr";
|
||||
pass = "";
|
||||
|
||||
}
|
||||
|
||||
public override function toString() {
|
||||
return getName()+" ["+id+"]";
|
||||
}
|
||||
|
||||
public function isAdmin() {
|
||||
return rights.has(Admin) || id==1;
|
||||
}
|
||||
|
||||
public static function login(user:db.User, email:String) {
|
||||
|
||||
user.lock();
|
||||
user.ldate = Date.now();
|
||||
user.update();
|
||||
App.current.session.setUser(user);
|
||||
if (App.current.session.data == null) App.current.session.data = {};
|
||||
//Who's connected, user1 or user2 ?
|
||||
App.current.session.data.whichUser = (email == user.email) ? 0 : 1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* is this user the manager of the current group
|
||||
*/
|
||||
public function isAmapManager() {
|
||||
var a = getAmap();
|
||||
if (a == null) return false;
|
||||
var ua = getUserAmap(a);
|
||||
if (ua == null) return false;
|
||||
return ua.hasRight(Right.GroupAdmin);
|
||||
}
|
||||
|
||||
function getUserAmap(amap:db.Amap):db.UserAmap {
|
||||
return db.UserAmap.get(this, amap);
|
||||
}
|
||||
|
||||
public function isFullyRegistred(){
|
||||
return pass != null && pass != "";
|
||||
}
|
||||
|
||||
public function makeMemberOf(group:db.Amap){
|
||||
var ua = db.UserAmap.get(this, group);
|
||||
if (ua == null) {
|
||||
ua = new db.UserAmap();
|
||||
ua.user = this;
|
||||
ua.amap = group;
|
||||
ua.insert();
|
||||
}
|
||||
return ua;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Est ce que ce membre a la gestion de ce contrat
|
||||
* si null, est ce qu'il a la gestion d'un des contrat, n'importe lequel (utilse pour afficher 'gestion contrat' dans la nav )
|
||||
* @param contract
|
||||
*/
|
||||
public function isContractManager(?contract:db.Contract ) {
|
||||
if (isAdmin()) return true;
|
||||
if (contract != null) {
|
||||
return canManageContract(contract);
|
||||
}else {
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.rights == null) return false;
|
||||
for (r in ua.rights) {
|
||||
switch(r) {
|
||||
case Right.ContractAdmin(cid):
|
||||
return true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function canManageAllContracts(){
|
||||
if (isAdmin()) return true;
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.rights == null) return false;
|
||||
for (r in ua.rights) {
|
||||
switch(r) {
|
||||
case Right.ContractAdmin(cid):
|
||||
if(cid==null) return true;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canAccessMessages():Bool {
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.hasRight(Right.Messages)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canAccessMembership():Bool {
|
||||
var ua = getUserAmap(getAmap());
|
||||
if (ua == null) return false;
|
||||
if (ua.hasRight(Right.Membership)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canManageContract(c:db.Contract):Bool {
|
||||
var ua = getUserAmap(c.amap);
|
||||
if (ua == null) return false;
|
||||
if (ua.hasRight(Right.ContractAdmin())) return true;
|
||||
if (ua.hasRight(Right.ContractAdmin(c.id))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getContractManager(?lock=false) {
|
||||
return Contract.manager.search($amap == amap && $contact == this, false);
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return get_name();
|
||||
}
|
||||
|
||||
public function get_name() {
|
||||
return lastName + " " + firstName;
|
||||
}
|
||||
|
||||
public function getCoupleName() {
|
||||
var n = lastName + " " + firstName;
|
||||
if (lastName2 != null) {
|
||||
n = n + " / " + lastName2 + " " + firstName2;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public function set_name(name:String) {
|
||||
var name = name.split(' ');
|
||||
firstName = name[0];
|
||||
lastName = name[1];
|
||||
return firstName+" "+lastName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a user password
|
||||
* @param p
|
||||
*/
|
||||
public function setPass(p:String) {
|
||||
if (p == null){
|
||||
this.pass = "";
|
||||
}else{
|
||||
this.pass = haxe.crypto.Md5.encode( App.config.get('key') + StringTools.trim(p));
|
||||
}
|
||||
return this.pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renvoie les commandes actuelles du user
|
||||
* @param _amap force une amap
|
||||
* @param lock=false
|
||||
*/
|
||||
public function getOrders(?_amap:db.Amap,?lock = false):List<UserContract> {
|
||||
var a = _amap == null ? getAmap() : _amap;
|
||||
var c = a.getActiveContracts(true);
|
||||
var cids = Lambda.map(c,function(m) return m.id);
|
||||
var pids = Lambda.map(db.Product.manager.search($contractId in cids,false), function(x) return x.id);
|
||||
var out = UserContract.manager.search(($userId == id || $userId2 == id) && $productId in pids, lock);
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie les commandes à partir d'une liste de contrats
|
||||
*/
|
||||
public function getOrdersFromContracts(c:Iterable<db.Contract>):List<db.UserContract> {
|
||||
var cids = Lambda.map(c,function(m) return m.id);
|
||||
var pids = Lambda.map(db.Product.manager.search($contractId in cids,false), function(x) return x.id);
|
||||
return UserContract.manager.search(($userId == id || $userId2 == id) && $productId in pids, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie les commandes de contrat variables à partir d'une distribution
|
||||
*/
|
||||
public function getOrdersFromDistrib(d:db.Distribution):List<db.UserContract> {
|
||||
var pids = Lambda.map(db.Product.manager.search($contractId == d.contract.id, false), function(x) return x.id);
|
||||
return UserContract.manager.search(($userId == id || $userId2 == id) && $distributionId==d.id && $productId in pids , false);
|
||||
}
|
||||
|
||||
public function get_amap():Amap {
|
||||
return getAmap();
|
||||
}
|
||||
|
||||
/**
|
||||
* renvoie l'amap selectionnée par le user en cours
|
||||
*/
|
||||
public function getAmap() {
|
||||
|
||||
if (App.current.user != null && id != App.current.user.id) throw "This function is valid only for the current user";
|
||||
if (App.current.session == null) return null;
|
||||
if (App.current.session.data == null ) return null;
|
||||
var a = App.current.session.data.amapId;
|
||||
if (a == null) {
|
||||
return null;
|
||||
}else {
|
||||
return Amap.manager.get(a,false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get groups this user belongs to
|
||||
*/
|
||||
public function getAmaps():List<db.Amap> {
|
||||
return Lambda.map(UserAmap.manager.search($user == this, false), function(o) return o.amap);
|
||||
}
|
||||
|
||||
public function isMemberOf(amap:Amap) {
|
||||
return UserAmap.manager.select($user == this && $amapId == amap.id, false) != null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renvoie la liste des contrats dans lequel l'adherent a des commandes
|
||||
* @param lock=false
|
||||
* @return
|
||||
*/
|
||||
public function getContracts(?lock=false):Array<Contract> {
|
||||
var out = [];
|
||||
var ucs = getOrders(lock);
|
||||
for (uc in ucs) {
|
||||
if (!Lambda.has(out, uc.product.contract)) {
|
||||
out.push(uc.product.contract);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge fields of 2 users, then delete the second (u2)
|
||||
* Be carefull : check before calling this function that u2 can be safely deleted !
|
||||
*/
|
||||
public function merge(u2:db.User) {
|
||||
|
||||
this.lock();
|
||||
u2.lock();
|
||||
|
||||
var m = function(a, b) {
|
||||
return a == null || a=="" ? b : a;
|
||||
}
|
||||
|
||||
this.address1 = m(this.address1, u2.address1);
|
||||
this.address2 = m(this.address2, u2.address2);
|
||||
this.zipCode = m(this.zipCode, u2.zipCode);
|
||||
this.city = m(this.city, u2.city);
|
||||
|
||||
//find how to merge the 2 names in each account
|
||||
if (this.email == u2.email) {
|
||||
|
||||
this.firstName = m(this.firstName, u2.firstName);
|
||||
this.lastName = m(this.lastName, u2.lastName);
|
||||
this.phone = m(this.phone, u2.phone);
|
||||
|
||||
} else if (this.email == u2.email2) {
|
||||
|
||||
this.firstName = m(this.firstName, u2.firstName2);
|
||||
this.lastName = m(this.lastName, u2.lastName2);
|
||||
this.phone = m(this.phone, u2.phone2);
|
||||
}
|
||||
|
||||
if (this.email2 == u2.email) {
|
||||
|
||||
this.firstName2 = m(this.firstName2, u2.firstName);
|
||||
this.lastName2 = m(this.lastName2, u2.lastName);
|
||||
this.phone2 = m(this.phone2, u2.phone);
|
||||
|
||||
} else if (this.email2 == u2.email2) {
|
||||
|
||||
this.firstName2 = m(this.firstName2, u2.firstName2);
|
||||
this.lastName2 = m(this.lastName2, u2.lastName2);
|
||||
this.phone2 = m(this.phone2, u2.phone2);
|
||||
|
||||
}
|
||||
|
||||
u2.delete();
|
||||
this.update();
|
||||
|
||||
}
|
||||
|
||||
public static function getOrCreate(firstName:String, lastName:String, email:String):db.User{
|
||||
var u = db.User.manager.select($email == email || $email2 == email, true);
|
||||
if (u == null){
|
||||
u = new db.User();
|
||||
u.firstName = firstName;
|
||||
u.lastName = lastName;
|
||||
u.email = email;
|
||||
u.insert();
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for similar users in the DB ( same firstName+lastName or same email )
|
||||
*/
|
||||
public static function __getSimilar(firstName:String, lastName:String, email:String,?firstName2:String, ?lastName2:String, ?email2:String):List<db.User> {
|
||||
var out = new Array();
|
||||
out = Lambda.array(User.manager.search($firstName.like(firstName) && $lastName.like(lastName), false));
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($firstName2.like(firstName) && $lastName2.like(lastName), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email), false)));
|
||||
|
||||
//recherche pour le deuxieme user
|
||||
if (lastName2 != "" && lastName2 != null && firstName2 != "" && firstName2 != null) {
|
||||
out = out.concat(Lambda.array(User.manager.search($firstName.like(firstName2) && $lastName.like(lastName2), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($firstName2.like(firstName2) && $lastName2.like(lastName2), false)));
|
||||
}
|
||||
if (email2 != null && email2 != "") {
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email2), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email2), false)));
|
||||
}
|
||||
|
||||
//dedouble
|
||||
var x = new Map<Int,db.User>();
|
||||
for ( oo in out) {
|
||||
x.set(oo.id, oo);
|
||||
}
|
||||
return Lambda.list(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for similar users in the DB ( same email )
|
||||
*/
|
||||
public static function getSameEmail(email:String, ?email2:String){
|
||||
|
||||
var out = new Array();
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email), false)));
|
||||
if (email2 != null && email2 != "") {
|
||||
out = out.concat(Lambda.array(User.manager.search($email.like(email2), false)));
|
||||
out = out.concat(Lambda.array(User.manager.search($email2.like(email2), false)));
|
||||
}
|
||||
return Lambda.list(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users with no contracts
|
||||
**/
|
||||
public static function getUsers_NoContracts(?index:Int,?limit:Int):List<db.User> {
|
||||
var productsIds = App.current.user.getAmap().getProducts().map(function(x) return x.id);
|
||||
var uc = UserContract.manager.search($productId in productsIds, false);
|
||||
var uc2 = uc.map(function(x) return x.user.id); //liste des userId avec un contrat dans cette amap
|
||||
|
||||
// J. Le Clerc - BUGFIX#1 Ne pas oublier les contrats alternés
|
||||
for (u in uc) {
|
||||
if (u.user2 != null) {
|
||||
uc2.add(u.user2.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (uc2.length > 0){
|
||||
//les gens qui sont dans cette amap et qui n'ont pas de contrat de cette amap
|
||||
var ua = db.UserAmap.manager.unsafeObjects("select * from UserAmap where amapId=" + App.current.user.getAmap().id +" and userId NOT IN(" + uc2.join(",") + ")", false);
|
||||
return Lambda.map(ua, function(x) return x.user);
|
||||
}else{
|
||||
return App.current.user.amap.getMembers();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* User with contracts
|
||||
*/
|
||||
public static function getUsers_Contracts(?index:Int,?limit:Int):List<db.User> {
|
||||
var productsIds = App.current.user.getAmap().getProducts().map(function(x) return x.id);
|
||||
if (productsIds.length == 0) return new List();
|
||||
return db.User.manager.unsafeObjects("select u.* from User u, UserContract uc where uc.productId IN(" + productsIds.join(",") + ") AND (uc.userId=u.id OR uc.userId2=u.id) group by u.id ORDER BY u.lastName", false);
|
||||
}
|
||||
|
||||
|
||||
public static function getUsers_NoMembership(?index:Int,?limit:Int):List<db.User> {
|
||||
var ua = new List();
|
||||
if (index == null && limit == null) {
|
||||
ua = db.UserAmap.manager.search($amap == App.current.user.amap, false);
|
||||
}else {
|
||||
ua = db.UserAmap.manager.search($amap == App.current.user.amap,{limit:[index,limit]}, false);
|
||||
}
|
||||
|
||||
for (u in Lambda.array(ua)) {
|
||||
if (u.hasValidMembership()) ua.remove(u);
|
||||
}
|
||||
|
||||
return Lambda.map(ua, function(x) return x.user);
|
||||
}
|
||||
|
||||
public static function getUsers_NewUsers(?index:Int, ?limit:Int):List<db.User> {
|
||||
|
||||
var uas = db.UserAmap.manager.search($amap == App.current.user.amap, false);
|
||||
var ids = Lambda.map(uas, function(x) return x.user.id);
|
||||
if (index == null && limit == null) {
|
||||
return db.User.manager.search($pass == "" && ($id in ids), {orderBy:lastName} ,false);
|
||||
}else {
|
||||
return db.User.manager.search($pass == "" && ($id in ids), {limit:[index, limit] ,orderBy:lastName} , false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function sendInvitation(group:db.Amap) {
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if (isFullyRegistred()) throw t._("This user cannot receive an invitation");
|
||||
|
||||
/*var group : db.Amap = null;
|
||||
|
||||
if (App.current.user == null) {
|
||||
group = this.getAmaps().first();
|
||||
}else {
|
||||
//prend l'amap du user connecté qui a lancé l'invite.
|
||||
group = App.current.user.amap;
|
||||
}*/
|
||||
|
||||
//store token
|
||||
var k = sugoi.db.Session.generateId();
|
||||
sugoi.db.Cache.set("validation" + k, this.id, 60 * 60 * 24 * 30); //expire in 1 month
|
||||
|
||||
var e = new sugoi.mail.Mail();
|
||||
if (group != null){
|
||||
e.setSubject(t._("Invitation")+" "+group.name);
|
||||
}else{
|
||||
e.setSubject(t._("Invitation Cagette.net"));
|
||||
}
|
||||
|
||||
e.addRecipient(this.email,this.getName());
|
||||
e.setSender(App.config.get("default_email"),t._("Cagette.net"));
|
||||
|
||||
var html = App.current.processTemplate("mail/invitation.mtt", {
|
||||
email:email,
|
||||
email2:email2,
|
||||
groupName:(group == null?null:group.name),
|
||||
name:firstName,
|
||||
k:k
|
||||
} );
|
||||
e.setHtmlBody(html);
|
||||
|
||||
App.sendMail(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* cleaning before saving
|
||||
*/
|
||||
override public function insert() {
|
||||
clean();
|
||||
super.insert();
|
||||
}
|
||||
|
||||
override public function update() {
|
||||
clean();
|
||||
super.update();
|
||||
}
|
||||
|
||||
function clean() {
|
||||
|
||||
//emails
|
||||
this.email = this.email.toLowerCase();
|
||||
if (this.email2 != null) this.email2 = this.email2.toLowerCase();
|
||||
|
||||
//lastname
|
||||
if (this.lastName != null) this.lastName = this.lastName.toUpperCase();
|
||||
if (this.lastName2 != null) this.lastName2 = this.lastName2.toUpperCase();
|
||||
|
||||
if(pass==null) pass="";
|
||||
}
|
||||
|
||||
public function infos():UserInfo{
|
||||
return {
|
||||
id:id,
|
||||
name : getName(),
|
||||
email : email
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get form labels
|
||||
*/
|
||||
public static function getLabels():Map<String,String>{
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"firstName" => t._("First name"),
|
||||
"lastName" => t._("Last name"),
|
||||
"email" => t._("Email"),
|
||||
"phone" => t._("Phone"),
|
||||
"firstName2"=> t._("Partner first name"),
|
||||
"lastName2" => t._("Partner last name"),
|
||||
"email2" => t._("Partner email"),
|
||||
"phone2" => t._("Partner phone"),
|
||||
"lang" => t._("Language"),
|
||||
"address1" => t._("Address 1"),
|
||||
"address2" => t._("Address 2"),
|
||||
"zipCode" => t._("Zip code"),
|
||||
"city" => t._("City"),
|
||||
"rights" => t._("Rights"),
|
||||
"cdate" => t._("Registration date"),
|
||||
"flags" => t._("Options"),
|
||||
"pass" => t._("Password"),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Executable
+128
@@ -0,0 +1,128 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
enum Right{
|
||||
GroupAdmin; //can manage whole group
|
||||
ContractAdmin(?cid:Int); //can manage one or all contracts
|
||||
Membership; //can manage group members
|
||||
Messages; //can send messages
|
||||
}
|
||||
|
||||
/**
|
||||
* A user which is member of a group
|
||||
*/
|
||||
@:id(userId,amapId)
|
||||
class UserAmap extends Object
|
||||
{
|
||||
@:relation(amapId) public var amap : db.Amap;
|
||||
@:relation(userId) public var user : db.User;
|
||||
public var rights : SNull<SData<Array<Right>>>;
|
||||
public var balance : SFloat; //account balance in group currency
|
||||
static var CACHE = new Map<String,db.UserAmap>();
|
||||
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
balance = 0;
|
||||
}
|
||||
|
||||
public static function get(user:User, amap:Amap, ?lock = false) {
|
||||
if (user == null || amap == null) return null;
|
||||
//SPOD doesnt cache elements with double primary key, so lets do it manually
|
||||
var c = CACHE.get(user.id + "-" + amap.id);
|
||||
if (c == null) {
|
||||
c = manager.select($user == user && $amap == amap, true/*lock*/);
|
||||
CACHE.set(user.id + "-" + amap.id,c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public static function getOrCreate(user:db.User, group:db.Amap){
|
||||
var ua = get(user, group);
|
||||
if ( ua == null){
|
||||
ua = new UserAmap();
|
||||
ua.user = user;
|
||||
ua.amap = group;
|
||||
ua.insert();
|
||||
}
|
||||
return ua;
|
||||
}
|
||||
|
||||
/**
|
||||
* give right and update DB
|
||||
*/
|
||||
public function giveRight(r:Right) {
|
||||
|
||||
if (hasRight(r)) return;
|
||||
if (rights == null) rights = [];
|
||||
lock();
|
||||
rights.push(r);
|
||||
update();
|
||||
}
|
||||
|
||||
/**
|
||||
* remove right and update DB
|
||||
*/
|
||||
public function removeRight(r:Right) {
|
||||
if (rights == null) return;
|
||||
var newrights = [];
|
||||
for (right in rights.copy()) {
|
||||
if ( !Type.enumEq(right, r) ) {
|
||||
newrights.push(right);
|
||||
}
|
||||
}
|
||||
rights = newrights;
|
||||
update();
|
||||
}
|
||||
|
||||
public function hasRight(r:Right):Bool {
|
||||
if (this.user.isAdmin()) return true;
|
||||
if (rights == null) return false;
|
||||
for ( right in rights) {
|
||||
if ( Type.enumEq(r,right) ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRightName(r:Right):String {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return switch(r) {
|
||||
case Right.GroupAdmin : t._("Administrator");
|
||||
case Right.Messages : t._("Messaging");
|
||||
case Right.Membership : t._("Members management");
|
||||
case Right.ContractAdmin(cid) :
|
||||
if (cid == null) {
|
||||
t._("Management of all contracts");
|
||||
}else {
|
||||
var c = db.Contract.manager.get(cid);
|
||||
if(c==null) {
|
||||
t._("Deleted contract");
|
||||
}else{
|
||||
t._("::name:: contract management",{name:c.name});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function hasValidMembership():Bool {
|
||||
|
||||
if (amap.membershipRenewalDate == null) return false;
|
||||
var cotis = db.Membership.get(this.user, this.amap, this.amap.getMembershipYear());
|
||||
return cotis != null;
|
||||
}
|
||||
|
||||
override public function insert(){
|
||||
App.current.event(NewMember(this.user,this.amap));
|
||||
super.insert();
|
||||
}
|
||||
|
||||
public function getPaymentOperations(){
|
||||
return db.Operation.getPaymentOperations(user, amap);
|
||||
}
|
||||
|
||||
public function getLastOperations(limit){
|
||||
return db.Operation.getLastOperations(user, amap, limit);
|
||||
}
|
||||
|
||||
}
|
||||
Executable
+200
@@ -0,0 +1,200 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* a product order
|
||||
*/
|
||||
class UserContract extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
|
||||
@formPopulate("populate") @:relation(userId)
|
||||
public var user : User;
|
||||
|
||||
//shared order
|
||||
@formPopulate("populate") @:relation(userId2)
|
||||
public var user2 : SNull<User>;
|
||||
|
||||
public var quantity : SFloat;
|
||||
|
||||
@formPopulate("populateProducts") @:relation(productId)
|
||||
public var product : Product;
|
||||
|
||||
//store price (1 unit price) and fees (percentage not amount ) rate when the order is done
|
||||
public var productPrice : SFloat;
|
||||
public var feesRate : SInt; //fees in percentage
|
||||
|
||||
public var paid : SBool;
|
||||
|
||||
//if not null : varying orders
|
||||
@:relation(distributionId)
|
||||
public var distribution:SNull<db.Distribution>;
|
||||
|
||||
|
||||
@:relation(basketId)
|
||||
public var basket:SNull<db.Basket>;
|
||||
|
||||
public var date : SDateTime;
|
||||
public var flags : SFlags<OrderFlags>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
quantity = 1;
|
||||
paid = false;
|
||||
date = Date.now();
|
||||
flags = cast 0;
|
||||
feesRate = 0;
|
||||
}
|
||||
|
||||
public function populate() {
|
||||
return App.current.user.getAmap().getMembersFormElementData();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For shared alternated orders in AMAP contracts
|
||||
* @param distrib
|
||||
* @return false -> user , true -> user2
|
||||
*/
|
||||
public function getWhosTurn(distrib:Distribution) {
|
||||
if (distrib == null) throw "distribution is null";
|
||||
if (user2 == null) throw "this contract is not shared";
|
||||
|
||||
//compter le nbre de distrib pour ce contrat
|
||||
var c = Distribution.manager.count( $contract == product.contract && $date >= product.contract.startDate && $date <= distrib.date);
|
||||
var r = c % 2 == 0;
|
||||
if (flags.has(InvertSharedOrder)){
|
||||
return !r;
|
||||
}else{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
override public function toString() {
|
||||
if(product==null) return quantity +"x produit inconnu";
|
||||
return quantity + "x" + product.name;
|
||||
}
|
||||
|
||||
public function hasInvertSharedOrder():Bool{
|
||||
return flags.has(InvertSharedOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* On peut modifier si ça na pas deja été payé + commande encore ouvertes
|
||||
*/
|
||||
public function canModify():Bool {
|
||||
|
||||
var can = false;
|
||||
if (this.product.contract.type == db.Contract.TYPE_VARORDER) {
|
||||
|
||||
if (this.distribution.orderStartDate == null) {
|
||||
can = true;
|
||||
}else {
|
||||
var n = Date.now().getTime();
|
||||
can = n > this.distribution.orderStartDate.getTime() && n < this.distribution.orderEndDate.getTime();
|
||||
}
|
||||
}else {
|
||||
can = this.product.contract.isUserOrderAvailable();
|
||||
}
|
||||
|
||||
return can && !this.paid;
|
||||
}
|
||||
|
||||
/**
|
||||
* get users orders for a distribution
|
||||
*/
|
||||
public static function getOrders(contract:db.Contract, ?distribution:db.Distribution, ?csv = false):Array<UserOrder>{
|
||||
var view = App.current.view;
|
||||
var orders = new Array<db.UserContract>();
|
||||
if (contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
orders = contract.getOrders(distribution);
|
||||
}else {
|
||||
orders = contract.getOrders();
|
||||
}
|
||||
|
||||
var orders = service.OrderService.prepare(orders);
|
||||
|
||||
//CSV export
|
||||
if (csv) {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var data = new Array<Dynamic>();
|
||||
|
||||
for (o in orders) {
|
||||
data.push( {
|
||||
"name":o.userName,
|
||||
"productName":o.productName,
|
||||
"price":view.formatNum(o.productPrice),
|
||||
"quantity":view.formatNum(o.quantity),
|
||||
"fees":view.formatNum(o.fees),
|
||||
"total":view.formatNum(o.total),
|
||||
"paid":o.paid
|
||||
});
|
||||
}
|
||||
|
||||
var exportName = "";
|
||||
if (distribution != null){
|
||||
exportName = contract.amap.name + " - " + t._("Delivery ::contractName:: ", {contractName:contract.name}) + distribution.date.toString().substr(0, 10);
|
||||
}else{
|
||||
exportName = contract.amap.name + " - " + contract.name;
|
||||
}
|
||||
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(data, ["name", "productName", "price", "quantity", "fees", "total", "paid"], exportName+" - " + t._("Per member"));
|
||||
return null;
|
||||
}else{
|
||||
return orders;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the orders (varying orders) of a user for a multidistrib ( distribs with same day + same place )
|
||||
*
|
||||
* @param distribKey "$date|$placeId"
|
||||
*/
|
||||
public static function getUserOrdersByMultiDistrib(distribKey:String, user:db.User,group:db.Amap):Array<db.UserContract>{
|
||||
//var contracts = db.Contract.getActiveContracts(group);
|
||||
var contracts = db.Contract.manager.search($amap == group, false); //should be able to edit a contract which is closed
|
||||
for ( c in Lambda.array(contracts)){
|
||||
if (c.type == db.Contract.TYPE_CONSTORDERS){
|
||||
contracts.remove(c); //only varying orders
|
||||
}
|
||||
}
|
||||
|
||||
var cids = Lambda.map(contracts, function(x) return x.id);
|
||||
var start = Date.fromString(distribKey.split("|")[0] + " 00:00:00");
|
||||
var end = Date.fromString(distribKey.split("|")[0] + " 23:59:00");
|
||||
var ds = db.Distribution.manager.search($date > start && $date < end && ($contractId in cids), false);
|
||||
var out = [];
|
||||
for (d in ds) {
|
||||
out = out.concat(Lambda.array(user.getOrdersFromDistrib(d)));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function getTotalPrice(tmpOrder:OrderInSession){
|
||||
var t = 0.0;
|
||||
for ( o in tmpOrder.products){
|
||||
var p = db.Product.manager.get(o.productId, false);
|
||||
t += o.quantity * p.getPrice();
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
function check(){
|
||||
if(quantity==null) quantity == 1;
|
||||
}
|
||||
|
||||
override function update(){
|
||||
check();
|
||||
super.update();
|
||||
}
|
||||
|
||||
override function insert(){
|
||||
check();
|
||||
super.insert();
|
||||
}
|
||||
}
|
||||
Executable
+61
@@ -0,0 +1,61 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
/**
|
||||
* Vendor (producteur)
|
||||
*/
|
||||
class Vendor extends Object
|
||||
{
|
||||
public var id : SId;
|
||||
public var name : SString<128>;
|
||||
|
||||
public var email : STinyText;
|
||||
public var phone:SNull<SString<19>>;
|
||||
|
||||
public var address1:SNull<SString<64>>;
|
||||
public var address2:SNull<SString<64>>;
|
||||
public var zipCode:SString<32>;
|
||||
public var city:SString<25>;
|
||||
|
||||
public var desc : SNull<SText>;
|
||||
|
||||
public var linkText:SNull<SString<256>>;
|
||||
public var linkUrl:SNull<SString<256>>;
|
||||
|
||||
@hideInForms @:relation(imageId) public var image : SNull<sugoi.db.File>;
|
||||
|
||||
@:relation(amapId) public var amap : SNull<Amap>;
|
||||
|
||||
public function new()
|
||||
{
|
||||
super();
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
name = t._("Supplier");
|
||||
}
|
||||
|
||||
override function toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public function getActiveContracts(){
|
||||
var now = Date.now();
|
||||
return db.Contract.manager.search($vendor == this && $startDate < now && $endDate > now ,{orderBy:-startDate}, false);
|
||||
}
|
||||
|
||||
public static function getLabels(){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
return [
|
||||
"name" => t._("Supplier name"),
|
||||
"desc" => t._("Description"),
|
||||
"email" => t._("Email"),
|
||||
"phone" => t._("Phone"),
|
||||
"address1" => t._("Address 1"),
|
||||
"address2" => t._("Address 2"),
|
||||
"zipCode" => t._("Zip code"),
|
||||
"city" => t._("City"),
|
||||
"linkText" => t._("Link text"),
|
||||
"linkUrl" => t._("Link URL"),
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package db;
|
||||
import sys.db.Object;
|
||||
import sys.db.Types;
|
||||
|
||||
|
||||
@:id(userId,amapId)
|
||||
class WaitingList extends Object
|
||||
{
|
||||
@:relation(amapId) public var group : Amap;
|
||||
@:relation(userId) public var user : db.User;
|
||||
public var date : SDateTime;
|
||||
public var message : SText;
|
||||
|
||||
public function new(){
|
||||
super();
|
||||
message = "";
|
||||
date = Date.now();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user