Completed
Push — master ( eeaea7...0737e5 )
by Andreas
17:12
created

org_openpsa_projects_task_dba::get_agreement()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4.125

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 2
nop 0
dl 0
loc 9
ccs 3
cts 6
cp 0.5
crap 4.125
rs 10
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 3
        while ($task = $task->get_parent()) {
146 3
            if (isset($task->title)) {
147 3
                $label_elements[] = $task->title;
148
            }
149
        }
150
151 3
        $label = implode(' / ', array_reverse($label_elements));
152 3
        return trim($label);
153
    }
154
155
    public function get_icon() : string
156
    {
157
        return 'calendar-check-o';
158
    }
159
160
    /**
161
     * Populates contacts as resources lists
162
     */
163 18
    public function get_members()
164
    {
165 18
        if ($this->id) {
166 18
            $mc = org_openpsa_projects_task_resource_dba::new_collector('task', $this->id);
167 18
            $ret = $mc->get_rows(['orgOpenpsaObtype', 'person']);
168
169 18
            foreach ($ret as $data) {
170 4
                if ($data['orgOpenpsaObtype'] == org_openpsa_projects_task_resource_dba::CONTACT) {
171 1
                    $this->contacts[$data['person']] = true;
172
                } else {
173 4
                    $this->resources[$data['person']] = true;
174
                }
175
            }
176
        }
177 18
    }
178
179
    /**
180
     * Adds new contacts or resources
181
     */
182 16
    public function add_members(string $property, array $ids)
183
    {
184 16
        if ($property === 'contacts') {
185 4
            $type = org_openpsa_projects_task_resource_dba::CONTACT;
186 16
        } elseif ($property === 'resources') {
187 16
            $type = org_openpsa_projects_task_resource_dba::RESOURCE;
188
        } else {
189
            return;
190
        }
191
192 16
        foreach ($ids as $id) {
193 16
            $resource = new org_openpsa_projects_task_resource_dba();
194 16
            $resource->orgOpenpsaObtype = $type;
195 16
            $resource->task = $this->id;
196 16
            $resource->person = (int) $id;
197 16
            if ($resource->create()) {
198 16
                $this->{$property}[$id] = true;
199
            }
200
        }
201 16
    }
202
203 32
    private function _prepare_save() : bool
204
    {
205
        //Make sure we have end
206 32
        if (!$this->end || $this->end == -1) {
207 17
            $this->end = time();
208
        }
209
        //Make sure we have start
210 32
        if (!$this->start) {
211 17
            $this->start = min(time(), $this->end - 1);
212
        }
213
214
        // Reset start and end to start/end of day
215 32
        $this->start = strtotime('today', $this->start);
216 32
        $this->end = strtotime('tomorrow', $this->end) - 1;
217
218 32
        if ($this->start > $this->end) {
219
            debug_add("start ({$this->start}) is greater than end ({$this->end}), aborting", MIDCOM_LOG_ERROR);
220
            return false;
221
        }
222
223 32
        if ($agreement = $this->get_agreement()) {
224
            // Get customer company into cache from agreement's sales project
225
            try {
226 12
                $agreement = org_openpsa_sales_salesproject_deliverable_dba::get_cached($agreement);
227 12
                $this->hoursInvoiceableDefault = true;
228 12
                if (!$this->customer) {
229 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...
230 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...
231
                }
232 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...
233
            }
234
        } else {
235
            // No agreement, we can't be invoiceable
236 22
            $this->hoursInvoiceableDefault = false;
237
        }
238
239
        // Update hour caches
240 32
        $this->update_cache(false);
241
242 32
        return true;
243
    }
244
245 33
    public function get_agreement() : int
246
    {
247 33
        if ($this->up) {
248
            do {
249
                $parent = $this->get_parent();
250
            } 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...
251
            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...
252
        }
253 33
        return $this->agreement;
254
    }
255
256
    /**
257
     * Update hour report caches
258
     */
259 32
    public function update_cache(bool $update = true) : bool
