Passed
Push — master ( 284445...c8f645 )
by Andreas
17:17
created

org_openpsa_invoices_invoice_dba   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 282
Duplicated Lines 0 %

Test Coverage

Coverage 76.69%

Importance

Changes 0
Metric Value
eloc 123
c 0
b 0
f 0
dl 0
loc 282
ccs 102
cts 133
cp 0.7669
rs 8.4
wmc 50

19 Methods

Rating   Name   Duplication   Size   Complexity  
A _on_updating() 0 4 1
A get_label() 0 4 1
A get_label_property() 0 3 1
A _on_creating() 0 4 1
A get_icon() 0 3 1
A get_by_number() 0 9 2
A _pre_write_operations() 0 12 5
A get_status() 0 18 6
A get_billing_data() 0 7 2
A get_class_magic_default_privileges() 0 5 1
A _on_deleting() 0 19 4
B _recalculate_invoice_items() 0 42 9
A is_cancelable() 0 3 2
A _probe_invoice_item_for_task() 0 21 3
A get_invoice_items() 0 11 2
A generate_invoice_number() 0 5 1
A get_default() 0 4 1
A get_canceled_invoice() 0 6 2
A get_customer() 0 17 5

How to fix   Complexity   

Complex Class

Complex classes like org_openpsa_invoices_invoice_dba often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use org_openpsa_invoices_invoice_dba, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package org.openpsa.invoices
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 base class, keep logic here
11
 *
12
 * @property integer $sent
13
 * @property integer $due
14
 * @property integer $paid
15
 * @property integer $date
16
 * @property integer $deliverydate
17
 * @property integer $number
18
 * @property string $description
19
 * @property float $sum
20
 * @property integer $vat
21
 * @property integer $cancelationInvoice
22
 * @property integer $customer
23
 * @property integer $customerContact
24
 * @property integer $owner Sender of the invoice
25
 * @package org.openpsa.invoices
26
 */
