Passed
Push — master ( 2dc7fc...e8f302 )
by Andreas
34:28
created

org_openpsa_projects_project::list()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 2
nop 0
dl 0
loc 17
ccs 9
cts 9
cp 1
crap 3
rs 9.9332
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
 * @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 56
    public function __get($property)
98
    {
99 56
        if ($property == 'status_type') {
100 2
            return org_openpsa_projects_workflow::get_status_type($this->status);
101
        }
102 56
        return parent::__get($property);
103
    }
104
105 46
    public function _on_loaded()
106
    {
107 46
        if ($this->title == "") {
108 28
            $this->title = "Project #{$this->id}";
109
        }
110
111 46
        if (!$this->status) {
112
            //Default to proposed if no status is set
113 38
            $this->status = org_openpsa_projects_task_status_dba::PROPOSED;
114
        }
115 46
    }
116
117
    /**
118
     * List projects user can see
119
     */
120 3
    public static function list() : array
121
    {
122
        //Only query once per request
123 3
        static $cache = null;
124 3
        if ($cache === null) {
125
            $cache = [
126 1
                'all' => midcom::get()->i18n->get_string('all', 'midcom')
127
            ];
128
129 1
            $qb = self::new_query_builder();
130 1
            $qb->add_order('title');
131
132 1
            foreach ($qb->execute() as $project) {
133 1
                $cache[$project->guid] = $project->title;
134
            }
135
        }
136 3
        return $cache;
137
    }
138
139 3
    public function get_icon() : string
140
    {
141 3
        return 'tasks';
142
    }
143
144
    /**
145
     * Generate a user-readable label for the task using the task/project hierarchy
146
     */
147 3
    public function get_label() : string
148
    {
149 3
        $label_elements = [$this->title];
150 3
        $project = $this;
151 3
        while ($project = $project->get_parent()) {
152
            if (!empty($project->title)) {
153
                $label_elements[] = $project->title;
154
            }
155
        }
156
157 3
        $label = implode(' / ', array_reverse($label_elements));
158 3
        return trim($label);
159
    }
160
161
    public function get_salesproject() : org_openpsa_sales_salesproject_dba
162
    {
163
        return new org_openpsa_sales_salesproject_dba($this->id);
164
    }
165
166
    /**
167
     * Populates contacts as resources lists
168
     */
169 3
    public function get_members()
170
    {
171 3
        if ($this->guid) {
172 3
            $mc = org_openpsa_contacts_role_dba::new_collector('objectGuid', $this->guid);
173 3
            $ret = $mc->get_rows(['role', 'person']);
174
175 3
            foreach ($ret as $data) {
176 1
                if ($data['role'] == org_openpsa_projects_task_resource_dba::CONTACT) {
177 1
                    $this->contacts[$data['person']] = true;
178
                } else {
179 1
                    $this->resources[$data['person']] = true;
180
                }
181
            }
182
        }
183 3
    }
184
185
    /**
186
     * Get the number of tasks for the different status types
187
     */
188
    public function get_task_count() : array
189
    {
190
        $numbers = [
191
            'not_started' => 0,
192
            'ongoing' => 0,
193
            'on_hold' => 0,
194
            'closed' => 0,
195
            'rejected' => 0
196
        ];
197
        $task_mc = org_openpsa_projects_task_dba::new_collector('project', $this->id);
198
        $statuses = $task_mc->get_values('status');
199
        foreach ($statuses as $status) {
200
            $type = org_openpsa_projects_workflow::get_status_type($status);
201
            $numbers[$type]++;
202
        }
203
        return $numbers;
204
    }
205
206
    /**
207
     * Get the number of hours for the different status types
208
     */
209 1
    public function get_task_hours() : array
210
    {
211
        $numbers = [
212 1
            'plannedHours' => 0,
213
            'reportedHours' => 0
214
        ];
215 1
        $task_mc = org_openpsa_projects_task_dba::new_collector('project', $this->id);
216 1
        $tasks = $task_mc->get_rows(['plannedHours', 'reportedHours']);
217 1
        foreach ($tasks as $values) {
218 1
            $numbers['plannedHours'] += $values['plannedHours'];
219 1
            $numbers['reportedHours'] += $values['reportedHours'];
220
        }
221 1
        return $numbers;
222
    }
223
224
    /**
225
     * Set project information according to the situation of its tasks
226
     *
227
     * This adjusts the timeframe if necessary and tries to determine the project's
228
     * status according to the current task situation
229
     */
230 27
    public function refresh_from_tasks()
231
    {
232 27
        $update_required = false;
233
234 27
        $task_statuses = [];
235 27
        $status_types = [];
236
237 27
        $task_qb = org_openpsa_projects_task_dba::new_query_builder();
238 27
        $task_qb->add_constraint('project', '=', $this->id);
239 27
        $ret = $task_qb->execute();
240
241 27
        if (empty($ret)) {
242
            return;
243
        }
244
245 27
        foreach ($ret as $task) {
246 27
            if ($task->start < $this->start) {
247 9
                $this->start = $task->start;
248 9
                $update_required = true;
249
            }
250 27
            if ($task->end > $this->end) {
251 16
                $this->end = $task->end;
252 16
                $update_required = true;
253
            }
254
255 27
            $status_types[$task->status_type] = true;
256 27
            $task_statuses[$task->status] = true;
257
        }
258
259 27
        if (count($task_statuses) == 1) {
260
            // If all tasks are of the same type, that is the type to use then
261 27
            $new_status = key($task_statuses);
262
        } else {
263 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...
264
        }
265
266 27
        if (   $new_status !== null
267 27
            && $this->status != $new_status) {
268 21
            $this->status = $new_status;
269 21
            $update_required = true;
270
        }
271
272 27
        if ($update_required) {
273 24
            debug_add("Some project information needs to be updated, skipping RCS");
274 24
            $this->_use_rcs = false;
275 24
            $this->update();
276
        } else {
277 8
            debug_add("All project information is up-to-date");
278
        }
279 27
    }
280
281 1
    private function _find_status(array $map, array $status_types) : ?int
282
    {
283 1
        foreach (array_intersect_key($map, $status_types) as $new_status) {
284 1
            if (is_array($new_status)) {
285
                return $this->_find_status($new_status, $status_types);
286
            }
287 1
            return $new_status;
288
        }
289
        return null;
290
    }
291
}
292