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 }