All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.optaplanner.openshift.employeerostering.server.roster.RosterRestServiceImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.optaplanner.openshift.employeerostering.server.roster;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.optaplanner.openshift.employeerostering.server.common.AbstractRestServiceImpl;
import org.optaplanner.openshift.employeerostering.server.solver.WannabeSolverManager;
import org.optaplanner.openshift.employeerostering.shared.employee.Employee;
import org.optaplanner.openshift.employeerostering.shared.employee.EmployeeAvailability;
import org.optaplanner.openshift.employeerostering.shared.employee.view.EmployeeAvailabilityView;
import org.optaplanner.openshift.employeerostering.shared.roster.Pagination;
import org.optaplanner.openshift.employeerostering.shared.roster.PublishResult;
import org.optaplanner.openshift.employeerostering.shared.roster.Roster;
import org.optaplanner.openshift.employeerostering.shared.roster.RosterRestService;
import org.optaplanner.openshift.employeerostering.shared.roster.RosterState;
import org.optaplanner.openshift.employeerostering.shared.roster.view.AvailabilityRosterView;
import org.optaplanner.openshift.employeerostering.shared.roster.view.ShiftRosterView;
import org.optaplanner.openshift.employeerostering.shared.rotation.ShiftTemplate;
import org.optaplanner.openshift.employeerostering.shared.shift.Shift;
import org.optaplanner.openshift.employeerostering.shared.shift.view.ShiftView;
import org.optaplanner.openshift.employeerostering.shared.skill.Skill;
import org.optaplanner.openshift.employeerostering.shared.spot.Spot;
import org.optaplanner.openshift.employeerostering.shared.tenant.TenantConfiguration;
import org.optaplanner.openshift.employeerostering.shared.tenant.TenantRestService;

import static java.util.stream.Collectors.groupingBy;

public class RosterRestServiceImpl extends AbstractRestServiceImpl implements RosterRestService {

    @PersistenceContext
    private EntityManager entityManager;

    @Inject
    private WannabeSolverManager solverManager;

    @Inject
    private TenantRestService tenantRestService;

    // ************************************************************************
    // ShiftRosterView
    // ************************************************************************

    @Override
    @Transactional
    public ShiftRosterView getCurrentShiftRosterView(Integer tenantId, Integer pageNumber, Integer numberOfItemsPerPage) {
        RosterState rosterState = getRosterState(tenantId);
        LocalDate startDate = rosterState.getFirstPublishedDate();
        LocalDate endDate = rosterState.getFirstUnplannedDate();
        return getShiftRosterView(tenantId, startDate, endDate, Pagination.of(pageNumber, numberOfItemsPerPage));
    }

    @Override
    @Transactional
    public ShiftRosterView getShiftRosterView(final Integer tenantId,
                                              final String startDateString,
                                              final String endDateString) {

        return getShiftRosterView(tenantId, LocalDate.parse(startDateString), LocalDate.parse(endDateString));
    }

    private ShiftRosterView getShiftRosterView(final Integer tenantId,
                                               final LocalDate startDate,
                                               final LocalDate endDate,
                                               final Pagination pagination) {

        final List spots = entityManager.createNamedQuery("Spot.findAll", Spot.class)
                                              .setParameter("tenantId", tenantId)
                                              .setMaxResults(pagination.getNumberOfItemsPerPage())
                                              .setFirstResult(pagination.getFirstResultIndex())
                                              .getResultList();

        return getShiftRosterView(tenantId, startDate, endDate, spots);
    }

    private ShiftRosterView getShiftRosterView(final Integer tenantId,
                                               final LocalDate startDate,
                                               final LocalDate endDate) {

        final List spots = entityManager.createNamedQuery("Spot.findAll", Spot.class)
                                              .setParameter("tenantId", tenantId)
                                              .getResultList();

        return getShiftRosterView(tenantId, startDate, endDate, spots);
    }

    @Override
    @Transactional
    public ShiftRosterView getShiftRosterViewFor(Integer tenantId, String startDateString, String endDateString, List spots) {
        LocalDate startDate = LocalDate.parse(startDateString);
        LocalDate endDate = LocalDate.parse(endDateString);
        if (null == spots) {
            throw new IllegalArgumentException("spots is null!");
        }

        return getShiftRosterView(tenantId, startDate, endDate, spots);
    }

