code from amapei

This commit is contained in:
cagette@ct8
2020-09-26 18:25:18 +00:00
parent 42fe367911
commit 72b4699871
1966 changed files with 220437 additions and 2 deletions
Executable
+373
View File
@@ -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"),
];
}
}
+130
View File
@@ -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;
}
}
+22
View File
@@ -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*/};
}
}
+47
View File
@@ -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"),
];
}
}
+285
View File
@@ -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"),
];
}
}
+271
View File
@@ -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"),
];
}
}
+54
View File
@@ -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;
}
}
+45
View File
@@ -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));
}
}
+24
View File
@@ -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);
}
}
+28
View File
@@ -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;
}
+431
View File
@@ -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"),
];
}*/
}
+87
View File
@@ -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
}
}
}
+206
View File
@@ -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"),
];
}
}
+27
View File
@@ -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;
}
}
+23
View File
@@ -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';
}
}
+27
View File
@@ -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];
}
}
+26
View File
@@ -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
View File
@@ -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"),
];
}
}
+128
View File
@@ -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);
}
}
+200
View File
@@ -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();
}
}
+61
View File
@@ -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"),
];
}
}
+20
View File
@@ -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();
}
}