/**
* Axelor Business Solutions
*
* Copyright (C) 2016 Axelor (<http://axelor.com>).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.axelor.apps.hr.service.timesheet;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.joda.time.LocalTime;
import org.joda.time.Minutes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.axelor.apps.account.db.Invoice;
import com.axelor.apps.account.db.InvoiceLine;
import com.axelor.apps.account.service.invoice.generator.InvoiceLineGenerator;
import com.axelor.apps.base.db.DayPlanning;
import com.axelor.apps.base.db.IPriceListLine;
import com.axelor.apps.base.db.PriceList;
import com.axelor.apps.base.db.PriceListLine;
import com.axelor.apps.base.db.Product;
import com.axelor.apps.base.db.WeeklyPlanning;
import com.axelor.apps.base.db.repo.GeneralRepository;
import com.axelor.apps.base.db.repo.ProductRepository;
import com.axelor.apps.base.service.PriceListService;
import com.axelor.apps.base.service.UnitConversionService;
import com.axelor.apps.base.service.administration.GeneralService;
import com.axelor.apps.hr.db.Employee;
import com.axelor.apps.hr.db.LeaveRequest;
import com.axelor.apps.hr.db.Timesheet;
import com.axelor.apps.hr.db.TimesheetLine;
import com.axelor.apps.hr.db.repo.EmployeeRepository;
import com.axelor.apps.hr.db.repo.LeaveRequestRepository;
import com.axelor.apps.hr.db.repo.TimesheetLineRepository;
import com.axelor.apps.hr.db.repo.TimesheetRepository;
import com.axelor.apps.hr.exception.IExceptionMessage;
import com.axelor.apps.hr.service.employee.EmployeeService;
import com.axelor.apps.hr.service.project.ProjectTaskService;
import com.axelor.apps.hr.db.repo.PublicHolidayDayRepository;
import com.axelor.apps.hr.db.PublicHolidayDay;
import com.axelor.apps.project.db.ProjectTask;
import com.axelor.apps.project.db.repo.ProjectTaskRepository;
import com.axelor.auth.AuthUtils;
import com.axelor.auth.db.User;
import com.axelor.exception.AxelorException;
import com.axelor.exception.db.IException;
import com.axelor.i18n.I18n;
import com.axelor.inject.Beans;
import com.axelor.rpc.ActionRequest;
import com.axelor.rpc.ActionResponse;
import com.google.inject.Inject;
import com.google.inject.persist.Transactional;
public class TimesheetServiceImpl implements TimesheetService{
@Inject
protected EmployeeService employeeService;
@Inject
protected PriceListService priceListService;
@Inject
protected GeneralService generalService;
@Inject
protected ProjectTaskService projectTaskService;
@Inject
protected PublicHolidayDayRepository publicHolidayDayRepo;
@Inject
protected EmployeeRepository employeeRepo;
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
@Transactional(rollbackOn={Exception.class})
public void getTimeFromTask(Timesheet timesheet){
List<TimesheetLine> timesheetLineList = TimesheetLineRepository.of(TimesheetLine.class).all().filter("self.user = ?1 AND self.timesheet = null AND self.projectTask != null", timesheet.getUser()).fetch();
for (TimesheetLine timesheetLine : timesheetLineList) {
timesheet.addTimesheetLineListItem(timesheetLine);
}
Beans.get(TimesheetRepository.class).save(timesheet);
}
@Override
@Transactional(rollbackOn={Exception.class})
public void cancelTimesheet(Timesheet timesheet){
timesheet.setStatusSelect(TimesheetRepository.STATUS_CANCELED);
Beans.get(TimesheetRepository.class).save(timesheet);
}
@Override
public Timesheet generateLines(Timesheet timesheet, LocalDate fromGenerationDate, LocalDate toGenerationDate, BigDecimal logTime, ProjectTask projectTask, Product product) throws AxelorException{
User user = timesheet.getUser();
Employee employee = user.getEmployee();
if(fromGenerationDate == null) {
throw new AxelorException(String.format(I18n.get(IExceptionMessage.TIMESHEET_FROM_DATE)), IException.MISSING_FIELD);
}
if(toGenerationDate == null) {
throw new AxelorException(String.format(I18n.get(IExceptionMessage.TIMESHEET_TO_DATE)), IException.MISSING_FIELD);
}
if(product == null) {
throw new AxelorException(String.format(I18n.get(IExceptionMessage.TIMESHEET_PRODUCT)), IException.MISSING_FIELD);
}
if(employee == null){
throw new AxelorException(String.format(I18n.get(IExceptionMessage.LEAVE_USER_EMPLOYEE),user.getName()), IException.CONFIGURATION_ERROR);
}
WeeklyPlanning planning = user.getEmployee().getPlanning();
if(planning == null){
throw new AxelorException(String.format(I18n.get(IExceptionMessage.TIMESHEET_EMPLOYEE_DAY_PLANNING), user.getName()), IException.CONFIGURATION_ERROR);
}
List<DayPlanning> dayPlanningList = planning.getWeekDays();
LocalDate fromDate = fromGenerationDate;
LocalDate toDate = toGenerationDate;
Map<Integer,String> correspMap = new HashMap<Integer,String>();
correspMap.put(1, "monday");
correspMap.put(2, "tuesday");
correspMap.put(3, "wednesday");
correspMap.put(4, "thursday");
correspMap.put(5, "friday");
correspMap.put(6, "saturday");
correspMap.put(7, "sunday");
//Leaving list
List<LeaveRequest> leaveList = LeaveRequestRepository.of(LeaveRequest.class).all().filter("self.user = ?1 AND (self.statusSelect = 2 OR self.statusSelect = 3)", user).fetch();
//Public holidays list
List<PublicHolidayDay> publicHolidayList = employee.getPublicHolidayPlanning().getPublicHolidayDayList();
while(!fromDate.isAfter(toDate)){
DayPlanning dayPlanningCurr = new DayPlanning();
for (DayPlanning dayPlanning : dayPlanningList) {
if(dayPlanning.getName().equals(correspMap.get(fromDate.getDayOfWeek()))){
dayPlanningCurr = dayPlanning;
break;
}
}
if(dayPlanningCurr.getMorningFrom() != null || dayPlanningCurr.getMorningTo() != null || dayPlanningCurr.getAfternoonFrom() != null || dayPlanningCurr.getAfternoonTo() != null)
{
/*Check if the day is not a leaving day */
boolean noLeave = true;
if(leaveList != null){
for (LeaveRequest leave : leaveList) {
if((leave.getFromDate().isBefore(fromDate) && leave.getToDate().isAfter(fromDate))
|| leave.getFromDate().isEqual(fromDate) || leave.getToDate().isEqual(fromDate))
{
noLeave = false;
break;
}
}
}
/*Check if the day is not a public holiday */
boolean noPublicHoliday = true;
if(publicHolidayList != null){
for (PublicHolidayDay publicHoliday : publicHolidayList) {
if(publicHoliday.getDate().isEqual(fromDate))
{
noPublicHoliday = false;
break;
}
}
}
if(noLeave && noPublicHoliday){
TimesheetLine timesheetLine = createTimesheetLine(projectTask, product, user, fromDate, timesheet, employeeService.getUserDuration(logTime, employee.getDailyWorkHours(), true), "");
timesheetLine.setVisibleDuration(logTime);
}
}
fromDate=fromDate.plusDays(1);
}
return timesheet;
}
@Override
public LocalDate getFromPeriodDate(){
Timesheet timesheet = Beans.get(TimesheetRepository.class).all().filter("self.user = ?1 ORDER BY self.toDate DESC", AuthUtils.getUser()).fetchOne();
if(timesheet != null){
return timesheet.getToDate();
}
else{
return null;
}
}
public Timesheet getCurrentTimesheet(){
Timesheet timesheet = Beans.get(TimesheetRepository.class).all().filter("self.statusSelect = ?1 AND self.user.id = ?2", TimesheetRepository.STATUS_DRAFT, AuthUtils.getUser().getId()).order("-id").fetchOne();
if(timesheet != null){
return timesheet;
}
else{
return null;
}
}
public Timesheet getCurrentOrCreateTimesheet(){
Timesheet timesheet = getCurrentTimesheet();
if(timesheet == null)
timesheet = createTimesheet(AuthUtils.getUser(), generalService.getTodayDateTime().toLocalDate(), null);
return timesheet;
}
public Timesheet createTimesheet(User user, LocalDate fromDate, LocalDate toDate){
Timesheet timesheet = new Timesheet();
timesheet.setUser(user);
timesheet.setCompany(user.getActiveCompany());
timesheet.setFromDate(fromDate);
timesheet.setStatusSelect(TimesheetRepository.STATUS_DRAFT);
timesheet.setFullName(computeFullName(timesheet));
return timesheet;
}
public TimesheetLine createTimesheetLine(ProjectTask project, Product product, User user, LocalDate date, Timesheet timesheet, BigDecimal hours, String comments){
TimesheetLine timesheetLine = new TimesheetLine();
timesheetLine.setDate(date);
timesheetLine.setComments(comments);
timesheetLine.setProduct(product);
timesheetLine.setProjectTask(project);
timesheetLine.setUser(user);
timesheetLine.setDurationStored(hours);
timesheet.addTimesheetLineListItem(timesheetLine);
return timesheetLine;
}
public List<InvoiceLine> createInvoiceLines(Invoice invoice, List<TimesheetLine> timesheetLineList, int priority) throws AxelorException {
List<InvoiceLine> invoiceLineList = new ArrayList<InvoiceLine>();
int count = 0;
DateFormat ddmmFormat = new SimpleDateFormat("dd/MM");
HashMap<String, Object[]> timeSheetInformationsMap = new HashMap<String, Object[]>();
//Check if a consolidation by product and user must be done
boolean consolidate = generalService.getGeneral().getConsolidateTSLine();
for (TimesheetLine timesheetLine : timesheetLineList) {
Object[] tabInformations = new Object[5];
tabInformations[0] = timesheetLine.getProduct();
tabInformations[1] = timesheetLine.getUser();
//Start date
tabInformations[2] = timesheetLine.getDate();
//End date, useful only for consolidation
tabInformations[3] = timesheetLine.getDate();
tabInformations[4] = timesheetLine.getDurationStored();
String key = null;
if(consolidate){
key = timesheetLine.getProduct().getId() + "|" + timesheetLine.getUser().getId();
if (timeSheetInformationsMap.containsKey(key)){
tabInformations = timeSheetInformationsMap.get(key);
//Update date
if (timesheetLine.getDate().compareTo((LocalDate)tabInformations[2]) < 0){
//If date is lower than start date then replace start date by this one
tabInformations[2] = timesheetLine.getDate();
}else if (timesheetLine.getDate().compareTo((LocalDate)tabInformations[3]) > 0){
//If date is upper than end date then replace end date by this one
tabInformations[3] = timesheetLine.getDate();
}
tabInformations[4] = ((BigDecimal)tabInformations[4]).add(timesheetLine.getDurationStored());
}else{
timeSheetInformationsMap.put(key, tabInformations);
}
}else{
key = String.valueOf(timesheetLine.getId());
timeSheetInformationsMap.put(key, tabInformations);
}
timesheetLine.setInvoiced(true);
}
for(Object[] timesheetInformations : timeSheetInformationsMap.values()) {
String strDate = null;
Product product = (Product)timesheetInformations[0];
User user = (User)timesheetInformations[1];
LocalDate startDate = (LocalDate)timesheetInformations[2];
LocalDate endDate = (LocalDate)timesheetInformations[3];
BigDecimal durationStored = (BigDecimal) timesheetInformations[4];
if (consolidate){
strDate = ddmmFormat.format(startDate.toDate()) + " - " + ddmmFormat.format(endDate.toDate());
}else{
strDate = ddmmFormat.format(startDate.toDate());
}
invoiceLineList.addAll(this.createInvoiceLine(invoice, product, user, strDate, durationStored, priority*100+count));
count++;
}
return invoiceLineList;
}
public List<InvoiceLine> createInvoiceLine(Invoice invoice, Product product, User user, String date, BigDecimal durationStored, int priority) throws AxelorException {
Employee employee = user.getEmployee();
int discountTypeSelect = 1;
if(product == null){
throw new AxelorException(String.format(I18n.get(IExceptionMessage.TIMESHEET_PRODUCT)), IException.CONFIGURATION_ERROR);
}
BigDecimal price = product.getSalePrice();
BigDecimal discountAmount = product.getCostPrice();
BigDecimal qtyConverted = durationStored;
qtyConverted = Beans.get(UnitConversionService.class).convert(generalService.getGeneral().getUnitHours(), product.getUnit(), durationStored);
PriceList priceList = invoice.getPartner().getSalePriceList();
if(priceList != null) {
PriceListLine priceListLine = priceListService.getPriceListLine(product, qtyConverted, priceList);
if(priceListLine!=null){
discountTypeSelect = priceListLine.getTypeSelect();
}
if((generalService.getGeneral().getComputeMethodDiscountSelect() == GeneralRepository.INCLUDE_DISCOUNT_REPLACE_ONLY && discountTypeSelect == IPriceListLine.TYPE_REPLACE) || generalService.getGeneral().getComputeMethodDiscountSelect() == GeneralRepository.INCLUDE_DISCOUNT)
{
Map<String, Object> discounts = priceListService.getDiscounts(priceList, priceListLine, price);
if(discounts != null){
discountAmount = (BigDecimal) discounts.get("discountAmount");
price = priceListService.computeDiscount(price, (int) discounts.get("discountTypeSelect"), discountAmount);
}
}
else{
Map<String, Object> discounts = priceListService.getDiscounts(priceList, priceListLine, price);
if(discounts != null){
discountAmount = (BigDecimal) discounts.get("discountAmount");
if(discounts.get("price") != null) {
price = (BigDecimal) discounts.get("price");
}
}
}
}
String description = user.getFullName(),
productName = product.getName() + " " + "(" + date + ")";
InvoiceLineGenerator invoiceLineGenerator = new InvoiceLineGenerator(invoice, product, productName, price,
price,description,qtyConverted,product.getUnit(), null,priority,discountAmount,discountTypeSelect,
price.multiply(qtyConverted), null,false) {
@Override
public List<InvoiceLine> creates() throws AxelorException {
InvoiceLine invoiceLine = this.createInvoiceLine();
List<InvoiceLine> invoiceLines = new ArrayList<InvoiceLine>();
invoiceLines.add(invoiceLine);
return invoiceLines;
}
};
return invoiceLineGenerator.creates();
}
@Override
@Transactional
public void computeTimeSpent(Timesheet timesheet){
List<TimesheetLine> timesheetLineList = timesheet.getTimesheetLineList();
for (TimesheetLine timesheetLine : timesheetLineList) {
ProjectTask projectTask = timesheetLine.getProjectTask();
if(projectTask != null){
projectTask.setTimeSpent(timesheetLine.getDurationStored().add(this.computeSubTimeSpent(projectTask)));
this.computeParentTimeSpent(projectTask);
Beans.get(ProjectTaskRepository.class).save(projectTask);
}
}
}
public BigDecimal computeSubTimeSpent(ProjectTask projectTask){
BigDecimal sum = BigDecimal.ZERO;
List<ProjectTask> subProjectTaskList = Beans.get(ProjectTaskRepository.class).all().filter("self.project = ?1", projectTask).fetch();
if(subProjectTaskList == null || subProjectTaskList.isEmpty()){
return projectTask.getTimeSpent();
}
for (ProjectTask projectTaskIt : subProjectTaskList) {
sum = sum.add(this.computeSubTimeSpent(projectTaskIt));
}
return sum;
}
public void computeParentTimeSpent(ProjectTask projectTask){
ProjectTask parentProject = projectTask.getProject();
if(parentProject == null){
return;
}
parentProject.setTimeSpent(projectTask.getTimeSpent().add(this.computeTimeSpent(parentProject)));
this.computeParentTimeSpent(parentProject);
}
public BigDecimal computeTimeSpent(ProjectTask projectTask){
BigDecimal sum = BigDecimal.ZERO;
List<TimesheetLine> timesheetLineList = Beans.get(TimesheetLineRepository.class).all().filter("self.projectTask = ?1 AND self.timesheet.statusSelect = ?2", projectTask, TimesheetRepository.STATUS_VALIDATED).fetch();
for (TimesheetLine timesheetLine : timesheetLineList) {
sum = sum.add(timesheetLine.getDurationStored());
}
return sum;
}
public void getActivities(ActionRequest request, ActionResponse response){
List<Map<String,String>> dataList = new ArrayList<Map<String,String>>();
try{
List<Product> productList = Beans.get(ProductRepository.class).all().filter("self.isActivity = true").fetch();
for (Product product : productList) {
Map<String, String> map = new HashMap<String,String>();
map.put("name", product.getName());
map.put("id", product.getId().toString());
dataList.add(map);
}
response.setData(dataList);
}
catch(Exception e){
response.setStatus(-1);
response.setError(e.getMessage());
}
}
@Transactional
public void insertTSLine(ActionRequest request, ActionResponse response){
User user = AuthUtils.getUser();
ProjectTask projectTask = Beans.get(ProjectTaskRepository.class).find(new Long(request.getData().get("project").toString()));
Product product = Beans.get(ProductRepository.class).find(new Long(request.getData().get("activity").toString()));
LocalDate date = new LocalDate(request.getData().get("date").toString());
if(user != null){
Timesheet timesheet = Beans.get(TimesheetRepository.class).all().filter("self.statusSelect = 1 AND self.user.id = ?1", user.getId()).order("-id").fetchOne();
if(timesheet == null){
timesheet = createTimesheet(user, date, date);
}
BigDecimal minutes = new BigDecimal(Minutes.minutesBetween(new LocalTime(0,0), new LocalTime(request.getData().get("duration").toString())).getMinutes());
createTimesheetLine(projectTask, product, user, date, timesheet, minutes, request.getData().get("comments").toString());
Beans.get(TimesheetRepository.class).save(timesheet);
}
}
public String computeFullName(Timesheet timesheet){
User timesheetUser = timesheet.getUser();
LocalDateTime createdOn = timesheet.getCreatedOn();
if(timesheetUser != null && createdOn != null){
return timesheetUser.getFullName() + " " + createdOn.getDayOfMonth() + "/" + createdOn.getMonthOfYear()
+ "/" + timesheet.getCreatedOn().getYear() + " " + createdOn.getHourOfDay() + ":" + createdOn.getMinuteOfHour();
}
else if (timesheetUser != null){
return timesheetUser.getFullName()+" N°"+timesheet.getId();
}
else{
return "N°"+timesheet.getId();
}
}
public List<TimesheetLine> computeVisibleDuration(Timesheet timesheet){
List<TimesheetLine> timesheetLineList = timesheet.getTimesheetLineList();
Employee timesheetEmployee = timesheet.getUser().getEmployee();
BigDecimal employeeDailyWorkHours;
if (timesheetEmployee == null || timesheetEmployee.getDailyWorkHours() == null){
employeeDailyWorkHours = generalService.getGeneral().getDailyWorkHours();
}else{
employeeDailyWorkHours = timesheetEmployee.getDailyWorkHours();
}
for(TimesheetLine timesheetLine : timesheetLineList) {
timesheetLine.setVisibleDuration(employeeService.getUserDuration(timesheetLine.getDurationStored(), employeeDailyWorkHours, false));
}
timesheetLineList = projectTaskService._sortTimesheetLineByDate(timesheetLineList);
return timesheetLineList;
}
}