    @Transactional
    protected ShiftRosterView getShiftRosterView(Integer tenantId, LocalDate startDate, LocalDate endDate, List spotList) {
        ShiftRosterView shiftRosterView = new ShiftRosterView(tenantId, startDate, endDate);
        shiftRosterView.setSpotList(spotList);
        List employeeList = entityManager.createNamedQuery("Employee.findAll", Employee.class)
                                                   .setParameter("tenantId", tenantId)
                                                   .getResultList();
        shiftRosterView.setEmployeeList(employeeList);

        Set spotSet = new HashSet<>(spotList);
        TenantConfiguration tenantConfig = entityManager.createNamedQuery("TenantConfiguration.find",
                                                                          TenantConfiguration.class)
                                                        .setParameter("tenantId", tenantId).getSingleResult();

        List shiftList = entityManager.createNamedQuery("Shift.filterWithSpots", Shift.class)
                                             .setParameter("tenantId", tenantId)
                                             .setParameter("spotSet", spotSet)
                                             .setParameter("startDateTime", startDate.atStartOfDay(tenantConfig.getTimeZone()).toOffsetDateTime())
                                             .setParameter("endDateTime", endDate.atStartOfDay(tenantConfig.getTimeZone()).toOffsetDateTime())
                                             .getResultList();

        Map> spotIdToShiftViewListMap = new LinkedHashMap<>(spotList.size());
        for (Shift shift : shiftList) {
            spotIdToShiftViewListMap.computeIfAbsent(shift.getSpot().getId(), k -> new ArrayList<>()).add(
                                                                                                          new ShiftView(tenantConfig.getTimeZone(),
                                                                                                                        shift));
        }
        shiftRosterView.setSpotIdToShiftViewListMap(spotIdToShiftViewListMap);

        // TODO FIXME race condition solverManager's bestSolution might differ from the one we just fetched,
        // so the score might be inaccurate.
        Roster roster = solverManager.getRoster(tenantId);
        shiftRosterView.setScore(roster == null ? null : roster.getScore());
        shiftRosterView.setRosterState(getRosterState(tenantId));

        return shiftRosterView;
    }

    // ************************************************************************
    // AvailabilityRosterView
    // ************************************************************************

    @Override
    @Transactional
    public AvailabilityRosterView getCurrentAvailabilityRosterView(Integer tenantId,
                                                                   Integer pageNumber,
                                                                   Integer numberOfItemsPerPage) {
        RosterState rosterState = getRosterState(tenantId);
        LocalDate startDate = rosterState.getLastHistoricDate();
        LocalDate endDate = rosterState.getFirstUnplannedDate();
        return getAvailabilityRosterView(tenantId, startDate, endDate, Pagination.of(pageNumber, numberOfItemsPerPage));
    }

    @Override
    @Transactional
    public AvailabilityRosterView getAvailabilityRosterView(Integer tenantId, String startDateString, String endDateString) {
        LocalDate startDate = LocalDate.parse(startDateString);
        LocalDate endDate = LocalDate.parse(endDateString);
        return getAvailabilityRosterView(tenantId, startDate, endDate, entityManager.createNamedQuery("Employee.findAll",
                                                                                                      Employee.class)
                                                                                    .setParameter("tenantId", tenantId)
                                                                                    .getResultList());
    }

    @Override
    @Transactional
    public AvailabilityRosterView getAvailabilityRosterViewFor(Integer tenantId,
                                                               String startDateString,
                                                               String endDateString,
                                                               List employeeList) {
        LocalDate startDate = LocalDate.parse(startDateString);
        LocalDate endDate = LocalDate.parse(endDateString);
        if (employeeList == null) {
            throw new IllegalArgumentException("The employeeList (" + employeeList + ") must not be null.");
        }
        return getAvailabilityRosterView(tenantId, startDate, endDate, employeeList);
    }

    private AvailabilityRosterView getAvailabilityRosterView(final Integer tenantId,
                                                             final LocalDate startDate,
                                                             final LocalDate endDate,
                                                             final Pagination pagination) {

        final List employeeList = entityManager.createNamedQuery("Employee.findAll", Employee.class)
                                                         .setParameter("tenantId", tenantId)
                                                         .setMaxResults(pagination.getNumberOfItemsPerPage())
                                                         .setFirstResult(pagination.getFirstResultIndex())
                                                         .getResultList();

        return getAvailabilityRosterView(tenantId, startDate, endDate, employeeList);
    }

