0001: /*
0002: The contents of this file are subject to the Common Public Attribution License
0003: Version 1.0 (the "License"); you may not use this file except in compliance with
0004: the License. You may obtain a copy of the License at
0005: http://www.projity.com/license . The License is based on the Mozilla Public
0006: License Version 1.1 but Sections 14 and 15 have been added to cover use of
0007: software over a computer network and provide for limited attribution for the
0008: Original Developer. In addition, Exhibit A has been modified to be consistent
0009: with Exhibit B.
0010:
0011: Software distributed under the License is distributed on an "AS IS" basis,
0012: WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
0013: specific language governing rights and limitations under the License. The
0014: Original Code is OpenProj. The Original Developer is the Initial Developer and
0015: is Projity, Inc. All portions of the code written by Projity are Copyright (c)
0016: 2006, 2007. All Rights Reserved. Contributors Projity, Inc.
0017:
0018: Alternatively, the contents of this file may be used under the terms of the
0019: Projity End-User License Agreeement (the Projity License), in which case the
0020: provisions of the Projity License are applicable instead of those above. If you
0021: wish to allow use of your version of this file only under the terms of the
0022: Projity License and not to allow others to use your version of this file under
0023: the CPAL, indicate your decision by deleting the provisions above and replace
0024: them with the notice and other provisions required by the Projity License. If
0025: you do not delete the provisions above, a recipient may use your version of this
0026: file under either the CPAL or the Projity License.
0027:
0028: [NOTE: The text of this license may differ slightly from the text of the notices
0029: in Exhibits A and B of the license at http://www.projity.com/license. You should
0030: use the latest text at http://www.projity.com/license for your modifications.
0031: You may not remove this license text from the source files.]
0032:
0033: Attribution Information: Attribution Copyright Notice: Copyright © 2006, 2007
0034: Projity, Inc. Attribution Phrase (not exceeding 10 words): Powered by OpenProj,
0035: an open source solution from Projity. Attribution URL: http://www.projity.com
0036: Graphic Image as provided in the Covered Code as file: openproj_logo.png with
0037: alternatives listed on http://www.projity.com/logo
0038:
0039: Display of Attribution Information is required in Larger Works which are defined
0040: in the CPAL as a work which combines Covered Code or portions thereof with code
0041: not governed by the terms of the CPAL. However, in addition to the other notice
0042: obligations, all copies of the Covered Code in Executable and Source Code form
0043: distributed must, as a form of attribution of the original author, include on
0044: each user interface screen the "OpenProj" logo visible to all users. The
0045: OpenProj logo should be located horizontally aligned with the menu bar and left
0046: justified on the top left of the screen adjacent to the File menu. The logo
0047: must be at least 100 x 25 pixels. When users click on the "OpenProj" logo it
0048: must direct them back to http://www.projity.com.
0049: */
0050:
0051: package com.projity.pm.assignment;
0052:
0053: import java.io.IOException;
0054: import java.io.ObjectInputStream;
0055: import java.io.ObjectOutputStream;
0056: import java.io.Serializable;
0057: import java.util.Collection;
0058:
0059: import com.projity.configuration.CircularDependencyException;
0060: import com.projity.datatype.Duration;
0061: import com.projity.datatype.Rate;
0062: import com.projity.datatype.TimeUnit;
0063: import com.projity.document.Document;
0064: import com.projity.functor.IntervalConsumer;
0065: import com.projity.options.CalendarOption;
0066: import com.projity.pm.assignment.contour.AbstractContour;
0067: import com.projity.pm.assignment.contour.AbstractContourBucket;
0068: import com.projity.pm.assignment.contour.ContourTypes;
0069: import com.projity.pm.assignment.contour.PersonalContour;
0070: import com.projity.pm.assignment.contour.StandardContour;
0071: import com.projity.pm.calendar.CalendarService;
0072: import com.projity.pm.calendar.HasCalendar;
0073: import com.projity.pm.calendar.InvalidCalendarIntersectionException;
0074: import com.projity.pm.calendar.WorkCalendar;
0075: import com.projity.pm.calendar.WorkingCalendar;
0076: import com.projity.pm.costing.CostRateTables;
0077: import com.projity.pm.criticalpath.TaskSchedule;
0078: import com.projity.pm.resource.Resource;
0079: import com.projity.pm.scheduling.Delayable;
0080: import com.projity.pm.scheduling.DelayableImpl;
0081: import com.projity.pm.scheduling.Schedule;
0082: import com.projity.pm.scheduling.ScheduleInterval;
0083: import com.projity.pm.scheduling.ScheduleUtil;
0084: import com.projity.pm.task.NormalTask;
0085: import com.projity.pm.task.Task;
0086: import com.projity.pm.time.MutableInterval;
0087: import com.projity.strings.Messages;
0088: import com.projity.util.Alert;
0089: import com.projity.util.DateTime;
0090:
0091: /**
0092: * Class representing the non-scheduling part of resource assignments. It is immutable
0093: * @stereotype thing
0094: */
0095: public final class AssignmentDetail implements Schedule, HasCalendar,
0096: Cloneable, Serializable, ContourTypes {// , Schedule {
0097: static final long serialVersionUID = 867792734923243L;
0098: transient Rate rate = new Rate(1.0D, TimeUnit.PERCENT); // units can never be 0!!!
0099: double percentComplete = 0;
0100: long duration = 0;
0101: //TODO There is some stuff to figure out regarding contouring remaining work. If you change the contour, Project applies
0102: // the entire contour to the remaining work. If you keep completing and change the contour again, project keeps the
0103: // different contours active to where they were applied. The weird thing is that if you uncomplete the task, the work still
0104: // shows the various contours, even if the current contour is flat. This is a bug.
0105:
0106: private int workContourType; // these are only used for serializing the contour. They are not "up to date" otherwise
0107: private int costContourType;
0108: private transient AbstractContour workContour = StandardContour.FLAT_CONTOUR;
0109: private transient AbstractContour costContour = StandardContour.FLAT_CONTOUR;
0110:
0111: private WorkingCalendar actualExceptionsCalendar = null; // for when user enters actual work during non-work time of calendar
0112: private transient WorkingCalendar intersectionCalendar = null;
0113: private transient TaskSchedule taskSchedule = null;
0114:
0115: private transient Resource resource;
0116: private transient Task task;
0117: private transient Delayable delayable;
0118: private int requestDemandType = RequestDemandType.NONE;
0119: private int costRateIndex = CostRateTables.DEFAULT;
0120: private long overtimeWork = 0; // allowed overtime that is evenly distributed across contour
0121:
0122: private WorkingCalendar baselineCalendar = null; // only applies if this is a baseline
0123:
0124: /**
0125: * @return Returns the percentComplete.
0126: */
0127: public double getPercentComplete() {
0128: return percentComplete;
0129: }
0130:
0131: /**
0132: * @param percentComplete The percentComplete to set.
0133: */
0134: public void setPercentComplete(double percentComplete) {
0135: this .percentComplete = percentComplete;
0136: if (getTask() != null) // in case of called from web
0137: ((NormalTask) getTask()).adjustActualStartFromAssignments();
0138: }
0139:
0140: /**
0141: * @return Returns the duration.
0142: */
0143: public long getDuration() {
0144: return duration;
0145: }
0146:
0147: void recalculateDuration() {
0148: duration = workContour.calcTotalBucketDuration(0);
0149: }
0150:
0151: /**
0152: * @param taskSchedule The taskSchedule to set.
0153: */
0154: void setTaskSchedule(TaskSchedule taskSchedule) {
0155: this .taskSchedule = taskSchedule;
0156: }
0157:
0158: /**
0159: * Set just the units and the resource. This is the case when replacing a resource with another,
0160: * such as the default resource assignment being replaced with a true one
0161: * @param units
0162: * @param resource
0163: */
0164: void replaceResourceAndUnits(double units, Resource resource) {
0165: this .resource = resource;
0166: setUnits(units);
0167: }
0168:
0169: /**
0170: * Construct an assignment. The arguments are those that are presented in the assign resource dialog
0171: * @param task
0172: * @param resource
0173: * @param units
0174: * @param requestDemandType -Normally empty
0175: */
0176: AssignmentDetail(Task task, Resource resource, double units,
0177: int requestDemandType, long delay) {
0178: this .task = task;
0179: this .resource = resource;
0180: setUnits(units);
0181: this .requestDemandType = requestDemandType;
0182: this .delayable = new DelayableImpl(delay, 0);
0183: }
0184:
0185: private AssignmentDetail() { // used in cloning
0186:
0187: }
0188:
0189: /**
0190: * Accessor for units (value of work)
0191: * @return units
0192: */
0193: double getUnits() {
0194: return rate.getValue();
0195: }
0196:
0197: void setUnits(double units) {
0198: rate.setValue(units);
0199: }
0200:
0201: /**
0202: * Calculate the overtime value from the overtime work field. MSProject distributes
0203: * overtime uniformly over the working days of the assignment.
0204: * @return overtime value
0205: */
0206: double calcOvertimeUnits() {
0207: long workingDuration = calcWorkingDuration(); //TODO this should be stored
0208: if (workingDuration == 0) // take care of degenerate case
0209: return 0.0;
0210: return ((double) overtimeWork) / workingDuration;
0211: }
0212:
0213: void setOvertimeWork(long overtimeWork) {
0214: this .overtimeWork = overtimeWork;
0215: }
0216:
0217: /**
0218: * Calculate the total work by multiplying units by the calculated duration
0219: * @return total work.
0220: */
0221: long calcWork() {
0222: //hk
0223: return (long) (getUnits() * calcWorkingDuration());
0224: }
0225:
0226: public String toString() {
0227: return super .toString();
0228: // return "[start] " + new java.util.Date(getStart())
0229: // + "\n[end] " + new java.util.Date(getEnd())
0230: // +"\n[units] " + getUnits()// in hours
0231: // +"\n[work] " + getWork() / (1000*60*60); // in hours
0232: }
0233:
0234: /**
0235: * @return Returns the contour.
0236: */
0237: AbstractContour getWorkContour() {
0238: return workContour;
0239: }
0240:
0241: /**
0242: * @param contour The contour to set.
0243: * TODO get rid of this
0244: */
0245: public void debugSetWorkContour(AbstractContour contour) {
0246: this .workContour = contour;
0247: }
0248:
0249: /**
0250: * Accessor for the assignment's delay
0251: * @return delay
0252: */
0253: public long getDelay() {
0254: return delayable.getDelay();
0255: }
0256:
0257: void setDelay(long delay) {
0258: if (delay > 0)
0259: System.out.println("delay "
0260: + new java.util.Date(getStart())
0261: + new java.util.Date(getTaskStart()));
0262:
0263: delayable = new DelayableImpl(delay, delayable
0264: .getLevelingDelay());
0265: }
0266:
0267: public long getLevelingDelay() {
0268: return delayable.getLevelingDelay();
0269: }
0270:
0271: void setLevelingDelay(long levelingDelay) {
0272: delayable = new DelayableImpl(delayable.getDelay(),
0273: levelingDelay);
0274: }
0275:
0276: public long calcTotalDelay() {
0277: return delayable.calcTotalDelay();
0278: }
0279:
0280: void setContour(Object type, Collection bucketList) {
0281: AbstractContourBucket[] contour = new AbstractContourBucket[bucketList
0282: .size()];
0283: bucketList.toArray(contour);
0284: setContour(type, contour);
0285:
0286: }
0287:
0288: void setContour(Object type, AbstractContourBucket[] buckets) {
0289: if (type == HasTimeDistributedData.WORK) {
0290: workContour = PersonalContour.getInstance(buckets);
0291: } else if (type == HasTimeDistributedData.COST) {
0292: costContour = PersonalContour.getInstance(buckets);
0293: }
0294: }
0295:
0296: /**
0297: * @return Returns the requestDemandType.
0298: */
0299: public int getRequestDemandType() {
0300: return requestDemandType;
0301: }
0302:
0303: void setRequestDemandType(int requestDemandType) {
0304: this .requestDemandType = requestDemandType;
0305:
0306: }
0307:
0308: void setWorkContour(AbstractContour contour) {
0309: this .workContour = contour;
0310: }
0311:
0312: /**
0313: * @param duration The duration to set.
0314: */
0315: void adjustRemainingDuration(long newRemainingDuration) {
0316: if (newRemainingDuration < 0)
0317: newRemainingDuration = 0; // just in case
0318: if (getUnits() == 0) // take care of degenerate case
0319: newRemainingDuration = 0;
0320:
0321: long actualDuration = getActualDuration();
0322: long d = newRemainingDuration + actualDuration;
0323: if (actualDuration > 0 && !workContour.isPersonal()) // because the remaining might not have same contour
0324: workContour = PersonalContour.makePersonal(workContour,
0325: getDuration()); //use previous duration
0326: workContour = workContour.adjustDuration(d, actualDuration); // allow a personal contour to adjust itself
0327: d = workContour.calcTotalBucketDuration(d); // it is possible that the contour is shorter because we have eliminated a bucket at the end which is after empty time. The empty time must be removed too
0328: setDuration(d);
0329: }
0330:
0331: /**
0332: * @param units The units to set.
0333: */
0334: void adjustRemainingUnits(double newUnits) {
0335: workContour = workContour.adjustUnits(newUnits / getUnits(),
0336: getActualDuration()); // allow a personal contour to adjust itself
0337: setUnits(newUnits);
0338: }
0339:
0340: //this version has bugs. I have reverted to the older version below
0341:
0342: // void adjustRemainingWork(double multiplier) {
0343: // long dur;
0344: // boolean fixedDuration = ((NormalTask)getTask()).getSchedulingRule() == FixedDuration.getInstance();
0345: // if (!getResource().isLabor())
0346: // System.out.println("mater " + multiplier);
0347: // AbstractContour newContour = workContour;
0348: // if (fixedDuration)
0349: // newContour = workContour.adjustUnits(multiplier, getActualDuration());
0350: // else
0351: // newContour = workContour.contourAdjustWork(multiplier, getActualDuration());
0352: // if (workContour != newContour) { // adjust the work contour - the case of a personal contour
0353: // workContour = newContour; // if contour was changed
0354: // dur =workContour.calcTotalBucketDuration(getDuration()); // cannot perform simple multiplication of duration because non-work periods are NOT multiplied
0355: // } else {
0356: // dur = (long) (getActualDuration() + getRemainingDuration() * multiplier);
0357: // }
0358: // if (fixedDuration)
0359: // setUnits(getUnits() * multiplier);
0360: // else
0361: // setDuration(dur);
0362: // }
0363:
0364: void adjustRemainingWork(double multiplier) {
0365: long dur;
0366:
0367: AbstractContour newContour = workContour.contourAdjustWork(
0368: multiplier, getActualDuration());
0369: if (workContour != newContour) { // adjust the work contour - the case of a personal contour
0370: workContour = newContour; // if contour was changed
0371: dur = workContour.calcTotalBucketDuration(getDuration()); // cannot perform simple multiplication of duration because non-work periods are NOT multiplied
0372: } else {
0373: dur = (long) (getActualDuration() + getRemainingDuration()
0374: * multiplier);
0375: }
0376: setDuration(dur);
0377: setUnits(getUnits() / multiplier);
0378: }
0379:
0380: /**
0381: * MSProject displays duration as only the working duration except in fixed duration tasks.
0382: * @return duration with non-work periods excluded
0383: */
0384: long calcWorkingDuration() {
0385: return workContour.calcWorkingBucketDuration(getDuration());
0386: }
0387:
0388: /**
0389: * Allow setting of working duration. MSProject displays working duration (excludes non-work intervals) except when
0390: * task type is fixed duration, in which case it shows duration with non-work intervals
0391: * @param newWorkingDuration
0392: */
0393: void adjustWorkingDuration(long newWorkingDuration) {
0394: adjustRemainingDuration(getDuration()
0395: + (newWorkingDuration - calcWorkingDuration()));
0396: }
0397:
0398: /**
0399: * @return Returns the schedule.
0400: */
0401: TaskSchedule getTaskSchedule() {
0402: if (taskSchedule == null)
0403: return getTask().getCurrentSchedule();
0404: return taskSchedule;
0405: }
0406:
0407: TaskSchedule getTaskScheduleOfAssignment() {
0408: return taskSchedule;
0409: }
0410:
0411: void convertToBaselineAssignment(boolean useDefaultCalendar) {
0412: try {
0413: if (useDefaultCalendar)
0414: baselineCalendar = CalendarService.getInstance()
0415: .getDefaultInstance();
0416: else
0417: baselineCalendar = (WorkingCalendar) getEffectiveWorkCalendar()
0418: .clone();
0419: } catch (CloneNotSupportedException e) {
0420: // TODO Auto-generated catch block
0421: e.printStackTrace();
0422: }
0423: }
0424:
0425: boolean isBaseline() {
0426: return baselineCalendar != null;
0427: }
0428:
0429: public WorkCalendar getEffectiveWorkCalendar() {
0430: if (actualExceptionsCalendar != null)
0431: return actualExceptionsCalendar;
0432: if (baselineCalendar != null)
0433: return baselineCalendar;
0434: Resource resource = getResource();
0435: Task task = getTask();
0436: if (((NormalTask) task).isIgnoreResourceCalendar()
0437: || isInvalidIntersectionCalendar()
0438: || resource.getEffectiveWorkCalendar() == null)
0439: return task.getEffectiveWorkCalendar();
0440: // if no task calendar, or calendar is invalid due to empty intersection of task and resource calendars
0441: if (task.getWorkCalendar() == null)
0442: return resource.getEffectiveWorkCalendar();
0443:
0444: if (intersectionCalendar == null) {
0445: try {
0446: intersectionCalendar = ((WorkingCalendar) task
0447: .getEffectiveWorkCalendar())
0448: .intersectWith((WorkingCalendar) resource
0449: .getEffectiveWorkCalendar());
0450: } catch (InvalidCalendarIntersectionException e) {
0451: intersectionCalendar = WorkingCalendar.INVALID_INTERSECTION_CALENDAR;
0452: Alert.error(Messages
0453: .getString("Message.invalidIntersection"));
0454: return task.getEffectiveWorkCalendar();
0455: }
0456: }
0457: return intersectionCalendar;
0458: // need to use intersection work calendar
0459: }
0460:
0461: public boolean isInvalidIntersectionCalendar() {
0462: return intersectionCalendar == WorkingCalendar.INVALID_INTERSECTION_CALENDAR;
0463: }
0464:
0465: /**
0466: * @return Returns the task.
0467: */
0468: Task getTask() {
0469: return task;
0470: }
0471:
0472: /**
0473: * The assignment start is calculated like this:
0474: * Get the task start.
0475: * If the assignment is not started, use the task's dependency date if it's later than the start
0476: * Add in any delay
0477: */
0478: public long getStart() {
0479: long delay = this .calcTotalDelay();
0480: TaskSchedule ts = getTaskSchedule();
0481: long s = ts.getStart();
0482: if (percentComplete == 0) { // if no completion, use task dependency date if needed
0483: long d = ts.getDependencyDate();
0484: if (d > s && !task.startsBeforeProject())
0485: s = d;
0486: }
0487: if (delay > 0)
0488: s = getEffectiveWorkCalendar().add(s, delay, false); // use later time
0489: //TODO check if above should use task calendar or assignment calendar
0490: return s;
0491: }
0492:
0493: /**
0494: * Sets the assignment start. Since the assigment start is an offset (usually 0) from task start, setting a later start means
0495: * setting a delay for the assignment. Also, any levelling delay is cleared
0496: */
0497: public void setStart(long start) {
0498: if (start > getTaskStart()) {
0499: long delay = getEffectiveWorkCalendar().compare(start,
0500: getTaskStart(), false);
0501: setLevelingDelay(0);
0502: setDelay(delay);
0503: }
0504: }
0505:
0506: long getSplitDuration() {
0507: long dependencyStart = getDependencyStart();
0508: if (dependencyStart == 0
0509: || getDependencyStart() == getTaskStart())
0510: return 0;
0511:
0512: long remainingStart = Math.max(getTaskStart(), getStop());
0513: return Math.max(0, getEffectiveWorkCalendar().compare(
0514: getDependencyStart(), remainingStart, false));
0515: }
0516:
0517: // // if need to split
0518: // if (actualDuration >0 && actualDuration != durationMillis) {
0519: // long start = getStart();
0520: // long split = getTaskSchedule().getResume();
0521: // if (split > start) {
0522: // long splitDuration = task.getEffectiveWorkCalendar().compare(split,start,false); //TODO move this elsewhere
0523: // finish = getEffectiveWorkCalendar().add(finish,splitDuration,true);
0524: // }
0525: // }
0526: // return finish;
0527:
0528: long getFinish() {
0529: return getEnd();
0530:
0531: // // if need to split
0532: // if (actualDuration >0 && actualDuration != durationMillis) {
0533: // long start = getStart();
0534: // long split = getTaskSchedule().getResume();
0535: // if (split > start) {
0536: // long splitDuration = task.getEffectiveWorkCalendar().compare(split,start,false); //TODO move this elsewhere
0537: // finish = getEffectiveWorkCalendar().add(finish,splitDuration,true);
0538: // }
0539: // }
0540: // return finish;
0541: }
0542:
0543: /**
0544: * @return Returns the resource.
0545: */
0546: Resource getResource() {
0547: return resource;
0548: }
0549:
0550: AbstractContourBucket[] getContour(Object type) {
0551: return ((type == HasTimeDistributedData.COST) ? costContour
0552: : workContour).getContourBuckets();
0553: }
0554:
0555: /**
0556: * @return Returns the costContour.
0557: */
0558: AbstractContour getCostContour() {
0559: return costContour;
0560: }
0561:
0562: /**
0563: * @return Returns the costRateIndex.
0564: */
0565: int getCostRateIndex() {
0566: return costRateIndex;
0567: }
0568:
0569: void setCostRateIndex(int costRateIndex) {
0570: this .costRateIndex = costRateIndex;
0571: }
0572:
0573: Assignment getBaselineAssignment() {
0574: if (task == null) // case when just loading assignment in timesheet
0575: return null;
0576: return task.getBaselineAssignment(resource);
0577: }
0578:
0579: Assignment getBaselineAssignment(Object baseline,
0580: boolean createIfDoestExist) {
0581: if (task == null) // case when just loading assignment in timesheet
0582: return null;
0583: return task.getBaselineAssignment(resource, baseline,
0584: createIfDoestExist);
0585: }
0586:
0587: long effectiveBaselineStart() {
0588: Assignment baselineAssignment = getBaselineAssignment();
0589: if (baselineAssignment == null)
0590: return getStart();
0591: return baselineAssignment.getStart();
0592: }
0593:
0594: long effectiveBaselineStart(Object baseline) {
0595: Assignment baselineAssignment = getBaselineAssignment(baseline,
0596: false);
0597: if (baselineAssignment == null)
0598: return getStart();
0599: return baselineAssignment.getStart();
0600: }
0601:
0602: long effectiveBaselineFinish(Object baseline) {
0603: Assignment baselineAssignment = getBaselineAssignment(baseline,
0604: false);
0605: if (baselineAssignment == null)
0606: return getFinish();
0607: return baselineAssignment.getFinish();
0608: }
0609:
0610: // private void adjustActualDurationAndContour(long newActualDuration, long stop, long splitDuration) {
0611: // long oldActualDuration = actualDuration;
0612: // newActualDuration = Duration.millis(newActualDuration);
0613: // if (newActualDuration == oldActualDuration) // if no change do nothing
0614: // return;
0615: // actualDuration = newActualDuration; // need to set in now since it mayb be used below inside bucketsBetweenDurations
0616: //
0617: // if (newActualDuration == 0) { // if setting to 0 actuals
0618: // return;
0619: // }
0620: //
0621: // if (oldActualDuration > newActualDuration) {// trim off end
0622: // actualWorkContour = actualWorkContour.adjustDuration(newActualDuration); // simple, just truncate it.
0623: // } else {
0624: //System.out.println("Stop "+ new Date(stop) + " duration" + DurationFormat.format(stopResumeDuration));
0625: // ArrayList list;
0626: // if (actualWorkContour != null) // see if there are actuals
0627: // list = actualWorkContour.toArrayList(); // copy current actual contour to an array list
0628: // else // in case there are no actuals yet
0629: // list = new ArrayList();
0630: // if (actualDuration > durationMillis) { // if made longer than planned duration, adjust planned duration
0631: // durationMillis = actualDuration;
0632: // workContour = workContour.adjustDuration(durationMillis);
0633: // }
0634: // list.addAll(workContour.bucketsBetweenDurations(oldActualDuration, newActualDuration, durationMillis)); // add from work contour
0635: // actualWorkContour = PersonalContour.getInstance(list); // set new contour that combines the two above
0636: // //insert gap for stop/resume
0637: //System.out.println("contour before\n" + actualWorkContour.toString(newActualDuration));
0638: // if (stopResumeDuration > 0) {
0639: // actualWorkContour = ((PersonalContour)actualWorkContour).insertBucket(stop,PersonalContourBucket.getInstance(stopResumeDuration,0.0));
0640: // System.out.println("contour after\n" + actualWorkContour.toString(newActualDuration));
0641: // }
0642: //// System.out.println("new actual contour" + actualWorkContour.toString(newActualDuration));
0643: // }
0644: // }
0645:
0646: /** returns the amount of effort that the resource as available to work on the assignment
0647: *
0648: * @return
0649: */
0650: long getResourceAvailability() {
0651: WorkCalendar cal = resource.getEffectiveWorkCalendar();
0652: if (cal == null)
0653: cal = task.getOwningProject().getEffectiveWorkCalendar();
0654: //TODO implement time-scaled availability
0655: return (long) resource.getMaximumUnits()
0656: * cal.compare(getFinish(), getStart(), false);
0657: }
0658:
0659: void shift(long start, long end, long shiftDuration) {
0660: PersonalContour newContour = PersonalContour.makePersonal(
0661: workContour, getDuration());
0662: newContour = newContour.shift(start, end, shiftDuration);
0663: workContour = newContour;
0664: if (workContour.isPersonal())
0665: duration = workContour.calcTotalBucketDuration(duration);
0666: checkForNegativeDuration();
0667: }
0668:
0669: // in rare circumstances duration would go negative. prevent this
0670: private void checkForNegativeDuration() {
0671: if (duration < 0) {
0672: if (getDelay() > 0) // clear out any delay
0673: setDelay(0);
0674: duration = 0;
0675: }
0676:
0677: }
0678:
0679: void extend(long end, long extendDuration) {
0680: workContour = workContour.extend(end, extendDuration);
0681: if (workContour.isPersonal())
0682: duration = workContour.calcTotalBucketDuration(duration);
0683: else
0684: duration += extendDuration;
0685: checkForNegativeDuration();
0686: }
0687:
0688: /**
0689: * @param startOffset
0690: * @param extendDuration
0691: */
0692: public void extendBefore(long startOffset, long extendDuration) {
0693: workContour = workContour.extendBefore(startOffset,
0694: extendDuration);
0695: if (workContour.isPersonal())
0696: duration = workContour.calcTotalBucketDuration(duration);
0697: else
0698: duration -= extendDuration;
0699: checkForNegativeDuration();
0700:
0701: }
0702:
0703: /* (non-Javadoc)
0704: * @see com.projity.pm.scheduling.Schedule#split(java.lang.Object, long, long)
0705: */
0706: public void split(Object eventSource, long from, long to) {
0707: System.out
0708: .println("split should not be called for AssignmentDetail");
0709: }
0710:
0711: public void removeEmptyBucketAtDuration(long atDuration) {
0712: if (workContour.isPersonal()) {
0713: workContour = ((PersonalContour) workContour)
0714: .removeEmptyBucketAtDuration(atDuration);
0715: }
0716: }
0717:
0718: MutableInterval getRangeThatIntervalCanBeMoved(long start, long end) {
0719: return workContour.getRangeThatIntervalCanBeMoved(start, end);
0720: }
0721:
0722: /* (non-Javadoc)
0723: * @see com.projity.pm.calendar.HasCalendar#setWorkCalendar(com.projity.pm.calendar.WorkCalendar)
0724: */
0725: public void setWorkCalendar(WorkCalendar workCalendar) {
0726: }
0727:
0728: /* (non-Javadoc)
0729: * @see com.projity.pm.calendar.HasCalendar#getWorkCalendar()
0730: */
0731: public WorkCalendar getWorkCalendar() {
0732: return getEffectiveWorkCalendar();
0733: }
0734:
0735: /**
0736: * Use the start dependency in the task schedule
0737: */
0738: public long getDependencyStart() {
0739: return getTaskSchedule().getRemainingDependencyDate();
0740: }
0741:
0742: long getTaskStart() {
0743: return getTaskSchedule().getStart();
0744: }
0745:
0746: public void setDuration(long duration) {
0747: long stop = getStop(); // need to preserve completion
0748: if (Duration.millis(duration) < 0)
0749: duration = 0; // just in case
0750: this .duration = duration;
0751: setStop(stop);
0752: }
0753:
0754: public long getActualFinish() {
0755: if (getPercentComplete() < 1.0)
0756: return 0;
0757: return getEnd();
0758: // return getEffectiveWorkCalendar().add(getStop(),getRemainingDuration(),false);
0759: }
0760:
0761: /**
0762: * Sets the actual finish. This entails setting the end, the actual start (in case it's not set yet), the duration
0763: * and the actual duration.
0764: */
0765: public void setActualFinish(long actualFinish) {
0766: setEnd(actualFinish);
0767: setPercentComplete(1.0D);
0768: }
0769:
0770: public long getEnd() {
0771: WorkCalendar cal = getEffectiveWorkCalendar();
0772: long finish = cal.add(getStart(), getDuration(), true);
0773: if (getPercentComplete() > 0.0 && getPercentComplete() < 1.0) {
0774: long dependencyStart = getDependencyStart();
0775: if (dependencyStart > getTaskStart()) {
0776: long splitDuration = cal.compare(dependencyStart,
0777: getTaskStart(), false);
0778: finish = cal.add(finish, splitDuration, true);
0779: }
0780: }
0781: return finish;
0782: }
0783:
0784: public void setEnd(long end) {
0785: long remaining = getRemainingDuration();
0786: long oldEnd = getEnd();
0787: if (oldEnd == end)
0788: return;
0789: long stop = getStop();
0790: if (stop > end)
0791: end = stop;
0792: long durationDifference = getEffectiveWorkCalendar().compare(
0793: end, oldEnd, false);
0794: // if (stop != 0 && end > stop) {
0795: // durationDifference -= getSplitDuration();
0796: // System.out.println("split duration is " + DurationFormat.format(getSplitDuration()));
0797: // }
0798: //System.out.println("duration difference " + DurationFormat.format(durationDifference));
0799: //System.out.println("old end " + new Date(oldEnd) + " end " + new Date(end));
0800: long newRemaining = remaining + durationDifference;
0801: if (newRemaining < 0) // test to avoid negative duration
0802: newRemaining = 0L;
0803: adjustRemainingDuration(newRemaining);
0804:
0805: // long splitDuration = 0; //getSplitDuration();
0806: // durationActive += durationDifference + splitDuration;
0807: // durationSpan += durationDifference + splitDuration;
0808:
0809: setStop(stop);
0810: // long splitDuration = getSplitDuration();
0811: //
0812: // if (splitDuration >= 0) {
0813: // durationSpan = durationActive + splitDuration;
0814: // }
0815:
0816: }
0817:
0818: public long getActualStart() {
0819: if (percentComplete == 0)
0820: return 0;
0821: return getStart();
0822: }
0823:
0824: public void setActualStart(long actualStart) {
0825: setStart(actualStart);
0826: if (percentComplete == 0)
0827: setPercentComplete(INSTANT_COMPLETION);
0828: }
0829:
0830: /**
0831: * @return Returns the actualDuration.
0832: */
0833: public long getActualDuration() {
0834: return DateTime.closestDate(getDuration()
0835: * getPercentComplete());
0836: }
0837:
0838: /**
0839: * @param actualDuration The actualDuration to set. Will fix actual start if needed
0840: */
0841: public void setActualDuration(long actualDuration) {
0842: if (getDuration() > 0)
0843: setPercentComplete(((double) actualDuration)
0844: / getDuration());
0845: }
0846:
0847: public void clearDuration() {
0848: setDuration(0);
0849: }
0850:
0851: /* (non-Javadoc)
0852: * @see com.projity.pm.scheduling.Schedule#moveInterval(java.lang.Object, long, long, com.projity.pm.scheduling.ScheduleInterval)
0853: */
0854: public void moveInterval(Object eventSource, long start, long end,
0855: ScheduleInterval oldInterval, boolean isChild) {
0856: throw new RuntimeException(
0857: "cannot call moveInterval for an AssignmentDetail");
0858: }
0859:
0860: public void moveRemainingToDate(long date) {
0861: throw new RuntimeException(
0862: "cannot call moveRemainingToDate for an AssignmentDetail");
0863: }
0864:
0865: /* (non-Javadoc)
0866: * @see com.projity.pm.scheduling.Schedule#consumeIntervals(com.projity.functor.IntervalConsumer)
0867: */
0868: public void consumeIntervals(IntervalConsumer consumer) {
0869: throw new RuntimeException(
0870: "cannot call consumeIntervals for an AssignmentDetail");
0871: }
0872:
0873: public long getElapsedDuration() {
0874: return Math.round(getEffectiveWorkCalendar().compare(getEnd(),
0875: getStart(), true)
0876: * CalendarOption.getInstance()
0877: .getFractionOfDayThatIsWorking());
0878: }
0879:
0880: public long getRemainingDuration() {
0881: return DateTime
0882: .closestDate((getDuration() * (1.0D - percentComplete)));
0883: }
0884:
0885: public void setRemainingDuration(long remainingDuration) {
0886: // if (getDuration() > 0) {
0887: // double actual = (percentComplete * getDuration());
0888: // long total = (long) (actual + remainingDuration);
0889: // setDuration(total);
0890: // if (actual != 0.0D)
0891: // setPercentComplete(actual / total);
0892: //
0893: // }
0894: setPercentComplete(1.0D - ((double) remainingDuration)
0895: / getDuration());
0896: }
0897:
0898: /**
0899: * returns calculated completed date. If there is no advancement, the date is start date
0900: * @return
0901: */
0902: public long getStop() {
0903: long stop = 0;
0904: if (percentComplete > 0.0
0905: && percentComplete != INSTANT_COMPLETION) {
0906: stop = getEffectiveWorkCalendar().add(getStart(),
0907: getActualDuration(), true); // use earlier date
0908: }
0909: return stop;
0910: }
0911:
0912: public void setDependencyStart(long dependencyStart) {
0913: throw new RuntimeException(
0914: "cannot call setDependencyStart for an AssignmentDetail");
0915: }
0916:
0917: /**
0918: * Remove any filler periods in the contour after a certain date. This is used in the special case
0919: * of uncompleting a task. There might be filler periods in the contour due to a task dependency
0920: * that pushed out remaining work that was subsequently completed. When uncompleting, the remaining
0921: * work needs to have this filler period removed, otherwise there will be an extra delay.
0922: * @param stop Stop date
0923: */
0924: void removeFillerAfter(long stop) {
0925: long stopDuration = getEffectiveWorkCalendar().compare(stop,
0926: getStart(), false);
0927: workContour = workContour.removeFillerAfter(stopDuration);
0928: }
0929:
0930: public void setStop(long stop) {
0931: if (stop < getStart()) // if before start ignore
0932: return;
0933: if (percentComplete == 1.0 && stop >= getActualFinish()) // adjust to be no later than actual finish
0934: return;
0935:
0936: // System.out.println("setting stop to " + new java.util.Date(stop));
0937:
0938: if (stop <= getStart()) { // if getting rid of completion
0939: setPercentComplete(0);
0940: } else {
0941: long actualDuration = getEffectiveWorkCalendar().compare(
0942: stop, getStart(), false);
0943: // if (getDependencyStart() >= getStop() && getDependencyStart() < stop) {// if setting stop incorporates split due to dependency
0944: // actualDuration -= getSplitDuration();
0945: // }
0946: // duration = getWorkContour().calcTotalBucketDuration(0);
0947:
0948: long d = getDuration();
0949: if (d != 0)
0950: setPercentComplete(((double) actualDuration) / d);
0951: }
0952: }
0953:
0954: /* (non-Javadoc)
0955: * @see com.projity.pm.scheduling.Schedule#getResume()
0956: */
0957: public long getResume() {
0958: long resume = 0;
0959:
0960: if (percentComplete > 0.0)
0961: resume = getEffectiveWorkCalendar().add(getStart(),
0962: getActualDuration(), false); // use later date
0963: resume = Math.max(resume, getDependencyStart());
0964: if (workContour.isPersonal()) {
0965:
0966: }
0967:
0968: return resume;
0969: }
0970:
0971: /* (non-Javadoc)
0972: * @see com.projity.pm.scheduling.Schedule#setResume(long)
0973: */
0974: public void setResume(long resume) {
0975: long oldResume = getResume();
0976: long shift = getEffectiveWorkCalendar().compare(resume,
0977: oldResume, false);
0978: if (shift == 0)
0979: return;
0980:
0981: if (resume > oldResume) {
0982:
0983: }
0984: //
0985: // setStop(stop);
0986:
0987: }
0988:
0989: long extractDelay() {
0990: return workContour.extractDelay();
0991: }
0992:
0993: private static short DEFAULT_VERSION = 1;
0994: private short version = DEFAULT_VERSION;
0995:
0996: private void writeObject(ObjectOutputStream s) throws IOException {
0997: workContourType = workContour.getType();
0998: costContourType = costContour.getType();
0999: s.defaultWriteObject();
1000: rate.serialize(s);
1001: //delayable
1002: s.writeLong(delayable.getDelay());
1003: s.writeLong(delayable.getLevelingDelay());
1004: //personnal contours
1005: if (workContourType == CONTOURED)
1006: s.writeObject(workContour.getContourBuckets());
1007: if (costContourType == CONTOURED)
1008: s.writeObject(costContour.getContourBuckets());
1009:
1010: if (version >= 1) {
1011: s.writeBoolean(taskSchedule == null);
1012: if (taskSchedule != null)
1013: taskSchedule.serialize(s);
1014: }
1015: }
1016:
1017: private void readObject(ObjectInputStream s) throws IOException,
1018: ClassNotFoundException {
1019: s.defaultReadObject();
1020: rate = Rate.deserialize(s);
1021: //delayable
1022: delayable = new DelayableImpl(s.readLong(), s.readLong());
1023: //personnal contours
1024: if (workContourType == CONTOURED) {
1025: // try{
1026: workContour = PersonalContour
1027: .getInstance((AbstractContourBucket[]) s
1028: .readObject());
1029: // }catch(Exception e){
1030: // workContour = StandardContour.getStandardContour(workContourType);
1031: // }
1032: } else
1033: workContour = StandardContour
1034: .getStandardContour(workContourType);
1035: if (costContourType == CONTOURED) {
1036: // try{
1037: costContour = PersonalContour
1038: .getInstance((AbstractContourBucket[]) s
1039: .readObject());
1040: // }catch(Exception e){
1041: // costContour = StandardContour.getStandardContour(costContourType);
1042: // }
1043: } else
1044: costContour = StandardContour
1045: .getStandardContour(costContourType);
1046:
1047: if (version >= 1) {
1048: boolean nullSchedule = s.readBoolean();
1049: if (!nullSchedule) {
1050: taskSchedule = TaskSchedule.deserialize(s);
1051: taskSchedule.setTask(task);
1052: }
1053: }
1054: version = DEFAULT_VERSION;
1055:
1056: }
1057:
1058: public Object clone() {
1059: // try {
1060: /*
1061: private transient Rate rate = new Rate(1.0D); // units can never be 0!!!
1062: double percentComplete = 0;
1063: long duration = 0;
1064:
1065: // private Schedule schedule = new ScheduleImpl(this);
1066:
1067: // private long durationMillis; // This includes non-working duration (if personal contour). See also calcWorkingDuration()
1068: // long actualDuration = 0;
1069:
1070: //TODO There is some stuff to figure out regarding contouring remaining work. If you change the contour, Project applies
1071: // the entire contour to the remaining work. If you keep completing and change the contour again, project keeps the
1072: // different contours active to where they were applied. The weird thing is that if you uncomplete the task, the work still
1073: // shows the various contours, even if the current contour is flat. This is a bug.
1074:
1075:
1076: private int workContourType; // these are only used for serializing the contour. They are not "up to date" otherwise
1077: private int costContourType;
1078: private transient AbstractContour workContour = StandardContour.FLAT_CONTOUR;
1079: private transient AbstractContour costContour = StandardContour.FLAT_CONTOUR;
1080:
1081: private transient WorkCalendar actualExceptionsCalendar; // for when user enters actual work during non-work time of calendar
1082: private transient WorkingCalendar intersectionCalendar = null;
1083: private transient TaskSchedule taskSchedule = null;
1084:
1085: private transient Resource resource;
1086: private transient Task task;
1087: private transient Delayable delayable;
1088: private int requestDemandType = RequestDemandType.NONE;
1089: private int costRateIndex = CostRateTables.DEFAULT;
1090: private long overtimeWork = 0; // allowed overtime that is evenly distributed across contour
1091:
1092: private WorkingCalendar baselineCalendar = null; // only applies if this is a baseline
1093: */
1094: AssignmentDetail a = new AssignmentDetail();
1095: a.rate = (Rate) rate.clone();
1096: a.percentComplete = percentComplete;
1097: a.duration = duration;
1098: a.workContour = (AbstractContour) workContour.clone();
1099: a.costContour = costContour; //(AbstractContour)costContour.clone();
1100: a.actualExceptionsCalendar = actualExceptionsCalendar; //(WorkCalendar) actualExceptionsCalendar.clone();
1101: a.intersectionCalendar = intersectionCalendar; // (WorkingCalendar) intersectionCalendar.clone();
1102: a.taskSchedule = taskSchedule; // copy not clone
1103: a.resource = resource;
1104: a.task = task;
1105: a.delayable = (Delayable) ((DelayableImpl) delayable).clone();
1106: a.requestDemandType = requestDemandType;
1107: a.costRateIndex = costRateIndex;
1108: a.overtimeWork = overtimeWork;
1109: a.baselineCalendar = baselineCalendar;
1110: return a;
1111: // }
1112: // catch (CloneNotSupportedException e) {
1113: // throw new InternalError();
1114: // }
1115: }
1116:
1117: public long getOvertimeWork() {
1118: return overtimeWork;
1119: }
1120:
1121: public void setResource(Resource resource) {
1122: this .resource = resource;
1123: }
1124:
1125: public void setTask(Task task) {
1126: this .task = task;
1127: }
1128:
1129: boolean hasDuration() {
1130: return duration != 0;
1131: }
1132:
1133: public final boolean isTemporal() {
1134: return rate.getTimeUnit() != TimeUnit.NON_TEMPORAL;
1135: }
1136:
1137: /**
1138: * @param timeUnit
1139: */
1140: void setRateUnit(int timeUnit) {
1141: rate.setTimeUnit(timeUnit);
1142: }
1143:
1144: public final Rate getRate() {
1145: return rate;
1146: }
1147:
1148: public final void setRate(Rate rate) {
1149: this .rate = rate;
1150: }
1151:
1152: public void invalidateAssignmentCalendar() {
1153: if (intersectionCalendar != null)
1154: intersectionCalendar = null;
1155: if (actualExceptionsCalendar != null)
1156: actualExceptionsCalendar.invalidate();
1157: }
1158:
1159: /* (non-Javadoc)
1160: * @see com.projity.pm.calendar.HasCalendar#invalidateCalendar()
1161: */
1162: public Document invalidateCalendar() {
1163: return task.getOwningProject();
1164:
1165: }
1166:
1167: public boolean isJustModified() {
1168: Task task = getTask();
1169: if (task == null)
1170: return false;
1171: else
1172: return task.isJustModified();
1173: }
1174:
1175: public void addCalendarTime(long start, long end) {
1176: if (actualExceptionsCalendar == null) {
1177: WorkCalendar base = getEffectiveWorkCalendar();
1178: actualExceptionsCalendar = CalendarService.getInstance()
1179: .getStandardBasedInstance();
1180: try {
1181: actualExceptionsCalendar.setBaseCalendar(base);
1182: } catch (CircularDependencyException e) {
1183: // TODO Auto-generated catch block
1184: e.printStackTrace();
1185: }
1186: }
1187: actualExceptionsCalendar.addCalendarTime(start, end);
1188: }
1189:
1190: public void setComplete(boolean complete) {
1191: ScheduleUtil.setComplete(this , complete);
1192: }
1193:
1194: public boolean isComplete() {
1195: return getPercentComplete() == 1.0D;
1196: }
1197:
1198: public final long getEarliestStop() {
1199: return getStop();
1200: }
1201:
1202: public final long getCompletedThrough() {
1203: return getStop();
1204: }
1205:
1206: public void setCompletedThrough(long completedThrough) {
1207: setStop(completedThrough);
1208: }
1209:
1210: public Object backupDetail() {
1211: return clone();
1212: }
1213:
1214: public void restoreDetail(Object source, Object detail,
1215: boolean isChild) {
1216: }
1217:
1218: }
|