001    /**
002     * Copyright 2004-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.hr.time.timeblock;
017    
018    import java.math.BigDecimal;
019    import java.sql.Time;
020    import java.sql.Timestamp;
021    import java.util.ArrayList;
022    import java.util.Date;
023    import java.util.List;
024    
025    import javax.persistence.Transient;
026    
027    import org.apache.commons.lang.builder.EqualsBuilder;
028    import org.apache.commons.lang.builder.HashCodeBuilder;
029    import org.joda.time.DateTime;
030    import org.kuali.hr.time.assignment.Assignment;
031    import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
032    import org.kuali.hr.time.clocklog.ClockLog;
033    import org.kuali.hr.time.service.base.TkServiceLocator;
034    import org.kuali.hr.time.util.TkConstants;
035    import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
036    import org.kuali.rice.kim.api.identity.Person;
037    import org.kuali.rice.krad.bo.PersistableBusinessObjectBase;
038    
039    public class TimeBlock extends PersistableBusinessObjectBase implements Comparable {
040    
041        /**
042         *
043         */
044        private static final long serialVersionUID = -4164042707879641855L;
045    
046        private String tkTimeBlockId;
047        private String documentId;
048        private Long jobNumber;
049        private Long workArea;
050        private Long task;
051        private String earnCode;
052        private String earnCodeType;
053        private Timestamp beginTimestamp;
054        private Timestamp endTimestamp;
055    
056        @Transient
057        private java.sql.Date beginDate;
058        @Transient
059        private java.sql.Date endDate;
060        @Transient
061        private Time beginTime;
062        @Transient
063        private Time endTime;
064    
065        private Boolean clockLogCreated;
066        private BigDecimal hours = TkConstants.BIG_DECIMAL_SCALED_ZERO;
067        private BigDecimal amount = TkConstants.BIG_DECIMAL_SCALED_ZERO;
068        private String principalId;
069        private String userPrincipalId;
070        private Timestamp timestamp;
071        private String beginTimestampTimezone;
072        private String endTimestampTimezone;
073        private DateTime beginTimeDisplay;
074        private DateTime endTimeDisplay;
075        private String clockLogBeginId;
076        private String clockLogEndId;
077        private String assignmentKey;
078        private String overtimePref;
079        private boolean lunchDeleted;
080        
081        @Transient
082        private Boolean deleteable;
083        
084        @Transient
085        private Boolean overtimeEditable;
086        
087        @Transient
088        private Boolean regEarnCodeEditable;
089    
090    
091        // the two variables below are used to determine if a time block needs to be visually pushed forward / backward
092        @Transient
093        private Boolean pushBackward = false;
094    
095        private TimesheetDocumentHeader timesheetDocumentHeader;
096        private transient Person user;
097        
098        private transient List<TimeHourDetail> timeHourDetails = new ArrayList<TimeHourDetail>();
099        private transient List<TimeBlockHistory> timeBlockHistories = new ArrayList<TimeBlockHistory>();
100    
101        public TimeBlock() {
102        }
103    
104        public String getDocumentId() {
105            return documentId;
106        }
107    
108        public void setDocumentId(String documentId) {
109            this.documentId = documentId;
110        }
111    
112        public Long getJobNumber() {
113            return jobNumber;
114        }
115    
116        public void setJobNumber(Long jobNumber) {
117            this.jobNumber = jobNumber;
118        }
119    
120        public String getEarnCode() {
121            return earnCode;
122        }
123    
124        public void setEarnCode(String earnCode) {
125            this.earnCode = earnCode;
126        }
127    
128        public Timestamp getBeginTimestamp() {
129            return beginTimestamp;
130        }
131    
132        public void setBeginTimestamp(Timestamp beginTimestamp) {
133            this.beginTimestamp = beginTimestamp;
134        }
135    
136        public Timestamp getEndTimestamp() {
137            return endTimestamp;
138        }
139    
140        public void setEndTimestamp(Timestamp endTimestamp) {
141            this.endTimestamp = endTimestamp;
142        }
143    
144        public java.sql.Date getBeginDate() {
145            if (beginDate == null && this.getBeginTimestamp() != null) {
146                setBeginDate(new java.sql.Date(this.getBeginTimestamp().getTime()));
147            }
148            return beginDate;
149        }
150    
151        public void setBeginDate(java.sql.Date beginDate) {
152            this.beginDate = beginDate;
153        }
154    
155        public java.sql.Date getEndDate() {
156            if (endDate == null && this.getEndTimestamp() != null) {
157                setEndDate(new java.sql.Date(this.getEndTimestamp().getTime()));
158            }
159            return endDate;
160        }
161    
162        public void setEndDate(java.sql.Date endDate) {
163            this.endDate = endDate;
164        }
165    
166        public Time getBeginTime() {
167            if (beginTime == null && this.getBeginTimestamp() != null) {
168                setBeginTime(new java.sql.Time(this.getBeginTimestamp().getTime()));
169            }
170            return beginTime;
171        }
172    
173        public void setBeginTime(Time beginTime) {
174            this.beginTime = beginTime;
175        }
176    
177        public Time getEndTime() {
178            if (endTime == null && this.getEndTimestamp() != null) {
179                setEndTime(new java.sql.Time(this.getEndTimestamp().getTime()));
180            }
181            return endTime;
182        }
183    
184        public void setEndTime(Time endTime) {
185            this.endTime = endTime;
186        }
187    
188    
189        public Boolean getClockLogCreated() {
190            return clockLogCreated;
191        }
192    
193        public void setClockLogCreated(Boolean clockLogCreated) {
194            this.clockLogCreated = clockLogCreated;
195        }
196    
197        public BigDecimal getHours() {
198            return hours;
199        }
200    
201        public void setHours(BigDecimal hours) {
202            if (hours != null) {
203                this.hours = hours.setScale(TkConstants.BIG_DECIMAL_SCALE, TkConstants.BIG_DECIMAL_SCALE_ROUNDING);
204            } else {
205                this.hours = hours;
206            }
207        }
208    
209        public BigDecimal getAmount() {
210            return amount;
211        }
212    
213        public void setAmount(BigDecimal amount) {
214            if (amount != null) {
215                this.amount = amount.setScale(TkConstants.BIG_DECIMAL_SCALE, TkConstants.BIG_DECIMAL_SCALE_ROUNDING);
216            } else {
217                this.amount = amount;
218            }
219        }
220    
221        public String getUserPrincipalId() {
222            return userPrincipalId;
223        }
224    
225        public void setUserPrincipalId(String userPrincipalId) {
226            this.userPrincipalId = userPrincipalId;
227        }
228    
229        public Timestamp getTimestamp() {
230            return timestamp;
231        }
232    
233        public void setTimestamp(Timestamp timestamp) {
234            this.timestamp = timestamp;
235        }
236    
237        public String getBeginTimestampTimezone() {
238            return beginTimestampTimezone;
239        }
240    
241        public void setBeginTimestampTimezone(String beginTimestampTimezone) {
242            this.beginTimestampTimezone = beginTimestampTimezone;
243        }
244    
245        public String getEndTimestampTimezone() {
246            return endTimestampTimezone;
247        }
248    
249        public void setEndTimestampTimezone(String endTimestampTimezone) {
250            this.endTimestampTimezone = endTimestampTimezone;
251        }
252    
253        public String toCSVString() {
254            StringBuffer sb = new StringBuffer();
255            sb.append(this.beginTimestampTimezone + ",");
256            sb.append(this.earnCode + ",");
257            sb.append(this.endTimestampTimezone + ",");
258            sb.append(this.userPrincipalId + ",");
259            sb.append(this.amount + ",");
260            sb.append(this.beginTimestamp + ",");
261            sb.append(this.clockLogCreated + ",");
262            sb.append(this.endTimestamp + ",");
263            sb.append(this.hours + ",");
264            sb.append(this.jobNumber + ",");
265            sb.append(this.task + ",");
266            sb.append(this.tkTimeBlockId + ",");
267            sb.append(this.timestamp + ",");
268            sb.append(this.workArea + System.getProperty("line.separator"));
269            return sb.toString();
270        }
271    
272        public String getTkTimeBlockId() {
273            return tkTimeBlockId;
274        }
275    
276        public void setTkTimeBlockId(String tkTimeBlockId) {
277            this.tkTimeBlockId = tkTimeBlockId;
278        }
279    
280        public Long getWorkArea() {
281            return workArea;
282        }
283    
284        public void setWorkArea(Long workArea) {
285            this.workArea = workArea;
286        }
287    
288        public Long getTask() {
289            return task;
290        }
291    
292        public void setTask(Long task) {
293            this.task = task;
294        }
295    
296        public List<TimeHourDetail> getTimeHourDetails() {
297            return timeHourDetails;
298        }
299        
300        public void addTimeHourDetail(TimeHourDetail timeHourDetail) {
301            timeHourDetails.add(timeHourDetail);
302        }
303        
304        public void removeTimeHourDetail(TimeHourDetail timeHourDetail) {
305            timeHourDetails.remove(timeHourDetail);
306        }
307    
308        public void setTimeHourDetails(List<TimeHourDetail> timeHourDetails) {
309            this.timeHourDetails = timeHourDetails;
310        }
311    
312        public Boolean isPushBackward() {
313            return pushBackward;
314        }
315    
316            public void setPushBackward(Boolean pushBackward) {
317            this.pushBackward = pushBackward;
318        }
319    
320        /**
321         * Use this call for all GUI/Display related rendering of the BEGIN
322         * timestamp of the given time block. Timeblocks require pre-processing
323         * before there will be a non-null return value here.
324         *
325         * @return The Timeblock Begin time to display, with the Users Timezone
326         *         taken into account and applied to this DateTime object.
327         */
328        public DateTime getBeginTimeDisplay() {
329            return beginTimeDisplay;
330        }
331    
332        /**
333         * Helper to call DateTime.toDate().
334         *
335         * @return a java.util.Date representing the getBeginTimeDisplay() DateTime.
336         */
337        public Date getBeginTimeDisplayDate() {
338            return getBeginTimeDisplay().toDate();
339        }
340    
341        /*
342        *   fix timezone issues caused by JScript, for GUI use only,
343        */
344        public String getBeginTimeDisplayDateOnlyString() {
345            return this.getBeginTimeDisplay().toString(TkConstants.DT_BASIC_DATE_FORMAT);
346        }
347    
348        public String getBeginTimeDisplayTimeOnlyString() {
349            return this.getBeginTimeDisplay().toString(TkConstants.DT_BASIC_TIME_FORMAT);
350        }
351    
352        public String getEndTimeDisplayDateOnlyString() {
353            return this.getEndTimeDisplay().toString(TkConstants.DT_BASIC_DATE_FORMAT);
354        }
355    
356        public String getEndTimeDisplayTimeOnlyString() {
357            return this.getEndTimeDisplay().toString(TkConstants.DT_BASIC_TIME_FORMAT);
358        }
359    
360        /**
361         * Set this value with a DateTime that is in the current users Timezone. This
362         * should happen as a pre processing step for display purposes. Do not use these
363         * values for server-side computation.
364         *
365         * @param beginTimeDisplay
366         */
367        public void setBeginTimeDisplay(DateTime beginTimeDisplay) {
368            this.beginTimeDisplay = beginTimeDisplay;
369        }
370    
371        /**
372         * Use this call for all GUI/Display related rendering of the END
373         * timestamp of the given time block. Timeblocks require pre-processing
374         * before there will be a non-null return value here.
375         *
376         * @return The Timeblock end time to display, with the Users Timezone
377         *         taken into account and applied to this DateTime object.
378         */
379        public DateTime getEndTimeDisplay() {
380            return endTimeDisplay;
381        }
382    
383        /**
384         * Helper to call DateTime.toDate().
385         *
386         * @return a java.util.Date representing the getEndTimeDisplay() DateTime.
387         */
388        public Date getEndTimeDisplayDate() {
389            return getEndTimeDisplay().toDate();
390        }
391    
392        /**
393         * Set this value with a DateTime that is in the current users Timezone. This
394         * should happen as a pre processing step for display purposes. Do not use these
395         * values for server-side computation.
396         *
397         * @param endTimeDisplay
398         */
399        public void setEndTimeDisplay(DateTime endTimeDisplay) {
400            this.endTimeDisplay = endTimeDisplay;
401        }
402    
403        public TimesheetDocumentHeader getTimesheetDocumentHeader() {
404            if (timesheetDocumentHeader == null && this.getDocumentId() != null) {
405                setTimesheetDocumentHeader(TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(this.getDocumentId()));
406            }
407            return timesheetDocumentHeader;
408        }
409    
410        public void setTimesheetDocumentHeader(
411                TimesheetDocumentHeader timesheetDocumentHeader) {
412            this.timesheetDocumentHeader = timesheetDocumentHeader;
413        }
414    
415        public List<TimeBlockHistory> getTimeBlockHistories() {
416            return timeBlockHistories;
417        }
418    
419        public void setTimeBlockHistories(List<TimeBlockHistory> timeBlockHistories) {
420            this.timeBlockHistories = timeBlockHistories;
421        }
422    
423        public String getClockLogBeginId() {
424            return clockLogBeginId;
425        }
426    
427        public void setClockLogBeginId(String clockLogBeginId) {
428            this.clockLogBeginId = clockLogBeginId;
429        }
430    
431        public String getClockLogEndId() {
432            return clockLogEndId;
433        }
434    
435        public void setClockLogEndId(String clockLogEndId) {
436            this.clockLogEndId = clockLogEndId;
437        }
438    
439        public String getAssignmentKey() {
440            if (assignmentKey == null) {
441                AssignmentDescriptionKey adk = new AssignmentDescriptionKey(this.getJobNumber().toString(), this.getWorkArea().toString(), this.getTask().toString());
442                this.setAssignmentKey(adk.toAssignmentKeyString());
443            }
444            return assignmentKey;
445        }
446    
447        public void setAssignmentKey(String assignmentDescription) {
448            this.assignmentKey = assignmentDescription;
449        }
450    
451        public String getAssignmentDescription() {
452            AssignmentDescriptionKey adk = new AssignmentDescriptionKey(this.getJobNumber().toString(), this.getWorkArea().toString(), this.getTask().toString());
453            Assignment anAssignment = TkServiceLocator.getAssignmentService().getAssignment(adk, this.getBeginDate());
454            return anAssignment == null ? this.getAssignmentKey() : anAssignment.getAssignmentDescription();
455        }
456    
457    
458        /**
459         * Word on the street is that Object.clone() is a POS. We only need some
460         * basics for comparison, so we'll implement a simple copy constructor
461         * instead.
462         * <p/>
463         * TODO: Check whether or not it matters if the History is copied, this
464         * operation needs to be as inexpensive as possible.
465         *
466         * @param b The TimeBlock to copy values from when creating this instance.
467         */
468        protected TimeBlock(TimeBlock b) {
469            // TODO : Implement "copy" constructor.
470            this.tkTimeBlockId = b.tkTimeBlockId;
471            this.documentId = b.documentId;
472            this.jobNumber = b.jobNumber;
473            this.workArea = b.workArea;
474            this.task = b.task;
475            this.earnCode = b.earnCode;
476            this.beginTimestamp = new Timestamp(b.beginTimestamp.getTime());
477            this.endTimestamp = new Timestamp(b.endTimestamp.getTime());
478            this.clockLogCreated = b.clockLogCreated;
479            this.hours = b.hours;
480            this.amount = b.amount;
481            this.userPrincipalId = b.userPrincipalId;
482            this.timestamp = new Timestamp(b.timestamp.getTime());
483            this.beginTimeDisplay = b.beginTimeDisplay;
484            this.endTimeDisplay = b.endTimeDisplay;
485            this.pushBackward = b.pushBackward;
486            this.clockLogBeginId = b.clockLogBeginId;
487            this.clockLogEndId = b.clockLogEndId;
488            this.principalId = b.principalId;
489    
490            // We just set the reference for this object, since splitting the
491            // TimeBlock would be abnormal behavior.
492            this.timesheetDocumentHeader = b.timesheetDocumentHeader;
493    
494            //private List<TimeHourDetail> timeHourDetails = new ArrayList<TimeHourDetail>();
495            for (TimeHourDetail thd : b.timeHourDetails) {
496                this.timeHourDetails.add(thd.copy());
497            }
498    
499            // TODO: For now, not copying TimeBlockHistory - The Object extends this one, which seems odd.
500            //private List<TimeBlockHistory> timeBlockHistories = new ArrayList<TimeBlockHistory>();
501        }
502    
503        /**
504         * @return A new copy of this TimeBlock.
505         */
506        public TimeBlock copy() {
507            return new TimeBlock(this);
508        }
509    
510        public String getEarnCodeType() {
511            return earnCodeType;
512        }
513    
514        public void setEarnCodeType(String earnCodeType) {
515            this.earnCodeType = earnCodeType;
516        }
517    
518        /**
519         * This is for distribute time block page to sort it by begin date/time
520         *
521         * @see java.lang.Comparable#compareTo(java.lang.Object)
522         */
523        public int compareTo(Object o) {
524            return compareTo((TimeBlock) o);
525        }
526    
527        public int compareTo(TimeBlock tb) {
528            return this.getBeginTimestamp().compareTo(tb.getBeginTimestamp());
529        }
530    
531        public Boolean getEditable() {
532            return TkServiceLocator.getTimeBlockService().isTimeBlockEditable(this);
533        }
534    
535        public String getPrincipalId() {
536            return principalId;
537        }
538    
539        public void setPrincipalId(String principalId) {
540            this.principalId = principalId;
541        }
542    
543        public String getOvertimePref() {
544            return overtimePref;
545        }
546    
547        public void setOvertimePref(String overtimePref) {
548            this.overtimePref = overtimePref;
549        }
550    
551        /* apply grace period rule to times of time block
552         * These strings are for GUI of Actual time inquiry
553        */
554        public String getActualBeginTimeString() {
555            if (this.getClockLogBeginId() != null) {
556                if (isOvernightTimeClockLog(clockLogEndId)) {
557                    return new DateTime(beginTimestamp).toString(TkConstants.DT_FULL_DATE_TIME_FORMAT);
558                } else {
559                    ClockLog cl = TkServiceLocator.getClockLogService().getClockLog(this.getClockLogBeginId());
560                    if (cl != null) {
561                        return new DateTime(cl.getTimestamp()).toString(TkConstants.DT_FULL_DATE_TIME_FORMAT);
562                    }
563                }
564    
565            }
566            return "";
567        }
568    
569        public String getActualEndTimeString() {
570            if (this.getClockLogEndId() != null) {
571                if (isOvernightTimeClockLog(clockLogEndId)) {
572                    return new DateTime(endTimestamp).toString(TkConstants.DT_FULL_DATE_TIME_FORMAT);
573                } else {
574                    ClockLog cl = TkServiceLocator.getClockLogService().getClockLog(this.getClockLogEndId());
575                    if (cl != null) {
576                        return new DateTime(cl.getTimestamp()).toString(TkConstants.DT_FULL_DATE_TIME_FORMAT);
577                    }
578                }
579    
580            }
581            return "";
582        }
583    
584        private Boolean isOvernightTimeClockLog(String clockLogId) {
585            // https://jira.kuali.org/browse/KPME-1179
586            Integer overnightTimeBlocks = TkServiceLocator.getTimeBlockService().getOvernightTimeBlocks(clockLogEndId).size();
587            if (overnightTimeBlocks >= 2) {
588                return true;
589            }
590    
591            return false;
592        }
593    
594            public Boolean getDeleteable() {
595                    return TkServiceLocator.getPermissionsService().canDeleteTimeBlock(this);
596            }
597    
598            public Boolean getOvertimeEditable() {
599                    return TkServiceLocator.getPermissionsService().canEditOvertimeEarnCode(this);
600            }
601            
602            public Boolean getRegEarnCodeEditable() {
603                    return TkServiceLocator.getPermissionsService().canEditRegEarnCode(this);
604            }
605    
606        public Boolean getTimeBlockEditable(){
607            return TkServiceLocator.getPermissionsService().canEditTimeBlock(this);
608        }
609    
610        public boolean isLunchDeleted() {
611            return lunchDeleted;
612        }
613    
614        public void setLunchDeleted(boolean lunchDeleted) {
615            this.lunchDeleted = lunchDeleted;
616        }
617    
618            public Person getUser() {
619                    return user;
620            }
621    
622            public void setUser(Person user) {
623                    this.user = user;
624            }
625            
626            @Override
627            public boolean equals(Object obj) {
628                    if (obj == null) { 
629                            return false;
630                    }
631                    if (obj == this) { 
632                            return true;
633                    }
634                    if (obj.getClass() != getClass()) {
635                            return false;
636                    }
637                    TimeBlock timeBlock = (TimeBlock) obj;
638                    return new EqualsBuilder()
639                            .append(jobNumber, timeBlock.jobNumber)
640                            .append(workArea, timeBlock.workArea)
641                            .append(task, timeBlock.task)
642                            .append(earnCode, timeBlock.earnCode)
643                            .append(beginTimestamp, timeBlock.beginTimestamp)
644                            .append(endTimestamp, timeBlock.endTimestamp)
645                            .append(hours, timeBlock.hours)
646                            .append(timeHourDetails, timeBlock.timeHourDetails)
647                            .isEquals();
648            }
649    
650        @Override
651        public int hashCode() {
652            return new HashCodeBuilder(17, 31)
653                    .append(jobNumber)
654                    .append(workArea)
655                    .append(task)
656                    .append(earnCode)
657                    .append(beginTimestamp)
658                    .append(endTimestamp)
659                    .append(hours)
660                    .append(timeHourDetails)
661                    .toHashCode();
662        }
663        
664    }