Java tutorial
/** * Copyright 2004-2013 The Kuali Foundation * * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php * * 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.kuali.kpme.tklm.time.flsa; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; import org.joda.time.LocalDateTime; import org.kuali.kpme.core.util.HrConstants; import org.kuali.kpme.core.util.TKUtils; import org.kuali.kpme.tklm.leave.block.LeaveBlock; import org.kuali.kpme.tklm.time.timeblock.TimeBlock; import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetail; public class FlsaDay { private Map<String, List<TimeBlock>> earnCodeToTimeBlocks = new HashMap<String, List<TimeBlock>>(); private List<TimeBlock> appliedTimeBlocks = new ArrayList<TimeBlock>(); private Map<String, List<LeaveBlock>> earnCodeToLeaveBlocks = new HashMap<String, List<LeaveBlock>>(); private List<LeaveBlock> appliedLeaveBlocks = new ArrayList<LeaveBlock>(); Interval flsaDateInterval; LocalDateTime flsaDate; DateTimeZone timeZone; /** * * @param flsaDate A LocalDateTime because we want to be conscious of the * relative nature of this flsa/window * @param timeBlocks * @param timeZone The timezone we are constructing, relative. */ public FlsaDay(LocalDateTime flsaDate, List<TimeBlock> timeBlocks, List<LeaveBlock> leaveBlocks, DateTimeZone timeZone) { this.flsaDate = flsaDate; this.timeZone = timeZone; flsaDateInterval = new Interval(flsaDate.toDateTime(timeZone), flsaDate.toDateTime(timeZone).plusHours(24)); this.setTimeBlocks(timeBlocks); this.setLeaveBlocks(leaveBlocks); } /** * Handles the breaking apart of existing time blocks around FLSA boundaries. * * This method will compare the FLSA interval against the timeblock interval * to determine how many hours overlap. It will then examine the time hour * details * * @param timeBlocks a sorted list of time blocks. */ public void setTimeBlocks(List<TimeBlock> timeBlocks) { for (TimeBlock block : timeBlocks) { applyBlock(block, this.appliedTimeBlocks); } } /** * Handles the breaking apart of existing leave blocks around FLSA boundaries. * * This method will compare the FLSA interval against the leaveblock interval * to determine how many hours overlap. It will then examine the leave hour * details * * @param leaveBlocks a sorted list of time blocks. */ public void setLeaveBlocks(List<LeaveBlock> leaveBlocks) { for (LeaveBlock block : leaveBlocks) { applyBlock(block, this.appliedLeaveBlocks); } } /** * This method will compute the mappings present for this object: * * earnCodeToTimeBlocks * earnCodeToHours * */ public void remapTimeHourDetails() { List<TimeBlock> reApplied = new ArrayList<TimeBlock>(appliedTimeBlocks.size()); earnCodeToTimeBlocks.clear(); for (TimeBlock block : appliedTimeBlocks) { applyBlock(block, reApplied); } } /** * This method will compute the mappings present for this object: * * earnCodeToTimeBlocks * earnCodeToHours * */ public void remapLeaveHourDetails() { List<LeaveBlock> reApplied = new ArrayList<LeaveBlock>(appliedLeaveBlocks.size()); earnCodeToLeaveBlocks.clear(); for (LeaveBlock block : appliedLeaveBlocks) { applyBlock(block, reApplied); } } /** * This method determines if the provided TimeBlock is applicable to this * FLSA day, and if so will add it to the applyList. It could be the case * that a TimeBlock is on the boundary of the FLSA day so that only a * partial amount of the hours for that TimeBlock will count towards this * day. * * |---------+------------------+---------| * | Day 1 | Day 1/2 Boundary | Day 2 | * |---------+------------------+---------| * | Block 1 | | Block 2 | * |---------+------------------+---------| * * The not so obvious ascii diagram above is intended to illustrate the case * where on day one you have 1 fully overlapping time block (block1) and one * partially overlapping time block (block2). Block 2 belongs to both FLSA * Day 1 and Day 2. * * @param block A time block that we want to check and apply to this day. * @param applyList A list of time blocks we want to add applicable time blocks to. * * @return True if the block is applicable, false otherwise. The return * value can be used as a quick exit for the setTimeBlocks() method. * * TODO : Bucketing of partial FLSA days is still suspect, however real life examples of this are likely non-existent to rare. * * Danger may still lurk in day-boundary overlapping time blocks that have multiple Time Hour Detail entries. */ private boolean applyBlock(TimeBlock block, List<TimeBlock> applyList) { DateTime beginDateTime = block.getBeginDateTime().withZone(timeZone); DateTime endDateTime = block.getEndDateTime().withZone(timeZone); if (beginDateTime.isAfter(flsaDateInterval.getEnd())) return false; Interval timeBlockInterval = null; //Requested to have zero hour time blocks be able to be added to the GUI boolean zeroHoursTimeBlock = false; if (endDateTime.getMillis() > beginDateTime.getMillis()) { timeBlockInterval = new Interval(beginDateTime, endDateTime); } if (flsaDateInterval.contains(beginDateTime)) { zeroHoursTimeBlock = true; } Interval overlapInterval = flsaDateInterval.overlap(timeBlockInterval); long overlap = (overlapInterval == null) ? 0L : overlapInterval.toDurationMillis(); BigDecimal overlapHours = TKUtils.convertMillisToHours(overlap); if ((overlapHours.compareTo(BigDecimal.ZERO) == 0) && flsaDateInterval.contains(beginDateTime) && flsaDateInterval.contains(endDateTime)) { if (block.getHours().compareTo(BigDecimal.ZERO) > 0) { overlapHours = block.getHours(); } } // Local lookup for this time-block to ensure we are not over applicable hours. // You will notice below we are earn codes globally per day, and also locally per timeblock. // The local per-time block mapping is used only to verify that we have not gone over allocated overlap time // for the individual time block. Map<String, BigDecimal> localEarnCodeToHours = new HashMap<String, BigDecimal>(); if (zeroHoursTimeBlock || overlapHours.compareTo(BigDecimal.ZERO) > 0 || (flsaDateInterval.contains(beginDateTime) && StringUtils.equals(block.getEarnCodeType(), HrConstants.EARN_CODE_AMOUNT))) { List<TimeHourDetail> details = block.getTimeHourDetails(); for (TimeHourDetail thd : details) { BigDecimal localEcHours = localEarnCodeToHours.containsKey(thd.getEarnCode()) ? localEarnCodeToHours.get(thd.getEarnCode()) : BigDecimal.ZERO; //NOTE adding this in the last few hours before release.. remove if side effects are noticed if (overlapHours.compareTo(localEcHours) >= 0 || thd.getAmount().compareTo(BigDecimal.ZERO) == 0) { localEcHours = localEcHours.add(thd.getHours(), HrConstants.MATH_CONTEXT); localEarnCodeToHours.put(thd.getEarnCode(), localEcHours); } } List<TimeBlock> blocks = earnCodeToTimeBlocks.get(block.getEarnCode()); if (blocks == null) { blocks = new ArrayList<TimeBlock>(); earnCodeToTimeBlocks.put(block.getEarnCode(), blocks); } blocks.add(block); applyList.add(block); } return true; } /** * This method determines if the provided LeaveBlock is applicable to this * FLSA day, and if so will add it to the applyList. It could be the case * that a LeaveBlock is on the boundary of the FLSA day so that only a * partial amount of the hours for that LeaveBlock will count towards this * day. * * |---------+------------------+---------| * | Day 1 | Day 1/2 Boundary | Day 2 | * |---------+------------------+---------| * | Block 1 | | Block 2 | * |---------+------------------+---------| * * The not so obvious ascii diagram above is intended to illustrate the case * where on day one you have 1 fully overlapping leave block (block1) and one * partially overlapping leave block (block2). Block 2 belongs to both FLSA * Day 1 and Day 2. * * @param block A leave block that we want to check and apply to this day. * @param applyList A list of leave blocks we want to add applicable leave blocks to. * * @return True if the block is applicable, false otherwise. The return * value can be used as a quick exit for the setLeaveBlocks() method. * * TODO : Bucketing of partial FLSA days is still suspect, however real life examples of this are likely non-existent to rare. */ private boolean applyBlock(LeaveBlock block, List<LeaveBlock> applyList) { DateTime beginDateTime = new DateTime(block.getLeaveDate(), this.timeZone); DateTime endDateTime = new DateTime(block.getLeaveDate(), this.timeZone); if (beginDateTime.isAfter(flsaDateInterval.getEnd())) { return false; } Interval leaveBlockInterval = null; if (endDateTime.getMillis() > beginDateTime.getMillis()) { leaveBlockInterval = new Interval(beginDateTime, endDateTime); } Interval overlapInterval = flsaDateInterval.overlap(leaveBlockInterval); long overlap = (overlapInterval == null) ? 0L : overlapInterval.toDurationMillis(); BigDecimal overlapHours = TKUtils.convertMillisToHours(overlap); if ((overlapHours.compareTo(BigDecimal.ZERO) == 0) && flsaDateInterval.contains(beginDateTime) && flsaDateInterval.contains(endDateTime)) { if (block.getLeaveAmount().negate().compareTo(BigDecimal.ZERO) > 0) { overlapHours = block.getLeaveAmount().negate(); } } if (overlapHours.compareTo(BigDecimal.ZERO) > 0) { List<LeaveBlock> blocks = earnCodeToLeaveBlocks.get(block.getEarnCode()); if (blocks == null) { blocks = new ArrayList<LeaveBlock>(); earnCodeToLeaveBlocks.put(block.getEarnCode(), blocks); } blocks.add(block); applyList.add(block); } return true; } public Map<String, List<TimeBlock>> getEarnCodeToTimeBlocks() { return earnCodeToTimeBlocks; } public void setEarnCodeToTimeBlocks(Map<String, List<TimeBlock>> earnCodeToTimeBlocks) { this.earnCodeToTimeBlocks = earnCodeToTimeBlocks; } public List<TimeBlock> getAppliedTimeBlocks() { return appliedTimeBlocks; } public void setAppliedTimeBlocks(List<TimeBlock> appliedTimeBlocks) { this.appliedTimeBlocks = appliedTimeBlocks; } public Map<String, List<LeaveBlock>> getEarnCodeToLeaveBlocks() { return earnCodeToLeaveBlocks; } public void setEarnCodeToLeaveBlocks(Map<String, List<LeaveBlock>> earnCodeToLeaveBlocks) { this.earnCodeToLeaveBlocks = earnCodeToLeaveBlocks; } public List<LeaveBlock> getAppliedLeaveBlocks() { return appliedLeaveBlocks; } public void setAppliedLeaveBlocks(List<LeaveBlock> appliedLeaveBlocks) { this.appliedLeaveBlocks = appliedLeaveBlocks; } }