Passed
Push — master ( 7ff907...c48af4 )
by Andreas
17:26
created

org_openpsa_projects_task_dba::refresh_status()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.3332

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 3
nop 0
dl 0
loc 24
ccs 8
cts 12
cp 0.6667
crap 3.3332
rs 9.8333
c 0
b 0
f 0
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
 * MidCOM wrapped access to the MgdSchema class, keep logic here
11
 *
12
 * @property integer $up
13
 * @property integer $project
14
 * @property integer $start
15
 * @property integer $end
16
 * @property string $title
17
 * @property string $description
18
 * @property float $plannedHours
19
 * @property integer $status cache of last status
20
 * @property integer $agreement
21
 * @property integer $customer
22
 * @property integer $manager
23
 * @property float $reportedHours
24
 * @property float $invoicedHours
25
 * @property float $invoiceableHours
26
 * @property boolean $hoursInvoiceableDefault Are hours invoiceable by default ?
27
 * @property integer $priority
28
 * @property integer $orgOpenpsaAccesstype Shortcut for various ACL scenarios
29
 * @property integer $orgOpenpsaObtype Used to a) distinguish OpenPSA objects in QB b) store object "subtype" (project vs task etc)
30
 * @property string $orgOpenpsaOwnerWg The "owner" workgroup of this object
31
 * @package org.openpsa.projects
32
 */