    @Transactional
    protected AvailabilityRosterView getAvailabilityRosterView(Integer tenantId,
                                                               LocalDate startDate,
                                                               LocalDate endDate,
                                                               List employeeList) {
        AvailabilityRosterView availabilityRosterView = new AvailabilityRosterView(tenantId, startDate, endDate);
        List spotList = entityManager.createNamedQuery("Spot.findAll", Spot.class)
                                           .setParameter("tenantId", tenantId)
                                           .getResultList();
        availabilityRosterView.setSpotList(spotList);

        availabilityRosterView.setEmployeeList(employeeList);

        Map> employeeIdToShiftViewListMap = new LinkedHashMap<>(employeeList.size());
        List unassignedShiftViewList = new ArrayList<>();
        Set employeeSet = new HashSet<>(employeeList);
        TenantConfiguration tenantConfig = entityManager.createNamedQuery("TenantConfiguration.find",
                                                                          TenantConfiguration.class)
                                                        .setParameter("tenantId", tenantId).getSingleResult();

        List shiftList = entityManager.createNamedQuery("Shift.filterWithEmployees", Shift.class)
                                             .setParameter("tenantId", tenantId)
                                             .setParameter("startDateTime", startDate.atStartOfDay(tenantConfig.getTimeZone()).toOffsetDateTime())
                                             .setParameter("endDateTime", endDate.atStartOfDay(tenantConfig.getTimeZone()).toOffsetDateTime())
                                             .setParameter("employeeSet", employeeSet)
                                             .getResultList();

        for (Shift shift : shiftList) {
            if (shift.getEmployee() != null) {
                employeeIdToShiftViewListMap.computeIfAbsent(shift.getEmployee().getId(),
                                                             k -> new ArrayList<>())
                                            .add(new ShiftView(tenantConfig.getTimeZone(), shift));
            } else {
                unassignedShiftViewList.add(new ShiftView(tenantConfig.getTimeZone(), shift));
            }
        }
        availabilityRosterView.setEmployeeIdToShiftViewListMap(employeeIdToShiftViewListMap);
        availabilityRosterView.setUnassignedShiftViewList(unassignedShiftViewList);
        Map> employeeIdToAvailabilityViewListMap = new LinkedHashMap<>(
                                                                                                            employeeList.size());
        List employeeAvailabilityList = entityManager.createNamedQuery(
                                                                                             "EmployeeAvailability.filterWithEmployee", EmployeeAvailability.class)
                                                                           .setParameter("tenantId", tenantId)
                                                                           .setParameter("startDateTime", startDate.atStartOfDay(tenantConfig.getTimeZone()).toOffsetDateTime())
                                                                           .setParameter("endDateTime", endDate.atStartOfDay(tenantConfig.getTimeZone()).toOffsetDateTime())
                                                                           .setParameter("employeeSet", employeeSet)
                                                                           .getResultList();
        for (EmployeeAvailability employeeAvailability : employeeAvailabilityList) {
            employeeIdToAvailabilityViewListMap.computeIfAbsent(employeeAvailability.getEmployee().getId(),
                                                                k -> new ArrayList<>())
                                               .add(new EmployeeAvailabilityView(tenantConfig.getTimeZone(), employeeAvailability));
        }
        availabilityRosterView.setEmployeeIdToAvailabilityViewListMap(employeeIdToAvailabilityViewListMap);

        // TODO FIXME race condition solverManager's bestSolution might differ from the one we just fetched,
        // so the score might be inaccurate.
        Roster roster = solverManager.getRoster(tenantId);
        availabilityRosterView.setScore(roster == null ? null : roster.getScore());
        availabilityRosterView.setRosterState(getRosterState(tenantId));
        return availabilityRosterView;
    }

    // ************************************************************************
    // Other
    // ************************************************************************

    @Override
    public void solveRoster(Integer tenantId) {
        solverManager.solve(tenantId);
    }

    @Override
    public void terminateRosterEarly(Integer tenantId) {
        solverManager.terminate(tenantId);
    }

