Passed
Push — master ( dd49f8...f56759 )
by Andreas
12:21
created

org_openpsa_invoices_invoice_dba::get_status()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.4822

Importance

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