260
    {
261 32
        if (!$this->id) {
262 18
            return false;
263
        }
264
265 32
        debug_add("updating hour caches");
266
267 32
        $hours = $this->list_hours();
268 32
        $stat = true;
269
270 32
        $this->reportedHours = $hours['reported'];
271 32
        $this->invoicedHours = $hours['invoiced'];
272 32
        $this->invoiceableHours = $hours['invoiceable'];
273
274
        try {
275 32
            $agreement = new org_openpsa_sales_salesproject_deliverable_dba($this->get_agreement());
276 12
            $agreement->update_units($this->id, $hours);
277 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...
278
        }
279
280 32
        if ($update) {
281 18
            $this->_use_rcs = false;
282 18
            $this->_skip_acl_refresh = true;
283 18
            $this->_skip_parent_refresh = true;
284 18
            $stat = $this->update();
285 18
            debug_add("saving updated values to database returned {$stat}");
286
        }
287 32
        return $stat;
288
    }
289
290 32
    private function list_hours() : array
291
    {
292
        $hours = [
293 32
            'reported'    => 0,
294
            'invoiced'    => 0,
295
            'invoiceable' => 0,
296
        ];
297
298 32
        $report_mc = org_openpsa_expenses_hour_report_dba::new_collector('task', $this->id);
299 32
        $report_mc->add_value_property('hours');
300 32
        $report_mc->add_value_property('invoice');
301 32
        $report_mc->add_value_property('invoiceable');
302 32
        $report_mc->execute();
303
304 32
        foreach ($report_mc->list_keys() as $guid => $empty) {
305 16
            $report_data = $report_mc->get($guid);
306 16
            $report_hours = $report_data['hours'];
307
308 16
            $hours['reported'] += $report_hours;
309
310 16
            if ($report_data['invoiceable']) {
311 7
                if ($report_data['invoice']) {
312 4
                    $hours['invoiced'] += $report_hours;
313
                } else {
314 7
                    $hours['invoiceable'] += $report_hours;
315
                }
316
            }
317
        }
318 32
        return $hours;
319
    }
320
321 31
    private function _update_parent() : bool
322
    {
323 31
        if (!$this->_skip_parent_refresh) {
324 27
            $project = new org_openpsa_projects_project($this->project);
325 27
            $project->refresh_from_tasks();
326
        }
327
328 31
        return true;
329
    }
330
331
    /**
332
     * Queries status objects
333
     */
334 7
    public function get_status() : array
335
    {
336
        //Simplistic approach
337 7
        $mc = org_openpsa_projects_task_status_dba::new_collector('task', $this->id);
338 7
        if ($this->status > org_openpsa_projects_task_status_dba::PROPOSED) {
339
            //Only get proposed status objects here if are not over that phase
340 5
            $mc->add_constraint('type', '<>', org_openpsa_projects_task_status_dba::PROPOSED);
341
        }
342 7
        if (!empty($this->resources)) {
343
            //Do not ever set status to declined if we still have resources left
344
            $mc->add_constraint('type', '<>', org_openpsa_projects_task_status_dba::DECLINED);
345
        }
346 7
        $mc->add_order('timestamp', 'DESC');
347 7
        $mc->add_order('type', 'DESC'); //Our timestamps are not accurate enough so if we have multiple with same timestamp suppose highest type is latest
348 7
        $mc->set_limit(1);
349
350 7
        return $mc->get_rows(['type', 'comment', 'timestamp']);
351
    }
352
353 6
    public function refresh_status()
354
    {
355 6
        $this->_status = [
356
            'status_comment' => '',
357
            'status_time' => false,
358
        ];
359
360 6
        $ret = $this->get_status();
361 6
        if (empty($ret)) {
362
            //Failure to get status object
363
            debug_add('Could not find any status objects, defaulting to previous status');
364
            return;
365
        }
366 6
        $status = current($ret);
367
368
        //Update the status cache if necessary
369 6
        if ($this->status != $status['type']) {
370
            $this->status = $status['type'];
371
            $this->update();
372
        }
373
374
        //TODO: Check various combinations of accept/decline etc etc
375 6
        $this->_status['status_comment'] = $status['comment'];
376 6
        $this->_status['status_time'] = $status['timestamp'];
377 6
    }
378
}
379