Completed
Push — master ( aed8a1...cb9ab4 )
by Andreas
14:09
created

org_openpsa_projects_project   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 240
Duplicated Lines 0 %

Test Coverage

Coverage 81.39%

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 240
ccs 70
cts 86
cp 0.8139
rs 9.92
c 0
b 0
f 0
wmc 31

10 Methods

Rating   Name   Duplication   Size   Complexity  
A get_task_hours() 0 13 2
A get_members() 0 17 4
B refresh_from_tasks() 0 49 9
A _on_loaded() 0 9 3
A get_label() 0 12 3
A __get() 0 6 2
A get_task_count() 0 16 2
A get_salesproject() 0 3 1
A _find_status() 0 11 4
A get_icon() 0 3 1
1
<?php
2
/**
3
 * @package org.openpsa.projects
4
 * @author Nemein Oy http://www.nemein.com/
5
 * @copyright Nemein Oy http://www.nemein.com/
6
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
7
 */
8
9
/**
10
 * @property integer $id Local non-replication-safe database identifier
11
 * @property integer $up  In practice all salesprojects will have up of 0 but in case we someday wish to divide a huge salesproject to subprojects this is here
12
 * @property integer $start
13
 * @property integer $end
14
 * @property string $code
15
 * @property string $title
16
 * @property string $description
17
 * @property integer $state
18
 * @property integer $customer
19
 * @property integer $customerContact
20
 * @property integer $owner
21
 * @property integer $probability
22
 * @property float $value
23
 * @property float $profit
24
 * @property float $price
25
 * @property float $cost
26
 * @property integer $closeEst
27
 * @property string $guid
28
 * @property integer $status Current project status
29
 * @property float $plannedHours
30
 * @property float $reportedHours
31
 * @property float $invoicedHours
32
 * @property float $invoiceableHours
33
 * @property integer $orgOpenpsaAccesstype Shortcut for various ACL scenarios
34
 * @property string $orgOpenpsaOwnerWg The "owner" workgroup of this object
35
 * @package org.openpsa.projects
36
 */
