controllers.core.TimesheetController.java Source code

Java tutorial

Introduction

Here is the source code for controllers.core.TimesheetController.java

Source

/*! LICENSE
 *
 * Copyright (c) 2015, The Agile Factory SA and/or its affiliates. All rights
 * reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; version 2 of the License.
 *
 * 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 General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package controllers.core;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.inject.Inject;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.avaje.ebean.Ebean;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import be.objectify.deadbolt.java.actions.Dynamic;
import be.objectify.deadbolt.java.actions.Group;
import be.objectify.deadbolt.java.actions.Restrict;
import constants.IMafConstants;
import controllers.ControllersUtils;
import dao.pmo.ActorDao;
import dao.pmo.PortfolioEntryDao;
import dao.pmo.PortfolioEntryPlanningPackageDao;
import dao.timesheet.TimesheetDao;
import framework.services.account.IPreferenceManagerPlugin;
import framework.services.configuration.II18nMessagesPlugin;
import framework.services.notification.INotificationManagerPlugin;
import framework.services.session.IUserSessionManagerPlugin;
import framework.utils.Msg;
import framework.utils.Utilities;
import models.framework_models.account.NotificationCategory;
import models.framework_models.account.NotificationCategory.Code;
import models.pmo.Actor;
import models.pmo.PortfolioEntry;
import models.pmo.PortfolioEntryPlanningPackage;
import models.timesheet.TimesheetActivity;
import models.timesheet.TimesheetActivityType;
import models.timesheet.TimesheetEntry;
import models.timesheet.TimesheetLog;
import models.timesheet.TimesheetReport;
import play.Configuration;
import play.Logger;
import play.data.Form;
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.With;
import security.CheckTimesheetReportExists;
import utils.form.TimesheetReportApprovalFormData;

/**
 * The controller which allows to manage the timesheeting.
 * 
 * @author Johann Kohler
 */
public class TimesheetController extends Controller {
    @Inject
    private IUserSessionManagerPlugin userSessionManagerPlugin;
    @Inject
    private II18nMessagesPlugin i18nMessagesPlugin;
    @Inject
    private Configuration configuration;
    @Inject
    private INotificationManagerPlugin notificationManagerService;
    @Inject
    private IPreferenceManagerPlugin preferenceManagerPlugin;

    private static Logger.ALogger log = Logger.of(TimesheetController.class);

    private static Form<TimesheetReportApprovalFormData> timesheetReportApprovalFormTemplate = Form
            .form(TimesheetReportApprovalFormData.class);

    /**
     * Page to fill a timesheet by week.
     * 
     * @param stringDate
     *            a date in the format yyyy-MM-dd: the system gets the weekly
     *            report including this date, if empty it uses the current date.
     */
    @Restrict({ @Group(IMafConstants.TIMESHEET_ENTRY_PERMISSION) })
    public Result weeklyFill(String stringDate) {

        // get the current actor
        Actor actor = getCurrentActor();
        if (actor == null) {
            return redirect(controllers.dashboard.routes.DashboardController.index(0, false));
        }

        // get the report
        TimesheetReport report = getTimesheetReport(stringDate, actor);

        // get the available initiatives for the actor
        // the actor is either manager, direct stakeholder, portfolio manager,
        // portfolio stakeholder, delivery unit manager or delivery unit member
        List<PortfolioEntry> portfolioEntries = PortfolioEntryDao.getPEAsListByMember(actor.id);

        // get the activity types with at least one activity
        List<TimesheetActivityType> activityTypes = new ArrayList<TimesheetActivityType>();
        for (TimesheetActivityType activityType : TimesheetDao.getTimesheetActivityTypeAsList()) {
            if (activityType.timesheetActivities != null && activityType.timesheetActivities.size() > 0) {
                activityTypes.add(activityType);
            }

        }

        return ok(views.html.core.timesheet.timesheet_weekly_fill.render(report, portfolioEntries, activityTypes));
    }

