code from amapei
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
package service;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Distribution Service
|
||||
* @author web-wizard
|
||||
*/
|
||||
class DistributionService
|
||||
{
|
||||
/**
|
||||
* It will update the name of the operation with the new number of distributions
|
||||
* as well as the total amount
|
||||
* @param contract -
|
||||
*/
|
||||
public static function updateAmapContractOperations(contract:db.Contract) {
|
||||
|
||||
//Update all operations for this amap contract when payments are enabled
|
||||
if (contract.type == db.Contract.TYPE_CONSTORDERS && contract.amap.hasPayments()) {
|
||||
//Get all the users who have orders for this contract
|
||||
var users = contract.getUsers();
|
||||
for ( user in users ){
|
||||
|
||||
//Get the one operation for this amap contract and user
|
||||
var operation = db.Operation.findCOrderTransactionFor(contract, user);
|
||||
|
||||
if (operation != null)
|
||||
{
|
||||
//Get all the orders for this contract and user
|
||||
var orders = contract.getUserOrders(user);
|
||||
//Update this operation with the new number of distributions, this will affect the name of the operation
|
||||
//as well as the total amount to pay
|
||||
db.Operation.updateOrderOperation(operation, orders);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* checks if dates are correct and if that there is no other distribution in the same time range
|
||||
* and for the same contract and place
|
||||
* @param d
|
||||
*/
|
||||
public static function checkDistrib(d:db.Distribution) {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var view = App.current.view;
|
||||
|
||||
var c = d.contract;
|
||||
|
||||
var distribs1;
|
||||
var distribs2;
|
||||
var distribs3;
|
||||
//We are checking that there is no existing distribution with an overlapping time frame for the same place and contract
|
||||
if (d.id == null) { //We need to check there the id as $id != null doesn't work in the manager.search
|
||||
//Looking for existing distributions with a time range overlapping the start of the about to be created distribution
|
||||
distribs1 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.date && $end >= d.date, false);
|
||||
//Looking for existing distributions with a time range overlapping the end of the about to be created distribution
|
||||
distribs2 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.end && $end >= d.end, false);
|
||||
//Looking for existing distributions with a time range included in the time range of the about to be created distribution
|
||||
distribs3 = db.Distribution.manager.search($contract == c && $place == d.place && $date >= d.date && $end <= d.end, false);
|
||||
}
|
||||
else {
|
||||
//Looking for existing distributions with a time range overlapping the start of the about to be created distribution
|
||||
distribs1 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.date && $end >= d.date && $id != d.id, false);
|
||||
//Looking for existing distributions with a time range overlapping the end of the about to be created distribution
|
||||
distribs2 = db.Distribution.manager.search($contract == c && $place == d.place && $date <= d.end && $end >= d.end && $id != d.id, false);
|
||||
//Looking for existing distributions with a time range included in the time range of the about to be created distribution
|
||||
distribs3 = db.Distribution.manager.search($contract == c && $place == d.place && $date >= d.date && $end <= d.end && $id != d.id, false);
|
||||
}
|
||||
|
||||
if (distribs1.length != 0 || distribs2.length != 0 || distribs3.length != 0) {
|
||||
throw new tink.core.Error(t._("There is already a distribution at this place overlapping with the time range you've selected."));
|
||||
}
|
||||
|
||||
if (d.date.getTime() > c.endDate.getTime()) throw new tink.core.Error(t._("The date of the delivery must be prior to the end of the contract (::contractEndDate::)", {contractEndDate:view.hDate(c.endDate)}));
|
||||
if (d.date.getTime() < c.startDate.getTime()) throw new tink.core.Error(t._("The date of the delivery must be after the begining of the contract (::contractBeginDate::)", {contractBeginDate:view.hDate(c.startDate)}));
|
||||
|
||||
if (c.type == db.Contract.TYPE_VARORDER ) {
|
||||
if (d.date.getTime() < d.orderEndDate.getTime() ) throw new tink.core.Error(t._("The distribution start date must be set after the orders end date."));
|
||||
if (d.orderStartDate.getTime() > d.orderEndDate.getTime() ) throw new tink.core.Error(t._("The orders end date must be set after the orders start date !"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new distribution and prevents distribution overlapping and other checks
|
||||
* @param contract -
|
||||
* @param date -
|
||||
* @param end -
|
||||
* @param placeId -
|
||||
* @param distributor1Id -
|
||||
* @param distributor2Id -
|
||||
* @param distributor3Id -
|
||||
* @param distributor4Id -
|
||||
* @param orderStartDate -
|
||||
* @param orderEndDate -
|
||||
* @param distributionCycle -
|
||||
* @param dispatchEvent=true -
|
||||
* @return db.Distribution
|
||||
*/
|
||||
public static function create(contract:db.Contract,date:Date,end:Date,placeId:Int,
|
||||
?distributor1Id:Int,?distributor2Id:Int,?distributor3Id:Int,?distributor4Id:Int,
|
||||
?orderStartDate:Date,?orderEndDate:Date,?distributionCycle:db.DistributionCycle,?dispatchEvent=true):db.Distribution {
|
||||
|
||||
var d = new db.Distribution();
|
||||
d.contract = contract;
|
||||
d.date = date;
|
||||
d.place = db.Place.manager.get(placeId);
|
||||
d.distributionCycle = distributionCycle;
|
||||
if(distributor1Id != null) d.distributor1 = db.User.manager.get(distributor1Id);
|
||||
if(distributor2Id != null) d.distributor2 = db.User.manager.get(distributor2Id);
|
||||
if(distributor3Id != null) d.distributor3 = db.User.manager.get(distributor3Id);
|
||||
if(distributor4Id != null) d.distributor4 = db.User.manager.get(distributor4Id);
|
||||
if(contract.type==db.Contract.TYPE_VARORDER){
|
||||
d.orderStartDate = orderStartDate;
|
||||
d.orderEndDate = orderEndDate;
|
||||
}
|
||||
|
||||
if (end == null) {
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 60);
|
||||
}
|
||||
else {
|
||||
d.end = new Date(d.date.getFullYear(), d.date.getMonth(), d.date.getDate(), end.getHours(), end.getMinutes(), 0);
|
||||
}
|
||||
|
||||
DistributionService.checkDistrib(d);
|
||||
|
||||
if(distributionCycle == null && dispatchEvent) {
|
||||
var e :Event = NewDistrib(d);
|
||||
App.current.event(e);
|
||||
}
|
||||
|
||||
if (d.date == null){
|
||||
return d;
|
||||
} else {
|
||||
d.insert();
|
||||
|
||||
//In case this is a distrib for an amap contract with payments enabled, it will update all the operations
|
||||
//names and amounts with the new number of distribs
|
||||
updateAmapContractOperations(d.contract);
|
||||
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies an existing distribution and prevents distribution overlapping and other checks
|
||||
* @param d -
|
||||
* @param date -
|
||||
* @param end -
|
||||
* @param placeId -
|
||||
* @param distributor1Id -
|
||||
* @param distributor2Id -
|
||||
* @param distributor3Id -
|
||||
* @param distributor4Id -
|
||||
* @param orderStartDate -
|
||||
* @param orderEndDate -
|
||||
* @return db.Distribution
|
||||
*/
|
||||
public static function edit(d:db.Distribution,date:Date,end:Date,placeId:Int,
|
||||
distributor1Id:Int,distributor2Id:Int,distributor3Id:Int,distributor4Id:Int,
|
||||
orderStartDate:Date,orderEndDate:Date,?dispatchEvent=true):db.Distribution {
|
||||
|
||||
//We prevent others from modifying it
|
||||
d.lock();
|
||||
|
||||
d.date = date;
|
||||
d.place = db.Place.manager.get(placeId);
|
||||
d.distributor1 = db.User.manager.get(distributor1Id);
|
||||
d.distributor2 = db.User.manager.get(distributor2Id);
|
||||
d.distributor3 = db.User.manager.get(distributor3Id);
|
||||
d.distributor4 = db.User.manager.get(distributor4Id);
|
||||
if(d.contract.type==db.Contract.TYPE_VARORDER){
|
||||
d.orderStartDate = orderStartDate;
|
||||
d.orderEndDate = orderEndDate;
|
||||
}
|
||||
|
||||
if (end == null) {
|
||||
d.end = DateTools.delta(d.date, 1000.0 * 60 * 60);
|
||||
}
|
||||
else {
|
||||
d.end = new Date(d.date.getFullYear(), d.date.getMonth(), d.date.getDate(), end.getHours(), end.getMinutes(), 0);
|
||||
}
|
||||
|
||||
DistributionService.checkDistrib(d);
|
||||
|
||||
if(dispatchEvent) App.current.event(EditDistrib(d));
|
||||
|
||||
if (d.date == null){
|
||||
return d;
|
||||
} else {
|
||||
d.update();
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether there are orders with non zero quantity for non amap contract
|
||||
* @param d -
|
||||
* @return Bool
|
||||
*/
|
||||
public static function canDelete(d:db.Distribution):Bool{
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_CONSTORDERS) return true;
|
||||
|
||||
var quantity = 0.0;
|
||||
for ( order in d.getOrders() ){
|
||||
quantity += order.quantity;
|
||||
}
|
||||
return quantity == 0.0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a distribution
|
||||
* @param d -
|
||||
* @param dispatchEvent=true -
|
||||
*/
|
||||
public static function delete(d:db.Distribution,?dispatchEvent=true) {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( !canDelete(d) ) {
|
||||
throw new tink.core.Error(t._("Deletion non possible: some orders are saved for this delivery."));
|
||||
}
|
||||
|
||||
var contract = d.contract;
|
||||
d.lock();
|
||||
if (dispatchEvent) {
|
||||
App.current.event(DeleteDistrib(d));
|
||||
}
|
||||
d.delete();
|
||||
//In case this is a distrib for an amap contract with payments enabled, it will update all the operations
|
||||
//names and amounts with the new number of distribs
|
||||
updateAmapContractOperations(contract);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the correct start and end dates
|
||||
* @param dc -
|
||||
* @param datePointer -
|
||||
*/
|
||||
public static function getDates(dc:db.DistributionCycle, datePointer:Date) {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
var startDate = new Date(datePointer.getFullYear(),datePointer.getMonth(),datePointer.getDate(),dc.startHour.getHours(),dc.startHour.getMinutes(),0);
|
||||
var orderStartDate = null;
|
||||
var orderEndDate = null;
|
||||
if (dc.contract.type == db.Contract.TYPE_VARORDER){
|
||||
|
||||
if (dc.daysBeforeOrderEnd == null || dc.daysBeforeOrderStart == null) throw new tink.core.Error(t._("daysBeforeOrderEnd or daysBeforeOrderStart is null"));
|
||||
|
||||
var a = DateTools.delta(startDate, -1.0 * dc.daysBeforeOrderStart * 1000 * 60 * 60 * 24);
|
||||
var h : Date = dc.openingHour;
|
||||
orderStartDate = new Date(a.getFullYear(), a.getMonth(), a.getDate(), h.getHours(), h.getMinutes(), 0);
|
||||
|
||||
var a = DateTools.delta(startDate, -1.0 * dc.daysBeforeOrderEnd * 1000 * 60 * 60 * 24);
|
||||
var h : Date = dc.closingHour;
|
||||
orderEndDate = new Date(a.getFullYear(), a.getMonth(), a.getDate(), h.getHours(), h.getMinutes(), 0);
|
||||
}
|
||||
return { date: startDate, orderStartDate: orderStartDate, orderEndDate: orderEndDate };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all the distributions from the first date
|
||||
* @param dc -
|
||||
*/
|
||||
public static function createCycleDistribs(dc:db.DistributionCycle) {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
//switch end date to 23:59 to avoid the last distribution to be skipped
|
||||
dc.endDate = tools.DateTool.setHourMinute(dc.endDate,23,59);
|
||||
|
||||
if (dc.id == null) throw new tink.core.Error(t._("this distributionCycle has not been recorded"));
|
||||
|
||||
//iterations
|
||||
//For first distrib
|
||||
var datePointer = new Date(dc.startDate.getFullYear(), dc.startDate.getMonth(), dc.startDate.getDate(), 12, 0, 0);
|
||||
//why hour=12 ? because if we set hour to 0, it switch to 23 (-1) or 1 (+1) on daylight saving time switch dates, thus changing the day!!
|
||||
var firstDistribDate = new Date(datePointer.getFullYear(),datePointer.getMonth(),datePointer.getDate(),dc.startHour.getHours(),dc.startHour.getMinutes(),0);
|
||||
for(i in 0...100) {
|
||||
|
||||
if(i != 0){ //All distribs except the first one
|
||||
var oneDay = 1000 * 60 * 60 * 24.0;
|
||||
switch(dc.cycleType) {
|
||||
case Weekly :
|
||||
datePointer = DateTools.delta(datePointer, oneDay * 7.0);
|
||||
App.log("on ajoute "+(oneDay * 7.0)+"millisec pour ajouter 7 jours");
|
||||
App.log('pointer : $datePointer');
|
||||
|
||||
case BiWeekly :
|
||||
datePointer = DateTools.delta(datePointer, oneDay * 14.0);
|
||||
|
||||
case TriWeekly :
|
||||
datePointer = DateTools.delta(datePointer, oneDay * 21.0);
|
||||
|
||||
case Monthly :
|
||||
var n = tools.DateTool.getWhichNthDayOfMonth(firstDistribDate);
|
||||
var dayOfWeek = firstDistribDate.getDay();
|
||||
var nextMonth = new Date(datePointer.getFullYear(), datePointer.getMonth() + 1, 1, 0, 0, 0);
|
||||
datePointer = tools.DateTool.getNthDayOfMonth(nextMonth.getFullYear(), nextMonth.getMonth(), dayOfWeek, n);
|
||||
if (datePointer.getMonth() != nextMonth.getMonth()) {
|
||||
datePointer = tools.DateTool.getNthDayOfMonth(nextMonth.getFullYear(), nextMonth.getMonth(), dayOfWeek, n - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//stop if cycle end is reached
|
||||
if (datePointer.getTime() > dc.endDate.getTime()) {
|
||||
break;
|
||||
}
|
||||
|
||||
var dates = getDates(dc, datePointer);
|
||||
|
||||
service.DistributionService.create(dc.contract,dates.date,
|
||||
new Date(datePointer.getFullYear(),datePointer.getMonth(),datePointer.getDate(),dc.endHour.getHours(),dc.endHour.getMinutes(),0),
|
||||
dc.place.id,null,null,null,null,dates.orderStartDate,dates.orderEndDate,dc);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all distributions which are part of this cycle
|
||||
* @param cycle -
|
||||
*/
|
||||
public static function deleteCycleDistribs(cycle:db.DistributionCycle){
|
||||
|
||||
cycle.lock();
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var view = App.current.view;
|
||||
|
||||
var children = db.Distribution.manager.search($distributionCycle == cycle, true);
|
||||
var messages = [];
|
||||
if(children.length != 0) {
|
||||
|
||||
var contract = Lambda.array(children)[0].contract;
|
||||
for ( d in children ){
|
||||
|
||||
if (d.contract.type == db.Contract.TYPE_VARORDER && !canDelete(d) ){
|
||||
messages.push(t._("The delivery of the ::delivDate:: could not be deleted because it has orders.", {delivDate:view.hDate(d.date)}));
|
||||
}else{
|
||||
d.delete();
|
||||
}
|
||||
}
|
||||
|
||||
//In case this is a distrib cycle for an amap contract with payments enabled, it will update all the operations
|
||||
//names and amounts with the new number of distribs
|
||||
updateAmapContractOperations(contract);
|
||||
|
||||
}
|
||||
cycle.delete();
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new distribution cycle and prevents distribution overlapping and other checks
|
||||
* @param contract -
|
||||
* @param cycleType -
|
||||
* @param startDate -
|
||||
* @param endDate -
|
||||
* @param startHour -
|
||||
* @param endHour -
|
||||
* @param daysBeforeOrderStart -
|
||||
* @param daysBeforeOrderEnd -
|
||||
* @param openingHour -
|
||||
* @param closingHour -
|
||||
* @param placeId -
|
||||
* @param dispatchEvent=true -
|
||||
* @return db.DistributionCycle
|
||||
*/
|
||||
public static function createCycle(contract:db.Contract,cycleType:db.DistributionCycle.CycleType,startDate:Date,endDate:Date,
|
||||
startHour:Date,endHour:Date,daysBeforeOrderStart:Null<Int>,daysBeforeOrderEnd:Null<Int>,openingHour:Null<Date>,closingHour:Null<Date>,
|
||||
placeId:Int,?dispatchEvent=true):db.DistributionCycle {
|
||||
|
||||
//Generic variables
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var view = App.current.view;
|
||||
|
||||
var dc = new db.DistributionCycle();
|
||||
dc.contract = contract;
|
||||
dc.cycleType = cycleType;
|
||||
dc.startDate = startDate;
|
||||
dc.endDate = endDate;
|
||||
dc.startHour = startHour;
|
||||
dc.endHour = endHour;
|
||||
dc.place = db.Place.manager.get(placeId);
|
||||
|
||||
if (contract.type == db.Contract.TYPE_VARORDER) {
|
||||
dc.daysBeforeOrderStart = daysBeforeOrderStart;
|
||||
dc.daysBeforeOrderEnd = daysBeforeOrderEnd;
|
||||
dc.openingHour = openingHour;
|
||||
dc.closingHour = closingHour;
|
||||
}
|
||||
|
||||
if (dc.endDate.getTime() > contract.endDate.getTime()) {
|
||||
throw new tink.core.Error(t._("The date of the delivery must be prior to the end of the contract (::contractEndDate::)", {contractEndDate:view.hDate(contract.endDate)}));
|
||||
}
|
||||
if (dc.startDate.getTime() < contract.startDate.getTime()) {
|
||||
throw new tink.core.Error(t._("The date of the delivery must be after the begining of the contract (::contractBeginDate::)", {contractBeginDate:view.hDate(contract.startDate)}));
|
||||
}
|
||||
|
||||
if(dispatchEvent){
|
||||
App.current.event(NewDistribCycle(dc));
|
||||
}
|
||||
|
||||
dc.insert();
|
||||
createCycleDistribs(dc);
|
||||
|
||||
return dc;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package service;
|
||||
using Lambda;
|
||||
using tools.ObjectListTool;
|
||||
/**
|
||||
* Service for managing groups
|
||||
* @author fbarbut
|
||||
*/
|
||||
class GroupService
|
||||
{
|
||||
|
||||
public function new()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* copy groups.
|
||||
* @param g
|
||||
*/
|
||||
public static function duplicateGroup(g:db.Amap){
|
||||
|
||||
var d = new db.Amap();
|
||||
d.name = g.name+" (copy)";
|
||||
d.contact = g.contact;
|
||||
d.txtIntro = g.txtIntro;
|
||||
d.txtHome = g.txtHome;
|
||||
d.txtDistrib = g.txtDistrib;
|
||||
d.extUrl = g.extUrl;
|
||||
d.membershipRenewalDate = g.membershipRenewalDate;
|
||||
d.membershipPrice = g.membershipPrice;
|
||||
d.vatRates = g.vatRates;
|
||||
d.flags = g.flags;
|
||||
d.groupType = g.groupType;
|
||||
d.image = g.image;
|
||||
d.regOption = g.regOption;
|
||||
d.currency = g.currency;
|
||||
d.currencyCode = g.currencyCode;
|
||||
d.allowedPaymentsType = g.allowedPaymentsType;
|
||||
d.checkOrder = g.checkOrder;
|
||||
d.IBAN = g.IBAN;
|
||||
d.insert();
|
||||
|
||||
//put me in the group
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
static function duplicateCategories(from:db.Amap,to:db.Amap){
|
||||
|
||||
}
|
||||
|
||||
static function duplicateContract(){
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Get users with rights in this group
|
||||
**/
|
||||
public static function getGroupMembersWithRights(group:db.Amap,?rights:Array<db.UserAmap.Right>):Array<db.User>{
|
||||
|
||||
var membersWithAnyRights = db.UserAmap.manager.search($rights!=null && $amap==group,false).array();
|
||||
if(rights==null){
|
||||
return Lambda.map(membersWithAnyRights,function(ua) return ua.user).array();
|
||||
}else{
|
||||
var members = [];
|
||||
for( m in membersWithAnyRights){
|
||||
for(r in rights){
|
||||
if(m.hasRight(r)){
|
||||
members.push(m.user);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return members.deduplicate();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
package service;
|
||||
import Common;
|
||||
import tink.core.Error;
|
||||
|
||||
/**
|
||||
* Order Service
|
||||
* @author web-wizard,fbarbut
|
||||
*/
|
||||
class OrderService
|
||||
{
|
||||
|
||||
static function canHaveFloatQt(product:db.Product):Bool{
|
||||
return product.hasFloatQt || product.wholesale || product.variablePrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a product Order
|
||||
*
|
||||
* @param quantity
|
||||
* @param productId
|
||||
*/
|
||||
public static function make(user:db.User, quantity:Float, product:db.Product, ?distribId:Int, ?paid:Bool, ?user2:db.User, ?invert:Bool):db.UserContract {
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if(product.contract.type==db.Contract.TYPE_VARORDER && distribId==null) throw "You have to provide a distribId";
|
||||
if(quantity==null) throw "Quantity is null";
|
||||
|
||||
//quantity
|
||||
if ( !canHaveFloatQt(product) ){
|
||||
if( !tools.FloatTool.isInt(quantity) ) {
|
||||
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer",{product:product.name}));
|
||||
}
|
||||
}
|
||||
|
||||
//multiweight : make one row per product
|
||||
if (product.multiWeight && quantity > 1.0){
|
||||
if (product.multiWeight && quantity != Math.abs(quantity)) throw t._("multi-weighing products should be ordered only with integer quantities");
|
||||
|
||||
var o = null;
|
||||
for ( i in 0...Math.round(quantity)){
|
||||
o = make(user, 1, product, distribId, paid, user2, invert);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
//checks
|
||||
if (quantity <= 0) return null;
|
||||
|
||||
//check for previous orders on the same distrib
|
||||
var prevOrders = new List<db.UserContract>();
|
||||
if (distribId == null) {
|
||||
prevOrders = db.UserContract.manager.search($product==product && $user==user, true);
|
||||
}else {
|
||||
prevOrders = db.UserContract.manager.search($product==product && $user==user && $distributionId==distribId, true);
|
||||
}
|
||||
|
||||
//Create order object
|
||||
var o = new db.UserContract();
|
||||
o.product = product;
|
||||
o.quantity = quantity;
|
||||
o.productPrice = product.price;
|
||||
if (product.contract.hasPercentageOnOrders()) {
|
||||
o.feesRate = product.contract.percentageValue;
|
||||
}
|
||||
o.user = user;
|
||||
if (user2 != null) {
|
||||
o.user2 = user2;
|
||||
if (invert != null) o.flags.set(InvertSharedOrder);
|
||||
}
|
||||
if (paid != null) o.paid = paid;
|
||||
if (distribId != null) o.distribution = db.Distribution.manager.get(distribId);
|
||||
|
||||
//cumulate quantities if there is a similar previous order
|
||||
if (prevOrders.length > 0 && !product.multiWeight) {
|
||||
for (prevOrder in prevOrders) {
|
||||
//if (!prevOrder.paid) {
|
||||
o.quantity += prevOrder.quantity;
|
||||
prevOrder.delete();
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
//create a basket object
|
||||
if (distribId != null){
|
||||
var dist = o.distribution;
|
||||
var basket = db.Basket.getOrCreate(user, dist.place, dist.date);
|
||||
o.basket = basket;
|
||||
}
|
||||
|
||||
o.insert();
|
||||
|
||||
//Stocks
|
||||
if (o.product.stock != null) {
|
||||
var c = o.product.contract;
|
||||
if (c.hasStockManagement()) {
|
||||
//trace("stock for "+quantity+" x "+product.name);
|
||||
if (o.product.stock == 0) {
|
||||
if (App.current.session != null) {
|
||||
App.current.session.addMessage(t._("There is no more '::productName::' in stock, we removed it from your order", {productName:o.product.name}), true);
|
||||
}
|
||||
o.quantity -= quantity;
|
||||
if ( o.quantity <= 0 ) {
|
||||
o.delete();
|
||||
return null;
|
||||
}
|
||||
}else if (o.product.stock - quantity < 0) {
|
||||
var canceled = quantity - o.product.stock;
|
||||
o.quantity -= canceled;
|
||||
o.update();
|
||||
|
||||
if (App.current.session != null) {
|
||||
var msg = t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore", {productName:o.product.name, oQuantity:o.quantity});
|
||||
App.current.session.addMessage(msg, true);
|
||||
}
|
||||
o.product.lock();
|
||||
o.product.stock = 0;
|
||||
o.product.update();
|
||||
App.current.event(StockMove({product:o.product, move:0 - (quantity - canceled) }));
|
||||
|
||||
}else {
|
||||
o.product.lock();
|
||||
o.product.stock -= quantity;
|
||||
o.product.update();
|
||||
App.current.event(StockMove({product:o.product, move:0 - quantity}));
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Edit an existing order (quantity)
|
||||
*/
|
||||
public static function edit(order:db.UserContract, newquantity:Float, ?paid:Bool , ?user2:db.User,?invert:Bool) {
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
order.lock();
|
||||
|
||||
//quantity
|
||||
if (newquantity == null) newquantity = 0;
|
||||
if ( !canHaveFloatQt(order.product) ){
|
||||
if( !tools.FloatTool.isInt(newquantity) ) {
|
||||
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer",{product:order.product.name}));
|
||||
}
|
||||
}
|
||||
|
||||
//paid
|
||||
if (paid != null) {
|
||||
order.paid = paid;
|
||||
}else {
|
||||
if (order.quantity < newquantity) order.paid = false;
|
||||
}
|
||||
|
||||
//shared order
|
||||
if (user2 != null){
|
||||
order.user2 = user2;
|
||||
if (invert == true) order.flags.set(InvertSharedOrder);
|
||||
if (invert == false) order.flags.unset(InvertSharedOrder);
|
||||
}else{
|
||||
order.user2 = null;
|
||||
order.flags.unset(InvertSharedOrder);
|
||||
}
|
||||
|
||||
//stocks
|
||||
var e : Event = null;
|
||||
if (order.product.stock != null) {
|
||||
var c = order.product.contract;
|
||||
|
||||
if (c.hasStockManagement()) {
|
||||
|
||||
if (newquantity < order.quantity) {
|
||||
|
||||
//on commande moins que prévu : incrément de stock
|
||||
order.product.lock();
|
||||
order.product.stock += (order.quantity-newquantity);
|
||||
e = StockMove({product:order.product, move:0 - (order.quantity-newquantity) });
|
||||
|
||||
}else {
|
||||
|
||||
//on commande plus que prévu : décrément de stock
|
||||
var addedquantity = newquantity - order.quantity;
|
||||
|
||||
if (order.product.stock - addedquantity < 0) {
|
||||
|
||||
//stock is not enough, reduce order
|
||||
newquantity = order.quantity + order.product.stock;
|
||||
if( App.current.session!=null) App.current.session.addMessage(t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore", {productName:order.product.name, oQuantity:newquantity}), true);
|
||||
|
||||
e = StockMove({product:order.product, move: 0 - order.product.stock });
|
||||
|
||||
order.product.lock();
|
||||
order.product.stock = 0;
|
||||
|
||||
}else{
|
||||
|
||||
//stock is big enough
|
||||
order.product.lock();
|
||||
order.product.stock -= addedquantity;
|
||||
|
||||
e = StockMove({ product:order.product, move: 0 - addedquantity });
|
||||
}
|
||||
}
|
||||
order.product.update();
|
||||
}
|
||||
}
|
||||
|
||||
//update order
|
||||
if (newquantity == 0) {
|
||||
order.quantity = 0;
|
||||
order.paid = true;
|
||||
order.update();
|
||||
}else {
|
||||
order.quantity = newquantity;
|
||||
order.update();
|
||||
}
|
||||
|
||||
App.current.event(e);
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete an order
|
||||
*/
|
||||
public static function delete(order:db.UserContract) {
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if(order==null) throw new Error(t._("This order has already been deleted."));
|
||||
|
||||
order.lock();
|
||||
|
||||
if (order.quantity == 0) {
|
||||
|
||||
var contract = order.product.contract;
|
||||
var user = order.user;
|
||||
|
||||
//Amap Contract
|
||||
if ( contract.type == db.Contract.TYPE_CONSTORDERS ) {
|
||||
|
||||
order.delete();
|
||||
|
||||
if( contract.amap.hasPayments() ){
|
||||
var orders = contract.getUserOrders(user);
|
||||
if( orders.length == 0 ){
|
||||
var operation = db.Operation.findCOrderTransactionFor(contract, user);
|
||||
if(operation!=null) operation.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
else { //Variable orders contract
|
||||
|
||||
//Get the basket for this user
|
||||
var place = order.distribution.place;
|
||||
var basket = db.Basket.get(user, place, order.distribution.date);
|
||||
|
||||
if( contract.amap.hasPayments() ){
|
||||
var orders = basket.getOrders();
|
||||
//Check if it is the last order, if yes then delete the related operation
|
||||
if( orders.length == 1 && orders.first().id==order.id ){
|
||||
var operation = db.Operation.findVOrderTransactionFor(order.distribution.getKey(), user, place.amap);
|
||||
if(operation!=null) operation.delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
order.delete();
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(t._("Deletion not possible: quantity is not zero."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a simple dataset, ready to be displayed
|
||||
*/
|
||||
public static function prepare(orders:Iterable<db.UserContract>):Array<UserOrder> {
|
||||
var out = new Array<UserOrder>();
|
||||
var orders = Lambda.array(orders);
|
||||
var view = App.current.view;
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
for (o in orders) {
|
||||
|
||||
var x : UserOrder = cast { };
|
||||
x.id = o.id;
|
||||
x.userId = o.user.id;
|
||||
x.userName = o.user.getCoupleName();
|
||||
x.userEmail = o.user.email;
|
||||
|
||||
//shared order
|
||||
if (o.user2 != null){
|
||||
x.userId2 = o.user2.id;
|
||||
x.userName2 = o.user2.getCoupleName();
|
||||
x.userEmail2 = o.user2.email;
|
||||
}
|
||||
|
||||
//deprecated
|
||||
x.productId = o.product.id;
|
||||
x.productRef = o.product.ref;
|
||||
x.productQt = o.product.qt;
|
||||
x.productUnit = o.product.unitType;
|
||||
x.productPrice = o.productPrice;
|
||||
x.productImage = o.product.getImage();
|
||||
x.productHasFloatQt = o.product.hasFloatQt;
|
||||
x.productHasVariablePrice = o.product.variablePrice;
|
||||
//new way
|
||||
x.product = o.product.infos();
|
||||
x.product.price = o.productPrice;//do not use current price, but price of the order
|
||||
|
||||
|
||||
x.quantity = o.quantity;
|
||||
|
||||
//smartQt
|
||||
if (x.quantity == 0.0){
|
||||
x.smartQt = t._("Canceled");
|
||||
}else if(x.productHasFloatQt || x.productHasVariablePrice || o.product.wholesale){
|
||||
x.smartQt = view.smartQt(x.quantity, x.productQt, x.productUnit);
|
||||
}else{
|
||||
x.smartQt = Std.string(x.quantity);
|
||||
}
|
||||
|
||||
//product name.
|
||||
if ( x.productHasVariablePrice || x.productQt==null || x.productUnit==null ){
|
||||
x.productName = o.product.name;
|
||||
}else{
|
||||
x.productName = o.product.name + " " + view.formatNum(x.productQt) +" "+ view.unit(x.productUnit,x.productQt>1);
|
||||
}
|
||||
|
||||
x.subTotal = o.quantity * o.productPrice;
|
||||
|
||||
var c = o.product.contract;
|
||||
|
||||
if ( o.feesRate!=0 ) {
|
||||
|
||||
x.fees = x.subTotal * (o.feesRate/100);
|
||||
x.percentageName = c.percentageName;
|
||||
x.percentageValue = o.feesRate;
|
||||
x.total = x.subTotal + x.fees;
|
||||
|
||||
}else {
|
||||
x.total = x.subTotal;
|
||||
}
|
||||
|
||||
//flags
|
||||
x.paid = o.paid;
|
||||
x.invertSharedOrder = o.flags.has(InvertSharedOrder);
|
||||
x.contractId = c.id;
|
||||
x.contractName = c.name;
|
||||
x.canModify = o.canModify();
|
||||
|
||||
out.push(x);
|
||||
}
|
||||
|
||||
return sort(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms an order : create real orders from tmp orders in session
|
||||
* @param order
|
||||
*/
|
||||
public static function confirmSessionOrder(tmpOrder:OrderInSession){
|
||||
var orders = [];
|
||||
var user = db.User.manager.get(tmpOrder.userId);
|
||||
for (o in tmpOrder.products){
|
||||
o.product = db.Product.manager.get(o.productId);
|
||||
orders.push( make(user, o.quantity, o.product, o.distributionId) );
|
||||
}
|
||||
|
||||
App.current.event(MakeOrder(orders));
|
||||
App.current.session.data.order = null;
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send an order-by-products report to the coordinator
|
||||
*/
|
||||
public static function sendOrdersByProductReport(d:db.Distribution){
|
||||
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.addRecipient(d.contract.contact.email , d.contract.contact.getName());
|
||||
m.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
m.setSubject('[${d.contract.amap.name}] Distribution du ${App.current.view.dDate(d.date)} (${d.contract.name})');
|
||||
var orders = service.ReportService.getOrdersByProduct(d);
|
||||
|
||||
var html = App.current.processTemplate("mail/ordersByProduct.mtt", {
|
||||
contract:d.contract,
|
||||
distribution:d,
|
||||
orders:orders,
|
||||
formatNum:App.current.view.formatNum,
|
||||
currency:App.current.view.currency,
|
||||
dDate:App.current.view.dDate,
|
||||
hHour:App.current.view.hHour,
|
||||
group:d.contract.amap
|
||||
} );
|
||||
|
||||
m.setHtmlBody(html);
|
||||
App.sendMail(m);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Order summary for a member
|
||||
* WARNING : its for one distrib, not for a whole basket !
|
||||
*/
|
||||
public static function sendOrderSummaryToMembers(d:db.Distribution){
|
||||
|
||||
var title = '[${d.contract.amap.name}] Votre commande pour le ${App.current.view.dDate(d.date)} (${d.contract.name})';
|
||||
|
||||
for( user in d.getUsers() ){
|
||||
|
||||
var m = new sugoi.mail.Mail();
|
||||
m.addRecipient(user.email , user.getName(),user.id);
|
||||
if(user.email2!=null) m.addRecipient(user.email2 , user.getName(),user.id);
|
||||
m.setSender(App.config.get("default_email"),"Cagette.net");
|
||||
m.setSubject(title);
|
||||
var orders = prepare(d.contract.getUserOrders(user,d));
|
||||
|
||||
var html = App.current.processTemplate("mail/orderSummaryForMember.mtt", {
|
||||
contract:d.contract,
|
||||
distribution:d,
|
||||
orders:orders,
|
||||
formatNum:App.current.view.formatNum,
|
||||
currency:App.current.view.currency,
|
||||
dDate:App.current.view.dDate,
|
||||
hHour:App.current.view.hHour,
|
||||
group:d.contract.amap
|
||||
} );
|
||||
|
||||
m.setHtmlBody(html);
|
||||
App.sendMail(m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function sort(orders:Array<UserOrder>){
|
||||
|
||||
//order by lastname (+lastname2 if exists), then contract
|
||||
orders.sort(function(a, b) {
|
||||
|
||||
if (a.userName + a.userId + a.userName2 + a.userId2 + a.contractId > b.userName + b.userId + b.userName2 + b.userId2 + b.contractId ) {
|
||||
|
||||
return 1;
|
||||
}
|
||||
if (a.userName + a.userId + a.userName2 + a.userId2 + a.contractId < b.userName + b.userId + b.userName2 + b.userId2 + b.contractId ) {
|
||||
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return orders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package service;
|
||||
import Common;
|
||||
|
||||
/**
|
||||
* Payment Service
|
||||
* @author web-wizard
|
||||
*/
|
||||
class PaymentService
|
||||
{
|
||||
/**
|
||||
* Get all available payment types, including one from plugins
|
||||
*/
|
||||
public static function getAllPaymentTypes(){
|
||||
var types = [
|
||||
new payment.Cash(),
|
||||
new payment.Check(),
|
||||
new payment.Transfer(),
|
||||
new payment.MoneyPot(),
|
||||
];
|
||||
|
||||
var e = App.current.event(GetPaymentTypes({types:types}));
|
||||
return switch(e){
|
||||
case GetPaymentTypes(d): d.types;
|
||||
default : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static function getAllowedPaymentTypes(group:db.Amap):Array<payment.Payment>{
|
||||
var out :Array<payment.Payment> = [];
|
||||
|
||||
//populate with activated payment types.
|
||||
var all = getAllPaymentTypes();
|
||||
if ( group.allowedPaymentsType == null ) return [];
|
||||
for ( t in group.allowedPaymentsType){
|
||||
|
||||
var found = Lambda.find(all, function(a) return a.type == t);
|
||||
if (found != null) out.push(found);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static function getPaymentTypesForManualEntry(group:db.Amap){
|
||||
|
||||
var out = [];
|
||||
var paymentTypes = [];
|
||||
var allowedPaymentTypes = service.PaymentService.getAllowedPaymentTypes(group);
|
||||
if ( !Lambda.exists(allowedPaymentTypes, function(obj) return obj.type == "moneypot" ) ) {
|
||||
paymentTypes = allowedPaymentTypes;
|
||||
}
|
||||
else {
|
||||
paymentTypes = service.PaymentService.getAllPaymentTypes();
|
||||
}
|
||||
for ( t in paymentTypes ){
|
||||
if(t.type != "moneypot") out.push({label:t.name,value:t.type});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto validate a distribution.
|
||||
* This is called by the hourly cron
|
||||
*
|
||||
* @param distrib
|
||||
*/
|
||||
public static function validateDistribution(distrib:db.Distribution) {
|
||||
|
||||
for ( user in distrib.getUsers()){
|
||||
var basket = db.Basket.get(user, distrib.place, distrib.date);
|
||||
validateBasket(basket);
|
||||
}
|
||||
//finally validate distrib
|
||||
distrib.lock();
|
||||
distrib.validated = true;
|
||||
distrib.update();
|
||||
}
|
||||
|
||||
public static function unvalidateDistribution(distrib:db.Distribution) {
|
||||
|
||||
for ( user in distrib.getUsers()){
|
||||
var basket = db.Basket.get(user, distrib.place, distrib.date);
|
||||
unvalidateBasket(basket);
|
||||
}
|
||||
//finally validate distrib
|
||||
distrib.lock();
|
||||
distrib.validated = false;
|
||||
distrib.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto validate a basket
|
||||
*
|
||||
* @param basket
|
||||
*/
|
||||
public static function validateBasket(basket:db.Basket) {
|
||||
|
||||
if (basket == null || basket.isValidated()) return false;
|
||||
|
||||
//mark orders as paid
|
||||
var orders = basket.getOrders();
|
||||
for ( order in orders ){
|
||||
|
||||
order.lock();
|
||||
order.paid = true;
|
||||
order.update();
|
||||
}
|
||||
|
||||
//validate order operation and payments
|
||||
var operation = basket.getOrderOperation(false);
|
||||
if (operation != null){
|
||||
|
||||
operation.lock();
|
||||
operation.pending = false;
|
||||
operation.update();
|
||||
|
||||
for ( payment in basket.getPayments()){
|
||||
|
||||
if ( payment.pending){
|
||||
payment.lock();
|
||||
payment.pending = false;
|
||||
payment.update();
|
||||
}
|
||||
}
|
||||
|
||||
var o = orders.first();
|
||||
updateUserBalance(o.user, o.distribution.place.amap);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function unvalidateBasket(basket:db.Basket) {
|
||||
|
||||
if (basket == null || !basket.isValidated()) return false;
|
||||
|
||||
//mark orders as paid
|
||||
var orders = basket.getOrders();
|
||||
for ( order in orders ){
|
||||
|
||||
order.lock();
|
||||
order.paid = false;
|
||||
order.update();
|
||||
}
|
||||
|
||||
//validate order operation and payments
|
||||
var operation = basket.getOrderOperation(false);
|
||||
if (operation != null){
|
||||
|
||||
operation.lock();
|
||||
operation.pending = true;
|
||||
operation.update();
|
||||
|
||||
for ( payment in basket.getPayments()){
|
||||
|
||||
if (!payment.pending){
|
||||
payment.lock();
|
||||
payment.pending = true;
|
||||
payment.update();
|
||||
}
|
||||
}
|
||||
|
||||
var o = orders.first();
|
||||
updateUserBalance(o.user, o.distribution.place.amap);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* update user balance
|
||||
*/
|
||||
public static function updateUserBalance(user:db.User,group:db.Amap){
|
||||
|
||||
var ua = db.UserAmap.getOrCreate(user, group);
|
||||
var b = sys.db.Manager.cnx.request('SELECT SUM(amount) FROM Operation WHERE userId=${user.id} and groupId=${group.id} and !(type=2 and pending=1)').getFloatResult(0);
|
||||
b = Math.round(b * 100) / 100;
|
||||
ua.balance = b;
|
||||
ua.update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package service;
|
||||
|
||||
class PlaceService{
|
||||
|
||||
/**
|
||||
* Geocode a place with Google Geocode API
|
||||
*/
|
||||
public static function geocode(p:db.Place):{lat:Float,lng:Float}{
|
||||
var apiKey = App.config.get("google_geocoding_key");
|
||||
if(apiKey==null) return null;
|
||||
|
||||
var gc = new sugoi.apis.google.GeoCode(apiKey);
|
||||
var address = p.getAddress();
|
||||
//var comp = "administrative_area:" + p.city + "|postal_code:" + p.zipCode + "|country:FR";
|
||||
//Sys.print(address+"<br/>"+comp+"<br/>");
|
||||
|
||||
var geo = gc.geocode( address , null);
|
||||
var coords = geo[0].geometry.location;
|
||||
|
||||
p.lock();
|
||||
p.lat = coords.lat;
|
||||
p.lng = coords.lng;
|
||||
p.update();
|
||||
|
||||
return {lat:p.lat,lng:p.lng};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package service;
|
||||
|
||||
class ProductService{
|
||||
|
||||
|
||||
/**
|
||||
* Batch disable products
|
||||
*/
|
||||
public static function batchDisableProducts(productIds:Array<Int>){
|
||||
|
||||
var data = {pids:productIds,enable:false};
|
||||
var contract = db.Product.manager.get(productIds[0], true).contract;
|
||||
var products = contract.getProducts(false);
|
||||
|
||||
App.current.event( BatchEnableProducts(data) );
|
||||
|
||||
for ( pid in data.pids){
|
||||
|
||||
var p = db.Product.manager.get(pid, true);
|
||||
|
||||
if ( Lambda.find(products,function(p) return p.id==pid)==null ) throw 'product $pid is not in this contract !';
|
||||
|
||||
p.active = false;
|
||||
p.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Batch enable products
|
||||
*/
|
||||
public static function batchEnableProducts(productIds:Array<Int>){
|
||||
|
||||
var data = {pids:productIds,enable:true};
|
||||
var contract = db.Product.manager.get(productIds[0], true).contract;
|
||||
var products = contract.getProducts(false);
|
||||
|
||||
App.current.event( BatchEnableProducts(data) );
|
||||
|
||||
for ( pid in data.pids){
|
||||
|
||||
var p = db.Product.manager.get(pid, true);
|
||||
|
||||
if ( Lambda.find(products,function(p) return p.id==pid)==null ) throw 'product $pid is not in this contract !';
|
||||
|
||||
p.active = true;
|
||||
p.update();
|
||||
}
|
||||
}
|
||||
|
||||
inline public static function getHTPrice(ttcPrice:Float,vatRate:Float):Float{
|
||||
return ttcPrice / (1 + vatRate / 100);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package service;
|
||||
import Common;
|
||||
|
||||
class ReportService{
|
||||
|
||||
/**
|
||||
Get orders grouped by products.
|
||||
*/
|
||||
public static function getOrdersByProduct( distribution:db.Distribution, ?csv = false):Array<OrderByProduct>{
|
||||
var view = App.current.view;
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
var where = "";
|
||||
|
||||
var exportName = t._("Delivery ::contractName:: of the ", {contractName:distribution.contract.name}) + distribution.date.toString().substr(0, 10);
|
||||
where += ' and p.contractId = ${distribution.contract.id}';
|
||||
if (distribution.contract.type == db.Contract.TYPE_VARORDER ) {
|
||||
where += ' and up.distributionId = ${distribution.id}';
|
||||
}
|
||||
|
||||
//Product price will be an average if price changed
|
||||
var sql = 'select
|
||||
SUM(quantity) as quantity,
|
||||
MAX(p.id) as pid,
|
||||
p.name as pname,
|
||||
AVG(up.productPrice) as price,
|
||||
AVG(p.vat) as vat,
|
||||
p.ref as ref,
|
||||
SUM(quantity*up.productPrice) as totalTTC
|
||||
from UserContract up, Product p
|
||||
where up.productId = p.id
|
||||
$where
|
||||
group by ref,pname,price
|
||||
order by pname asc;';
|
||||
|
||||
var res = sys.db.Manager.cnx.request(sql).results();
|
||||
var orders = [];
|
||||
|
||||
//populate with full product names
|
||||
for ( r in res){
|
||||
|
||||
var o : OrderByProduct = {
|
||||
quantity:1.0 * r.quantity,
|
||||
smartQt:"",
|
||||
pid:r.pid,
|
||||
pname:r.pname,
|
||||
ref:r.ref,
|
||||
priceHT: service.ProductService.getHTPrice(r.price,r.vat),
|
||||
priceTTC: r.price,
|
||||
vat:r.vat,
|
||||
totalTTC : r.totalTTC,
|
||||
totalHT : service.ProductService.getHTPrice( r.totalTTC ,r.vat),
|
||||
weightOrVolume:"",
|
||||
};
|
||||
|
||||
//smartQt
|
||||
var p = db.Product.manager.get(r.pid, false);
|
||||
if( p.hasFloatQt || p.variablePrice ){
|
||||
o.smartQt = view.smartQt(o.quantity, p.qt, p.unitType);
|
||||
}else{
|
||||
o.smartQt = Std.string(o.quantity);
|
||||
}
|
||||
o.weightOrVolume = view.smartQt(o.quantity, p.qt, p.unitType);
|
||||
|
||||
if ( /*p.hasFloatQt || p.variablePrice ||*/ p.qt==null || p.unitType==null){
|
||||
o.pname = p.name;
|
||||
}else{
|
||||
o.pname = p.name + " " + view.formatNum(p.qt) +" " + view.unit(p.unitType, o.quantity > 1);
|
||||
}
|
||||
|
||||
//special case : if product is multiweight, we should count the records number ( and not SUM quantities )
|
||||
if (p.multiWeight){
|
||||
sql = 'select
|
||||
COUNT(up.id) as quantity
|
||||
from UserContract up, Product p
|
||||
where up.productId = p.id and up.quantity > 0 and p.id=${p.id}
|
||||
$where';
|
||||
var count = sys.db.Manager.cnx.request(sql).getIntResult(0);
|
||||
o.smartQt = ""+count;
|
||||
}
|
||||
|
||||
orders.push(o);
|
||||
}
|
||||
|
||||
if (csv) {
|
||||
var data = new Array<Dynamic>();
|
||||
for (o in orders) {
|
||||
data.push({
|
||||
"quantity":view.formatNum(o.quantity),
|
||||
"pname":o.pname,
|
||||
"ref":o.ref,
|
||||
"priceHT":view.formatNum(o.priceHT),
|
||||
"priceTTC":view.formatNum(o.priceTTC),
|
||||
"totalHT":view.formatNum(o.totalHT),
|
||||
"totalTTC":view.formatNum(o.totalTTC),
|
||||
});
|
||||
}
|
||||
|
||||
sugoi.tools.Csv.printCsvDataFromObjects(data, ["quantity", "pname","ref", "priceHT","priceTTC","totalHT","totalTTC"],"Export-"+exportName+"-par produits");
|
||||
return null;
|
||||
}else{
|
||||
return orders;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getTurnoverFromOrdersByProducts(ordersByProduct:Array<OrderByProduct>):{turnoverHT:Float,turnoverTTC:Float}{
|
||||
var out = {turnoverHT:0.0,turnoverTTC:0.0};
|
||||
for( o in ordersByProduct){
|
||||
out.turnoverHT += o.totalHT;
|
||||
out.turnoverTTC += o.totalTTC;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package service;
|
||||
import tink.core.Error;
|
||||
|
||||
/**
|
||||
* User Service
|
||||
* @author fbarbut
|
||||
*/
|
||||
class UserService
|
||||
{
|
||||
|
||||
var user : db.User;
|
||||
|
||||
public function new(u:db.User)
|
||||
{
|
||||
this.user = u;
|
||||
}
|
||||
|
||||
/**
|
||||
* User login service
|
||||
* @param email
|
||||
* @param password
|
||||
*/
|
||||
public static function login(email:String, password:String){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
//user exists ?
|
||||
var user = db.User.manager.select( $email == email || $email2 == email , true);
|
||||
if (user == null) throw new Error(404,t._("There is no account with this email"));
|
||||
|
||||
//new account
|
||||
if (!user.isFullyRegistred()) {
|
||||
var group = user.getAmaps().first();
|
||||
user.sendInvitation(group);
|
||||
var text = t._("Your account have not been validated yet. We sent an e-mail to ::email:: to finalize your subscription!",{email:user.email});
|
||||
throw new Error(403,text);
|
||||
}
|
||||
|
||||
var pass = haxe.crypto.Md5.encode( App.config.get('key') + password );
|
||||
|
||||
if (user.pass != pass) {
|
||||
throw new Error(403,t._("Invalid password"));
|
||||
}
|
||||
|
||||
db.User.login(user, email);
|
||||
|
||||
//register the user to the current group if needed
|
||||
var group = App.current.getCurrentGroup();
|
||||
if (group != null && group.regOption == db.Amap.RegOption.Open && db.UserAmap.get(user, group) == null){
|
||||
user.makeMemberOf(group);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Full registration by a user himself
|
||||
**/
|
||||
public static function register(firstName:String, lastName:String, email:String, phone:String, pass:String){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if (!sugoi.form.validators.EmailValidator.check(email)){
|
||||
throw new Error(500,t._("Invalid email address"));
|
||||
}
|
||||
|
||||
if ( db.User.getSameEmail(email).length > 0 ) {
|
||||
throw new Error(409,t._("We already have an account with this email address"));
|
||||
}
|
||||
|
||||
var user = new db.User();
|
||||
user.email = email;
|
||||
user.firstName = firstName;
|
||||
user.lastName = lastName;
|
||||
user.phone = phone;
|
||||
user.setPass(pass);
|
||||
user.insert();
|
||||
|
||||
var group = App.current.getCurrentGroup();
|
||||
if (group != null && group.regOption == db.Amap.RegOption.Open){
|
||||
user.makeMemberOf(group);
|
||||
}
|
||||
|
||||
db.User.login(user, email);
|
||||
}
|
||||
|
||||
/**
|
||||
Soft registration :
|
||||
- Somebody creates/import a new user ,
|
||||
- or pre-registration in a waiting list
|
||||
**/
|
||||
public static function softRegistration(firstName:String, lastName:String, email:String){
|
||||
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if (!sugoi.form.validators.EmailValidator.check(email)){
|
||||
throw new Error(500,t._("Invalid email address"));
|
||||
}
|
||||
|
||||
if ( db.User.getSameEmail(email).length > 0 ) {
|
||||
throw new Error(409,t._("We already have an account with this email address"));
|
||||
}
|
||||
|
||||
var user = new db.User();
|
||||
user.email = email;
|
||||
user.firstName = firstName;
|
||||
user.lastName = lastName;
|
||||
user.insert();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* get users belonging to a group
|
||||
* @param group -
|
||||
* @return Array<db.User>
|
||||
*/
|
||||
public static function getFromGroup(group:db.Amap):Array<db.User>{
|
||||
return Lambda.array( group.getMembers() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package service;
|
||||
import tink.core.Error;
|
||||
import db.UserAmap.Right;
|
||||
|
||||
class WaitingListService{
|
||||
|
||||
|
||||
public static function registerToWl(user:db.User,group:db.Amap,message:String){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
canRegister(user,group);
|
||||
|
||||
var w = new db.WaitingList();
|
||||
w.user = user;
|
||||
w.group = group;
|
||||
w.message = message;
|
||||
w.insert();
|
||||
|
||||
//emails
|
||||
var html = t._("<p><b>::name::</b> suscribed to the waiting list of <b>::group::</b> on ::date::</p>",{
|
||||
group:group.name,
|
||||
name:user.name,
|
||||
date:App.current.view.hDate(Date.now())
|
||||
});
|
||||
if(message!=null && message!=""){
|
||||
html += t._("<p>He/she left this message :<br/>\"::message::\"</p>",{message:message});
|
||||
}
|
||||
|
||||
for( u in service.GroupService.getGroupMembersWithRights(group,[Right.GroupAdmin,Right.Membership]) ){
|
||||
|
||||
App.quickMail(
|
||||
u.email,
|
||||
t._("[::group::] ::name:: suscribed to the waiting list.",{group:group.name,name:user.name}),
|
||||
html,
|
||||
group
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function canRegister(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
|
||||
if ( db.WaitingList.manager.select($amapId == group.id && $user == user) != null) {
|
||||
throw new Error(t._("You are already in the waiting list of this group"));
|
||||
}
|
||||
if ( db.UserAmap.manager.select($amapId == group.id && $user == user) != null) {
|
||||
throw new Error(t._("You are already member of this group."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
the user cancels his request
|
||||
**/
|
||||
public static function removeFromWl(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( user == null) {
|
||||
throw new Error(t._("You should be logged in."));
|
||||
}
|
||||
|
||||
var wl = db.WaitingList.manager.select($amapId == group.id && $user == user,true);
|
||||
|
||||
if ( wl == null) {
|
||||
throw new Error(t._("You are not in the waiting list of this group"));
|
||||
}
|
||||
wl.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
an admin cancels a request
|
||||
**/
|
||||
public static function cancelRequest(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( user == null) throw "user is null";
|
||||
var wl = db.WaitingList.manager.select($amapId == group.id && $user == user,true);
|
||||
if ( wl == null) throw "this user is not in waiting list";
|
||||
|
||||
//email the requester
|
||||
App.quickMail(
|
||||
wl.user.email,
|
||||
t._("[::group::] Membership request refused.",{group:group.name}),
|
||||
t._("Your membership request for <b>::group::</b> has been refused.",{group:group.name})
|
||||
);
|
||||
|
||||
//email others admin
|
||||
for( u in service.GroupService.getGroupMembersWithRights(group,[Right.GroupAdmin,Right.Membership]) ){
|
||||
if(u.id==App.current.user.id) continue;
|
||||
App.quickMail(
|
||||
u.email,
|
||||
t._("[::group::] ::name:: membership request has been refused by ::admin::.",{group:group.name, name:user.name, admin:App.current.user.name}),
|
||||
t._("<p><b>::name::</b> was registred to the waiting list.</p><p><b>::admin::</b> has refused his/her request.</p>",{name:user.name, admin:App.current.user.name}),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
wl.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
an admin approves a request
|
||||
**/
|
||||
public static function approveRequest(user:db.User,group:db.Amap){
|
||||
var t = sugoi.i18n.Locale.texts;
|
||||
if ( user == null) throw "user is null";
|
||||
var wl = db.WaitingList.manager.select($amapId == group.id && $user == user,true);
|
||||
if ( wl == null) throw "this user is not in waiting list";
|
||||
|
||||
if (db.UserAmap.get(user, group, false) == null){
|
||||
var ua = new db.UserAmap();
|
||||
ua.amap = wl.group;
|
||||
ua.user = wl.user;
|
||||
ua.insert();
|
||||
}
|
||||
|
||||
wl.delete();
|
||||
|
||||
//email the requester
|
||||
App.quickMail(
|
||||
wl.user.email,
|
||||
t._("[::group::] Membership request accepted.",{group:group.name}),
|
||||
t._("<p>Your membership request for <b>::group::</b> has been accepted !</p><p>You're now a member of the group.</p>",{group:group.name}),
|
||||
group
|
||||
);
|
||||
|
||||
//email others admin
|
||||
for( u in service.GroupService.getGroupMembersWithRights(group,[Right.GroupAdmin,Right.Membership]) ){
|
||||
if(u.id==App.current.user.id) continue;
|
||||
App.quickMail(
|
||||
u.email,
|
||||
t._("[::group::] ::name:: membership request has been accepted by ::admin::.",{group:group.name, name:user.name, admin:App.current.user.name}),
|
||||
t._("<p><b>::name::</b> was registred to the waiting list.</p><p><b>::admin::</b> has accepted his/her request.</p>",{name:user.name, admin:App.current.user.name}),
|
||||
group
|
||||
);
|
||||
}
|
||||
|
||||
wl.delete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user