You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
423 lines
15 KiB
423 lines
15 KiB
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;
|
|
|
|
}
|
|
}
|