37
class org_openpsa_projects_project extends midcom_core_dbaobject
38
{
39
    public $__midcom_class_name__ = __CLASS__;
40
    public $__mgdschema_class_name__ = 'org_openpsa_project';
41
42
    public $autodelete_dependents = [
43
        org_openpsa_contacts_role_dba::class => 'objectGuid'
44
    ];
45
46
    public $contacts = []; //Shorthand access for contact members
47
    public $resources = []; // --''--
48
49
    /**
50
     * Map that defines project status changes based on what types of tasks are available
51
     *
52
     * First-level key is the project's own status type
53
     * Beginning from second level, keys are parsed from top to bottom, and the first match is used
54
     * Null values mean no change
55
     *
56
     * @var array
57
     */
58
    private $_status_map = [
59
        'rejected' => [
60
            'ongoing' => org_openpsa_projects_task_status_dba::REOPENED, // If there's an ongoing task, the project seems to have resumed
61
            'not_started' => org_openpsa_projects_task_status_dba::PROPOSED, // There are pending tasks, so maybe the project is back to start
62
            'rejected' => null, // Sanity check
63
            'on_hold' => org_openpsa_projects_task_status_dba::ONHOLD, //Blocker task
64
            'closed' => org_openpsa_projects_task_status_dba::COMPLETED //Work seems to have been finished
65
        ],
66
        'not_started' => [
67
            'ongoing' => org_openpsa_projects_task_status_dba::STARTED, //Work seems to have been started
68
            'on_hold' => org_openpsa_projects_task_status_dba::ONHOLD, // Or is on hold
69
            'not_started' => null,
70
            'closed' => org_openpsa_projects_task_status_dba::COMPLETED // Or is even finished already
71
        ],
72
        'ongoing' => [
73
            'ongoing' => null, //Only do something if there are no ongoing tasks
74
            'on_hold' => org_openpsa_projects_task_status_dba::ONHOLD, //Blocker task
75
            'not_started' => [
76
                'closed' => org_openpsa_projects_task_status_dba::ONHOLD, //Project is in limbo: Some tasks are finished, others didn't begin yet
77
                'not_started' => org_openpsa_projects_task_status_dba::PROPOSED //Back to start: Someone withdrew acceptance
78
            ],
79
            'closed' => org_openpsa_projects_task_status_dba::ONHOLD //Work seems to have been finished
80
        ],
81
        'closed' => [
82
            'not_started' => org_openpsa_projects_task_status_dba::REOPENED, //Something new came up, reopen
83
            'ongoing' => org_openpsa_projects_task_status_dba::REOPENED, //Something new came up, reopen
84
            'closed' => null, //Sanity check
85
            'on_hold' => org_openpsa_projects_task_status_dba::ONHOLD
86
        ],
87
        'on_hold' => [
88
            'on_hold' => null, //only if no task is on hold we have to look for something else
89
            'not_started' => [
90
                'closed' => null,
91
                'not_started' => org_openpsa_projects_task_status_dba::PROPOSED // If nothing is closed, ongoing or on hold, let's try not_started
92
            ],
93
            'closed' => org_openpsa_projects_task_status_dba::COMPLETED // If nothing is not_started, ongoing or on hold, let's try closed
94
        ]
95
    ];
96
97 54
    public function __get($property)
98
    {
99 54
        if ($property == 'status_type') {
100 2
            return org_openpsa_projects_workflow::get_status_type($this->status);
101
        }
102 54
        return parent::__get($property);
103
    }
104
105 45
    public function _on_loaded()
106
    {
107 45
        if ($this->title == "") {
108 30
            $this->title = "Project #{$this->id}";
109
        }
110
111 45
        if (!$this->status) {
112
            //Default to proposed if no status is set
113 40
            $this->status = org_openpsa_projects_task_status_dba::PROPOSED;
114
        }
115 45
    }
116
117 2
    public function get_icon()
118
    {
119 2
        return 'tasks';
120
    }
121
122
    /**
123
     * Generate a user-readable label for the task using the task/project hierarchy
124
     */
125 2
    public function get_label()
126
    {
127 2
        $label_elements = [$this->title];
128 2
        $project = $this;
129 2
        while ($project = $project->get_parent()) {
130
            if (!empty($project->title)) {
131
                $label_elements[] = $project->title;
132
            }
133
        }
134
135 2
        $label = implode(' / ', array_reverse($label_elements));
136 2
        return trim($label);
137
    }
138
139
    public function get_salesproject()
140
    {
141
        return new org_openpsa_sales_salesproject_dba($this->id);
142
    }
143
144
    /**
145
     * Populates contacts as resources lists
146
     */
147 3
    public function get_members()
148
    {
149 3
        if (!$this->guid) {
150
            return false;
151
        }
152
153 3
        $mc = org_openpsa_contacts_role_dba::new_collector('objectGuid', $this->guid);
154 3
        $ret = $mc->get_rows(['role', 'person']);
155
156 3
        foreach ($ret as $data) {
157 1
            if ($data['role'] == org_openpsa_projects_task_resource_dba::CONTACT) {
158 1
                $this->contacts[$data['person']] = true;
159
            } else {
160 1
                $this->resources[$data['person']] = true;
161
            }
162
        }
163 3
        return true;
164
    }
165
166
    /**
167
     * Get the number of tasks for the different status types
168
     *
169
     * @return array The task status overview
170
     */
171
    public function get_task_count()
172
    {
173
        $numbers = [
174
            'not_started' => 0,
175
            'ongoing' => 0,
176
            'on_hold' => 0,
177
            'closed' => 0,
178
            'rejected' => 0
179
        ];
180
        $task_mc = org_openpsa_projects_task_dba::new_collector('project', $this->id);
181
        $statuses = $task_mc->get_values('status');
182
        foreach ($statuses as $status) {
183
            $type = org_openpsa_projects_workflow::get_status_type($status);
184
            $numbers[$type]++;
185
        }
186
        return $numbers;
187
    }
188
189
    /**
190
     * Get the number of hours for the different status types
191
     *
192
     * @return array The task hours overview
193
     */
194 1
    public function get_task_hours()
195
    {
196
        $numbers = [
197 1
            'plannedHours' => 0,
198
            'reportedHours' => 0
199
        ];
200 1
        $task_mc = org_openpsa_projects_task_dba::new_collector('project', $this->id);
201 1
        $tasks = $task_mc->get_rows(['plannedHours', 'reportedHours']);
202 1
        foreach ($tasks as $values) {
203 1
            $numbers['plannedHours'] += $values['plannedHours'];
204 1
            $numbers['reportedHours'] += $values['reportedHours'];
205
        }
206 1
        return $numbers;
207
    }
208
209
    /**
210
     * Set project information according to the situation of its tasks
211
     *
212
     * This adjusts the timeframe if necessary and tries to determine the project's
213
     * status according to the current task situation
214
     */
215 26
    public function refresh_from_tasks()
216
    {
217 26
        $update_required = false;
218
219 26
        $task_statuses = [];
220 26
        $status_types = [];
221
222 26
        $task_qb = org_openpsa_projects_task_dba::new_query_builder();
223 26
        $task_qb->add_constraint('project', '=', $this->id);
224 26
        $ret = $task_qb->execute();
225
226 26
        if (empty($ret)) {
227
            return;
228
        }
229
230 26
        foreach ($ret as $task) {
231 26
            if ($task->start < $this->start) {
0 ignored issues
show
Bug Best Practice introduced by
The property start does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
232 9
                $this->start = $task->start;
233 9
                $update_required = true;
234
            }
235 26
            if ($task->end > $this->end) {
0 ignored issues
show
Bug Best Practice introduced by
The property end does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
236 18
                $this->end = $task->end;
237 18
                $update_required = true;
238
            }
239
240 26
            $status_types[$task->status_type] = true;
0 ignored issues
show
Bug Best Practice introduced by
The property status_type does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
241 26
            $task_statuses[$task->status] = true;
0 ignored issues
show
Bug Best Practice introduced by
The property status does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
242
        }
243
244 26
        if (count($task_statuses) == 1) {
245
            // If all tasks are of the same type, that is the type to use then
246 26
            $new_status = key($task_statuses);
247
        } else {
248 1
            $new_status = $this->_find_status($this->_status_map[$this->status_type], $status_types);
0 ignored issues
show
Bug Best Practice introduced by
The property status_type does not exist on org_openpsa_projects_project. Since you implemented __get, consider adding a @property annotation.
Loading history...
249
        }
250
251 26
        if (   $new_status !== null
252 26
            && $this->status != $new_status) {
253 22
            $this->status = $new_status;
254 22
            $update_required = true;
255
        }
256
257 26
        if ($update_required) {
258 25
            debug_add("Some project information needs to be updated, skipping RCS");
259 25
            $this->_use_rcs = false;
260 25
            return $this->update();
261
        }
262 7
        debug_add("All project information is up-to-date");
263 7
        return true;
264
    }
265
266 1
    private function _find_status(array $map, array $status_types)
267
    {
268 1
        foreach ($map as $type => $new_status) {
269 1
            if (array_key_exists($type, $status_types)) {
270 1
                if (is_array($new_status)) {
271
                    return $this->_find_status($new_status, $status_types);
272
                }
273 1
                return $new_status;
274
            }
275
        }
276
        return null;
277
    }
278
}
279