/** * 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; } }