27
class org_openpsa_invoices_invoice_dba extends midcom_core_dbaobject implements org_openpsa_invoices_interfaces_customer
28
{
29
    public $__midcom_class_name__ = __CLASS__;
30
    public $__mgdschema_class_name__ = 'org_openpsa_invoice';
31
32
    public $autodelete_dependents = [
33
        org_openpsa_invoices_invoice_item_dba::class => 'invoice'
34
    ];
35
36
    private $_billing_data;
37
38 6
    public function get_status() : string
39
    {
40 6
        if ($this->id == 0) {
41
            return 'scheduled';
42
        }
43 6
        if ($this->cancelationInvoice) {
44 1
            return 'canceled';
45
        }
46 6
        if ($this->sent == 0) {
47 5
            return 'unsent';
48
        }
49 2
        if ($this->paid > 0) {
50 1
            return 'paid';
51
        }
52 1
        if ($this->due < time()) {
53
            return 'overdue';
54
        }
55 1
        return 'open';
56
    }
57
58
    public function get_icon() : string
59
    {
60
        return 'file-text-o';
61
    }
62
63
    public static function get_by_number(int $number) : ?self
64
    {
65
        $qb = self::new_query_builder();
66
        $qb->add_constraint('number', '=', $number);
67
        $result = $qb->execute();
68
        if (count($result) == 1) {
69
            return $result[0];
70
        }
71
        return null;
72
    }
73
74
    /**
75
     * Human-readable label for cases like Asgard navigation
76
     */
77 10
    public function get_label() : string
78
    {
79 10
        $config = midcom_baseclasses_components_configuration::get('org.openpsa.invoices', 'config');
80 10
        return sprintf($config->get('invoice_number_format'), $this->number);
81
    }
82
83
    /**
84
     * Label property (for Asgard chooser and the likes)
85
     */
86
    public function get_label_property() : string
87
    {
88
        return 'number';
89
    }
90
91 19
    public function _on_creating() : bool
92
    {
93 19
        $this->_pre_write_operations();
94 19
        return true;
95
    }
96
97 14
    public function _on_updating() : bool
98
    {
99 14
        $this->_pre_write_operations();
100 14
        return true;
101
    }
102
103 23
    private function _pre_write_operations()
104
    {
105 23
        if ($this->sent > 0) {
106 7
            $time = time();
107 7
            if (!$this->date) {
108 5
                $this->date = $time;
109
            }
110 7
            if (!$this->deliverydate) {
111 6
                $this->deliverydate = $time;
112
            }
113 7
            if ($this->due == 0) {
114 6
                $this->due = ($this->get_default('due') * 3600 * 24) + $this->date;
115
            }
116
        }
117 23
    }
118
119 19
    public function _on_deleting() : bool
120
    {
121 19
        if (!midcom::get()->auth->request_sudo('org.openpsa.invoices')) {
122
            debug_add('Failed to get SUDO privileges, skipping invoice hour deletion silently.', MIDCOM_LOG_ERROR);
123
            return false;
124
        }
125
126 19
        $qb = self::new_query_builder();
127 19
        $qb->add_constraint('cancelationInvoice', '=', $this->id);
128 19
        foreach ($qb->execute() as $canceled) {
129
            $canceled->cancelationInvoice = 0;
130
            if (!$canceled->update()) {
131
                debug_add("Failed to remove cancelation reference from invoice #{$canceled->id}, last Midgard error was: " . midcom_connection::get_error_string(), MIDCOM_LOG_ERROR);
132
                return false;
133
            }
134
        }
135
136 19
        midcom::get()->auth->drop_sudo();
137 19
        return parent::_on_deleting();
138
    }
139
140
    /**
141
     * By default all authenticated users should be able to do
142
     * whatever they wish with relatedto objects, later we can add
143
     * restrictions on object level as necessary.
144
     */
145
    public function get_class_magic_default_privileges()
146
    {
147
        $privileges = parent::get_class_magic_default_privileges();
148
        $privileges['ANONYMOUS']['midgard:read'] = MIDCOM_PRIVILEGE_DENY;
149
        return $privileges;
150
    }
151
152
    /**
153
     * Get the default value for invoice
154
     */
155 15
    public function get_default(string $attribute)
156
    {
157 15
        $billing_data = $this->get_billing_data();
158 15
        return $billing_data->{$attribute};
159
    }
160
161
    /**
162
     * an invoice is cancelable if it is no cancelation invoice
163
     * itself and got no related cancelation invoice
164
     */
165 2
    public function is_cancelable() : bool
166
    {
167 2
        return (!$this->cancelationInvoice && !$this->get_canceled_invoice());
168
    }
169
170
    /**
171
     * returns the invoice that got canceled through this invoice, if any
172
     */
173 2
    public function get_canceled_invoice() : ?self
174
    {
175 2
        $qb = self::new_query_builder();
176 2
        $qb->add_constraint('cancelationInvoice', '=', $this->id);
177
178 2
        return $qb->get_result(0) ?: null;
179
    }
180
181
    /**
182
     * Create & recalculate existing invoice_items by tasks
183
     */
184 2
    public function _recalculate_invoice_items()
185
    {
186 2
        $result_tasks = [];
187
188
        //get hour_reports for this invoice - mc ?
189 2
        $qb = org_openpsa_expenses_hour_report_dba::new_query_builder();
190 2
        $qb->add_constraint('invoice', '=', $this->id);
191
192
        // sums up the hours of hour_reports for each task
193 2
        foreach ($qb->execute() as $hour_report) {
194 1
            if (!array_key_exists($hour_report->task, $result_tasks)) {
195 1
                $result_tasks[$hour_report->task] = 0;
196
            }
197 1
            if ($hour_report->invoiceable) {
198 1
                $result_tasks[$hour_report->task] += $hour_report->hours;
199
            }
200
        }
201
202 2
        foreach ($result_tasks as $task_id => $hours) {
203 1
            $invoice_item = $this->_probe_invoice_item_for_task($task_id);
204
205 1
            $task = new org_openpsa_projects_task_dba($task_id);
206 1
            if ($agreement = $task->get_agreement()) {
207 1
                $deliverable = org_openpsa_sales_salesproject_deliverable_dba::get_cached($agreement);
208 1
                $invoice_item->pricePerUnit = $deliverable->pricePerUnit;
0 ignored issues
show
Bug Best Practice introduced by
The property pricePerUnit does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
209 1
                $invoice_item->deliverable = $deliverable->id;
210
                //calculate price
211 1
                if (   $deliverable->invoiceByActualUnits
0 ignored issues
show
Bug Best Practice introduced by
The property invoiceByActualUnits does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
212 1
                    || $deliverable->plannedUnits == 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property plannedUnits does not exist on midcom_core_dbaobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
213 1
                    $invoice_item->units = $hours;
214
                } else {
215 1
                    $invoice_item->units = $deliverable->plannedUnits;
216
                }
217
            } else {
218
                $invoice_item->units = $hours;
219
            }
220
221 1
            if ($invoice_item->description == '') {
222 1
                $invoice_item->description = $task->title;
223
            }
224
225 1
            $invoice_item->update();
226
        }
227 2
    }
228
229
    /**
230
     * Get corresponding invoice_items indexed by GUID
231
     *
232
     * @return org_openpsa_invoices_invoice_item_dba[]
233
     */
234 15
    public function get_invoice_items() : array
235
    {
236 15
        $qb = org_openpsa_invoices_invoice_item_dba::new_query_builder();
237 15
        $qb->add_constraint('invoice', '=', $this->id);
238 15
        $qb->add_order('position', 'ASC');
239
240 15
        $items = [];
241 15
        foreach ($qb->execute() as $item) {
242 4
            $items[$item->guid] = $item;
243
        }
244 15
        return $items;
245
    }
246
247
    /**
248
     * Get the billing data for the invoice
249
     */
250 16
    public function get_billing_data() : org_openpsa_invoices_billing_data_dba
251
    {
252 16
        if (!$this->_billing_data) {
253
            //checks for billing_data already attached to a customer relocated previous private class to data.php
254 16
            $this->_billing_data = org_openpsa_invoices_billing_data_dba::get_by_object($this);
255
        }
256 16
        return $this->_billing_data;
257
    }
258
259 1
    public function get_customer()
260
    {
261 1
        if (!empty($this->customer)) {
262
            try {
263
                return org_openpsa_contacts_group_dba::get_cached($this->customer);
264
            } catch (midcom_error $e) {
265
                $e->log();
266
            }
267
        }
268 1
        if (!empty($this->customerContact)) {
269
            try {
270 1
                return org_openpsa_contacts_person_dba::get_cached($this->customerContact);
271
            } catch (midcom_error $e) {
272
                $e->log();
273
            }
274
        }
275
        return null;
276
    }
277
278
    /**
279
     * Get invoice_item for the passed task id, if there is no item it will return a newly created one
280
     */
281 1
    private function _probe_invoice_item_for_task(int $task_id) : org_openpsa_invoices_invoice_item_dba
282
    {
283
        //check if there is already an invoice_item for this task
284 1
        $qb_invoice_item = org_openpsa_invoices_invoice_item_dba::new_query_builder();
285 1
        $qb_invoice_item->add_constraint('invoice', '=', $this->id);
286 1
        $qb_invoice_item->add_constraint('task', '=', $task_id);
287
288 1
        $invoice_items = $qb_invoice_item->execute();
289 1
        if (empty($invoice_items)) {
290 1
            $invoice_item = new org_openpsa_invoices_invoice_item_dba();
291 1
            $invoice_item->task = $task_id;
292 1
            $invoice_item->invoice = $this->id;
293 1
            $invoice_item->create();
294
        } else {
295 1
            $invoice_item = $invoice_items[0];
296 1
            if (count($invoice_items) > 1) {
297
                debug_add('More than one item found for task #' . $task_id . ', only returning the first', MIDCOM_LOG_INFO);
298
            }
299
        }
300
301 1
        return $invoice_item;
302
    }
303
304 10
    public function generate_invoice_number()
305
    {
306 10
        $client_class = midcom_baseclasses_components_configuration::get('org.openpsa.sales', 'config')->get('calculator');
307 10
        $calculator = new $client_class;
308 10
        return $calculator->generate_invoice_number();
309
    }
310
}
311