    /**
     * Save the weekly timesheet.
     */
    @Restrict({ @Group(IMafConstants.TIMESHEET_ENTRY_PERMISSION) })
    public Result weeklySave() {

        String[] dataString = request().body().asFormUrlEncoded().get("data");

        try {
            JSONObject dataJson = new JSONObject(dataString[0]);

            // get the report
            Long reportId = dataJson.getLong("reportId");
            TimesheetReport report = TimesheetDao.getTimesheetReportById(reportId);

            // create the date format
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

            // get the actor
            Actor actor = getCurrentActor();

            // check the report belongs to the sign-in user
            if (!report.actor.id.equals(actor.id)) {
                return forbidden(views.html.error.access_forbidden.render(""));
            }

            // check the report is editable
            if (!report.isEditable()) {
                Utilities.sendErrorFlashMessage(Msg.get("core.timesheet.fill.save.error.non_editable"));
                return redirect(
                        controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)));
            }

            Ebean.beginTransaction();

            try {

                // get the entries
                JSONArray entriesJson = dataJson.getJSONArray("entries");

                for (int i = 0; i < entriesJson.length(); i++) {

                    JSONObject entryJson;
                    try {
                        entryJson = entriesJson.getJSONObject(i);
                    } catch (JSONException e) {
                        entryJson = null;
                    }

                    if (entryJson != null) {

                        // get the attributes
                        boolean inDB = entryJson.getBoolean("inDB");

                        Boolean toRemove;
                        try {
                            toRemove = entryJson.getBoolean("toRemove");
                        } catch (JSONException e) {
                            toRemove = null;
                        }

                        Long entryId;
                        try {
                            entryId = entryJson.getLong("entryId");
                        } catch (JSONException e) {
                            entryId = null;
                        }

                        Long portfolioEntryId;
                        try {
                            portfolioEntryId = entryJson.getLong("portfolioEntryId");
                        } catch (JSONException e) {
                            portfolioEntryId = null;
                        }

                        Long packageId;
                        try {
                            packageId = entryJson.getLong("packageId");
                        } catch (JSONException e) {
                            packageId = null;
                        }

                        Long activityId;
                        try {
                            activityId = entryJson.getLong("activityId");
                        } catch (JSONException e) {
                            activityId = null;
                        }

                        // get the logs
                        JSONArray logsJson = entryJson.getJSONArray("logs");

                        if (logsJson.length() == 7) {

                            TimesheetEntry entry = null;
                            if (inDB) {
                                entry = TimesheetDao.getTimesheetEntryById(entryId);
                            } else {
                                entry = new TimesheetEntry();
                                entry.timesheetReport = report;
                            }

                            if (inDB && toRemove) {
                                entry.doDelete();
                            } else {

                                entry.portfolioEntry = null;
                                entry.portfolioEntryPlanningPackage = null;
                                entry.timesheetActivity = null;

                                if (portfolioEntryId != null) { // initiative
                                    entry.portfolioEntry = PortfolioEntryDao.getPEById(portfolioEntryId);
                                    if (packageId != null) {
                                        entry.portfolioEntryPlanningPackage = PortfolioEntryPlanningPackageDao
                                                .getPEPlanningPackageById(packageId);
                                    }
                                } else { // activity
                                    entry.timesheetActivity = TimesheetDao.getTimesheetActivityById(activityId);
                                }

                                entry.save();

                                for (int j = 0; j < logsJson.length(); j++) {

                                    JSONObject logJson = logsJson.getJSONObject(j);

                                    // get the attributes
                                    double hours = logJson.getDouble("hours");

                                    Long logId;
                                    try {
                                        logId = logJson.getLong("logId");
                                    } catch (JSONException e) {
                                        logId = null;
                                    }

                                    TimesheetLog log = null;
                                    if (inDB) {
                                        log = TimesheetDao.getTimesheetLogById(logId);
                                    } else {
                                        log = new TimesheetLog();
                                        log.timesheetEntry = entry;

                                        Calendar cal = Calendar.getInstance();
                                        cal.setTime(report.startDate);
                                        cal.add(Calendar.DAY_OF_YEAR, j);
                                        log.logDate = cal.getTime();
                                    }

                                    log.hours = hours;

                                    log.save();

                                }
                            }

                        }

                    }

                }

                Ebean.commitTransaction();

            } catch (Exception e) {
                Ebean.rollbackTransaction();
                return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(),
                        getI18nMessagesPlugin());
            }

            Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.fill.save.successful"));

            return redirect(controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)));

        } catch (Exception e) {
            return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(),
                    getI18nMessagesPlugin());
        }

    }

    /**
     * Copy the entries (without "hours") of the previous report to the given
     * report (for a weekly).
     * 
     * @param reportId
     *            the report id
     */
    @Restrict({ @Group(IMafConstants.TIMESHEET_ENTRY_PERMISSION) })
    public Result weeklyCopy(Long reportId) {

        // get the report
        TimesheetReport report = TimesheetDao.getTimesheetReportById(reportId);

        // get the previous report
        TimesheetReport previousReport = TimesheetDao.getTimesheetReportByActorAndStartDate(report.actor.id,
                report.getPreviousStartDate());

        if (previousReport != null && previousReport.timesheetEntries != null
                && previousReport.timesheetEntries.size() > 0) {
            for (TimesheetEntry previousEntry : previousReport.timesheetEntries) {

                Ebean.beginTransaction();
                try {

                    TimesheetEntry entry = new TimesheetEntry();
                    entry.timesheetReport = report;
                    entry.portfolioEntry = previousEntry.portfolioEntry;
                    entry.portfolioEntryPlanningPackage = previousEntry.portfolioEntryPlanningPackage;
                    entry.timesheetActivity = previousEntry.timesheetActivity;
                    entry.save();

                    for (int i = 0; i < 7; i++) {

                        TimesheetLog log = new TimesheetLog();
                        log.hours = 0.0;
                        log.timesheetEntry = entry;

                        Calendar cal = Calendar.getInstance();
                        cal.setTime(report.startDate);
                        cal.add(Calendar.DAY_OF_YEAR, i);
                        log.logDate = cal.getTime();

                        log.save();
                    }

                    Ebean.commitTransaction();

                } catch (Exception e) {
                    Ebean.rollbackTransaction();
                    return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(),
                            getI18nMessagesPlugin());
                }
            }
            Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.fill.weekly.copy.successful"));
        } else {
            Utilities.sendInfoFlashMessage(Msg.get("core.timesheet.fill.weekly.copy.info.empty"));
        }

        // create the date format
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        return redirect(controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)));
    }

    /**
     * Submit a weekly timesheet.
     * 
     * @param reportId
     *            the report id
     */
    @Restrict({ @Group(IMafConstants.TIMESHEET_ENTRY_PERMISSION) })
    public Result weeklySubmit(Long reportId) {

        // get the report
        TimesheetReport report = TimesheetDao.getTimesheetReportById(reportId);

        // create the date format
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

        // get the actor
        Actor actor = getCurrentActor();

        // check the report belongs to the sign-in user
        if (!report.actor.id.equals(actor.id)) {
            return forbidden(views.html.error.access_forbidden.render(""));
        }

        // check the report is editable
        if (!report.isEditable()) {
            Utilities.sendErrorFlashMessage(Msg.get("core.timesheet.fill.save.error.non_editable"));
            return redirect(controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)));
        }

        report.orgUnit = actor.orgUnit;

        if (TimesheetDao.getTimesheetReportMustApprove(this.getPreferenceManagerPlugin())) {
            report.status = TimesheetReport.Status.SUBMITTED;

            if (actor.manager != null) {
                ActorDao.sendNotification(this.getNotificationManagerService(), this.getI18nMessagesPlugin(),
                        actor.manager, NotificationCategory.getByCode(Code.TIMESHEET),
                        controllers.core.routes.ActorController
                                .viewWeeklyTimesheet(report.actor.id, sdf.format(report.startDate)).url(),
                        "core.timesheet.fill.submit.notification.title.with_approval",
                        "core.timesheet.fill.submit.notification.message.with_approval", actor.getName());
            }

            Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.fill.submit.successful.with_approval"));
        } else {
            report.status = TimesheetReport.Status.APPROVED;
            Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.fill.submit.successful.without_approval"));
        }

        report.save();

        return redirect(controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)));
    }

    /**
     * Get the packages of a portfolio entry.
     */
    @Restrict({ @Group(IMafConstants.TIMESHEET_ENTRY_PERMISSION) })
    public Result getPackages() {

        JsonNode json = request().body().asJson();
        Long portfolioEntryId = json.findPath("portfolioEntryId").asLong();

        List<PortfolioEntryPlanningPackage> packages = PortfolioEntryPlanningPackageDao
                .getPEPlanningPackageAsListByPE(portfolioEntryId);
        List<OptionData> options = new ArrayList<OptionData>();

        for (PortfolioEntryPlanningPackage p : packages) {
            options.add(new OptionData(p.id, p.getName()));
        }

        ObjectMapper mapper = new ObjectMapper();

        return ok((JsonNode) mapper.valueToTree(options));
    }

    /**
     * Get the activities of type.
     */
    @Restrict({ @Group(IMafConstants.TIMESHEET_ENTRY_PERMISSION) })
    public Result getActivities() {

        JsonNode json = request().body().asJson();
        Long activityTypeId = json.findPath("activityTypeId").asLong();

        List<TimesheetActivity> activities = TimesheetDao.getTimesheetActivityAsListByActivityType(activityTypeId);
        List<OptionData> options = new ArrayList<OptionData>();

        for (TimesheetActivity activity : activities) {
            options.add(new OptionData(activity.id, activity.getName()));
        }

        ObjectMapper mapper = new ObjectMapper();

        return ok((JsonNode) mapper.valueToTree(options));
    }

    /**
     * Process (accept or reject) a timesheet.
     */
    @With(CheckTimesheetReportExists.class)
    @Dynamic(IMafConstants.TIMESHEET_APPROVAL_DYNAMIC_PERMISSION)
    public Result processTimesheet() {

        // bind the form
        Form<TimesheetReportApprovalFormData> boundForm = timesheetReportApprovalFormTemplate.bindFromRequest();
        TimesheetReportApprovalFormData formData = boundForm.get();

        return processTimesheet(formData.id, formData.action, formData.comments);
    }

    /**
     * Approve a timesheet.
     * 
     * @param id
     *            the timesheet report id
     */
    @With(CheckTimesheetReportExists.class)
    @Dynamic(IMafConstants.TIMESHEET_APPROVAL_DYNAMIC_PERMISSION)
    public Result approveTimesheet(Long id) {
        return processTimesheet(id, "APPROVE", null);
    }

    /**
     * Process (accept or reject) a timesheet.
     * 
     * @param reportId
     *            the timesheet report id
     * @param action
     *            the action (APPROVE, REJECT)
     * @param comments
     *            the possible comments
     */
    private Result processTimesheet(Long reportId, String action, String comments) {

        // get the report
        TimesheetReport report = TimesheetDao.getTimesheetReportById(reportId);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String startDate = Utilities.getDateFormat(null).format(report.startDate);
        String endDate = Utilities.getDateFormat(null).format(report.getEndDate());

        // check the report could be processed
        if (!report.isProcessable()) {
            return forbidden(views.html.error.access_forbidden.render(""));
        }

        if (action.equals("APPROVE")) {

            report.status = TimesheetReport.Status.APPROVED;
            report.save();

            if (comments != null && !comments.equals("")) {
                // with comments
                ActorDao.sendNotification(this.getNotificationManagerService(), this.getI18nMessagesPlugin(),
                        report.actor, NotificationCategory.getByCode(Code.TIMESHEET),
                        controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)).url(),
                        "core.timesheet.approve.notification.title",
                        "core.timesheet.approve.notification.message.with_comments", startDate, endDate, comments);
            } else {
                // without comments
                ActorDao.sendNotification(this.getNotificationManagerService(), this.getI18nMessagesPlugin(),
                        report.actor, NotificationCategory.getByCode(Code.TIMESHEET),
                        controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)).url(),
                        "core.timesheet.approve.notification.title",
                        "core.timesheet.approve.notification.message.without_comments", startDate, endDate);
            }

            Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.approve.successful"));
        }

        if (action.equals("REJECT")) {

            report.status = TimesheetReport.Status.REJECTED;
            report.save();

            if (comments != null && !comments.equals("")) {
                // with comments
                ActorDao.sendNotification(this.getNotificationManagerService(), this.getI18nMessagesPlugin(),
                        report.actor, NotificationCategory.getByCode(Code.TIMESHEET),
                        controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)).url(),
                        "core.timesheet.reject.notification.title",
                        "core.timesheet.reject.notification.message.with_comments", startDate, endDate, comments);
            } else {
                // without comments
                ActorDao.sendNotification(this.getNotificationManagerService(), this.getI18nMessagesPlugin(),
                        report.actor, NotificationCategory.getByCode(Code.TIMESHEET),
                        controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)).url(),
                        "core.timesheet.reject.notification.title",
                        "core.timesheet.reject.notification.message.without_comments", startDate, endDate);
            }

            Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.reject.successful"));
        }

        return redirect(request().getHeader("referer"));
    }

    /**
     * Send a reminder for a timesheet.
     * 
     * @param id
     *            the timesheet report id
     */
    @With(CheckTimesheetReportExists.class)
    @Dynamic(IMafConstants.TIMESHEET_APPROVAL_DYNAMIC_PERMISSION)
    public Result sendReminderTimesheet(Long id) {

        // get the report
        TimesheetReport report = TimesheetDao.getTimesheetReportById(id);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String startDate = Utilities.getDateFormat(null).format(report.startDate);
        String endDate = Utilities.getDateFormat(null).format(report.getEndDate());

        // send the notification
        ActorDao.sendNotification(this.getNotificationManagerService(), this.getI18nMessagesPlugin(), report.actor,
                NotificationCategory.getByCode(Code.TIMESHEET),
                controllers.core.routes.TimesheetController.weeklyFill(sdf.format(report.startDate)).url(),
                "core.timesheet.send_reminder.notification.title",
                "core.timesheet.send_reminder.notification.message", startDate, endDate);

        Utilities.sendSuccessFlashMessage(Msg.get("core.timesheet.send_reminder.successful"));

        return redirect(request().getHeader("referer"));
    }

    /**
     * Get the timesheet report of an actor for a given string date.
     * 
     * if the string date is empty: the start date corresponds to the first day
     * (monday) of the current week<br/>
     * else: the start date corresponds to the first day (monday) of the week
     * including the given date
     * 
     * Note: if the report doesn't exist, the system creates it.
     * 
     * @param stringDate
     *            a date in the format yyyy-MM-dd: the system gets the weekly
     *            report including this date, if empty it uses the current date.
     * @param actor
     *            the actor of the timesheet
     */
    public static TimesheetReport getTimesheetReport(String stringDate, Actor actor) {

        // get the date: either given as a parameter or the current
        Date date = null;
        if (!stringDate.equals("")) {
            try {
                DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
                date = formatter.parse(stringDate);
            } catch (ParseException e) {
                log.error(e.getMessage());
                return null;
            }
        } else {
            date = new Date();
        }

        // get the first day of the week including the date
        // we consider the first day as Monday
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.clear(Calendar.MINUTE);
        cal.clear(Calendar.SECOND);
        cal.clear(Calendar.MILLISECOND);
        cal.setFirstDayOfWeek(Calendar.MONDAY);
        cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
        Date startDate = cal.getTime();

        TimesheetReport report = TimesheetDao.getTimesheetReportByActorAndStartDate(actor.id, startDate);
        if (report == null) {
            report = new TimesheetReport();
            report.actor = actor;
            report.orgUnit = actor.orgUnit;
            report.type = TimesheetReport.Type.WEEKLY;
            report.startDate = startDate;
            report.status = TimesheetReport.Status.OPEN;
            report.save();
        }

        return report;

    }

    /**
     * Get the current actor.
     */
    private Actor getCurrentActor() {

        try {

            String uid = getUserSessionManagerPlugin().getUserSessionId(ctx());
            Actor a = ActorDao.getActorByUid(uid);
            if (a != null) {
                return a;
            } else {
                log.warn("impossible to find the actor of the sign-in user");
            }

        } catch (Exception e) {
            log.error("error when getting the actor of the current user", e);
        }

        return null;
    }

    /**
     * An option data is used in a select list.
     * 
     * @author Johann Kohler
     * 
     */
    public static class OptionData {

        public Long value;
        public String text;

        /**
         * Default constructor.
         * 
         * @param value
         *            the value of the option, correspond to an id
         * @param text
         *            the text of the option
         */
        public OptionData(Long value, String text) {
            this.value = value;
            this.text = text;
        }
    }

    /**
     * Get the user session manager service.
     */
    private IUserSessionManagerPlugin getUserSessionManagerPlugin() {
        return userSessionManagerPlugin;
    }

    /**
     * Get the i18n messages service.
     */
    private II18nMessagesPlugin getI18nMessagesPlugin() {
        return i18nMessagesPlugin;
    }

    /**
     * Get the Play configuration service.
     */
    private Configuration getConfiguration() {
        return configuration;
    }

    /**
     * Get the notification manager service.
     */
    private INotificationManagerPlugin getNotificationManagerService() {
        return this.notificationManagerService;
    }

    /**
     * Get the preference manager service.
     */
    private IPreferenceManagerPlugin getPreferenceManagerPlugin() {
        return this.preferenceManagerPlugin;
    }
}