33
class org_openpsa_projects_task_dba extends midcom_core_dbaobject
34
{
35
    const OBTYPE = 6002;
36
37
    public $__midcom_class_name__ = __CLASS__;
38
    public $__mgdschema_class_name__ = 'org_openpsa_task';
39
40
    public $autodelete_dependents = [
41
        org_openpsa_projects_task_status_dba::class => 'task',
42
        org_openpsa_projects_task_resource_dba::class => 'task',
43
    ];
44
45
    public $contacts = []; //Shorthand access for contact members
46
    public $resources = []; // --''--
47
    public $_skip_acl_refresh = false;
48
    public $_skip_parent_refresh = false;
49
    private $_status;
50
51
    /**
52
     * Deny midgard:read by default
53
     */
54 1
    public function get_class_magic_default_privileges()
55
    {
56 1
        $privileges = parent::get_class_magic_default_privileges();
57 1
        $privileges['EVERYONE']['midgard:read'] = MIDCOM_PRIVILEGE_DENY;
58 1
        return $privileges;
59
    }
60
61 18
    public function _on_creating()
62
    {
63 18
        $this->orgOpenpsaObtype = self::OBTYPE;
64 18
        if (!$this->manager) {
65 17
            $this->manager = midcom_connection::get_user();
66
        }
67 18
        return $this->_prepare_save();
68
    }
69
70 40
    public function _on_loaded()
71
    {
72 40
        if ($this->title == "") {
73 17
            $this->title = "Task #{$this->id}";
74
        }
75
76 40
        if (!$this->status) {
77
            //Default to proposed if no status is set
78 32
            $this->status = org_openpsa_projects_task_status_dba::PROPOSED;
79
        }
80 40
    }
81
82 28
    public function refresh() : bool
83
    {
84 28
        $this->contacts = [];
85 28
        $this->resources = [];
86 28
        $this->_status = null;
87 28
        return parent::refresh();
88
    }
89
90 48
    public function __get($property)
91
    {
92 48
        if ($property == 'status_type') {
93 31
            return org_openpsa_projects_workflow::get_status_type($this->status);
94
        }
95 48
        if (in_array($property, ['status_comment', 'status_time'])) {
96 6
            if ($this->_status === null) {
97 6
                $this->refresh_status();
98
            }
99 6
            return $this->_status[$property];
100
        }
101 48
        return parent::__get($property);
102
    }
103
104 31
    public function _on_updating()
105
    {
106 31
        return $this->_prepare_save();
107
    }
108
109 31
    public function _on_updated()
110
    {
111
        // Sync the object's ACL properties into MidCOM ACL system
112 31
        if (   !$this->_skip_acl_refresh) {
113 14
            if ($this->orgOpenpsaAccesstype && $this->orgOpenpsaOwnerWg) {
114
                debug_add("Synchronizing task ACLs to MidCOM");
115
                $sync = new org_openpsa_core_acl_synchronizer();
116
                $sync->write_acls($this, $this->orgOpenpsaOwnerWg, $this->orgOpenpsaAccesstype);
117
            }
118
119
            //Ensure manager can do stuff
120 14
            if ($this->manager) {
121 11
                $manager_person = midcom::get()->auth->get_user($this->manager);
122 11
                $this->set_privilege('midgard:read', $manager_person->id, MIDCOM_PRIVILEGE_ALLOW);
123 11
                $this->set_privilege('midgard:create', $manager_person->id, MIDCOM_PRIVILEGE_ALLOW);
124 11
                $this->set_privilege('midgard:delete', $manager_person->id, MIDCOM_PRIVILEGE_ALLOW);
125 11
                $this->set_privilege('midgard:update', $manager_person->id, MIDCOM_PRIVILEGE_ALLOW);
126
            }
127
        }
128
129 31
        $this->_update_parent();
130 31
    }
131
132 18
    public function _on_deleting()
133
    {
134 18
        $this->update_cache(false);
135 18
        return parent::_on_deleting();
136
    }
137
138
    /**
139
     * Generate a user-readable label for the task using the task/project hierarchy
140
     */
141 3
    public function get_label() : string
142
    {
143 3
        $label_elements = [$this->title];
144 3
        $task = $this;
145
        do {
146 3
            $label_elements[] = $task->title;
147 3
        } while ($task = $task->get_parent());
148
149 3
        $label = implode(' / ', array_reverse($label_elements));
150 3
        return trim($label);
151
    }
152
153
    public function get_icon() : string
154
    {
155
        return 'calendar-check-o';
156
    }
157
158
    /**
159
     * Populates contacts as resources lists
160
     */
161 18
    public function get_members()
162
    {
163 18
        if ($this->id) {
164 18
            $mc = org_openpsa_projects_task_resource_dba::new_collector('task', $this->id);
165 18
            $ret = $mc->get_rows(['orgOpenpsaObtype', 'person']);
166
167 18
            foreach ($ret as $data) {
168 3
                if ($data['orgOpenpsaObtype'] == org_openpsa_projects_task_resource_dba::CONTACT) {
169 1
                    $this->contacts[$data['person']] = true;
170
                } else {
171 3
                    $this->resources[$data['person']] = true;
172
                }
173
            }
174
        }
175 18
    }
176
177
    /**
178
     * Adds new contacts or resources
179
     */
180 16
    public function add_members(string $property, array $ids)
181
    {
182 16
        if ($property === 'contacts') {
183 4
            $type = org_openpsa_projects_task_resource_dba::CONTACT;
184 16
        } elseif ($property === 'resources') {
185 16
            $type = org_openpsa_projects_task_resource_dba::RESOURCE;
186
        } else {
187
            return;
188
        }
189
190 16
        foreach ($ids as $id) {
191 16
            $resource = new org_openpsa_projects_task_resource_dba();
192 16
            $resource->orgOpenpsaObtype = $type;
193 16
            $resource->task = $this->id;
194 16
            $resource->person = (int) $id;
195 16
            if ($resource->create()) {
196 16
                $this->{$property}[$id] = true;
197
            }
198
        }
199 16
    }
200
201 32
    private function _prepare_save() : bool
202
    {
203
        //Make sure we have end
204 32
        if (!$this->end || $this->end == -1) {
205 17
            $this->end = time();
206
        }
207
        //Make sure we have start
208 32
        if (!$this->start) {
209 17
            $this->start = min(time(), $this->end - 1);
210
        }
211
212
        // Reset start and end to start/end of day
213 32
        $this->start = strtotime('today', $this->start);
214 32
        $this->end = strtotime('tomorrow', $this->end) - 1;
215
216 32
        if ($this->start > $this->end) {
217
            debug_add("start ({$this->start}) is greater than end ({$this->end}), aborting", MIDCOM_LOG_ERROR);
218
            return false;
219
        }
220
221 32
        if ($agreement = $this->get_agreement()) {
222
            // Get customer company into cache from agreement's sales project
223
            try {
224 12
                $agreement = org_openpsa_sales_salesproject_deliverable_dba::get_cached($agreement);
225 12
                $this->hoursInvoiceableDefault = true;
226 12
                if (!$this->customer) {
227 11
                    $salesproject = org_openpsa_sales_salesproject_dba::get_cached($agreement->salesproject);
0 ignored issues
show
Bug Best Practice introduced by
The property salesproject does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
228 12
                    $this->customer = $salesproject->customer;
0 ignored issues
show
Bug Best Practice introduced by
The property customer does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
229
                }
230 12
            } catch (midcom_error $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
231
            }
232
        } else {
233
            // No agreement, we can't be invoiceable
234 22
            $this->hoursInvoiceableDefault = false;
235
        }
236
237
        // Update hour caches
238 32
        $this->update_cache(false);
239
240 32
        return true;
241
    }
242
243 33
    public function get_agreement() : int
244
    {
245 33
        if ($this->up) {
246
            do {
247
                $parent = $this->get_parent();
248
            } while ($parent->up);
0 ignored issues
show
Bug Best Practice introduced by
The property up does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
249
            return $parent->agreement;
0 ignored issues
show
Bug Best Practice introduced by
The property agreement does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
250
        }
251 33
        return $this->agreement;
252
    }
253
254
    /**
255
     * Update hour report caches
256
     */
257 32
    public function update_cache(bool $update = true) : bool
258
    {
259 32
        if (!$this->id) {
260 18
            return false;
261
        }
262
263 32
        debug_add("updating hour caches");
264
265 32
        $hours = $this->list_hours();
266 32
        $stat = true;
267
268 32
        $this->reportedHours = $hours['reported'];
269 32
        $this->invoicedHours = $hours['invoiced'];
270 32
        $this->invoiceableHours = $hours['invoiceable'];
271
272
        try {
273 32
            $agreement = new org_openpsa_sales_salesproject_deliverable_dba($this->get_agreement());
274 12
            $agreement->update_units($this->id, $hours);
275 32
        } catch (midcom_error $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
276
        }
277
278 32
        if ($update) {
279 18
            $this->_use_rcs = false;
280 18
            $this->_skip_acl_refresh = true;
281 18
            $this->_skip_parent_refresh = true;
282 18
            $stat = $this->update();
283 18
            debug_add("saving updated values to database returned {$stat}");
284
        }
285 32
        return $stat;
286
    }
287
288 32
    private function list_hours() : array
289
    {
290
        $hours = [
291 32
            'reported'    => 0,
292
            'invoiced'    => 0,
293
            'invoiceable' => 0,
294
        ];
295
296 32
        $report_mc = org_openpsa_expenses_hour_report_dba::new_collector('task', $this->id);
297 32
        $report_mc->add_value_property('hours');
298 32
        $report_mc->add_value_property('invoice');
299 32
        $report_mc->add_value_property('invoiceable');
300 32
        $report_mc->execute();
301
302 32
        foreach ($report_mc->list_keys() as $guid => $empty) {
303 16
            $report_data = $report_mc->get($guid);
304 16
            $report_hours = $report_data['hours'];
305
306 16
            $hours['reported'] += $report_hours;
307
308 16
            if ($report_data['invoiceable']) {
309 7
                if ($report_data['invoice']) {
310 4
                    $hours['invoiced'] += $report_hours;
311
                } else {
312 7
                    $hours['invoiceable'] += $report_hours;
313
                }
314
            }
315
        }
316 32
        return $hours;
317
    }
318
319 31
    private function _update_parent() : bool
320
    {
321 31
        if (!$this->_skip_parent_refresh) {
322 27
            $project = new org_openpsa_projects_project($this->project);
323 27
            $project->refresh_from_tasks();
324
        }
325
326 31
        return true;
327
    }
328
329
    /**
330
     * Queries status objects
331
     */
332 7
    public function get_status() : array
333
    {
334
        //Simplistic approach
335 7
        $mc = org_openpsa_projects_task_status_dba::new_collector('task', $this->id);
336 7
        if ($this->status > org_openpsa_projects_task_status_dba::PROPOSED) {
337
            //Only get proposed status objects here if are not over that phase
338 5
            $mc->add_constraint('type', '<>', org_openpsa_projects_task_status_dba::PROPOSED);
339
        }
340 7
        if (!empty($this->resources)) {
341
            //Do not ever set status to declined if we still have resources left
342
            $mc->add_constraint('type', '<>', org_openpsa_projects_task_status_dba::DECLINED);
343
        }
344 7
        $mc->add_order('id', 'DESC');
345 7
        $mc->set_limit(1);
346 7
        return $mc->get_rows(['type', 'comment', 'metadata_created']);
347
    }
348
349 6
    public function refresh_status()
350
    {
351 6
        $this->_status = [
352
            'status_comment' => '',
353
            'status_time' => false,
354
        ];
355
356 6
        $ret = $this->get_status();
357 6
        if (empty($ret)) {
358
            //Failure to get status object
359
            debug_add('Could not find any status objects, defaulting to previous status');
360
            return;
361
        }
362 6
        $status = current($ret);
363
364
        //Update the status cache if necessary
365 6
        if ($this->status != $status['type']) {
366
            $this->status = $status['type'];
367
            $this->update();
368
        }
369
370
        //TODO: Check various combinations of accept/decline etc etc
371 6
        $this->_status['status_comment'] = $status['comment'];
372 6
        $this->_status['status_time'] = (int) $status['created']->format('U');
373 6
    }
374
}
375