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