Improved: Convert ProjectServices.xml mini-lang to groovyDSL (OFBIZ-13078)
Convert following services :
* createTimeEntryInTimesheet
* updateTimeEntryByWorkEffort
* getProject
* createProject
* updateProject
* updateProjectRole
* createProjectTask
* copyProject
* copyProjectToTemplate
* scheduleProject
* getProjectPhaseList
* getProjectTaskList
* getProjectTask
* getProjectsByParties
* getTasksByParties
* updateTaskAndRelatedInfo
* updateTaskAssigment
* addProjectTimeToNewInvoice
* addProjectTimeToInvoice
* addValidationPartiesToTask
diff --git a/projectmgr/minilang/ProjectServices.xml b/projectmgr/minilang/ProjectServices.xml
deleted file mode 100644
index bb756b2..0000000
--- a/projectmgr/minilang/ProjectServices.xml
+++ /dev/null
@@ -1,1531 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
- Licensed to the Apache Software Foundation (ASF) under one
- or more contributor license agreements. See the NOTICE file
- distributed with this work for additional information
- regarding copyright ownership. The ASF licenses this file
- to you under the Apache License, Version 2.0 (the
- "License"); you may not use this file except in compliance
- with the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing,
- software distributed under the License is distributed on an
- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- KIND, either express or implied. See the License for the
- specific language governing permissions and limitations
- under the License.
--->
-
-<simple-methods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns="http://ofbiz.apache.org/Simple-Method" xsi:schemaLocation="http://ofbiz.apache.org/Simple-Method http://ofbiz.apache.org/dtds/simple-methods.xsd">
-
- <simple-method method-name="updateProject" short-description="Update a project">
- <set-service-fields service-name="updateWorkEffort" map="parameters" to-map="updWorkEffort"/>
- <call-service service-name="updateWorkEffort" in-map-name="updWorkEffort"/>
- <if-not-empty field="parameters.organizationPartyId">
- <set field="updProjectRole.newPartyId" from-field="parameters.organizationPartyId"/>
- <set field="updProjectRole.roleTypeId" value="INTERNAL_ORGANIZATIO"/>
- <call-simple-method method-name="updProjectRole"/>
- </if-not-empty>
- <if-not-empty field="parameters.clientBillingPartyId">
- <set field="updProjectRole.newPartyId" from-field="parameters.clientBillingPartyId"/>
- <set field="updProjectRole.roleTypeId" value="CLIENT_BILLING"/>
- <call-simple-method method-name="updProjectRole"/>
- </if-not-empty>
- <if-not-empty field="parameters.emailAddress">
- <if-validate-method field="parameters.emailAddress" method="isEmail">
- <else><add-error><fail-property resource="PartyUiLabels" property="PartyEmailAddressNotFormattedCorrectly"/></add-error></else>
- </if-validate-method>
- <check-errors/>
-
- <!-- find exist e-mail address. if have not exist then create new-->
- <entity-condition entity-name="WorkEffortContactMechView" list="existEmailAddresses">
- <condition-list combine="and">
- <condition-expr field-name="workEffortId" from-field="parameters.workEffortId"/>
- <condition-expr field-name="contactMechTypeId" value="EMAIL_ADDRESS"/>
- <condition-expr field-name="infoString" from-field="parameters.emailAddress" ignore-case="true"/>
- </condition-list>
- </entity-condition>
- <filter-list-by-date list="existEmailAddresses"/>
- <if-empty field="existEmailAddresses">
- <!-- expire old work effort's e-mail address -->
- <entity-and list="oldEmailAddresses" entity-name="WorkEffortContactMechView">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- <field-map field-name="contactMechTypeId" value="EMAIL_ADDRESS"/>
- </entity-and>
- <filter-list-by-date list="oldEmailAddresses"/>
- <iterate list="oldEmailAddresses" entry="oldEmailAddress">
- <entity-and list="oldWorkEffortContactMechs" entity-name="WorkEffortContactMech">
- <field-map field-name="workEffortId" from-field="oldEmailAddress.workEffortId"/>
- <field-map field-name="contactMechId" from-field="oldEmailAddress.contactMechId"/>
- </entity-and>
- <first-from-list list="oldWorkEffortContactMechs" entry="oldWorkEffortContactMech"/>
- <now-timestamp field="oldWorkEffortContactMech.thruDate"/>
- <store-value value-field="oldWorkEffortContactMech"/>
- </iterate>
-
- <!-- create new work effort's e-mail address -->
- <set field="emailAddressIn.emailAddress" from-field="parameters.emailAddress"/>
- <set field="emailAddressIn.contactMechTypeId" value="EMAIL_ADDRESS"/>
- <call-service service-name="createEmailAddress" in-map-name="emailAddressIn">
- <result-to-field result-name="contactMechId"/>
- </call-service>
- <set field="workEffortContactMechIn.workEffortId" from-field="parameters.workEffortId"/>
- <set field="workEffortContactMechIn.contactMechId" from-field="contactMechId"/>
- <set field="workEffortContactMechIn.contactMechTypeId" value="EMAIL_ADDRESS"/>
- <set field="workEffortContactMechIn.infoString" from-field="parameters.emailAddress"/>
- <call-service service-name="createWorkEffortContactMech" in-map-name="workEffortContactMechIn"/>
- </if-empty>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="updProjectRole"
- short-description="update/create a specif role and type for a project
- input map: updProjectRole.newPartyId, updProjectRole.roleTypeId">
- <entity-and entity-name="WorkEffortPartyAssignment" list="workEffortPartyAssignments" filter-by-date="true">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- <field-map field-name="roleTypeId" from-field="updProjectRole.roleTypeId"/>
- </entity-and>
- <!-- end current record if required -->
- <if-not-empty field="workEffortPartyAssignments">
- <first-from-list list="workEffortPartyAssignments" entry="workEffortPartyAssignment"/>
- <if-compare-field field="updProjectRole.newPartyId" operator="not-equals" to-field="workEffortPartyAssignment.partyId">
- <now-timestamp field="workEffortPartyAssignment.thruDate"/>
- <store-value value-field="workEffortPartyAssignment"/>
- </if-compare-field>
- </if-not-empty>
- <!-- create new record if required -->
- <if>
- <condition>
- <or>
- <if-empty field="workEffortPartyAssignments"/>
- <and>
- <not><if-empty field="workEffortPartyAssignments"/></not>
- <if-compare-field field="updProjectRole.newPartyId" operator="not-equals" to-field="workEffortPartyAssignment.partyId"/>
- </and>
- </or>
- </condition>
- <then>
- <make-value entity-name="WorkEffortPartyAssignment" value-field="workEffortPartyAssignment"/>
- <set field="workEffortPartyAssignment.workEffortId" from-field="parameters.workEffortId"/>
- <set field="workEffortPartyAssignment.partyId" from-field="updProjectRole.newPartyId"/>
- <set field="workEffortPartyAssignment.roleTypeId" from-field="updProjectRole.roleTypeId"/>
- <now-timestamp field="workEffortPartyAssignment.fromDate"/>
- <create-value value-field="workEffortPartyAssignment"/>
- </then>
- </if>
- </simple-method>
-
- <simple-method method-name="createProjectTask" short-description="Create a project task and optionally assign">
- <!-- create task -->
- <if-empty field="parameters.statusId">
- <set field="parameters.statusId" value="PTS_CREATED"/>
- </if-empty>
- <call-simple-method method-name="createWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- <set field="parameters.workEffortId" from-field="newEntity.workEffortId"/>
- <!-- optionally assign to party -->
- <if-not-empty field="parameters.partyId">
- <call-simple-method method-name="assignPartyToWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- </if-not-empty>
- <!-- optionally enter estimated time and required skill -->
- <if-not-empty field="parameters.estimatedHours">
- <set field="parameters.estimatedDuration" from-field="parameters.estimatedHours" type="Double"/>
- <if-empty field="parameters.skillTypeId">
- <set field="parameters.skillTypeId" value="_NA_"/>
- </if-empty>
- <set-service-fields service-name="createWorkEffortSkillStandard" map="parameters" to-map="createWorkEffortSkillStandard"/>
- <call-service service-name="createWorkEffortSkillStandard" in-map-name="createWorkEffortSkillStandard"/>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="updateTaskAndRelatedInfo" short-description="Update the task and when info is provided update the related information too">
- <set-service-fields service-name="updateWorkEffort" map="parameters" to-map="updateWorkeffort"/>
- <call-service service-name="updateWorkEffort" in-map-name="updateWorkeffort"/>
- <if-not-empty field="parameters.estimatedDuration">
- <set-service-fields service-name="updateWorkEffortSkillStandard" map="parameters" to-map="updateWorkEffortSkillStandard"/>
- <if-empty field="parameters.skillTypeId">
- <entity-and entity-name="WorkEffortSkillStandard" list="workEffortSkillStandards">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- </entity-and>
- <if-not-empty field="workEffortSkillStandards">
- <first-from-list list="workEffortSkillStandards" entry="workEffortSkillStandard"/>
- <set field="updateWorkEffortSkillStandard.skillTypeId" from-field="workEffortSkillStandard.skillTypeId"/>
- <else>
- <set field="updateWorkEffortSkillStandard.skillTypeId" value="_NA_"/>
- </else>
- </if-not-empty>
- </if-empty>
- <entity-one entity-name="WorkEffortSkillStandard" value-field="workEffortSkillStandard">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- <field-map field-name="skillTypeId" from-field="updateWorkEffortSkillStandard.skillTypeId"/>
- </entity-one>
- <if-not-empty field="workEffortSkillStandard">
- <call-service service-name="updateWorkEffortSkillStandard" in-map-name="updateWorkEffortSkillStandard"/>
- <else>
- <call-service service-name="createWorkEffortSkillStandard" in-map-name="updateWorkEffortSkillStandard"/>
- </else>
- </if-not-empty>
- </if-not-empty>
- <!-- if required can update more task related info here -->
- </simple-method>
-
- <simple-method method-name="updateTaskAssigment"
- short-description="Update task to resource assignment, if required create a new one by re-assigment">
- <field-to-result field="parameters.workEffortId" result-name="workEffortId"/>
- <if>
- <!-- check if a change in partyId Or roletypeId: need to delete and create new -->
- <condition>
- <or>
- <and>
- <not><if-empty field="parameters.newPartyId"/></not>
- <if-compare-field field="parameters.partyId" to-field="parameters.newPartyId" operator="not-equals"/>
- </and>
- <and>
- <not><if-empty field="parameters.newRoleTypeId"/></not>
- <if-compare-field field="parameters.roleTypeId" to-field="parameters.newRoleTypeId" operator="not-equals"/>
- </and>
- </or>
- </condition>
- <then>
- <!-- roleType and/or partyId changed: end old and create new assign-->
- <entity-one entity-name="WorkEffortPartyAssignment" value-field="workEffortPartyAssignment"/>
- <set field="workEffortPartyAssignment.delegateReasonEnumId" from-field="parameters.delegateReasonEnumId"/>
- <set field="workEffortPartyAssignment.comments" from-field="parameters.comments"/>
- <set field="workEffortPartyAssignment.assignedByUserLoginId" from-field="userLogin.userLoginId"/>
- <now-timestamp field="workEffortPartyAssignment.thruDate"/>
- <store-value value-field="workEffortPartyAssignment"/>
- <!-- create a new one -->
- <make-value entity-name="WorkEffortPartyAssignment" value-field="newAssign"/>
- <set field="newAssign.workEffortId" from-field="parameters.workEffortId"/>
- <set field="newAssign.partyId" from-field="parameters.newPartyId"/>
- <set field="newAssign.roleTypeId" from-field="parameters.newRoleTypeId"/>
- <set field="newAssign.assignedByUserLoginId" from-field="userLogin.userLoginId"/>
- <now-timestamp field="newAssign.fromDate"/>
- <set field="newAssign.statusId" value="PAS_ASSIGNED"/>
- <create-value value-field="newAssign"/>
- </then>
- <else>
- <set field="fromDate" from-field="parameters.fromDate" type="Timestamp"/>
- <entity-one entity-name="WorkEffortPartyAssignment" value-field="assignment">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- <field-map field-name="partyId" from-field="parameters.partyId"/>
- <field-map field-name="roleTypeId" from-field="parameters.roleTypeId"/>
- <field-map field-name="fromDate" from-field="fromDate"/>
- </entity-one>
- <if-not-empty field="assignment">
- <!-- status changed or assignment ended -->
- <if-compare field="parameters.statusId" value="PAS_ENDED" operator="equals">
- <!-- special case to indicate end of assignment -->
- <now-timestamp field="assignment.thruDate"/>
- <clear-field field="parameters.statusId"/>
- </if-compare>
- <set-nonpk-fields map="parameters" value-field="assignment"/>
- <store-value value-field="assignment"/>
- <if-compare field="assignment.statusId" value="PAS_COMPLETED" operator="equals">
- <call-simple-method method-name="updateTaskStatusToComplete"/>
- </if-compare>
- <else>
- <!-- new assignment -->
- <call-simple-method method-name="assignPartyToWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- </else>
- </if-not-empty>
- </else>
- </if>
- </simple-method>
-
- <simple-method method-name="updateTaskStatusToComplete"
- short-description="Check partyassignments on a task, if all completes set task status to completed and set actual completiondate to now">
- <entity-and entity-name="WorkEffortPartyAssignment" list="assignments" filter-by-date="true">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- </entity-and>
- <!-- check if all open assignments were completed -->
- <if-not-empty field="assignments">
- <iterate list="assignments" entry="assignment">
- <if-compare field="assignment.statusId" value="PAS_COMPLETED" operator="not-equals">
- <set field="status" value="notcomplete"/>
- </if-compare>
- </iterate>
- </if-not-empty>
- <if-empty field="status">
- <now-timestamp field="parameters.actualCompletionDate"/>
- <set field="parameters.currentStatusId" value="PTS_COMPLETED"/>
- <call-simple-method method-name="updateWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- <!-- check for related customer request, set these too to completed -->
- <entity-and entity-name="CustRequestWorkEffort" list="custRequests">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- </entity-and>
- <if-not-empty field="custRequests">
- <iterate list="custRequests" entry="custReq">
- <set field="updStat.custRequestId" from-field="custReq.custRequestId"/>
- <set field="updStat.statusId" value="CRQ_COMPLETED"/>
- <call-service service-name="updateCustRequest" in-map-name="updStat"/>
- </iterate>
- </if-not-empty>
- </if-empty>
- </simple-method>
-
- <simple-method method-name="scheduleProject" short-description="Project Scheduler sets the planningdates according task requirements and available resources">
- <!--
- theory behind the program
- - - - - - - - - - - - - -
- (program under development)
- Assumptions for tasks and resources
- 1. a workday has 8 hours.
- 2. a workweek has 40 hours and 5 days.
- 3. The order of the execution of the tasks is set by the workeffortassociation.
- 4. The default start of the project is today
- 5. default length of a task is 3 day if not planned hours entered
-
- The steps of the program are:
- 1. read all tasks and check if there are predesessors, when not set he estimated dates
- for critical path processing:
- * ES - Earliest Start time
- * EF - Earliest Finish time
- * LS - Latest Start time
- * LF - Latest Finish time
-
- EF = LF task is on the critical path
-
- 2. call a recursive java function to set all the dependant tasks.
- -->
- <field-to-result field="parameters.projectId" result-name="projectId"/>
-
- <!-- find a starting point being either the estimated start date of a project or the earliest actual start date. -->
- <entity-condition entity-name="ProjectAndPhaseAndTask" list="tasks">
- <condition-expr field-name="actualStartDate" operator="not-equals" from-field="null"/>
- <order-by field-name="-actualStartDate"/>
- </entity-condition>
-
- <if-not-empty field="tasks">
- <!-- remove all estimated dates -->
- <iterate list="tasks" entry="task">
- <clear-field field="task.estimatedStartDate"/>
- <clear-field field="task.estimatedCompletionDate"/>
- </iterate>
-
- <first-from-list list="tasks" entry="task"/>
- <set field="startDate" from-field="task.actualStartDate"/>
- <set field="taskId" from-field="task.workEffortId"/>
- <else>
- <now-timestamp field="generalStartDate"/>
- </else>
- </if-not-empty>
- <while>
- <condition>
- <if-empty field="generalStartDate"/>
- </condition>
- <then>
- <entity-and entity-name="WorkEffortAssoc" list="assocs">
- <field-map field-name="workEffortIdTo" from-field="taskId"/>
- </entity-and>
- <if-not-empty field="assocs">
- <iterate list="assocs" entry="assoc">
- <clear-field field="hours"/>
- <set field="getTask.taskId" from-field="assoc.workEffortIdFrom"/>
- <call-service service-name="getProjectTask" in-map-name="getTask">
- <result-to-field result-name="estimatedHours"/>
- <result-to-field result-name="actualHours"/>
- </call-service>
- <if-not-empty field="estimatedHours">
- <if-not-empty field="actualHours">
- <if-compare-field field="estimatedHours" operator="greater" to-field="actualHours">
- <set field="hours" from-field="estimatedHours" type="Double"/>
- <else>
- <set field="hours" from-field="actualHours" type="Double"/>
- </else>
- </if-compare-field>
- <else>
- <set field="hours" from-field="estimatedHours" type="Double"/>
- </else>
- </if-not-empty>
- </if-not-empty>
- <if-not-empty field="actualHours">
- <set field="hours" from-field="actualHours" type="Double"/>
- </if-not-empty>
- <if-empty field="hours">
- <set field="hours" value="16" type="Double"/>
- </if-empty>
- <if-empty field="highestHours">
- <set field="highestHours" from-field="hours" type="Double"/>
- <set field="preDesessorId" from-field="assoc.workEffortIdFrom"/>
- <else>
- <if-compare-field field="highestHours" operator="less" to-field="hours">
- <set field="highestHours" from-field="hours" type="Double"/>
- <set field="preDesessorId" from-field="assoc.workEffortIdFrom"/>
- </if-compare-field>
- </else>
- </if-empty>
- </iterate>
- <set field="taskId" from-field="preDesessorId"/>
- <calculate field="taskDays">
- <calcop operator="divide" field="higestHours"/>
- <number value="8"/>
- </calculate>
- <calculate field="taskDays">
- <calcop operator="multiply" field="taskDays"/>
- <number value="-1"/>
- </calculate>
- <call-class-method class-name="org.apache.ofbiz.base.util.UtilDateTime" method-name="addDaysToTimestamp" ret-field="startDate">
- <field field="startDate"/>
- <field field="taskDays"/>
- </call-class-method>
- <else>
- <entity-one entity-name="WorkEffort" value-field="workEffort">
- <field-map field-name="workEffortId" from-field="taskId"/>
- </entity-one>
- <if-not-empty field="workEffortId.parentWorkEffortId">
- <set field="taskId" from-field="workEffortId.parentWorkEffortId"/>
- <else>
- <set field="generalStartDate" from-field="startDate"/>
- </else>
- </if-not-empty>
- </else>
- </if-not-empty>
- </then>
- </while>
-
- <!-- create the tasklist -->
- <entity-one entity-name="WorkEffort" value-field="project">
- <field-map field-name="workEffortId" from-field="parameters.projectId"/>
- </entity-one>
- <get-related value-field="project" relation-name="ChildWorkEffort" list="phases"/>
- <if-not-empty field="phases">
- <iterate list="phases" entry="phase">
- <get-related value-field="phase" relation-name="ChildWorkEffort" list="tasks"/>
- <if-not-empty field="tasks">
- <iterate list="tasks" entry="task">
- <get-related value-field="task" relation-name="ToWorkEffortAssoc" list="t.prevTasks"/>
- <if-empty field="t.prevTasks">
- <!-- no predecessors so i can set the dates -->
- <now-timestamp field="upd.estimatedStartDate"/>
- <now-timestamp field="currentDate"/>
- <call-class-method class-name="org.apache.ofbiz.project.Various" method-name="calculateCompletionDate" ret-field="upd.estimatedCompletionDate">
- <field field="task" type="org.apache.ofbiz.entity.GenericValue"/>
- <field field="currentDate" type="java.sql.Timestamp"/>
- </call-class-method>
- <set field="upd.workEffortId" from-field="task.workEffortId"/>
- <call-service service-name="updateWorkEffort" in-map-name="upd"/>
- <entity-one entity-name="WorkEffort" value-field="newTask">
- <field-map field-name="workEffortId" from-field="task.workEffortId"/>
- </entity-one>
- <call-class-method class-name="org.apache.ofbiz.project.Various" method-name="setDatesFollowingTasks">
- <field field="newTask" type="org.apache.ofbiz.entity.GenericValue"/>
- </call-class-method>
- </if-empty>
- </iterate>
- </if-not-empty>
- </iterate>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="updateTimeEntryByWorkeffort" short-description="Update workeffort by workeffortId and timesheetId ">
- <field-to-result field="parameters.timesheetId" result-name="timesheetId"/>
- <if-empty field="parameters.workEffortId">
- <return/>
- </if-empty>
- <if-compare field="parameters.workEffortId" operator="equals" value="Totals">
- <return/>
- </if-compare>
- <entity-one entity-name="Timesheet" value-field="timesheet"/>
-
- <!-- check if party assigned to task, when not add with roletype of project, if assigned check status -->
- <entity-and entity-name="WorkEffortPartyAssignment" list="assigns" filter-by-date="true">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- <field-map field-name="partyId" from-field="timesheet.partyId"/>
- </entity-and>
- <if-empty field="assigns">
- <set field="getpr.taskId" from-field="parameters.workEffortId"/>
- <call-service service-name="getProjectIdAndNameFromTask" in-map-name="getpr">
- <result-to-field result-name="projectId"/>
- </call-service>
- <entity-and entity-name="WorkEffortPartyAssignment" list="projectAssigns" filter-by-date="true">
- <field-map field-name="workEffortId" from-field="projectId"/>
- <field-map field-name="partyId" from-field="timesheet.partyId"/>
- </entity-and>
- <first-from-list list="projectAssigns" entry="projectAssign"/>
- <set field="parameters.partyId" from-field="timesheet.partyId"/>
- <set field="parameters.roleTypeId" from-field="projectAssign.roleTypeId"/>
- <set field="parameters.statusId" value="PAS_ASSIGNED"/>
- <call-simple-method method-name="assignPartyToWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- </if-empty>
- <check-errors/>
-
- <!-- check if the actual start date is set, when not set it to todays date -->
- <if-empty field="project.actualStartDate">
- <entity-one entity-name="WorkEffort" value-field="workEffort"/>
- <now-timestamp field="workEffort.actualStartDate"/>
- <store-value value-field="workEffort"/>
- </if-empty>
-
- <get-related value-field="timesheet" relation-name="TimeEntry" list="timeEntries"/>
-
- <!-- update existing entries -->
- <set field="hours" value="0" type="Double"/>
- <if-not-empty field="timeEntries">
- <iterate list="timeEntries" entry="timeEntry">
- <if-compare-field field="timeEntry.workEffortId" to-field="parameters.workEffortId" operator="equals">
- <if-compare-field field="timeEntry.rateTypeId" to-field="parameters.rateTypeId" operator="equals">
- <if-not-empty field="timeEntry.hours">
- <remove-value value-field="timeEntry"/>
- <else>
- <!-- translate the date into the daynumber -->
- <call-class-method class-name="org.apache.ofbiz.base.util.UtilDateTime" method-name="getIntervalInDays" ret-field="dayNumber">
- <field field="timesheet.fromDate" type="java.sql.Timestamp"/>
- <field field="timeEntry.fromDate" type="java.sql.Timestamp"/>
- </call-class-method>
- <!-- get the related field -->
- <if-not-empty field="parameters.hoursDay${dayNumber}">
- <set field="hours" from-field="parameters.hoursDay${dayNumber}" type="Double"/>
- <else>
- <set field="hours" value="0" type="Double"/>
- </else>
- </if-not-empty>
- <call-simple-method method-name="updateTimeEntry"/>
- <set field="parameters.hoursDay${dayNumber}" value="-999999" type="Double"/>
- </else>
- </if-not-empty>
- </if-compare-field>
- </if-compare-field>
- </iterate>
- </if-not-empty>
-
- <!-- process not yet done fields -->
- <loop count="7" field="dayNr">
- <if-not-empty field="parameters.hoursDay${dayNr}">
- <if-compare field="parameters.hoursDay${dayNr}" value="-999999" operator="not-equals">
- <set field="hours" from-field="parameters.hoursDay${dayNr}" type="Double"/>
- <call-class-method class-name="org.apache.ofbiz.base.util.UtilDateTime" method-name="addDaysToTimestamp" ret-field="fromDate">
- <field field="timesheet.fromDate" type="java.sql.Timestamp"/>
- <field field="dayNr" type="int"/>
- </call-class-method>
- <call-simple-method method-name="updateTimeEntry"/>
- </if-compare>
- </if-not-empty>
- </loop>
-
- <!-- update the assignment status if required -->
- <if-compare field="parameters.checkComplete" value="Y" operator="equals">
- <entity-and entity-name="WorkEffortPartyAssignment" list="assigns" filter-by-date="true">
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- <field-map field-name="partyId" from-field="timesheet.partyId"/>
- </entity-and>
- <first-from-list list="assigns" entry="alreadyAssign"/>
- <if-compare field="alreadyAssign.statusId" value="PAS_COMPLETED" operator="not-equals">
- <set field="upStat.partyId" from-field="timesheet.partyId"/>
- <set field="upStat.statusId" value="PAS_COMPLETED"/>
- <set field="upStat.roleTypeId" from-field="alreadyAssign.roleTypeId"/>
- <set field="upStat.workEffortId" from-field="parameters.workEffortId"/>
- <set field="upStat.fromDate" from-field="alreadyAssign.fromDate"/>
- <call-service service-name="updateTaskAssigment" in-map-name="upStat"/>
- </if-compare>
- </if-compare>
- </simple-method>
-
- <simple-method method-name="getProjectIdAndNameFromTask" short-description="Get the projectId when a phase or task is provided." login-required="true">
- <if-empty field="parameters.taskId">
- <if-empty field="parameters.phaseId">
- <return/>
- </if-empty>
- </if-empty>
- <if-not-empty field="parameters.taskId"><!-- taskId is provided -->
- <entity-one entity-name="WorkEffort" value-field="task">
- <field-map field-name="workEffortId" from-field="parameters.taskId"/>
- </entity-one>
- <if-not-empty field="task">
- <get-related-one value-field="task" relation-name="ParentWorkEffort" to-value-field="phase"/>
- <else>
- <return/>
- </else>
- </if-not-empty>
- <else><!-- phaseId is provided -->
- <entity-one entity-name="WorkEffort" value-field="phase">
- <field-map field-name="workEffortId" from-field="parameters.phaseId"/>
- </entity-one>
- </else>
- </if-not-empty>
- <!-- get project info -->
- <if-not-empty field="phase">
- <get-related-one value-field="phase" relation-name="ParentWorkEffort" to-value-field="project"/>
- <field-to-result field="project.workEffortId" result-name="projectId"/>
- <field-to-result field="project.workEffortName" result-name="projectName"/>
- <field-to-result field="phase.workEffortId" result-name="phaseId"/>
- <field-to-result field="phase.workEffortName" result-name="phaseName"/>
- <field-to-result field="task.workEffortId" result-name="taskId"/>
- <field-to-result field="task.workEffortName" result-name="taskName"/>
- <set field="taskWbsId" value="${project.workEffortId}.${phase.sequenceNum}.${task.sequenceNum}"/>
- <field-to-result field="taskWbsId"/>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="copyProjectToTemplate" short-description="copy a project to a workeffortType starting with 'template'">
- <set field="toTemplate" value="dummy"/>
- <call-simple-method method-name="copyProject"/>
- </simple-method>
-
- <simple-method method-name="copyProject" short-description="copy a project with related phases and tasks however no actual data">
- <entity-one entity-name="WorkEffort" value-field="project">
- <field-map field-name="workEffortId" from-field="parameters.projectId"/>
- </entity-one>
-
- <if-empty field="project">
- <field-to-result field="parameters.projectId" result-name="projectId"/>
- <add-error error-list-name="error_list">
- <fail-property resource="ProjectMgrUiLabels" property="ProjectMgrErrorProjectNotFound"/>
- </add-error>
- </if-empty>
- <if-empty field="parameters.workEffortName">
- <set field="parameters.workEffortName" from-field="project.workEffortName"/>
- </if-empty>
- <if-empty field="parameters.description">
- <set field="parameters.description" from-field="project.description"/>
- </if-empty>
- <if-not-empty field="toTemplate">
- <set field="parameters.workEffortTypeId" value="PROJECT_TEMPLATE"/>
- <else>
- <set field="parameters.workEffortTypeId" value="PROJECT"/>
- </else>
- </if-not-empty>
- <set field="parameters.currentStatusId" value="PRJ_ACTIVE"/>
- <clear-field field="parameters.workEffortId"/>
- <call-simple-method method-name="createWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- <set field="newProjectId" from-field="newEntity.workEffortId"/>
-
- <!-- copy assigned parties -->
- <get-related value-field="project" relation-name="WorkEffortPartyAssignment" list="partiesAll"/>
- <filter-list-by-date list="partiesAll" to-list="parties"/>
- <if-not-empty field="parties">
- <iterate list="parties" entry="party">
- <set field="parameters.workEffortId" from-field="newProjectId"/>
- <set field="parameters.partyId" from-field="party.partyId"/>
- <set field="parameters.roleTypeId" from-field="party.roleTypeId"/>
- <set field="parameters.statusId" value="PAS_ASSIGNED"/>
- <call-simple-method method-name="assignPartyToWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- </iterate>
- </if-not-empty>
-
- <get-related value-field="project" relation-name="ChildWorkEffort" list="phases"/>
- <if-not-empty field="phases">
- <iterate list="phases" entry="phase">
- <clear-field field="newPhase"/>
- <if-not-empty field="toTemplate">
- <set field="parameters.workEffortTypeId" value="PHASE_TEMPLATE"/>
- <else>
- <set field="parameters.workEffortTypeId" value="PHASE"/>
- </else>
- </if-not-empty>
- <set field="parameters.workEffortName" from-field="phase.workEffortName"/>
- <set field="parameters.workEffortParentId" from-field="newProjectId"/>
- <set field="parameters.currentStatusId" value="_NA_"/>
- <clear-field field="parameters.workEffortId"/>
- <call-simple-method method-name="createWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- <set field="newPhaseId" from-field="newEntity.workEffortId"/>
-
- <get-related value-field="phase" relation-name="ChildWorkEffort" list="tasks"/>
- <if-not-empty field="tasks">
- <iterate list="tasks" entry="task">
- <clear-field field="newTask"/>
- <if-not-empty field="toTemplate">
- <set field="newTask.workEffortTypeId" value="TASK_TEMPLATE"/>
- <else>
- <set field="newTask.workEffortTypeId" value="TASK"/>
- </else>
- </if-not-empty>
- <set field="parameters.workEffortName" from-field="task.workEffortName"/>
- <set field="parameters.priority" from-field="task.priority"/>
- <set field="parameters.workEffortParentId" from-field="newPhaseId"/>
- <set field="parameters.currentStatusId" value="PTS_CREATED"/>
- <clear-field field="parameters.workEffortId"/>
- <call-simple-method method-name="createWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- </iterate>
- </if-not-empty>
- </iterate>
- </if-not-empty>
- <field-to-result field="newProjectId" result-name="projectId"/>
- </simple-method>
-
- <simple-method method-name="getProject" short-description="get Project information" login-required="true">
- <if-empty field="parameters.projectId">
- <return/>
- </if-empty>
- <if-not-empty field="parameters.partyId">
- <set field="parameters.hoursPartyId" from-field="parameters.partyId"/>
- </if-not-empty>
- <entity-one entity-name="WorkEffort" value-field="project">
- <field-map field-name="workEffortId" from-field="parameters.projectId"/>
- </entity-one>
- <set field="highInfo.projectId" from-field="project.workEffortId"/>
- <set field="highInfo.projectName" from-field="project.workEffortName"/>
- <set field="highInfo.projectDescription" from-field="project.description"/>
- <set field="highInfo.estimatedStartDate" from-field="project.estimatedStartDate"/>
- <set field="highInfo.estimatedCompletionDate" from-field="project.estimatedCompletionDate"/>
- <set field="highInfo.actualStartDate" from-field="project.actualStartDate"/>
- <set field="highInfo.actualCompletionDate" from-field="project.actualCompletionDate"/>
- <set field="highInfo.scopeEnumId" from-field="project.scopeEnumId"/>
- <set field="highInfo.createdStamp" from-field="project.createdStamp"/>
- <entity-one entity-name="StatusItem" value-field="highSeq">
- <field-map field-name="statusId" from-field="project.currentStatusId"/>
- </entity-one>
- <set field="highInfo.createdDate" from-field="project.createdDate"/>
- <set field="highInfo.parentProjectId" from-field="project.workEffortParentId"/>
- <!-- loop through the related phases and tasks -->
- <call-simple-method method-name="combineInfo"/>
-
- <!-- get e-mail address -->
- <entity-and entity-name="WorkEffortContactMechView" list="emailAddresses">
- <field-map field-name="workEffortId" from-field="highInfo.projectId"/>
- <field-map field-name="contactMechTypeId" value="EMAIL_ADDRESS"/>
- </entity-and>
- <filter-list-by-date list="emailAddresses"/>
- <if-not-empty field="emailAddresses">
- <first-from-list list="emailAddresses" entry="emailAddress"/>
- <set field="highInfo.emailAddress" from-field="emailAddress.infoString"/>
- </if-not-empty>
-
- <!-- closed overrules everything -->
- <if-compare field="project.currentStatusId" value="PRJ_CLOSED" operator="equals">
- <set field="highInfo.currentStatusId" value="PRJ_CLOSED"/>
- </if-compare>
-
- <call-simple-method method-name="createDates"/>
-
- <!-- results -->
- <field-to-result field="highInfo" result-name="projectInfo"/>
- <field-to-result field="parameters.projectId" result-name="projectId"/>
- </simple-method>
-
- <simple-method method-name="getProjectPhaseList" short-description="get Project Phase information" login-required="true">
- <if-empty field="parameters.projectId">
- <return/>
- </if-empty>
- <entity-and entity-name="WorkEffort" list="phases">
- <field-map field-name="workEffortTypeId" value="PHASE"/>
- <field-map field-name="workEffortParentId" from-field="parameters.projectId"/>
- <order-by field-name="sequenceNum"/>
- <order-by field-name="workEffortName"/>
- </entity-and>
- <if-not-empty field="phases">
- <iterate list="phases" entry="phase">
- <!-- get the phase seq id -->
- <entity-one entity-name="StatusItem" value-field="highSeq">
- <field-map field-name="statusId" from-field="phase.currentStatusId"/>
- </entity-one>
- <clear-field field="highInfo"/>
- <set field="highInfo.sequenceId" from-field="highSeq.sequenceId"/>
- <set field="highInfo.phaseId" from-field="phase.workEffortId"/>
- <set field="highInfo.phaseSeqNum" from-field="phase.sequenceNum"/>
- <set field="highInfo.phaseName" from-field="phase.workEffortName"/>
- <set field="highInfo.phaseDescription" from-field="phase.description"/>
- <set field="highInfo.scopeEnumId" from-field="phase.scopeEnumId"/>
-
- <!-- loop through the related tasks and combine information -->
- <call-simple-method method-name="combineInfo"/>
-
- <!-- merge estimated and actual dates -->
- <call-simple-method method-name="createDates"/>
-
- <field-to-list field="highInfo" list="phaseList"/>
- </iterate>
- </if-not-empty>
-
- <!-- results -->
- <field-to-result field="phaseList"/>
- <field-to-result field="parameters.projectId" result-name="projectId"/>
- </simple-method>
-
- <simple-method method-name="getProjectTaskList" short-description="get Project Phase/task information" login-required="true">
- <if-empty field="parameters.projectId">
- <return/>
- </if-empty>
- <entity-and entity-name="ProjectAndPhaseAndTask" list="tasks">
- <field-map field-name="projectId" from-field="parameters.projectId"/>
- <order-by field-name="phaseSeqNum"/>
- <order-by field-name="phaseName"/>
- <order-by field-name="sequenceNum"/>
- <order-by field-name="workEffortName"/>
- </entity-and>
- <if-not-empty field="tasks">
- <iterate list="tasks" entry="lowInfo">
- <set field="highInfo.phaseName" from-field="lowInfo.phaseName"/>
- <set field="highInfo.phaseSeqNum" from-field="lowInfo.phaseSeqNum"/>
- <set field="highInfo.taskId" from-field="lowInfo.workEffortId"/>
- <call-simple-method method-name="combineInfo"/>
- <clear-field field="highInfo.sequenceId"/>
- <call-simple-method method-name="createDates"/>
- <set field="highInfo.workEffortId" from-field="lowInfo.workEffortId"/>
- <set field="highInfo.workEffortName" from-field="lowInfo.workEffortName"/>
- <set field="highInfo.sequenceNum" from-field="lowInfo.sequenceNum"/>
- <field-to-list field="highInfo" list="taskList"/>
- <clear-field field="highInfo"/>
- </iterate>
- </if-not-empty>
- <!-- results -->
- <field-to-result field="taskList"/>
- <field-to-result field="parameters.projectId" result-name="projectId"/>
- </simple-method>
-
- <simple-method method-name="getProjectTask" short-description="get Project task information" login-required="true">
- <entity-one entity-name="WorkEffort" value-field="lowInfo">
- <field-map field-name="workEffortId" from-field="parameters.taskId"/>
- </entity-one>
- <set field="highInfo.taskId" from-field="lowInfo.workEffortId"/>
- <set field="highInfo.taskSeqNum" from-field="lowInfo.sequenceNum"/>
- <set field="highInfo.taskName" from-field="lowInfo.workEffortName"/>
- <set field="highInfo.taskDescription" from-field="lowInfo.description"/>
- <set field="highInfo.scopeEnumId" from-field="lowInfo.scopeEnumId"/>
- <set field="highInfo.workEffortParentId" from-field="lowInfo.workEffortParentId"/>
- <call-simple-method method-name="combineInfo"/>
- <field-to-result field="highInfo" result-name="taskInfo"/>
- </simple-method>
-
- <simple-method method-name="getProjectsByParties" short-description="get Project information by party member" login-required="true">
- <entity-condition entity-name="ProjectAndPhaseAndTaskParty" list="tasks">
- <condition-list combine="and">
- <condition-expr field-name="projectId" operator="equals" from-field="parameters.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="partyId" operator="equals" from-field="parameters.partyId" ignore-if-empty="true"/>
- <condition-expr field-name="partyId" operator="not-equals" from-field="null"/>
- </condition-list>
- <order-by field-name="projectId"/>
- <order-by field-name="partyId"/>
- </entity-condition>
- <if-not-empty field="tasks">
- <iterate list="tasks" entry="task">
- <if>
- <condition>
- <and>
- <not><if-empty field="projectParty"/></not>
- <if-compare-field field="task.partyId" to-field="projectParty.partyId" operator="not-equals"/>
- </and>
- </condition>
- <then>
- <field-to-list field="projectParty" list="projectParties"/>
- <clear-field field="projectParty"/>
- <clear-field field="highInfo"/>
- </then>
- </if>
-
- <if-empty field="projectParty">
- <set field="projectParty.partyId" from-field="task.partyId"/>
- <entity-one entity-name="PartyNameView" value-field="partyNameView">
- <field-map field-name="partyId" from-field="task.partyId"/>
- </entity-one>
- <if-not-empty field="partyNameView">
- <set field="projectParty.partyName" value="${partyNameView.lastName},${partyNameView.firstName}${partyNameView.groupName}"/>
- </if-not-empty>
- <set field="projectParty.roleTypeId" from-field="task.roleTypeId"/>
- <set field="projectParty.fromDate" from-field="task.fromDate"/>
- <set field="projectParty.thruDate" from-field="task.thruDate"/>
- </if-empty>
-
- <!-- get the planned/actual hours -->
- <set field="lowInfo" from-field="task"/>
- <set field="parameters.hoursPartyId" from-field="task.partyId"/>
- <call-simple-method method-name="getHours" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- <set field="projectParty.plannedHours" from-field="highInfo.plannedHours" type="Double"/>
- <set field="projectParty.actualHours" from-field="highInfo.actualHours" type="Double"/>
- </iterate>
- <if-not-empty field="projectParty">
- <field-to-list field="projectParty" list="projectParties"/>
- </if-not-empty>
- <if-not-empty field="projectParties">
- <field-to-result field="projectParties"/>
- </if-not-empty>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="getTasksByParties" short-description="get task information by party member" login-required="true">
- <!-- get the list of tasks optionaly selected for a party -->
- <entity-condition entity-name="WorkEffortPartyAssignment" list="tasks" filter-by-date="true">
- <condition-list combine="and">
- <condition-expr field-name="partyId" operator="equals" from-field="parameters.partyId" ignore-if-empty="true"/>
- <condition-expr field-name="workEffortId" operator="equals" from-field="parameters.workEffortId" ignore-if-empty="true"/>
- </condition-list>
- <order-by field-name="workEffortId"/>
- <order-by field-name="partyId"/>
- </entity-condition>
- <if-not-empty field="tasks">
- <iterate list="tasks" entry="task">
- <if>
- <condition >
- <and>
- <not>
- <if-empty field="taskParty"/>
- </not>
- <if-compare-field field="task.partyId" to-field="taskParty.partyId" operator="not-equals"/>
- </and>
- </condition>
- <then>
- <field-to-list field="taskParty" list="taskParties"/>
- <clear-field field="taskParty"/>
- <clear-field field="highInfo"/>
- </then>
- </if>
-
- <if-empty field="taskParty">
- <set field="taskParty.partyId" from-field="task.partyId"/>
- <entity-one entity-name="PartyNameView" value-field="partyNameView">
- <field-map field-name="partyId" from-field="task.partyId"/>
- </entity-one>
- <if-not-empty field="partyNameView">
- <set field="taskParty.partyName" value="${partyNameView.lastName},${partyNameView.firstName}${partyNameView.groupName}"/>
- </if-not-empty>
- <set field="taskParty.roleTypeId" from-field="task.roleTypeId"/>
- <set field="taskParty.statusId" from-field="task.statusId"/>
- <set field="taskParty.fromDate" from-field="task.fromDate"/>
- <set field="taskParty.thruDate" from-field="task.thruDate"/>
- </if-empty>
-
- <!-- get the planned hours -->
- <set field="lowInfo" from-field="task"/>
- <set field="parameters.hoursPartyId" from-field="task.partyId"/>
- <get-related-one value-field="task" relation-name="WorkEffort" to-value-field="lowInfo"/>
- <call-simple-method method-name="getHours" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- <set field="taskParty.plannedHours" from-field="highInfo.plannedHours" type="Double"/>
- <set field="taskParty.actualHours" from-field="highInfo.actualHours" type="Double"/>
- <set field="taskParty.originalActualHours" from-field="highInfo.originalActualHours" type="Double"/>
- </iterate>
- <if-not-empty field="taskParty">
- <field-to-list field="taskParty" list="taskParties"/>
- </if-not-empty>
- <if-not-empty field="taskParties">
- <field-to-result field="taskParties"/>
- </if-not-empty>
- </if-not-empty>
- </simple-method>
-
- <!-- Internal functions -->
- <simple-method method-name="combineStatusInfo" short-description="combine lower level status">
- <!-- the status for a project or phase is
- IN_PROGRESS if at least one task still in progress
- COMPLETED if all task are either completed or cancelled
- CREATED if other conditions does not apply
- For a task the status is
- IN_PROGRESS if it has at least one resource and at least a time entry
- ASSIGNED if it has at least one resource but no time entry associated
- -->
- <!-- if lowlevel type equals TASK then get create the status first -->
- <if-compare field="lowInfo.workEffortTypeId" value="TASK" operator="equals">
- <set field="highInfo.currentStatusId" from-field="lowInfo.currentStatusId"/>
- <if-compare field="lowInfo.currentStatusId" value="PTS_CREATED" operator="equals">
- <get-related value-field="lowInfo" relation-name="WorkEffortPartyAssignment" list="assignsAll"/>
- <filter-list-by-date list="assignsAll" to-list="assigns"/>
- <if-not-empty field="assigns">
- <set field="highInfo.currentStatusId" value="PTS_CREATED_AS"/><!-- task is ASSIGNED -->
- </if-not-empty>
- <get-related value-field="lowInfo" relation-name="TimeEntry" list="entries"/>
- <if-not-empty field="entries">
- <set field="highInfo.currentStatusId" value="PTS_CREATED_IP"/><!-- task is IN_PROGRESS -->
- </if-not-empty>
- </if-compare>
- <return/>
- </if-compare>
- <entity-count count-field="tasksCount" entity-name="ProjectPhaseTaskAssignmentView">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- </condition-list>
- </entity-count>
- <log level="info" message="related tasks count ====> ${tasksCount}"/>
- <entity-count count-field="completedTasks" entity-name="ProjectPhaseTaskAssignmentView">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- </condition-list>
- <having-condition-list combine="or">
- <condition-expr field-name="taskStatusId" operator="equals" value="PTS_COMPLETED"/>
- <condition-expr field-name="taskStatusId" operator="equals" value="PTS_CANCELLED"/>
- </having-condition-list>
- </entity-count>
- <log level="info" message="related completed tasks count ====> ${completedTasks}"/>
- <entity-count count-field="assignedTasks" entity-name="ProjectPhaseTaskAssignmentView">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- </condition-list>
- <having-condition-list combine="and">
- <condition-expr field-name="entriesCount" value="0"/>
- <condition-expr field-name="resourceCount" operator="greater-equals" value="1"/>
- <condition-expr field-name="taskStatusId" operator="not-equals" value="PTS_COMPLETED"/>
- <condition-expr field-name="taskStatusId" operator="not-equals" value="PTS_CANCELLED"/>
- </having-condition-list>
- </entity-count>
- <log level="info" message="related assigned tasks count ====> ${assignedTasks}"/>
- <entity-count count-field="inprogressTasks" entity-name="ProjectPhaseTaskAssignmentView">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- </condition-list>
- <having-condition-list combine="and">
- <condition-expr field-name="entriesCount" operator="greater-equals" value="1"/>
- <condition-expr field-name="resourceCount" operator="greater-equals" value="1"/>
- <condition-expr field-name="taskStatusId" operator="not-equals" value="PTS_COMPLETED"/>
- <condition-expr field-name="taskStatusId" operator="not-equals" value="PTS_CANCELLED"/>
- </having-condition-list>
- </entity-count>
- <log level="info" message="related in progress tasks count ====> ${inprogressTasks}"/>
- <if>
- <condition>
- <or>
- <if-compare field="inprogressTasks" type="Long" operator="greater" value="0"/>
- <if-compare field="assignedTasks" type="Long" operator="greater" value="0"/>
- </or>
- </condition>
- <then>
- <set field="highInfo.currentStatusId" value="PTS_CREATED_IP"/>
- </then>
- <else>
- <if>
- <condition>
- <if-compare-field operator="equals" field="completedTasks" to-field="tasksCount"/>
- </condition>
- <then>
- <set field="highInfo.currentStatusId" value="PTS_COMPLETED"/>
- </then>
- <else>
- <set field="highInfo.currentStatusId" value="PTS_CREATED"/>
- </else>
- </if>
- </else>
- </if>
- </simple-method>
-
- <simple-method method-name="combineDatesAndPlannedHoursInfo" short-description="combine lower level start end dates and planned hours for a project, phase or task">
- <entity-condition entity-name="ProjectPhaseTaskSklSumView" list="summaryInfos">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- </condition-list>
- <select-field field-name="projectId"/>
- <select-field field-name="estimatedStartDate"/>
- <select-field field-name="actualStartDate"/>
- <select-field field-name="estimatedCompletionDate"/>
- <select-field field-name="actualCompletionDate"/>
- <select-field field-name="plannedHours"/>
- <select-field field-name="priority"/>
- </entity-condition>
-
- <!-- Now used TimeEntries to update (or not) actual start and end Date -->
- <first-from-list list="summaryInfos" entry="summaryInfo"/>
- <set field="highInfo.estimatedStartDate" from-field="summaryInfo.estimatedStartDate"/>
- <set field="highInfo.estimatedCompletionDate" from-field="summaryInfo.estimatedCompletionDate"/>
- <set field="highInfo.actualStartDate" from-field="summaryInfo.actualStartDate"/>
- <set field="highInfo.actualCompletionDate" from-field="summaryInfo.actualCompletionDate"/>
- <set field="highInfo.priority" from-field="summaryInfo.priority"/>
- <set field="highInfo.plannedHours" from-field="summaryInfo.plannedHours"/>
- <!-- <log level="info" message="projectId=${highInfo.projectId} phaseId=${highInfo.phaseId} taskId=${highInfo.taskId} highInfo.plannedHours=${highInfo.plannedHours}"/>-->
- <!-- update actual start date by the min date form sub tasks associated TimeEntries
- (if before actualStartDate field) -->
- <entity-condition entity-name="ProjectPhaseTaskActualEntrySumView" list="summaryEntriesInfos">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- </condition-list>
- <select-field field-name="actualEntryStartDate"/>
- </entity-condition>
- <first-from-list list="summaryEntriesInfos" entry="timeEntriesInfo"/>
- <if-not-empty field="timeEntriesInfo">
- <if-not-empty field="timeEntriesInfo.actualEntryStartDate">
- <if>
- <condition>
- <or>
- <if-empty field="highInfo.actualStartDate"/>
- <if-compare-field field="highInfo.actualStartDate" operator="greater" type="Timestamp" to-field="timeEntriesInfo.actualEntryStartDate"/>
- </or>
- </condition>
- <then>
- <set field="highInfo.actualStartDate" from-field="timeEntriesInfo.actualEntryStartDate"/>
- </then>
- </if>
- </if-not-empty>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="combineInfo" short-description="combine lower level status, dates of tasks.">
- <call-simple-method method-name="combineStatusInfo"/>
- <call-simple-method method-name="combineDatesAndPlannedHoursInfo"/>
- <call-simple-method method-name="combineActualHours"/>
- </simple-method>
-
- <simple-method method-name="combineActualHours" short-description="combine lower level Actual hours info.">
- <!--
- -to calculate actual hours : the declared number of hours in time entry should be multiplied by the
- max percentage declared in PartyRate if a valid party rate can be found for the party associated to a
- the timesheet associated to this time entry and has the same rateType as this timeEntry
- -actualHoursOriginal is the total of hours in time entries without application of percentage declared in partyRate
- -->
- <clear-field field="originalHours"/>
- <clear-field field="actualHours"/>
- <clear-field field="originalActualHours"/>
-
- <!-- I- get timeEntries for which there is no rate (originalHours)-->
- <entity-condition entity-name="ProjectPhaseTaskActualNotRatedHoursView" list="notRatedValues">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- <condition-expr field-name="hoursPartyId" from-field="parameters.hoursPartyId" ignore-if-empty="true"/>
- </condition-list>
- <select-field field-name="totalOriginalHours"/>
- </entity-condition>
- <first-from-list list="notRatedValues" entry="notRatedValue"/>
- <set field="originalHours" from-field="notRatedValue.totalOriginalHours" type="Double"/>
-
- <!-- II- get total for timeEntries having a partyRate that should be applied
- before applying rate (totalOriginalHours)
- after applying rate (totalRatedHours)-->
- <entity-condition entity-name="ProjectPhaseTaskActualRatedHoursView" list="ratedValues">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- <condition-expr field-name="hoursPartyId" from-field="parameters.hoursPartyId" ignore-if-empty="true"/>
- </condition-list>
- <select-field field-name="totalOriginalHours"/>
- <select-field field-name="totalRatedHours"/>
- </entity-condition>
- <first-from-list list="ratedValues" entry="ratedValue"/>
- <!-- not used ratedValue.totalRatedHours because not works, reason seem to be totalRatedHours is a calculated field ??? -->
- <call-object-method method-name="getDouble" obj-field="ratedValue" ret-field="actualHours">
- <string value="totalRatedHours"/>
- </call-object-method>
-
- <if-empty field="actualHours">
- <set field="actualHours" from-field="originalHours"/>
- <else>
- <calculate field="actualHours" type="Double">
- <calcop operator="add" field="originalHours">
- <calcop operator="get" field="actualHours"/>
- </calcop>
- </calculate>
- </else>
- </if-empty>
-
- <if-empty field="originalHours">
- <set field="originalActualHours" from-field="ratedValue.totalOriginalHours" type="Double"/>
- <else>
- <calculate field="originalActualHours" type="Double">
- <calcop operator="add" field="originalHours">
- <calcop operator="get" field="ratedValue.totalOriginalHours"/>
- </calcop>
- </calculate>
- </else>
- </if-empty>
-
- <set field="highInfo.originalActualHours" from-field="originalActualHours"/>
- <set field="highInfo.actualHours" from-field="actualHours"/>
- <!-- do the same but for non-billed hours -->
- <!-- first get not rated hours -->
- <entity-condition entity-name="ProjectPhaseTaskActualNotRatedHoursView" list="notRatedValues">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- <condition-expr field-name="hoursPartyId" from-field="parameters.hoursPartyId" ignore-if-empty="true"/>
- <condition-expr field-name="invoiceId" from-field="nullField"/>
- </condition-list>
- <select-field field-name="totalOriginalHours"/>
- </entity-condition>
- <first-from-list list="notRatedValues" entry="notRatedValue"/>
- <set field="actualNonBilledHours" from-field="notRatedValue.totalOriginalHours" type="Double"/>
- <!-- second get non billed for entries having an invoiceId -->
- <entity-condition entity-name="ProjectPhaseTaskActualRatedHoursView" list="ratedValues">
- <condition-list>
- <condition-expr field-name="projectId" from-field="highInfo.projectId" ignore-if-empty="true"/>
- <condition-expr field-name="phaseId" from-field="highInfo.phaseId" ignore-if-empty="true"/>
- <condition-expr field-name="taskId" from-field="highInfo.taskId" ignore-if-empty="true"/>
- <condition-expr field-name="hoursPartyId" from-field="parameters.hoursPartyId" ignore-if-empty="true"/>
- <condition-expr field-name="invoiceId" from-field="nullField"/>
- </condition-list>
- <select-field field-name="totalOriginalHours"/>
- <select-field field-name="totalRatedHours"/>
- </entity-condition>
- <first-from-list list="ratedValues" entry="ratedValue"/>
- <call-object-method method-name="getDouble" obj-field="ratedValue" ret-field="actualHours">
- <string value="totalOriginalHours"/>
- </call-object-method>
-
- <if-not-empty field="actualNonBilledHours">
- <calculate field="actualNonBilledHours" type="Double">
- <calcop operator="get" field="actualNonBilledHours">
- <calcop operator="add" field="actualHours"/>
- </calcop>
- </calculate>
- <else>
- <set field="actualNonBilledHours" from-field="totalOriginalHours" default-value="0" type="Double"/>
- </else>
- </if-not-empty>
- <set field="highInfo.actualNonBilledHours" from-field="actualNonBilledHours" type="Double"/>
- </simple-method>
-
- <simple-method method-name="combineInfoOld" short-description="combine lower level status, dates of tasks.">
- <!-- in/output highInfo infoMap -->
- <!-- input lowInfo info map -->
- <!-- set the dates from the lower level tasks -->
- <if-not-empty field="lowInfo.estimatedStartDate">
- <if-empty field="highInfo.estimatedStartDate">
- <set field="highInfo.estimatedStartDate" from-field="lowInfo.estimatedStartDate"/>
- <else>
- <if-compare-field to-field="lowInfo.estimatedStartDate" field="highInfo.estimatedStartDate" operator="greater" type="Timestamp">
- <set field="highInfo.estimatedStartDate" from-field="lowInfo.estimatedStartDate"/>
- </if-compare-field>
- </else>
- </if-empty>
- </if-not-empty>
- <if-not-empty field="lowInfo.estimatedCompletionDate">
- <if-empty field="highInfo.estimatedCompletionDate">
- <set field="highInfo.estimatedCompletionDate" from-field="lowInfo.estimatedCompletionDate"/>
- <else>
- <if-compare-field to-field="lowInfo.estimatedCompletionDate" field="highInfo.estimatedCompletionDate" operator="less" type="Timestamp">
- <set field="highInfo.estimatedCompletionDate" from-field="lowInfo.estimatedCompletionDate"/>
- </if-compare-field>
- </else>
- </if-empty>
- </if-not-empty>
- <if-not-empty field="lowInfo.actualStartDate">
- <if-empty field="highInfo.actualStartDate">
- <set field="highInfo.actualStartDate" from-field="lowInfo.actualStartDate"/>
- <else>
- <if-compare-field to-field="lowInfo.actualStartDate" field="highInfo.actualStartDate" operator="greater" type="Timestamp">
- <set field="highInfo.actualStartDate" from-field="lowInfo.actualStartDate"/>
- </if-compare-field>
- </else>
- </if-empty>
- </if-not-empty>
- <if-not-empty field="lowInfo.actualCompletionDate">
- <if-empty field="highInfo.actualCompletionDate">
- <set field="highInfo.actualCompletionDate" from-field="lowInfo.actualCompletionDate"/>
- <else>
- <if-compare-field to-field="lowInfo.actualCompletionDate" field="highInfo.actualCompletionDate" operator="less" type="Timestamp">
- <set field="highInfo.actualCompletionDate" from-field="lowInfo.actualCompletionDate"/>
- </if-compare-field>
- </else>
- </if-empty>
- </if-not-empty>
-
- <!-- combine the priorities -->
- <if-not-empty field="lowInfo.priority">
- <if-empty field="highInfo.priority">
- <set field="highInfo.priority" from-field="lowInfo.priority"/>
- <else>
- <if-compare-field to-field="lowInfo.priority" field="highInfo.priority" operator="greater">
- <set field="highInfo.priority" from-field="lowInfo.priority"/>
- </if-compare-field>
- </else>
- </if-empty>
- </if-not-empty>
-
- <!-- if lowlevel type equals TASK then get create the status first -->
- <if-compare field="lowInfo.workEffortTypeId" value="TASK" operator="equals">
- <if-compare field="lowInfo.currentStatusId" value="PTS_CREATED" operator="equals">
- <get-related value-field="lowInfo" relation-name="WorkEffortPartyAssignment" list="assignsAll"/>
- <filter-list-by-date list="assignsAll" to-list="assigns"/>
- <if-not-empty field="assigns">
- <set field="lowInfo.currentStatusId" value="PTS_CREATED_AS"/><!-- task is assigned -->
- </if-not-empty>
- <get-related value-field="lowInfo" relation-name="TimeEntry" list="entries"/>
- <if-not-empty field="entries">
- <set field="lowInfo.currentStatusId" value="PTS_CREATED_IP"/><!-- task is in progress -->
- </if-not-empty>
- </if-compare>
- </if-compare>
-
- <entity-one entity-name="StatusItem" value-field="status">
- <field-map field-name="statusId" from-field="lowInfo.currentStatusId"/>
- </entity-one>
- <if-empty field="highInfo.sequenceId">
- <set field="highInfo.sequenceId" from-field="status.sequenceId"/>
- <else>
- <if>
- <condition>
- <and>
- <if-compare-field field="highInfo.sequenceId" to-field="status.sequenceId" operator="less"/>
- <and>
- <if-compare field="highInfo.sequenceId" value="05" operator="not-equals"/>
- <if-compare field="status.sequenceId" value="09" operator="not-equals"/>
- </and>
- </and>
- </condition>
- <then>
- <set field="highInfo.sequenceId" from-field="status.sequenceId"/>
- </then>
- <else>
- <set field="highInfo.sequenceId" value="05"/>
- </else>
- </if>
- </else>
- </if-empty>
- </simple-method>
-
- <simple-method method-name="createDates" short-description="merge the estimated and actual dates">
- <!-- input/output is 'highInfo map -->
- <!-- create dates taking the last known one to save space on the list -->
- <if-not-empty field="highInfo.actualStartDate">
- <set field="highInfo.startDate" from-field="highInfo.actualStartDate"/>
- <else>
- <set field="highInfo.startDate" from-field="highInfo.estimatedStartDate"/>
- </else>
- </if-not-empty>
- <if-not-empty field="highInfo.actualCompletionDate">
- <set field="highInfo.completionDate" from-field="highInfo.actualCompletionDate"/>
- <else>
- <set field="highInfo.completionDate" from-field="highInfo.estimatedCompletionDate"/>
- </else>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="updateTimeEntry" short-description="">
- <if-compare field="hours" value="-1" operator="equals">
- <return/>
- </if-compare>
- <if-not-empty field="timeEntry.timeEntryId">
- <if-compare field="hours" operator="equals" value="0">
- <set field="teDelMap.timeEntryId" from-field="timeEntry.timeEntryId"/>
- <call-service service-name="deleteTimeEntry" in-map-name="teDelMap"/>
- <else>
- <clear-field field="teUpdMap"/>
- <set field="teUpdMap.hours" from-field="hours" type="Double"/>
- <set field="teUpdMap.timeEntryId" from-field="timeEntry.timeEntryId"/>
- <set field="teUpdMap.rateTypeId" from-field="parameters.rateTypeId"/>
- <set field="teUpdMap.partyId" from-field="userLogin.partyId"/>
- <call-service service-name="updateTimeEntry" in-map-name="teUpdMap"/>
- </else>
- </if-compare>
- <else>
- <if-compare field="hours" operator="not-equals" value="0">
- <set-service-fields service-name="createTimeEntry" map="parameters" to-map="teCreMap"/>
- <set field="teCreMap.hours" from-field="hours" type="Double"/>
- <set field="teCreMap.fromDate" from-field="fromDate"/>
- <set field="teCreMap.partyId" from-field="userLogin.partyId"/>
- <call-service service-name="createTimeEntry" in-map-name="teCreMap"/>
- </if-compare>
- </else>
- </if-not-empty>
- </simple-method>
-
- <simple-method method-name="createTimeEntryInTimesheet" short-description="Creates TimeEntry and searches for a timesheetId if not provided">
- <if-not-empty field="parameters.fromDate">
- <if-empty field="parameters.timesheetId">
- <entity-condition entity-name="Timesheet" list="timesheets">
- <condition-list combine="and">
- <condition-expr field-name="fromDate" operator="less-equals" from-field="parameters.fromDate"/>
- <condition-expr field-name="thruDate" operator="greater-equals" from-field="parameters.fromDate"/>
- <condition-expr field-name="partyId" operator="equals" from-field="parameters.partyId"/>
- </condition-list>
- </entity-condition>
- <if-not-empty field="timesheets">
- <!-- use existing timesheet -->
- <first-from-list list="timesheets" entry="timesheet"/>
- <if-compare field="timesheet.statusId" value="TIMESHEET_IN_PROCESS" operator="equals">
- <set field="parameters.timesheetId" from-field="timesheet.timesheetId"/>
- <else>
- <add-error>
- <fail-property resource="ProjectMgrUiLabels" property="ProjectMgrCannotAddToTimesheet"/>
- </add-error>
- <check-errors/>
- </else>
- </if-compare>
- <else>
- <!-- create new timesheet -->
- <set field="parameters.requiredDate" from-field="parameters.fromDate"/>
- <call-simple-method method-name="createTimesheetForThisWeek" xml-resource="component://workeffort/minilang/timesheet/TimesheetServices.xml"/>
- <set field="parameters.timesheetId" from-field="newEntity.timesheetId"/>
- </else>
- </if-not-empty>
- </if-empty>
- </if-not-empty>
- <!-- get role for this party in this project -->
- <if-empty field="parameters.roleTypeId">
- <entity-and entity-name="ProjectPartyAndPhaseAndTask" list="taskRoles" filter-by-date="true">
- <field-map field-name="partyId" from-field="parameters.partyId"/>
- <field-map field-name="workEffortId" from-field="parameters.workEffortId"/>
- </entity-and>
- <first-from-list list="taskRoles" entry="taskRole"/>
- <set field="parameters.roleTypeId" from-field="taskRole.roleTypeId"/>
- <set field="parameters.statusId" value="PAS_ASSIGNED"/>
- <call-simple-method method-name="assignPartyToWorkEffort" xml-resource="component://workeffort/minilang/workeffort/WorkEffortSimpleServices.xml"/>
- </if-empty>
- <call-simple-method method-name="createTimeEntry" xml-resource="component://workeffort/minilang/timesheet/TimesheetServices.xml"/>
- </simple-method>
-
- <simple-method method-name="addProjectTimeToNewInvoice" short-description="add all reported time on all completed timesheets from all workefforts for a project">
- <!-- recreate the invoice if still in preparation in order to correct errors. -->
- <if-compare operator="equals" value="Y" field="parameters.reCreate">
- <entity-one entity-name="Invoice" value-field="invoice"/>
- <if-empty field="invoice">
- <add-error>
- <fail-property resource="WorkEffortUiLabels" property="WorkEffortTimesheetCannotFindInvoice"/>
- </add-error>
- <check-errors/>
- </if-empty>
-
- <call-simple-method method-name="checkInvoiceStatusInProgress" xml-resource="component://accounting/minilang/invoice/InvoiceServices.xml"/>
-
- <entity-and entity-name="TimeEntry" list="entries">
- <field-map field-name="invoiceId" from-field="parameters.invoiceId"/>
- </entity-and>
- <iterate list="entries" entry="timeEntry">
- <clear-field field="timeEntry.invoiceId"/>
- <clear-field field="timeEntry.invoiceItemSeqId"/>
- <store-value value-field="timeEntry"/>
- </iterate>
- <set field="removeItems.invoiceId" from-field="parameters.invoiceId"/>
- <remove-by-and entity-name="InvoiceItem" map="removeItems"/>
- <set field="notFirst" value="Y"/><!-- do not create, only add -->
- </if-compare>
- <entity-condition entity-name="ProjectPhaseTaskAndTimeEntryTimeSheet" list="tasks">
- <condition-list combine="and">
- <condition-expr field-name="projectId" operator="equals" from-field="parameters.projectId"/>
- <condition-expr field-name="invoiceId" operator="equals" from-field="nullField"/>
- <condition-expr field-name="timesheetStatusId" operator="equals" value="TIMESHEET_COMPLETED"/>
- <condition-expr field-name="fromDate" operator="less" from-field="parameters.thruDate" ignore-if-empty="true"/>
- </condition-list>
- <order-by field-name="workEffortId"/>
- </entity-condition>
- <if-empty field="tasks">
- <add-error>
- <fail-property resource="ProjectMgrUiLabels" property="ProjectMgrNoTimeentryItemsFound"/>
- </add-error>
- <check-errors/>
- </if-empty>
- <iterate list="tasks" entry="task">
- <if-empty field="notFirst">
- <!-- first time so create invoice -->
- <set-service-fields service-name="addWorkEffortTimeToNewInvoice" map="parameters" to-map="addTaskToNewInvoice"/>
- <set field="addTaskToNewInvoice.workEffortId" from-field="task.workEffortId"/>
- <set field="addTaskToNewInvoice.combineInvoiceItem" value="Y"/>
- <set field="addTaskToNewInvoice.thruDate" from-field="parameters.thruDate"/>
- <call-service service-name="addWorkEffortTimeToNewInvoice" in-map-name="addTaskToNewInvoice">
- <result-to-field result-name="invoiceId" field="parameters.invoiceId"/>
- </call-service>
- <set field="addTaskToInvoice.combineInvoiceItem" value="Y"/>
- <field-to-result field="parameters.invoiceId" result-name="invoiceId"/>
- <set field="notFirst" value="Y"/>
- <else>
- <if>
- <condition>
- <or>
- <if-empty field="oldWorkeffortId"/>
- <if-compare-field operator="not-equals" field="oldWorkeffortId" to-field="task.workEffortId"/>
- </or>
- </condition>
- <then>
- <!-- add to created invoice -->
- <set field="addTaskToInvoice.combineInvoiceItem" value="Y"/>
- <set field="addTaskToInvoice.invoiceId" from-field="parameters.invoiceId"/>
- <set field="addTaskToInvoice.workEffortId" from-field="task.workEffortId"/>
- <set field="addTaskToInvoice.thruDate" from-field="parameters.thruDate"/>
- <call-service service-name="addWorkEffortTimeToInvoice" in-map-name="addTaskToInvoice"/>
- </then>
- </if>
- <set field="oldWorkeffortId" from-field="task.workEffortId"/>
- </else>
- </if-empty>
- </iterate>
- </simple-method>
-
- <simple-method method-name="addProjectTimeToInvoice" short-description="add all reported time on all completed timesheets
- from all workefforts for a project to an existing invoice">
- <entity-one entity-name="Invoice" value-field="invoice"/>
- <if-empty field="invoice">
- <add-error>
- <fail-property resource="WorkEffortUiLabels" property="WorkEffortTimesheetCannotFindInvoice"/>
- </add-error>
- <check-errors/>
- </if-empty>
-
- <call-simple-method method-name="checkInvoiceStatusInProgress" xml-resource="component://accounting/minilang/invoice/InvoiceServices.xml"/>
-
- <entity-condition entity-name="ProjectPhaseTaskAndTimeEntryTimeSheet" list="tasks">
- <condition-list combine="and">
- <condition-expr field-name="projectId" operator="equals" from-field="parameters.projectId"/>
- <condition-expr field-name="invoiceId" operator="equals" from-field="nullField"/>
- <condition-expr field-name="timesheetStatusId" operator="equals" value="TIMESHEET_COMPLETED"/>
- <condition-expr field-name="fromDate" operator="less" from-field="parameters.thruDate" ignore-if-empty="true"/>
- </condition-list>
- <order-by field-name="workEffortId"/>
- </entity-condition>
-
- <if-empty field="tasks">
- <add-error>
- <fail-property resource="ProjectMgrUiLabels" property="ProjectMgrNoTimeentryItemsFound"/>
- </add-error>
- <check-errors/>
- </if-empty>
-
- <iterate list="tasks" entry="task">
- <if>
- <condition>
- <or>
- <if-empty field="oldWorkeffortId"/>
- <if-compare-field operator="not-equals" field="oldWorkeffortId" to-field="task.workEffortId"/>
- </or>
- </condition>
- <then>
- <!-- add to created invoice -->
- <set field="addTaskToInvoice.combineInvoiceItem" value="Y"/>
- <set field="addTaskToInvoice.invoiceId" from-field="parameters.invoiceId"/>
- <set field="addTaskToInvoice.workEffortId" from-field="task.workEffortId"/>
- <set field="addTaskToInvoice.thruDate" from-field="parameters.thruDate"/>
- <call-service service-name="addWorkEffortTimeToInvoice" in-map-name="addTaskToInvoice"/>
- </then>
- </if>
- <set field="oldWorkeffortId" from-field="task.workEffortId"/>
- </iterate>
- </simple-method>
-
- <simple-method method-name="addValidationPartiesToTask" short-description="SECA to add either project-testing or -approval parties to a task when a task is set to complete">
- <!-- check if this is the last party which completed his task -->
- <entity-condition entity-name="WorkEffortPartyAssignment" list="openTasks" filter-by-date="true">
- <condition-list combine="and">
- <condition-expr field-name="workEffortId" operator="equals" from-field="parameters.workEffortId"/>
- <condition-expr field-name="statusId" operator="not-equals" value="PAS_COMPLETED"/>
- <condition-expr field-name="partyId" operator="not-equals" from-field="parameters.partyId"/>
- </condition-list>
- </entity-condition>
- <if-empty field="openTasks">
- <set field="getProject.taskId" from-field="parameters.workEffortId"/>
- <call-service service-name="getProjectIdAndNameFromTask" in-map-name="getProject">
- <result-to-field result-name="projectId"/>
- </call-service>
- <!-- see who is reponsible for testing/validation in this project -->
- <entity-condition entity-name="WorkEffortPartyAssignment" list="assigns" filter-by-date="true">
- <condition-list combine="and">
- <condition-expr field-name="workEffortId" operator="equals" from-field="projectId"/>
- <condition-expr field-name="partyId" operator="not-equals" from-field="parameters.partyId"/><!-- should not test/val own work -->
- <condition-list combine="or">
- <condition-expr field-name="roleTypeId" operator="equals" value="PROVIDER_VALIDATOR"/>
- <condition-expr field-name="roleTypeId" operator="equals" value="PROVIDER_TESTER"/>
- </condition-list>
- </condition-list>
- </entity-condition>
- <if-not-empty field="assigns">
- <set field="addAssign.workEffortId" from-field="parameters.workEffortId"/>
- <set field="addAssign.statusId" value="PAS_ASSIGNED"/>
- <iterate list="assigns" entry="assign">
- <make-value entity-name="WorkEffortPartyAssignment" value-field="newAssign"/>
- <set field="newAssign.workEffortId" from-field="parameters.workEffortId"/>
- <set field="newAssign.partyId" from-field="assign.partyId"/>
- <set field="newAssign.roleTypeId" from-field="assign.roleTypeId"/>
- <set field="newAssign.assignedByUserLoginId" value="system"/>
- <now-timestamp field="newAssign.fromDate"/>
- <set field="newAssign.statusId" value="PAS_ASSIGNED"/>
- <create-value value-field="newAssign"/>
- </iterate>
- <else>
- <log level="info" message="No validation parties defined in this project: no validation parties added...."/>
- </else>
- </if-not-empty>
- <else>
- <log level="info" message="Not the last party who completes his task: validation parties not added...."/>
- </else>
- </if-empty>
- </simple-method>
-
-</simple-methods>
diff --git a/projectmgr/servicedef/services.xml b/projectmgr/servicedef/services.xml
index 038ceb5..1c55aed 100644
--- a/projectmgr/servicedef/services.xml
+++ b/projectmgr/servicedef/services.xml
@@ -22,17 +22,18 @@
xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/services.xsd">
<description>Project Manager service definitions.</description>
- <service name="createTimeEntryInTimesheet" default-entity-name="TimeEntry" engine="simple" auth="true"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="createTimeEntryInTimesheet">
+ <service name="createTimeEntryInTimesheet" default-entity-name="TimeEntry" engine="groovy" auth="true"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="createTimeEntryInTimesheet">
<description>Creates TimeEntry and searches for a timesheetId, if required it will create a weekly time sheet, it also assigns the party to the task</description>
<permission-service service-name="projectMgrPermission" main-action="CREATE"/>
- <auto-attributes include="pk" mode="OUT" optional="false"/>
+ <auto-attributes include="pk" mode="OUT"/>
<auto-attributes include="nonpk" mode="IN" optional="true"/>
<attribute name="roleTypeId" type="String" mode="IN" optional="true"/>
<attribute name="timesheetId" type="String" mode="OUT" optional="true"/>
<attribute name="fromDate" type="Timestamp" mode="OUT" optional="true"/>
</service>
+ <!-- FIXME -->
<service name="createProjectTimesheet" engine="simple" default-entity-name="Timesheet"
location="component://workeffort/minilang/timesheet/TimesheetServices.xml" invoke="createTimesheetForThisWeek">
<description>Creates Timesheet for this week if no required date specified.</description>
@@ -45,6 +46,7 @@
<attribute name="requiredDate" type="Timestamp" mode="IN" optional="true"/>
</service>
+ <!-- FIXME -->
<service name="projectMgrPermission" engine="simple"
location="component://projectmgr/minilang/ProjectPermissionServices.xml" invoke="projectMgrPermission">
<implements service="permissionInterface"/>
@@ -56,17 +58,17 @@
<attribute name="timesheetId" type="String" mode="IN" optional="true"/>
<attribute name="timeEntryId" type="String" mode="IN" optional="true"/>
</service>
-
+ <!-- FIXME -->
<service name="projectMgrRequestPermission" engine="simple"
location="component://projectmgr/minilang/ProjectPermissionServices.xml" invoke="projectMgrRequestPermission">
<implements service="permissionInterface"/>
</service>
-
- <service name="updateTimeEntryByWorkeffort" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="updateTimeEntryByWorkeffort">
+
+ <service name="updateTimeEntryByWorkEffort" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="updateTimeEntryByWorkEffort">
<description>Update workeffort by workeffortId and timesheetId </description>
<permission-service service-name="projectMgrPermission" main-action="UPDATE"/>
- <attribute name="timesheetId" type="String" mode="INOUT" optional="false"/>
+ <attribute name="timesheetId" type="String" mode="INOUT"/>
<attribute name="workEffortId" type="String" mode="INOUT" optional="true"/><!-- when empty will be ignored -->
<attribute name="fromDate" type="Timestamp" mode="INOUT" optional="true"/>
<attribute name="rateTypeId" type="String" mode="IN" optional="true"/>
@@ -81,8 +83,8 @@
<attribute name="checkComplete" type="String" mode="IN" optional="true"/>
</service>
- <service name="getProject" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getProject">
+ <service name="getProject" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getProject">
<description>Get project information and related phase and task info.</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="projectId" type="String" mode="INOUT" optional="true"/>
@@ -91,8 +93,8 @@
</service>
<service name="createProject" engine="groovy" default-entity-name="WorkEffort"
- location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServices.groovy" invoke="createProject">
- <description>Create a new Project</description>
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="createProject">
+ <description>Create a new Project.</description>
<permission-service service-name="projectMgrPermission" main-action="CREATE"/>
<implements service="interfaceWorkEffort"/>
<auto-attributes mode="INOUT" include="pk" optional="true"/>
@@ -110,8 +112,9 @@
<override name="workEffortName" optional="false"/>
<override name="currentStatusId" optional="false"/>
</service>
- <service name="updateProject" default-entity-name="WorkEffort" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="updateProject">
+
+ <service name="updateProject" default-entity-name="WorkEffort" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="updateProject">
<description>Update a Project</description>
<permission-service service-name="projectMgrPermission" main-action="UPDATE"/>
<implements service="interfaceWorkEffort"/>
@@ -120,21 +123,27 @@
<attribute name="clientBillingPartyId" type="String" mode="IN" optional="true"/>
<attribute name="emailAddress" type="String" mode="IN" optional="true"/>
</service>
+ <service name="updateProjectRole" default-entity-name="WorkEffortPartyAssignment" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="updateProjectRole">
+ <description>Update a Project Role</description>
+ <permission-service service-name="projectMgrPermission" main-action="UPDATE"/>
+ <implements service="createWorkEffortPartyAssignment"/>
+ </service>
- <service name="createProjectTask" engine="simple" default-entity-name="WorkEffort"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="createProjectTask">
+ <service name="createProjectTask" engine="groovy" default-entity-name="WorkEffort"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="createProjectTask">
<description>Create a new task and optionally assign to a resource.</description>
<permission-service service-name="projectMgrPermission" main-action="CREATE"/>
<implements service="interfaceWorkEffort"/>
<auto-attributes mode="INOUT" include="pk" optional="true"/>
<attribute name="partyId" type="String" mode="IN" optional="true"/>
<attribute name="roleTypeId" type="String" mode="IN" optional="true"/>
- <attribute name="statusId" type="String" mode="IN" optional="true"/>
+ <attribute name="statusId" type="String" mode="IN" default-value="PTS_CREATED"/>
<attribute name="fromDate" type="Timestamp" mode="OUT" optional="true"/>
<attribute name="quickAssignPartyId" type="String" mode="IN" optional="true"/>
<attribute name="requirementId" type="String" mode="IN" optional="true"/>
<attribute name="communicationEventId" type="String" mode="IN" optional="true"/>
- <attribute name="skillTypeId" type="String" mode="IN" optional="true"/>
+ <attribute name="skillTypeId" type="String" mode="IN" default-value="_NA_"/>
<attribute name="estimatedHours" type="Double" mode="IN" optional="true"/>
<override name="workEffortTypeId" optional="false"/>
<override name="workEffortName" optional="false"/>
@@ -142,33 +151,31 @@
<override name="workEffortParentId" optional="false"/>
</service>
- <service name="copyProject" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="copyProject">
+ <service name="copyProject" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="copyProject">
<description>Copy a project planning data but ignore the actual data.</description>
<permission-service service-name="projectMgrPermission" main-action="CREATE"/>
- <attribute name="projectId" type="String" mode="INOUT" optional="false"/>
+ <attribute name="projectId" type="String" mode="INOUT"/>
<attribute name="fromDate" type="Timestamp" mode="OUT" optional="true"/>
<attribute name="workEffortId" type="String" mode="OUT" optional="true"/>
</service>
- <service name="copyProjectToTemplate" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="copyProjectToTemplate">
+ <service name="copyProjectToTemplate" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="copyProject">
<description>Copy a project planning data to a template project.</description>
- <permission-service service-name="projectMgrPermission" main-action="CREATE"/>
- <attribute name="projectId" type="String" mode="INOUT" optional="false"/>
- <attribute name="fromDate" type="Timestamp" mode="OUT" optional="true"/>
- <attribute name="workEffortId" type="String" mode="OUT" optional="true"/>
+ <implements service="copyProject"/>
+ <attribute name="toTemplate" type="String" mode="IN" default-value="Y"/>
</service>
- <service name="scheduleProject" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="scheduleProject">
+ <service name="scheduleProject" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="scheduleProject">
<description>(re) calculate the estimated start and enddates of tasks within a project</description>
<permission-service service-name="projectMgrPermission" main-action="UPDATE"/>
<attribute name="projectId" type="String" mode="INOUT" optional="true"/>
</service>
- <service name="getProjectIdAndNameFromTask" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getProjectIdAndNameFromTask">
+ <service name="getProjectIdAndNameFromTask" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getProjectIdAndNameFromTask">
<description>Get the projectId and Name when a phase or task is provided.</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="taskId" type="String" mode="INOUT" optional="true"/>
@@ -181,24 +188,24 @@
<attribute name="taskWbsId" type="String" mode="OUT" optional="true"/>
</service>
- <service name="getProjectPhaseList" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getProjectPhaseList">
+ <service name="getProjectPhaseList" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getProjectPhaseList">
<description>Get project phase information and related task info.</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="projectId" type="String" mode="INOUT" optional="true"/>
<attribute name="phaseList" type="List" mode="OUT" optional="true"/>
</service>
- <service name="getProjectTaskList" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getProjectTaskList">
+ <service name="getProjectTaskList" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getProjectTaskList">
<description>Get project phase information and related task info.</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="projectId" type="String" mode="INOUT" optional="true"/>
<attribute name="taskList" type="List" mode="OUT" optional="true"/>
</service>
- <service name="getProjectTask" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getProjectTask">
+ <service name="getProjectTask" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getProjectTask">
<description>Get project task information and related timesheet info.</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="hoursPartyId" type="String" mode="IN" optional="true"/>
@@ -208,8 +215,8 @@
<attribute name="taskInfo" type="Map" mode="OUT" optional="true"/>
</service>
- <service name="getProjectsByParties" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getProjectsByParties">
+ <service name="getProjectsByParties" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getProjectsByParties">
<description>Get project information by party</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="projectId" type="String" mode="INOUT" optional="true"/>
@@ -217,8 +224,8 @@
<attribute name="projectParties" type="List" mode="OUT" optional="true"/>
</service>
- <service name="getTasksByParties" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="getTasksByParties">
+ <service name="getTasksByParties" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="getTasksByParties">
<description>Get project information by party</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<attribute name="workEffortId" type="String" mode="INOUT" optional="true"/>
@@ -226,8 +233,8 @@
<attribute name="taskParties" type="List" mode="OUT" optional="true"/>
</service>
- <service name="updateTaskAndRelatedInfo" default-entity-name="WorkEffort" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="updateTaskAndRelatedInfo">
+ <service name="updateTaskAndRelatedInfo" default-entity-name="WorkEffort" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="updateTaskAndRelatedInfo">
<description>Update a task and related info</description>
<permission-service service-name="projectMgrPermission" main-action="VIEW"/>
<implements service="interfaceWorkEffort"/>
@@ -239,8 +246,8 @@
<attribute name="estimatedDuration" mode="IN" type="Double" optional="true"/>
</service>
- <service name="updateTaskAssigment" default-entity-name="WorkEffortPartyAssignment" engine="simple"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="updateTaskAssigment">
+ <service name="updateTaskAssigment" default-entity-name="WorkEffortPartyAssignment" engine="groovy"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="updateTaskAssigment">
<description>Update a WorkEffortPartyAssignment Entity, including set enddate and create new</description>
<permission-service service-name="projectMgrPermission" main-action="UPDATE"/>
<attribute name="workEffortId" type="String" mode="INOUT"/>
@@ -254,26 +261,26 @@
<attribute name="delegateReasonEnumId" type="String" mode="IN" optional="true"/>
<attribute name="comments" type="String" mode="IN" optional="true"/>
</service>
- <service name="addProjectTimeToNewInvoice" engine="simple" auth="true"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="addProjectTimeToNewInvoice">
+ <service name="addProjectTimeToNewInvoice" engine="groovy" auth="true"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="addProjectTimeToInvoice">
<description>Add Project Time to a new Invoice</description>
- <attribute name="projectId" type="String" mode="IN" optional="false"/>
+ <attribute name="projectId" type="String" mode="IN"/>
<attribute name="partyIdFrom" type="String" mode="IN" optional="true"/>
<attribute name="partyId" type="String" mode="IN" optional="true"/>
<attribute name="thruDate" type="Timestamp" mode="IN" optional="true"/>
<attribute name="invoiceId" type="String" mode="INOUT" optional="true"/>
- <attribute name="reCreate" type="String" mode="IN" optional="true"/>
+ <attribute name="reCreate" type="String" mode="IN" default-value="Y"/>
</service>
- <service name="addProjectTimeToInvoice" engine="simple" auth="true"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="addProjectTimeToInvoice">
+ <service name="addProjectTimeToInvoice" engine="groovy" auth="true"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="addProjectTimeToInvoice">
<description>Add Project Time to an existing Invoice. The billed party must be the same between the two invoice.</description>
- <attribute name="projectId" type="String" mode="IN" optional="false"/>
- <attribute name="invoiceId" type="String" mode="IN" optional="false"/>
- <attribute name="billedPartyId" type="String" mode="IN" optional="false"/>
+ <attribute name="projectId" type="String" mode="IN"/>
+ <attribute name="invoiceId" type="String" mode="IN"/>
+ <attribute name="billedPartyId" type="String" mode="IN"/>
<attribute name="thruDate" type="Timestamp" mode="IN" optional="true"/>
</service>
- <service name="addValidationPartiesToTask" engine="simple" auth="true"
- location="component://projectmgr/minilang/ProjectServices.xml" invoke="addValidationPartiesToTask">
+ <service name="addValidationPartiesToTask" engine="groovy" auth="true"
+ location="component://projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy" invoke="addValidationPartiesToTask">
<description>SECA to add either project-testing or -approval parties to a task when a task is set to complete</description>
<attribute name="workEffortId" type="String" mode="IN" optional="true"/>
<attribute name="partyId" type="String" mode="IN" optional="true"/>
diff --git a/projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy b/projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy
new file mode 100644
index 0000000..20db675
--- /dev/null
+++ b/projectmgr/src/main/groovy/org/apache/ofbiz/projectmgr/ProjectServicesScript.groovy
@@ -0,0 +1,1097 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * 'License'); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+package org.apache.ofbiz.projectmgr
+
+import org.apache.ofbiz.base.util.UtilDateTime
+import org.apache.ofbiz.base.util.UtilValidate
+import org.apache.ofbiz.entity.GenericValue
+import org.apache.ofbiz.entity.condition.EntityCondition
+import org.apache.ofbiz.entity.condition.EntityConditionBuilder
+import org.apache.ofbiz.project.Various
+
+import java.sql.Timestamp
+
+/**
+ * Create a project
+ * @return 'Success response containing the projectId, error response otherwise.
+ */
+Map createProject() {
+ Map serviceResult
+ if (parameters.templateId) {
+ serviceResult = run service: 'copyProjet', with: [*: parameters,
+ projectId: parameters.templateId]
+ } else {
+ serviceResult = run service: 'createWorkEffort', with: [*: parameters,
+ currentStatusId: 'PRJ_ACTIVE']
+ }
+ Map serviceMap = [*: parameters,
+ workEffortId: serviceResult.workEffortId]
+
+ // Add roles
+ if (parameters.organizationPartyId) {
+ run service: 'updateProjectRole', with: [*: serviceMap,
+ partyId: parameters.organizationPartyId,
+ roleTypeId: 'INTERNAL_ORGANIZATIO']
+ }
+ if (parameters.clientBillingPartyId) {
+ run service: 'updateProjectRole', with: [*: serviceMap,
+ partyId: parameters.clientBillingPartyId,
+ roleTypeId: 'CLIENT_BILLING']
+ }
+
+ // create new work effort's e-mail address
+ if (parameters.emailAddress) {
+ if (!UtilValidate.isEmail(parameters.emailAddress)) {
+ return error(label('PartyUiLabels', 'PartyEmailAddressNotFormattedCorrectly'))
+ }
+ run service: 'createWorkEffortEmailAddress', with: serviceMap
+ }
+ return success([projectId: serviceMap.workEffortId, workEffortId: serviceMap.workEffortId])
+}
+
+/**
+ * Update a project
+ * @return 'Success response after updating, error response otherwise.
+ */
+Map updateProject() {
+ run service: 'updateWorkEffort', with: parameters
+
+ // Add roles
+ if (parameters.organizationPartyId) {
+ run service: 'updateProjectRole', with: [*: parameters,
+ partyId: parameters.organizationPartyId,
+ roleTypeId: 'INTERNAL_ORGANIZATIO']
+ }
+ if (parameters.clientBillingPartyId) {
+ run service: 'updateProjectRole', with: [*: parameters,
+ partyId: parameters.clientBillingPartyId,
+ roleTypeId: 'CLIENT_BILLING']
+ }
+
+ // update new work effort's e-mail address
+ if (parameters.emailAddress) {
+ if (!UtilValidate.isEmail(parameters.emailAddress)) {
+ return error(label('PartyUiLabels', 'PartyEmailAddressNotFormattedCorrectly'))
+ }
+ GenericValue existEmailAddress = from('WorkEffortContactMechView')
+ .where(workEffortId: parameters.workEffortId,
+ contactMechTypeId: 'EMAIL_ADDRESS')
+ .filterByDate()
+ .queryFirst()
+ if (existEmailAddress) {
+ run service: 'updateWorkEffortEmailAddress', with: [*: parameters,
+ oldContactMechId: existEmailAddress.contactMechId]
+ } else {
+ run service: 'createWorkEffortEmailAddress', with: parameters
+ }
+ }
+ return success()
+}
+
+/**
+ * update/create a specif role and type for a project
+ * @return 'Success response after updating, error response otherwise.
+ */
+Map updateProjectRole() {
+ GenericValue workEffortPartyAssignment = from('WorkEffortPartyAssignment')
+ .where(workEffortId: parameters.workEffortId,
+ roleTypeId: parameters.roleTypeId)
+ .filterByDate()
+ .queryFirst()
+
+ if (workEffortPartyAssignment) {
+ if (parameters.partyId != workEffortPartyAssignment.partyId) {
+ run service: 'expireWorkEffortPartyAssignment', with: workEffortPartyAssignment.getAllFields()
+ } else {
+ return success()
+ }
+ }
+ run service: 'assignPartyToWorkEffort', with: parameters
+ return success()
+}
+
+/**
+ * Create a project task and optionally assign
+ * @return 'Success response after creation, error response otherwise.
+ */
+Map createProjectTask() {
+ // create task
+ Map serviceResult = run service: 'createWorkEffort', with: parameters
+ Map serviceMap = [*: parameters,
+ workEffortId: serviceResult.workEffortId]
+
+ // optionally assign to party
+ if (parameters.partyId) {
+ run service: 'assignPartyToWorkEffort', with: serviceMap
+ }
+
+ // optionally enter estimated time and required skill -->
+ if (parameters.estimatedHours) {
+ run service: 'createWorkEffortSkillStandard', with: [*: serviceMap,
+ estimatedDuration: parameters.estimatedHours]
+ }
+ return success([workEffortId: serviceMap.workEffortId])
+}
+
+/**
+ * Update the task and when info is provided update the related information too
+ * @return 'Success response after updating, error response otherwise.
+ */
+Map updateTaskAndRelatedInfo() {
+ run service: 'updateWorkEffort', with: parameters
+ if (parameters.estimatedDuration) {
+ Map serviceMap = [*: parameters]
+ if (!parameters.skillTypeId) {
+ GenericValue workEffortSkillStandard = from('WorkEffortSkillStandard')
+ .where(workEffortId: parameters.workEffortId)
+ .cache()
+ .queryFirst()
+ serviceMap.skillTypeId = workEffortSkillStandard ? workEffortSkillStandard.skillTypeId : '_NA_'
+ }
+ String actionNeed = (from('WorkEffortSkillStandard')
+ .where(workEffortId: parameters.workEffortId,
+ skillTypeId: serviceMap.skillTypeId)
+ .queryCount() > 0) ? 'update' : 'create'
+ run service: "${actionNeed}WorkEffortSkillStandard", with: serviceMap
+ }
+ return success()
+}
+
+/**
+ * Update task to resource assignment, if required create a new one by re-assigment
+ * @return 'Success response after updating, error response otherwise.
+ */
+Map updateTaskAssigment() {
+ // check if a change in partyId Or roleTypeId: need to delete and create new
+ Timestamp fromDate = parameters.fromDate
+ if ((parameters.newPartyId && parameters.partyId != parameters.newPartyId)
+ || (parameters.newRoleTypeId && parameters.roleTypeId != parameters.newRoleTypeId)) {
+ // roleType and/or partyId changed: end old and create new assign
+ run service: 'expireWorkEffortPartyAssignment', with: parameters
+ Map serviceResult = run service: 'createWorkEffortPartyAssignment', with: [*: parameters,
+ partyId: parameters.newPartyId,
+ statusId: 'PAS_ASSIGNED',
+ roleTypeId: parameters.newRoleTypeId]
+ fromDate = serviceResult.fromDate
+ } else {
+ GenericValue partyAssignment = from('WorkEffortAndPartyAssign').where(parameters).queryOne()
+ if (partyAssignment) {
+ if (parameters.statusId == 'PAS_ENDED') {
+ run service: 'expireWorkEffortPartyAssignment', with: parameters
+ }
+ if (parameters.statusId == 'PAS_COMPLETED') {
+ return updateTaskStatusToComplete(parameters.workEffortId)
+ }
+ }
+ String serviceName = partyAssignment ? 'updateWorkEffortPartyAssignment' : 'assignPartyToWorkEffort'
+ run service: serviceName, with: parameters
+ }
+ return success([workEffortId: parameters.workEffortId, fromDate: fromDate])
+}
+
+/**
+ * Check partyAssignments on a task, if all completes set task status to completed and set actual completionDate to now
+ * @return Success response after updating, error response otherwise.
+ */
+Map updateTaskStatusToComplete(String workEffortId) {
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ EQUALS(workEffortId: workEffortId)
+ NOT_EQUAL(statusId: 'PAS_COMPLETED')
+ }
+ boolean canComplete = from('WorkEffortPartyAssignment')
+ .where(condition)
+ .filterByDate()
+ .queryOne() == 0
+ if (canComplete) {
+ run service: 'updateWorkEffort', with: [
+ workEffortId: workEffortId,
+ currentStatusId: 'PTS_COMPLETED',
+ actualCompletionDate: UtilDateTime.nowTimestamp()]
+
+ // check for related customer request, set these too to completed
+ from('CustRequestWorkEffort')
+ .where(workEffortId: workEffortId)
+ .queryList()
+ .each {
+ run service: 'updateCustRequest', with: [custRequestId: it.custRequestId,
+ statusId: 'CRQ_COMPLETED']
+ }
+ }
+ return success([workEffortId: workEffortId])
+}
+
+/**
+ * Project Scheduler sets the planning dates according task requirements and available resources
+ * theory behind the program
+ * - - - - - - - - - - - - -
+ * (program under development)
+ * Assumptions for tasks and resources
+ * 1. a workday has 8 hours.
+ * 2. a workweek has 40 hours and 5 days.
+ * 3. The order of the execution of the tasks is set by the workeffort association.
+ * 4. The default start of the project is today
+ * 5. default length of a task is 3 day if not planned hours entered
+ *
+ * The steps of the program are:
+ * 1. read all tasks and check if there are predesessors, when not set he estimated dates
+ * for critical path processing:
+ * * ES - Earliest Start time
+ * * EF - Earliest Finish time
+ * * LS - Latest Start time
+ * * LF - Latest Finish time
+ *
+ * EF = LF task is on the critical path
+ *
+ * 2. call a recursive java function to set all the dependant tasks.
+ */
+Map scheduleProject() {
+ // find a starting point being either the estimated start date of a project or the earliest actual start date.
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ NOT_EQUAL(actualStartDate: null)
+ }
+ List tasks = from('ProjectAndPhaseAndTask')
+ .where(condition)
+ .orderBy('-actualStartDate')
+ .queryList()
+
+ Timestamp generalStartDate = null
+ Timestamp startDate = null
+ String taskId = null
+ // remove all estimated dates
+ if (tasks) {
+ startDate = tasks[0].actualStartDate
+ taskId = tasks[0].workEffortId
+ tasks.each {
+ tasks.estimatedStartDate = null
+ tasks.estimatedCompletionDate = null
+ }
+ } else {
+ generalStartDate = UtilDateTime.nowTimestamp()
+ }
+
+ while (!generalStartDate) {
+ BigDecimal highestHours
+ List assocs = from('WorkEffortAssoc')
+ .where(workEffortId: taskId)
+ .queryList()
+ if (assocs) {
+ assocs.each {
+ BigDecimal hours = 0
+ Map task = run service: 'getProjectTask', with: [taskId: it.workEffortIdFrom]
+ if (task.estimatedHours && task.actualHours) {
+ hours = (task.estimatedHours < task.actualHours) ? task.actualHours : task.estimatedHours
+ } else {
+ hours = task.actualHours ?: 16
+ }
+ if (!highestHours || highestHours < hours) {
+ highestHours = hours
+ preDesessorId = task.taskId
+ }
+ }
+ BigDecimal taskDays = -(highestHours / 8)
+ startDate = UtilDateTime.addDaysToTimestamp(startDate, taskDays)
+ } else {
+ GenericValue workEffort = from('WorkEffort').where(workEffortId: taskId).queryOne()
+ if (workEffort.parentWorkEffortId) {
+ taskId = workEffort.parentWorkEffortId
+ } else {
+ generalStartDate = startDate
+ }
+ }
+ }
+
+ // create the tasklist
+ Timestamp now = UtilDateTime.nowTimestamp()
+ GenericValue project = from('WorkEffort').where(workEffortId: parameters.projectId).queryOne()
+ project.getRelated('ChildWorkEffort', null, null, false)
+ .each {
+ it.getRelated('ChildWorkEffort', null, null, false)
+ .each { task ->
+ if (from('WorkEffortAssoc').where(workEffortIdFrom: task.workEffortId).queryOne()) {
+ // no predecessors so i can set the dates
+ run service: 'updateWorkEffort', with: [workEffortId: task.workEffortId,
+ estimatedStartDate: now,
+ estimatedCompletionDate: Various.calculateCompletionDate(task, now)]
+ Various.setDatesFollowingTasks(from('WorkEffort').where(workEffortId: task.workEffortId).queryOne())
+ }
+ }
+ }
+ return success()
+}
+
+/**
+ * Update workeffort by workEffortId and timesheetId
+ * @return 'Success response after updating, error response otherwise.
+ */
+Map updateTimeEntryByWorkEffort() {
+ if (!parameters.workEffortId || parameters.workEffortId == 'Totals') {
+ return success([timesheetId: parameters.timesheetId])
+ }
+ GenericValue timesheet = from('Timesheet').where(parameters).queryOne()
+ // check if party assigned to task, when not add with roletype of project, if assigned check status
+
+ List assigns = from('WorkEffortPartyAssignment')
+ .where(workEffortId: parameters.workEffortId,
+ partyId: timesheet.partyId)
+ .filterByDate()
+ .queryList()
+
+ if (!assigns) {
+ Map serviceResult = run service: 'getProjectIdAndNameFromTask', with: [taskId: parameters.workEffortId]
+ GenericValue projectAssign = from('WorkEffortPartyAssignment')
+ .where(workEffortId: serviceResult.projectId,
+ partyId: timesheet.partyId)
+ .queryFirst()
+ if (projectAssign) {
+ run service: 'assignPartyToWorkEffort', with: [*: parameters,
+ partyId: timesheet.partyId,
+ roleTypeId: projectAssign.roleTypeId,
+ statusId: 'PAS_ASSIGNED']
+ }
+
+ // check if the actual start date is set, when not set it to todays date
+ if (!project.actualStartDate) {
+ run service: 'updateWorkEffort', with: [workEffortId: parameters.workEffortId,
+ actualStartDate: UtilDateTime.nowTimestamp()]
+ }
+ }
+ List timeEntries = timesheet.getRelated('TimeEntry', null, null, false)
+
+ // update existing entries
+ BigDecimal hours = 0
+ timeEntries.findAll {
+ it.workEffortId == parameters.workEffortId &&
+ it.rateTypeId == parameters.rateTypeId
+ }.each {
+ // translate the date into the day number
+ int dayNumber = UtilDateTime.getIntervalInDays(timesheet.fromDate, it.fromDate)
+ hours = parameters["hoursDay${dayNumber}"] != null ? parameters["hoursDay${dayNumber}"] : -1
+ updateTimeEntry(parameters, it, hours, userLogin.partyId, null)
+ parameters["hoursDay${dayNumber}"] = -1
+ }
+
+ // process not yet done fields
+ for (int dayNr = 0; dayNr < 7; dayNr++) {
+ if (parameters["hoursDay${dayNr}"]) {
+ updateTimeEntry(parameters, null, parameters["hoursDay${dayNr}"], userLogin.partyId,
+ UtilDateTime.addDaysToTimestamp(timesheet.fromDate, dayNr))
+ }
+ }
+
+ // update the assignment status if required
+ if (parameters.checkComplete == 'Y') {
+ GenericValue alreadyAssign = from('WorkEffortPartyAssignment')
+ .where(workEffortId: parameters.workEffortId,
+ partyId: timesheet.partyId)
+ .filterByDate()
+ .queryFirst()
+ if (alreadyAssign.statusId != 'PAS_COMPLETED') {
+ run service: 'updateTaskAssigment', with: [partyId: timesheet.partyId,
+ statusId: 'PAS_COMPLETED',
+ roleTypeId: alreadyAssign.roleTypeId,
+ fromDate: alreadyAssign.fromDate,
+ workEffortId: parameters.workEffortId]
+ }
+ }
+ return success([timesheetId: timesheet.timesheetId])
+}
+
+/**
+ * Get the projectId when a phase or task is provided.
+ * @return 'Success response resolve project info, failure response otherwise.
+ */
+Map getProjectIdAndNameFromTask() {
+ if (!parameters.taskId && !parameters.phaseId) {
+ return failure(label('ProjectMgrUiLabels', 'ProjectMgrErrorProjectNotFound'))
+ }
+ GenericValue task = null
+ String phaseId = parameters.phaseId
+ if (!phaseId) {
+ task = from('WorkEffort').where(workEffortId: parameters.taskId).queryOne()
+ phaseId = task ? task.workEffortParentId : null
+ }
+ GenericValue phase = from('WorkEffort').where(workEffortId: phaseId).queryOne()
+ if (phase) {
+ GenericValue project = phase.getRelatedOne('ParentWorkEffort', true)
+
+ return success([projectId: project ? project.workEffortId : '',
+ projectName: project ? project.workEffortName : '',
+ phaseId: phase ? phase.workEffortId : '',
+ phaseName: phase ? phase.workEffortName : '',
+ taskId: task ? task.workEffortId : '',
+ taskName: task ? task.workEffortName : '',
+ taskWbsId: project?.workEffortId + '.' + phase?.sequenceNum + '.' + task?.sequenceNum])
+ }
+ return failure(label('ProjectMgrUiLabels', 'ProjectMgrErrorProjectNotFound'))
+}
+
+/**
+ * copy a project with related phases and tasks however no actual data"
+ * @return 'Success response containing the newProjectId in workEffortId and projectId
+ */
+Map copyProject() {
+ GenericValue project = from('WorkEffort').where(workEffortId: parameters.projectId).queryOne()
+ if (!project) {
+ return error(label('ProjectMgrUiLabels', 'ProjectMgrErrorProjectNotFound'))
+ }
+ parameters.workEffortName = parameters.workEffortName ?: project.workEffortName
+ parameters.description = parameters.description ?: project.description
+ parameters.workEffortTypeId = parameters.toTemplate == 'Y' ? 'PROJECT_TEMPLATE' : 'PROJECT'
+ parameters.currentStatusId = 'PRJ_ACTIVE'
+ Map serviceResult = run service: 'createWorkEffort', with: [*: parameters,
+ workEffortId: null]
+ String newProjectId = serviceResult.workEffortId
+
+ // copy assigned parties
+ from('WorkEffortAndPartyAssign')
+ .where(workEffortId: project.workEffortId)
+ .filterByDate()
+ .queryList()
+ .each {
+ run service: 'createWorkEffortPartyAssignment', with: [*: it.getAllFields(),
+ workEffortId: newProjectId,
+ fromDate: null,
+ statusId: 'PAS_ASSIGNED']
+ }
+
+ // copy phase
+ project.getRelated('ChildWorkEffort', null, null, false).each {
+ parameters.workEffortTypeId = parameters.toTemplate ? 'PHASE_TEMPLATE' : 'PHASE'
+ serviceResult = run service: 'createWorkEffort', with: [*: parameters,
+ workEffortName: it.workEffortName,
+ workEffortParentId: newProjectId,
+ currentStatusId: '_NA_',
+ workEffortId: null]
+ String newPhaseId = serviceResult.workEffortId
+ it.getRelated('ChildWorkEffort', null, null, false).each { task ->
+ task.workEffortTypeId = parameters.toTemplate ? 'TASK_TEMPLATE' : 'TASK'
+ task.workEffortParentId = newPhaseId
+ task.currentStatusId = 'PTS_CREATED'
+ run service: 'createWorkEffort', with: [*: task.getAllFields(),
+ workEffortId: null]
+ }
+ }
+ return success([workEffortId: newProjectId, projectId: newProjectId])
+}
+
+/**
+ * get Project information
+ * @return 'Success response containing the projectInfo and projectId
+ */
+Map getProject() {
+ GenericValue project = from('WorkEffort').where(workEffortId: parameters.projectId).cache().queryOne()
+ if (!project) {
+ return success()
+ }
+ Map highInfo = [projectId: project.workEffortId,
+ projectName: project.workEffortName,
+ projectDescription: project.description,
+ parentProjectId: project.workEffortParentId]
+ ['estimatedStartDate', 'estimatedCompletionDate', 'actualStartDate', 'currentStatusId',
+ 'actualCompletionDate', 'scopeEnumId', 'createdStamp', 'createdDate'].each {
+ highInfo.(it) = project.(it)
+ }
+
+ //loop through the related phases and tasks
+ highInfo = combineInfo(highInfo, highInfo, parameters.partyId)
+
+ // get e-mail address
+ GenericValue emailAddress = from('WorkEffortContactMechView')
+ .where(workEffortId: project.workEffortId,
+ contactMechTypeId: 'EMAIL_ADDRESS')
+ .filterByDate()
+ .queryFirst()
+ if (emailAddress) {
+ highInfo.emailAddress = emailAddress.infoString
+ }
+
+ highInfo = createDates(highInfo)
+
+ return success([projectInfo: highInfo,
+ projectId: project.workEffortId])
+}
+
+/**
+ * get Project Phase information
+ * @return 'Success response containing the phaseList and projectId
+ */
+Map getProjectPhaseList() {
+ GenericValue project = from('WorkEffort').where(workEffortId: parameters.projectId).cache().queryOne()
+ if (!project) {
+ return error(label('ProjectMgrUiLabels', 'ProjectMgrErrorProjectNotFound'))
+ }
+
+ List phaseList = []
+ from('WorkEffort')
+ .where(workEffortTypeId: 'PHASE', workEffortParentId: parameters.projectId)
+ .orderBy('sequenceNum', 'workEffortName')
+ .queryList()
+ .each {
+ Map highInfo = [sequenceNum: it.sequenceNum,
+ phaseId: it.workEffortId,
+ phaseSeqNum: it.sequenceNum,
+ phaseName: it.workEffortName,
+ phaseDescription: it.description,
+ scopeEnumId: it.scopeEnumId]
+ highInfo = combineInfo(highInfo, it, parameters.partyId)
+ highInfo = createDates(highInfo)
+ phaseList << highInfo
+ }
+ return success([phaseList: phaseList, projectId: parameters.projectId])
+}
+
+/**
+ * get Project Phase/task information
+ * @return 'Success response containing the taskList and projectId
+ */
+Map getProjectTaskList() {
+ GenericValue project = from('WorkEffort').where(workEffortId: parameters.projectId).cache().queryOne()
+ if (!project) {
+ return error(label('ProjectMgrUiLabels', 'ProjectMgrErrorProjectNotFound'))
+ }
+
+ List taskList = []
+ from('ProjectAndPhaseAndTask')
+ .where(workEffortTypeId: 'PHASE', workEffortParentId: parameters.projectId)
+ .orderBy('phaseName', 'phaseSeqNum', 'sequenceNum', 'workEffortName')
+ .queryList()
+ .each {
+ Map highInfo = [phaseName: it.phaseName,
+ phaseSeqNum: it.phaseSeqNum,
+ taskId: it.workEffortId]
+ highInfo = combineInfo(highInfo, it, parameters.partyId)
+ highInfo = createDates(highInfo)
+ highInfo.workEffortId = it.workEffortId
+ highInfo.workEffortName = it.workEffortName
+ highInfo.sequenceNum = it.sequenceNum
+ taskList << highInfo
+ }
+ return success([taskList: taskList, projectId: parameters.projectId])
+}
+
+/**
+ * Resolve information on task
+ * @return 'Success response containing the taskInfo
+ */
+Map getProjectTask() {
+ GenericValue task = from('WorkEffort').where(workEffortId: parameters.taskId).cache().queryOne()
+ Map highInfo = [taskId: task.workEffortId,
+ taskSeqNum: task.sequenceNum,
+ taskName: task.workEffortName,
+ taskDescription: task.description,
+ scopeEnumId: task.scopeEnumId,
+ workEffortParentId: task.workEffortParentId]
+ return success([taskInfo: combineInfo(highInfo, task, parameters.partyId)])
+}
+
+/**
+ * get Project information by party member
+ * @return 'Success response containing the projectParties
+ */
+Map getProjectsByParties() {
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ if (parameters.projectId) {
+ EQUALS(projectId: parameters.projectId)
+ }
+ if (parameters.partyId) {
+ EQUALS(partyId: parameters.partyId)
+ } else {
+ NOT_EQUAL(partyId: null)
+ }
+ }
+ List tasks = from('ProjectAndPhaseAndTaskParty')
+ .where(condition)
+ .orderBy('projectId', 'partyId')
+ .queryList()
+ List projectParties = []
+ Map projectParty = [:]
+ for (GenericValue task: tasks) {
+ if (projectParty && task.partyId != projectParty.partyId) {
+ projectParties << projectParty
+ projectParty = [:]
+ }
+ if (!projectParty) {
+ projectParty.partyId = task.partyId
+ GenericValue partyNameView = from('PartyNameView').where(partyId: task.partyId).cache().queryOne()
+ if (partyNameView) {
+ projectParty.partyName = "${partyNameView.lastName ?: ''},${partyNameView.firstName ?: ''}${partyNameView.groupName ?: ''}"
+ }
+ projectParty.roleTypeId = task.roleTypeId
+ projectParty.fromDate = task.fromDate
+ projectParty.thruDate = task.thruDate
+ }
+ // get the planned/actual hours
+ projectParty.putAll(getHours(task, task, task.partyId))
+ if (!projectParty) {
+ projectParties << projectParty
+ }
+ }
+
+ return success([projectParties: projectParties])
+}
+
+/**
+ * get task information by party member
+ * @return 'Success response containing the taskParties
+ */
+Map getTasksByParties() {
+ // get the list of tasks optionaly selected for a party
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ if (parameters.partyId) {
+ EQUALS(partyId: parameters.partyId)
+ }
+ if (parameters.workEffortId) {
+ EQUALS(workEffortId: parameters.workEffortId)
+ }
+ }
+ Map taskParty = [:]
+ List taskParties = []
+ from('WorkEffortPartyAssignment')
+ .where(condition)
+ .filterByDate()
+ .orderBy('workEffortId', 'partyId')
+ .queryList()
+ .each {
+ if (taskParty && taskParty.partyId != it.partyId) {
+ taskParties << taskParty
+ taskParty = [:]
+ }
+ if (!taskParty) {
+ taskParty.partyId = it.partyId
+ GenericValue partyNameView = from('PartyNameView').where(partyId: it.partyId).cache().queryOne()
+ if (partyNameView) {
+ taskParty.partyName = "${partyNameView.lastName ?: ''},${partyNameView.firstName ?: ''}${partyNameView.groupName ?: ''}"
+ }
+ taskParty.roleTypeId = it.roleTypeId
+ taskParty.statusId = it.statusId
+ taskParty.fromDate = it.fromDate
+ taskParty.thruDate = it.thruDate
+ }
+
+ // get the planned hours
+ taskParty.putAll(getHours(taskParty, it.getRelatedOne('WorkEffort', true), it.partyId))
+ }
+ if (taskParties) {
+ taskParties << taskParty
+ }
+ return success([taskParties: taskParties])
+}
+
+/**
+ * Creates TimeEntry and searches for a timesheetId if not provided
+ * @return 'Success response containing the timesheetId and fromDate, error response otherwise.
+ */
+Map createTimeEntryInTimesheet() {
+ String timesheetId = parameters.timesheetId
+ if (parameters.fromDate && !parameters.timesheetId) {
+ GenericValue timesheet = from('Timesheet').where(partyId: parameters.partyId).filterByDate().queryFirst()
+ if (timesheet) {
+ if (timesheet.statusId != 'TIMESHEET_IN_PROCESS') {
+ return error(label('ProjectMgrUiLabels', 'ProjectMgrCannotAddToTimesheet'))
+ }
+ timesheetId = timesheet.timesheetId
+ } else {
+ // create new timesheet
+ Map serviceReturn = run service: 'createTimesheetForThisWeek', with: [*: parameters,
+ requiredDate: parameters.fromDate]
+ timesheetId = serviceReturn.timesheetId
+ }
+ }
+ // get role for this party in this project
+ if (parameters.roleTypeId) {
+ GenericValue taskRole = from('ProjectPartyAndPhaseAndTask')
+ .where(partyId: parameters.partyId,
+ workEffortId: parameters.workEffortId)
+ .queryFirst()
+ run service: 'assignPartyToWorkEffort', with: [*: taskRole.getAllFields(),
+ statusId: 'PAS_ASSIGNED']
+ }
+ Map serviceResult = run service: 'createTimeEntry', with: [*: parameters,
+ timesheetId: timesheetId]
+ return success([fromDate: serviceResult.fromDate, timesheetId: timesheetId])
+}
+
+/**
+ * Add all reported time on all completed timeSheets from all workEfforts for a project
+ * @return 'Success response containing the invoiceId, error response otherwise.
+ */
+Map addProjectTimeToInvoice() {
+ // Recreate the invoice if still in preparation in order to correct errors.
+ boolean createInvoice = !parameters.reCreate
+ if (parameters.reCreate == 'Y') {
+ GenericValue invoice = from('Invoice').where(parameters).queryOne()
+ if (!invoice) {
+ return error(label('WorkEffortUiLabels', 'WorkEffortTimesheetCannotFindInvoice'))
+ }
+ //FIXME <call-simple-method method-name="checkInvoiceStatusInProgress"
+ // xml-resource="component://accounting/minilang/invoice/InvoiceServices.xml"/>
+ EntityCondition removeCond = EntityCondition.makeCondition('invoiceId', parameters.invoiceId)
+ delegator.storeByCondition('TimeEntry',
+ [invoiceId: null, invoiceItemSeqId: null],
+ removeCond)
+ delegator.removeByCondition('InvoiceItem', removeCond)
+ createInvoice = true //do not create, only add
+ }
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ EQUALS(projectId: parameters.projectId)
+ EQUALS(invoiceId: null)
+ EQUALS(timesheetStatusId: 'TIMESHEET_COMPLETED')
+ if (parameters.thruDate) {
+ LESS_THAN(fromDate: parameters.thruDate)
+ }
+ }
+ List tasks = from('ProjectPhaseTaskAndTimeEntryTimeSheet')
+ .where(condition)
+ .orderBy('workEffortId')
+ .queryList()
+ if (!tasks) {
+ return error(label('WorkEffortUiLabels', 'ProjectMgrNoTimeentryItemsFound'))
+ }
+ String invoiceId = parameters.invoiceId
+ if (createInvoice) {
+ Map serviceResult = run service: 'addWorkEffortTimeToNewInvoice', with: [*: parameters,
+ workEffortId: parameters.projectId,
+ combineInvoiceItem: 'Y']
+ invoiceId = serviceResult.invoiceId
+ }
+ tasks*.workEffortId.unique().each {
+ run service: 'addWorkEffortTimeToInvoice', with: [*: parameters,
+ invoiceId: invoiceId,
+ combineInvoiceItem: 'Y',
+ taskId: it]
+ }
+ return success([invoiceId: invoiceId])
+}
+
+/**
+ * SECA to add either project-testing or -approval parties to a task when a task is set to complete
+ * @return 'Success response after adding, error response otherwise.
+ */
+Map addValidationPartiesToTask() {
+ // check if this is the last party which completed his task
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ EQUALS(workEffortId: parameters.workEffortId)
+ NOT_EQUAL(statusId: 'PAS_COMPLETED')
+ NOT_EQUAL(partyId: parameters.partyId)
+ }
+ if (from('addValidationPartiesToTask')
+ .where(condition)
+ .queryCount() == 0) {
+ Map serviceResult = run service: 'getProjectIdAndNameFromTask', with: [taskId: parameters.workEffortId]
+ String projectId = serviceResult.projectId
+
+ // see who is responsible for testing/validation in this project
+ condition = new EntityConditionBuilder().AND {
+ EQUALS(workEffortId: projectId)
+ NOT_EQUAL(partyId: parameters.partyId)
+ IN(roleTypeId: ['PROVIDER_VALIDATOR', 'PROVIDER_TESTER'])
+ }
+ List assigns = from('WorkEffortPartyAssignment')
+ .where(condition)
+ .filterByDate()
+ .queryList()
+ if (assigns) {
+ assigns.each {
+ run service: 'createWorkEffortPartyAssignment', with: [*: it.getAllFields(),
+ workEffortId: parameters.workEffortId,
+ statusId: 'PAS_ASSIGNED']
+ }
+ } else {
+ logInfo('No validation parties defined in this project: no validation parties added....')
+ }
+ } else {
+ logInfo('Not the last party who completes his task: validation parties not added....')
+ }
+ return success()
+}
+
+// Internal functions
+/**
+ * combine lower level Actual hours info.
+ * @return Map highInfo with actual hours
+ */
+private Map combineActualHours(Map highInfo, String partyId) {
+ /* to calculate actual hours : the declared number of hours in time entry should be multiplied by the
+ * max percentage declared in PartyRate if a valid party rate can be found for the party associated to a
+ * the timesheet associated to this time entry and has the same rateType as this timeEntry
+ * actualHoursOriginal is the total of hours in time entries without application of percentage declared in partyRate
+ */
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ if (highInfo.projectId) {
+ EQUALS(projectId: highInfo.projectId)
+ }
+ if (highInfo.phaseId) {
+ EQUALS(phaseId: highInfo.phaseId)
+ }
+ if (highInfo.taskId) {
+ EQUALS(taskId: highInfo.taskId)
+ }
+ if (partyId) {
+ EQUALS(hoursPartyId: partyId)
+ }
+ }
+ Map notRatedValue = from('ProjectPhaseTaskActualNotRatedHoursView')
+ .where(condition)
+ .select('totalOriginalHours')
+ .queryFirst()
+ BigDecimal originalHours = notRatedValue.totalOriginalHours
+
+ /* II- get total for timeEntries having a partyRate that should be applied
+ before applying rate (totalOriginalHours)
+ after applying rate (totalRatedHours)
+ */
+ Map ratedValue = from('ProjectPhaseTaskActualRatedHoursView')
+ .where(condition)
+ .select('totalOriginalHours', 'totalRatedHours')
+ .queryFirst()
+ // not used ratedValue.totalRatedHours because not works, reason seem to be totalRatedHours is a calculated field ???
+ highInfo.actualHours = (ratedValue.totalRatedHours ?: 0) + (originalHours ?: 0)
+ highInfo.originalActualHours = originalHours ?: 0 + (ratedValue.totalOriginalHours ?: 0)
+
+ // do the same but for non-billed hours
+ // first get not rated hours
+ condition = new EntityConditionBuilder().AND {
+ if (highInfo.projectId) {
+ EQUALS(projectId: highInfo.projectId)
+ }
+ if (highInfo.phaseId) {
+ EQUALS(phaseId: highInfo.phaseId)
+ }
+ if (highInfo.taskId) {
+ EQUALS(taskId: highInfo.taskId)
+ }
+ if (partyId) {
+ EQUALS(hoursPartyId: partyId)
+ }
+ EQUALS(invoiceId: null)
+ }
+ notRatedValue = from('ProjectPhaseTaskActualNotRatedHoursView')
+ .where(condition)
+ .select('totalOriginalHours')
+ .queryFirst()
+ BigDecimal actualNonBilledHours = 0
+ if (notRatedValue.totalOriginalHours) {
+ // second get non billed for entries having an invoiceId
+ ratedValue = from('ProjectPhaseTaskActualRatedHoursView')
+ .where(condition)
+ .select('totalOriginalHours', 'totalRatedHours')
+ .queryFirst()
+ actualNonBilledHours = notRatedValue.totalOriginalHours + (ratedValue.totalOriginalHours ?: 0)
+ }
+ highInfo.actualNonBilledHours = actualNonBilledHours
+ return highInfo
+}
+
+/**
+ * combine lower level status, dates of tasks.
+ * @return Map highInfo
+ */
+private Map combineInfo(Map highInfo, Map lowInfo, String partyId) {
+ highInfo.currentStatusId = combineStatusInfo(highInfo, lowInfo)
+ highInfo = combineDatesAndPlannedHoursInfo(highInfo)
+ return combineActualHours(highInfo, partyId)
+}
+
+/**
+ * Merge the estimated and actual dates
+ * @return Map highInfo
+ */
+private Map createDates(Map highInfo) {
+ // input/output is 'highInfo map
+ // create dates taking the last known one to save space on the list
+ highInfo.startDate = highInfo.actualStartDate ?: highInfo.estimatedStartDate
+ highInfo.completionDate = highInfo.actualCompletionDate ?: highInfo.estimatedCompletionDate
+ return highInfo
+}
+
+/**
+ * Get the planned and estimated hours for a task and add to the highInfo map
+ * @return Map highInfo with hours
+ */
+private Map getHours(Map highInfo, Map lowInfo, String partyId) {
+ // input is 'lowInfo' map output is 'highInfo' map
+ // PartyId: if provided only the hours of that party
+ // add the planned hours together
+ from('WorkEffortSkillStandard')
+ .where(workEffortId: lowInfo.workEffortId)
+ .queryList()
+ .each {
+ if (it.estimatedDuration) {
+ highInfo.plannedHours = (highInfo.plannedHours ?: 0) + it.estimatedDuration
+ }
+ }
+
+ // get the actual billed / non billed hours
+ from('TimeEntry')
+ .where(workEffortId: lowInfo.workEffortId)
+ .queryList()
+ .each {
+ if (it.hours) {
+ GenericValue timesheet = it.getRelatedOne('Timesheet', true)
+
+ // check if only a part of the registered hours need to be taken into account
+ BigDecimal originalActualHours = it.hours
+ GenericValue partyRate = from('PartyRate')
+ .where(partyId: timesheet.partyId,
+ rateTypeId: it.rateTypeId)
+ .filterByDate(it.fromDate)
+ .queryFirst()
+ if (partyRate.percentageUsed) {
+ it.actualHours = (it.actualHours * partyRate.percentageUsed) / 100
+ }
+ if (partyId && timesheet.partyId == partyId) {
+ highInfo.originalActualHours = originalActualHours + (highInfo.originalActualHours ?: 0)
+ highInfo.actualHours = it.actualHours + (highInfo.actualHours ?: 0)
+ if (!it.invoiceId) {
+ highInfo.actualNonBilledHours = it.hours + (highInfo.actualNonBilledHours ?: 0)
+ }
+ }
+
+ // keep also a general total for the actual hours of all participants
+ highInfo.actualTotalHours = it.hours + highInfo.actualTotalHours ?: 0
+ if (!it.invoiceId) {
+ highInfo.actualNonBilledTotalHours = it.hours + (highInfo.actualNonBilledTotalHours ?: 0)
+ }
+ }
+ }
+ return highInfo
+}
+
+/**
+ * update a timeEntry in silence
+ * @return nothing
+ */
+private void updateTimeEntry(Map parameters, GenericValue timeEntry, BigDecimal hours, String partyId, Timestamp fromDate) {
+ if (hours == -1 ) {
+ return
+ }
+ if (timeEntry && timeEntry.timeEntryId) {
+ if (hours == 0) {
+ run service: 'deleteTimeEntry', with: [timeEntryId: timeEntry.timeEntryId]
+ } else {
+ run service: 'updateTimeEntry', with: [hours: hours,
+ timeEntryId: timeEntry.timeEntryId,
+ rateTypeId: timeEntry.rateTypeId,
+ partyId: timeEntry.partyId]
+ }
+ } else {
+ run service: 'createTimeEntry', with: [*: parameters,
+ hours: hours,
+ fromDate: fromDate,
+ partyId: partyId]
+ }
+}
+
+/**
+ * combine lower level status
+ * The status for a project or phase is
+ * * IN_PROGRESS if at least one task still in progress
+ * * COMPLETED if all task are either completed or cancelled
+ * * CREATED if other conditions does not apply
+ * For a task the status is
+ * * IN_PROGRESS if it has at least one resource and at least a time entry
+ * * ASSIGNED if it has at least one resource but no time entry associated
+ * @return statusId after analyse
+ */
+private String combineStatusInfo(Map highInfo, Map lowInfo = null) {
+ if (lowInfo && lowInfo.workEffortTypeId == 'TASK') {
+ if (lowInfo.currentStatusId == 'PTS_CREATED' &&
+ from('WorkEffortPartyAssignment')
+ .where(workEffortId: lowInfo.workEffortId)
+ .filterByDate()
+ .queryCount() > 0) {
+ return (from('TimeEntry')
+ .where(workEffortId: lowInfo.workEffortId)
+ .queryCount() > 0) ? 'PTS_CREATED_AS' : 'PTS_CREATED_IP'
+ }
+ return lowInfo.currentStatusId
+ }
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ if (highInfo.projectId) {
+ EQUALS(projectId: highInfo.projectId)
+ }
+ if (highInfo.phaseId) {
+ EQUALS(phaseId: highInfo.phaseId)
+ }
+ if (highInfo.taskId) {
+ EQUALS(taskId: highInfo.taskId)
+ }
+ }
+ long tasksCount = from('ProjectPhaseTaskAssignmentView').where(condition).queryCount()
+ EntityCondition completedCond = new EntityConditionBuilder().AND {
+ IN(taskStatusId: ['PTS_CANCELLED', 'PTS_COMPLETED'])
+ }
+ long completedTasks = from('ProjectPhaseTaskAssignmentView').where(condition).having(completedCond).queryCount()
+ if (completedTasks == tasksCount) {
+ return 'PTS_COMPLETED'
+ }
+
+ EntityCondition assignedCond = new EntityConditionBuilder().AND {
+ EQUALS(entriesCount: 0L)
+ GREATER_THAN_EQUAL_TO(resourceCount: 1L)
+ NOT_IN(taskStatusId: ['PTS_CANCELLED', 'PTS_COMPLETED'])
+ }
+ long assignedTasks = from('ProjectPhaseTaskAssignmentView').where(condition).having(assignedCond).queryCount()
+ EntityCondition relatedCond = new EntityConditionBuilder().AND {
+ GREATER_THAN_EQUAL_TO(entriesCount: 1L)
+ GREATER_THAN_EQUAL_TO(resourceCount: 1L)
+ NOT_IN(taskStatusId: ['PTS_CANCELLED', 'PTS_COMPLETED'])
+ }
+ long progressTasks = from('ProjectPhaseTaskAssignmentView').where(condition).having(relatedCond).queryCount()
+ return (progressTasks > 0 || assignedTasks > 0) ? 'PTS_CREATED_IP' : 'PTS_CREATED'
+}
+
+/**
+ * combine lower level start end dates and planned hours for a project, phase or task
+ * @return Map containning date info
+ */
+private Map combineDatesAndPlannedHoursInfo(Map highInfo) {
+ EntityCondition condition = new EntityConditionBuilder().AND {
+ if (highInfo.projectId) {
+ EQUALS(projectId: highInfo.projectId)
+ }
+ if (highInfo.phaseId) {
+ EQUALS(phaseId: highInfo.phaseId)
+ }
+ if (highInfo.taskId) {
+ EQUALS(taskId: highInfo.taskId)
+ }
+ }
+
+ // Now used TimeEntries to update (or not) actual start and end Date
+ Map summaryInfo = from('ProjectPhaseTaskSklSumView')
+ .where(condition)
+ .select('projectId', 'estimatedStartDate', 'actualStartDate',
+ 'estimatedCompletionDate', 'actualCompletionDate', 'plannedHours', 'priority')
+ .queryFirst()
+ if (summaryInfo) {
+ highInfo.putAll(summaryInfo.getAllFields())
+ }
+
+ // update actual start date by the min date form sub tasks associated TimeEntries
+ // (if before actualStartDate field)
+ Map timeEntriesInfo = from('ProjectPhaseTaskActualEntrySumView')
+ .where(condition)
+ .select('actualEntryStartDate')
+ .queryFirst()
+ if (timeEntriesInfo && highInfo.actualStartDate > timeEntriesInfo.actualEntryStartDate) {
+ highInfo.actualStartDate = timeEntriesInfo.actualEntryStartDate
+ }
+ return highInfo
+}
diff --git a/projectmgr/webapp/projectmgr/WEB-INF/controller.xml b/projectmgr/webapp/projectmgr/WEB-INF/controller.xml
index 2b332c6..1858c7d 100644
--- a/projectmgr/webapp/projectmgr/WEB-INF/controller.xml
+++ b/projectmgr/webapp/projectmgr/WEB-INF/controller.xml
@@ -372,13 +372,13 @@
</request-map>
<request-map uri="updateMyTimesheet">
<security https="true" auth="true"/>
- <event type="service-multi" invoke="updateTimeEntryByWorkeffort"/>
+ <event type="service-multi" invoke="updateTimeEntryByWorkEffort"/>
<response name="success" type="view-home" value="MyTimesheet"/>
<response name="error" type="view-home" value="MyTimesheet"/>
</request-map>
<request-map uri="updateTimesheet">
<security https="true" auth="true"/>
- <event type="service-multi" invoke="updateTimeEntryByWorkeffort"/>
+ <event type="service-multi" invoke="updateTimeEntryByWorkEffort"/>
<response name="success" type="view" value="Timesheet"/>
<response name="error" type="view" value="Timesheet"/>
</request-map>