Passed
Push — master ( bc2447...63fe1c )
by Andreas
23:39
created

org_openpsa_sales_salesproject_dba::get_customer()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6.6

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 7
nop 0
dl 0
loc 17
ccs 6
cts 10
cp 0.6
crap 6.6
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package org.openpsa.sales
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 $up In practice all salesprojects will have up of 0 but in case we someday wish to divide
13
            a huge salesproject to subprojects this is here
14
 * @property integer $start
15
 * @property integer $end
16
 * @property string $code
17
 * @property string $title
18
 * @property string $description
19
 * @property integer $state
20
 * @property integer $customer
21
 * @property integer $customerContact
22
 * @property integer $owner
23
 * @property integer $probability
24
 * @property float $value
25
 * @property float $profit
26
 * @property float $price
27
 * @property float $cost
28
 * @property integer $closeEst
29
 * @property integer $status Current project status
30
 * @property float $plannedHours
31
 * @property float $reportedHours
32
 * @property float $invoicedHours
33
 * @property float $invoiceableHours
34
 * @property integer $orgOpenpsaAccesstype Shortcut for various ACL scenarios
35
 * @property string $orgOpenpsaOwnerWg The "owner" workgroup of this object
36
 * @package org.openpsa.sales
37
 */
38
class org_openpsa_sales_salesproject_dba extends midcom_core_dbaobject implements org_openpsa_invoices_interfaces_customer
39
{
40
    public $__midcom_class_name__ = __CLASS__;
41
    public $__mgdschema_class_name__ = 'org_openpsa_salesproject';
42
43
    public $autodelete_dependents = [
44
        org_openpsa_contacts_role_dba::class => 'objectGuid'
45
    ];
46
47
    //org.openpsa.sales salesproject states
48
    const STATE_LOST = 11000;
49
    const STATE_ACTIVE = 11050;
50
    const STATE_WON = 11100;
51
    const STATE_DELIVERED = 11200;
52
    const STATE_INVOICED = 11300;
53
54
    // ... and because these constants suck for pratically everything..
55
    private $states = [
56
        self::STATE_LOST => 'lost',
57
        self::STATE_ACTIVE => 'active',
58
        self::STATE_WON => 'won',
59
        self::STATE_DELIVERED => 'delivered',
60
        self::STATE_INVOICED => 'invoiced'
61
    ];
62
63
    //org.openpsa.sales role types
64
    const ROLE_MEMBER = 10500;
65
66
    /**
67
     * Shorthand access for contact members
68
     */
69
    private $_contacts;
70
71 25
    public function refresh() : bool
72
    {
73 25
        $this->_contacts = null;
74 25
        return parent::refresh();
75
    }
76
77 2
    public function get_state() : string
78
    {
79 2
        if (array_key_exists($this->state, $this->states)) {
80 2
            return $this->states[$this->state];
81
        }
82
        return $this->states[self::STATE_ACTIVE];
83
    }
84
85
    /**
86
     * Calculates the prices of deliverables and adds them up to the salesproject value
87
     *
88
     * For subscriptions, we use already invoiced sums plus expected values (i.e. deliverable price)
89
     * until the deliverable's end time (or 12 months if the subscription is continuous)
90
     * Single deliveries are calculated based on their price and the already invoiced sum, when invoice by
91
     * actual units is true
92
     */
93 31
    public function calculate_price()
94
    {
95 31
        $value = 0;
96 31
        $cost = 0;
97
98 31
        $qb = org_openpsa_sales_salesproject_deliverable_dba::new_query_builder();
99 31
        $qb->add_constraint('salesproject', '=', $this->id);
100 31
        $qb->add_constraint('up', '=', 0);
101 31
        $qb->add_constraint('state', '<>', org_openpsa_sales_salesproject_deliverable_dba::STATE_DECLINED);
102 31
        foreach ($qb->execute() as $deliverable) {
103 31
            if ($deliverable->orgOpenpsaObtype == org_openpsa_products_product_dba::DELIVERY_SUBSCRIPTION) {
104 9
                $scheduler = new org_openpsa_invoices_scheduler($deliverable);
105 9
                if ($deliverable->end == 0) {
106
                    // FIXME: Get this from config key 'subscription_profit_months'
107 9
                    $cycles = $scheduler->calculate_cycles(12);
108
                } else {
109 3
                    $cycles = $scheduler->calculate_cycles();
110
                }
111 9
                $value += ($deliverable->price * $cycles) + $deliverable->invoiced;
112 9
                $cost += $deliverable->cost * $cycles;
113
            } else {
114 23
                $value += $deliverable->price;
115 23
                $cost += $deliverable->cost;
116 23
                if ($deliverable->invoiceByActualUnits) {
117 5
                    $value += $deliverable->invoiced;
118
                }
119
            }
120
        }
121 31
        $profit = $value - $cost;
122 31
        if (   $this->value != $value
123 31
            || $this->profit != $profit) {
124 11
            $this->value = $value;
125 11
            $this->profit = $value - $cost;
126 11
            $this->update();
127
        }
128 31
    }
129
130 1
    public static function generate_salesproject_number() : string
131
    {
132
        // TODO: Make configurable
133 1
        $year = date('Y');
134 1
        $qb = self::new_query_builder();
135 1
        $qb->add_order('metadata.created', 'DESC');
136 1
        $qb->add_constraint('metadata.created', '>=', $year . '-01-01 00:00:00');
137 1
        $previous = $qb->count_unchecked();
138
139 1
        return sprintf('%d-%04d', $year, $previous + 1);
140
    }
141
142 8
    public function get_project() : org_openpsa_projects_project
143
    {
144 8
        return new org_openpsa_projects_project($this->id);
145
    }
146
147 9
    public function get_customer()
148
    {
149 9
        if (!empty($this->customer)) {
150
            try {
151 6
                return org_openpsa_contacts_group_dba::get_cached($this->customer);
152
            } catch (midcom_error $e) {
153
                $e->log();
154
            }
155
        }
156 3
        if (!empty($this->customerContact)) {
157
            try {
158 2
                return org_openpsa_contacts_person_dba::get_cached($this->customerContact);
159
            } catch (midcom_error $e) {
160
                $e->log();
161
            }
162
        }
163 1
        return null;
164
    }
165
166 43
    public function __get($property)
167
    {
168 43
        if ($property == 'contacts') {
169 6
            if ($this->_contacts === null) {
170 5
                $this->get_members();
171
            }
172 6
            return $this->_contacts;
173
        }
174 43
        return parent::__get($property);
175
    }
176
177 22
    public function _on_creating()
178
    {
179 22
        $this->start = $this->start ?: time();
180 22
        $this->state = $this->state ?: self::STATE_ACTIVE;
181 22
        $this->owner = $this->owner ?: midcom_connection::get_user();
182
183 22
        return true;
184
    }
185
186 13
    public function _on_updating()
187
    {
188 13
        if (   $this->state != self::STATE_ACTIVE
189 13
            && !$this->end) {
190
            //Not active anymore and end not set, set it to now
191 3
            $this->end = time();
192
        }
193 13
        if (   $this->end
194 13
            && $this->state == self::STATE_ACTIVE) {
195
            //Returned to active state, clear the end marker.
196 5
            $this->end = 0;
197
        }
198
199 13
        return true;
200
    }
201
202 39
    public function _on_loaded()
203
    {
204 39
        if (empty($this->title)) {
205 28
            $this->title = "salesproject #{$this->id}";
206
        }
207 39
    }
208
209 13
    public function _on_updated()
210
    {
211
        //Ensure owner can do stuff regardless of other ACLs
212 13
        if ($owner_person = midcom::get()->auth->get_user($this->owner)) {
213 5
            $this->set_privilege('midgard:read', $owner_person->id, MIDCOM_PRIVILEGE_ALLOW);
214 5
            $this->set_privilege('midgard:create', $owner_person->id, MIDCOM_PRIVILEGE_ALLOW);
215 5
            $this->set_privilege('midgard:delete', $owner_person->id, MIDCOM_PRIVILEGE_ALLOW);
216 5
            $this->set_privilege('midgard:update', $owner_person->id, MIDCOM_PRIVILEGE_ALLOW);
217
        }
218 13
    }
219
220
    /**
221
     * Populates contacts as resources lists
222
     */
223 6
    function get_members()
224
    {
225 6
        $this->_contacts = [];
226
        // Make sure primary contact comes out on top
227 6
        if ($this->customerContact) {
228 1
            $this->_contacts[$this->customerContact] = true;
229
        }
230 6
        if ($this->id) {
231 6
            $mc = org_openpsa_contacts_role_dba::new_collector('objectGuid', $this->guid);
232 6
            $mc->add_constraint('role', '=', self::ROLE_MEMBER);
233
234 6
            $this->_contacts += array_fill_keys($mc->get_values('person'), true);
235
        }
236 6
    }
237
238
    /**
239
     * Marks the salesproject as delivered if no active or pending deliverables are left
240
     */
241 1
    public function mark_delivered()
242
    {
243 1
        $this->mark(self::STATE_DELIVERED);
244 1
    }
245
246
    /**
247
     * Marks the salesproject as invoiced if no pending deliverables are left
248
     */
249 3
    public function mark_invoiced()
250
    {
251 3
        $this->mark(self::STATE_INVOICED);
252 3
    }
253
254 3
    private function mark(int $state)
255
    {
256 3
        if ($this->state >= $state) {
257
            return;
258
        }
259
260 3
        $mc = org_openpsa_sales_salesproject_deliverable_dba::new_collector('salesproject', $this->id);
261 3
        $mc->add_constraint('state', '<', $state);
262 3
        $mc->add_constraint('state', '<>', org_openpsa_sales_salesproject_deliverable_dba::STATE_DECLINED);
263 3
        $mc->execute();
264
265 3
        if ($mc->count() == 0) {
266
            $this->state = $state;
267
            $this->update();
268
        }
269 3
    }
270
}
271