    @Override
    @Transactional
    public Roster buildRoster(Integer tenantId) {
        List skillList = entityManager.createNamedQuery("Skill.findAll", Skill.class)
                                             .setParameter("tenantId", tenantId)
                                             .getResultList();
        List spotList = entityManager.createNamedQuery("Spot.findAll", Spot.class)
                                           .setParameter("tenantId", tenantId)
                                           .getResultList();
        List employeeList = entityManager.createNamedQuery("Employee.findAll", Employee.class)
                                                   .setParameter("tenantId", tenantId)
                                                   .getResultList();
        List employeeAvailabilityList = entityManager.createNamedQuery(
                                                                                             "EmployeeAvailability.findAll", EmployeeAvailability.class)
                                                                           .setParameter("tenantId", tenantId)
                                                                           .getResultList();
        List shiftList = entityManager.createNamedQuery("Shift.findAll", Shift.class)
                                             .setParameter("tenantId", tenantId)
                                             .getResultList();

        // TODO fill in the score too - do we inject a ScoreDirectorFactory?
        return new Roster((long) tenantId, tenantId,
                          skillList, spotList, employeeList, employeeAvailabilityList,
                          tenantRestService.getTenantConfiguration(tenantId), getRosterState(tenantId), shiftList);
    }

    @Override
    @Transactional
    public void updateShiftsOfRoster(Roster newRoster) {
        Integer tenantId = newRoster.getTenantId();
        // TODO HACK avoids optimistic locking exception while solve(), but it circumvents optimistic locking completely
        Map employeeIdMap = entityManager.createNamedQuery("Employee.findAll", Employee.class)
                                                         .setParameter("tenantId", tenantId)
                                                         .getResultList().stream().collect(Collectors.toMap(Employee::getId, Function.identity()));
        Map shiftIdMap = entityManager.createNamedQuery("Shift.findAll", Shift.class)
                                                   .setParameter("tenantId", tenantId)
                                                   .getResultList().stream().collect(Collectors.toMap(Shift::getId, Function.identity()));

        for (Shift shift : newRoster.getShiftList()) {
            Shift attachedShift = shiftIdMap.get(shift.getId());
            if (attachedShift == null) {
                continue;
            }
            attachedShift.setEmployee((shift.getEmployee() == null)
                    ? null : employeeIdMap.get(shift.getEmployee().getId()));
        }
    }

    @Override
    @Transactional
    public PublishResult publishAndProvision(Integer tenantId) {
        TenantConfiguration tenantConfiguration = tenantRestService.getTenantConfiguration(tenantId);
        RosterState rosterState = getRosterState(tenantId);
        LocalDate publishFrom = rosterState.getFirstDraftDate();
        LocalDate publishTo = publishFrom.plusDays(rosterState.getPublishLength());
        LocalDate firstUnplannedDate = rosterState.getFirstUnplannedDate();
        // Publish
        rosterState.setFirstDraftDate(publishTo);
        // Provision
        List shiftTemplateList = entityManager.createNamedQuery("ShiftTemplate.findAll", ShiftTemplate.class)
                                                             .setParameter("tenantId", tenantId)
                                                             .getResultList();
        Map> dayOffsetToShiftTemplateListMap = shiftTemplateList.stream()
                                                                                             .collect(groupingBy(ShiftTemplate::getStartDayOffset));
        int dayOffset = rosterState.getUnplannedRotationOffset();
        LocalDate shiftDate = firstUnplannedDate;
        for (int i = 0; i < rosterState.getPublishLength(); i++) {
            List dayShiftTemplateList = dayOffsetToShiftTemplateListMap.get(dayOffset);
            for (ShiftTemplate shiftTemplate : dayShiftTemplateList) {
                Shift shift = shiftTemplate.createShiftOnDate(shiftDate, rosterState.getRotationLength(), tenantConfiguration.getTimeZone(), false);
                entityManager.persist(shift);
            }
            shiftDate = shiftDate.plusDays(1);
            dayOffset = (dayOffset + 1) % rosterState.getRotationLength();
        }
        rosterState.setUnplannedRotationOffset(dayOffset);
        return new PublishResult(publishFrom, publishTo);
    }

    @Override
    public RosterState getRosterState(Integer tenantId) {
        return entityManager.createNamedQuery("RosterState.find", RosterState.class)
                            .setParameter("tenantId", tenantId)
                            .getSingleResult();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy