Completed
Push — developer ( 8830e0...c20012 )
by Błażej
63:58 queued 51:15
created

ServiceContracts::updateUsedUnits()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
/**
4
 * ServiceContracts CRMEntity class.
5
 *
6
 * @copyright YetiForce Sp. z o.o
7
 * @license   YetiForce Public License 3.0 (licenses/LicenseEN.txt or yetiforce.com)
8
 * @author    Mariusz Krzaczkowski <[email protected]>
9
 */
10
class ServiceContracts extends CRMEntity
11
{
12
	public $table_name = 'vtiger_servicecontracts';
13
	public $table_index = 'servicecontractsid';
14
	public $column_fields = [];
15
16
	/** Indicator if this is a custom module or standard module */
17
	public $IsCustomModule = true;
18
19
	/**
20
	 * Mandatory table for supporting custom fields.
21
	 */
22
	public $customFieldTable = ['vtiger_servicecontractscf', 'servicecontractsid'];
23
24
	/**
25
	 * Mandatory for Saving, Include tables related to this module.
26
	 */
27
	public $tab_name = ['vtiger_crmentity', 'vtiger_servicecontracts', 'vtiger_servicecontractscf', 'vtiger_entity_stats'];
28
29
	/**
30
	 * Mandatory for Saving, Include tablename and tablekey columnname here.
31
	 */
32
	public $tab_name_index = [
33
		'vtiger_crmentity' => 'crmid',
34
		'vtiger_servicecontracts' => 'servicecontractsid',
35
		'vtiger_servicecontractscf' => 'servicecontractsid',
36
		'vtiger_entity_stats' => 'crmid', ];
37
38
	/**
39
	 * Mandatory for Listing (Related listview).
40
	 */
41
	public $list_fields = [
42
		// Format: Field Label => Array(tablename, columnname)
43
		// tablename should not have prefix 'vtiger_'
44
		'Subject' => ['servicecontracts', 'subject'],
45
		'Assigned To' => ['crmentity', 'smownerid'],
46
		'Related to' => ['servicecontracts', 'sc_related_to'],
47
		'Status' => ['servicecontracts', 'contract_status'],
48
		'Used Units' => ['servicecontracts', 'used_units'],
49
		'Total Units' => ['servicecontracts', 'total_units'],
50
		'Contract No' => ['servicecontracts', 'contract_no'],
51
	];
52
	public $list_fields_name = [
53
		// Format: Field Label => fieldname
54
		'Subject' => 'subject',
55
		'Assigned To' => 'assigned_user_id',
56
		'Related To' => 'sc_related_to',
57
		'Status' => 'contract_status',
58
		'Used Units' => 'used_units',
59
		'Total Units' => 'total_units',
60
		'Contract No' => 'contract_no',
61
	];
62
63
	/**
64
	 * @var string[] List of fields in the RelationListView
65
	 */
66
	public $relationFields = ['subject', 'assigned_user_id', 'contract_no', 'used_units', 'total_units'];
67
	// Make the field link to detail view
68
	public $list_link_field = 'subject';
69
	// For Popup listview and UI type support
70
	public $search_fields = [
71
		// Format: Field Label => Array(tablename, columnname)
72
		// tablename should not have prefix 'vtiger_'
73
		'Subject' => ['servicecontracts', 'subject'],
74
		'Status' => ['servicecontracts', 'contract_status'],
75
		'Due Date' => ['servicecontracts', 'due_date'],
76
		'Start Date' => ['servicecontracts', 'start_date'],
77
		'Type' => ['servicecontracts', 'contract_type'],
78
		'Related to' => ['servicecontracts', 'sc_related_to'],
79
		'Used Units' => ['servicecontracts', 'used_units'],
80
		'Total Units' => ['servicecontracts', 'total_units'],
81
		'Assigned To' => ['crmentity', 'smownerid'],
82
		'Contract No' => ['servicecontracts', 'contract_no'],
83
	];
84
	public $search_fields_name = [
85
		// Format: Field Label => fieldname
86
		'Subject' => 'subject',
87
		'Status' => 'contract_status',
88
		'Due Date' => 'due_date',
89
		'Start Date' => 'start_date',
90
		'Type' => 'contract_type',
91
		'Related To' => 'sc_related_to',
92
		'Used Units' => 'used_units',
93
		'Total Units' => 'total_units',
94
		'Assigned To' => 'assigned_user_id',
95
		'Contract No' => 'contract_no',
96
	];
97
	// For Popup window record selection
98
	public $popup_fields = ['subject'];
99
	// For Alphabetical search
100
	public $def_basicsearch_col = 'subject';
101
	// Column value to use on detail view record text display
102
	public $def_detailview_recname = 'subject';
103
	// Used when enabling/disabling the mandatory fields for the module.
104
	// Refers to vtiger_field.fieldname values.
105
	public $mandatory_fields = ['subject', 'assigned_user_id'];
106
	// Callback function list during Importing
107
	public $special_functions = ['set_import_assigned_user'];
108
	public $default_order_by = '';
109
	public $default_sort_order = 'ASC';
110
111
	/**
112
	 * Invoked when special actions are performed on the module.
113
	 *
114
	 * @param string Module name
115
	 * @param string Event Type
116
	 */
117 2
	public function moduleHandler($moduleName, $eventType)
118
	{
119 2
		if ($eventType === 'module.postinstall') {
120
			$moduleInstance = vtlib\Module::getInstance($moduleName);
121
122
			$accModuleInstance = vtlib\Module::getInstance('Accounts');
123
			$accModuleInstance->setRelatedList($moduleInstance, 'Service Contracts', ['add'], 'getDependentsList');
124
125
			$conModuleInstance = vtlib\Module::getInstance('Contacts');
126
			$conModuleInstance->setRelatedList($moduleInstance, 'Service Contracts', ['add'], 'getDependentsList');
127
128
			$helpDeskInstance = vtlib\Module::getInstance('HelpDesk');
129
			$helpDeskInstance->setRelatedList($moduleInstance, 'Service Contracts', ['ADD', 'SELECT']);
130
131
			// Initialize module sequence for the module
132
			\App\Fields\RecordNumber::setNumber($moduleName, 'SERCON', 1);
133
			$dbCommand = \App\Db::getInstance()->createCommand();
134
			// Make the picklist value 'Complete' for status as non-editable
135
			$dbCommand->update('vtiger_contract_status', ['presence' => 0], ['contract_status' => 'Complete'])->execute();
136
			// Mark the module as Standard module
137
			$dbCommand->update('vtiger_tab', ['customized' => 0], ['name' => $moduleName])->execute();
138 2
		} elseif ($eventType === 'module.disabled') {
139 1
			App\EventHandler::setInActive('ServiceContracts_ServiceContractsHandler_Handler');
140 1
		} elseif ($eventType === 'module.enabled') {
141 1
			App\EventHandler::setActive('ServiceContracts_ServiceContractsHandler_Handler');
142
		}
143 2
	}
144
145
	/**
146
	 * Handle saving related module information.
147
	 * NOTE: This function has been added to CRMEntity (base class).
148
	 * You can override the behavior by re-defining it here.
149
	 */
150
	public function saveRelatedModule($module, $crmid, $with_module, $with_crmids, $relatedName = false)
151
	{
152
		if (!is_array($with_crmids)) {
153
			$with_crmids = [$with_crmids];
154
		}
155
		foreach ($with_crmids as $with_crmid) {
156
			if ($with_module == 'HelpDesk') {
157
				parent::saveRelatedModule($module, $crmid, $with_module, $with_crmid);
158
				$this->updateHelpDeskRelatedTo($crmid, $with_crmid);
159
				$this->updateServiceContractState($crmid);
160
			} else {
161
				parent::saveRelatedModule($module, $crmid, $with_module, $with_crmid, $relatedName);
162
			}
163
		}
164
	}
165
166
	/**
167
	 * Function to Update the parent_id of HelpDesk with sc_related_to of ServiceContracts if the parent_id is not set.
168
	 *
169
	 * @param int $focusId
170
	 * @param int $entityIds
171
	 */
172
	public function updateHelpDeskRelatedTo($focusId, $entityIds)
173
	{
174
		$dataReader = (new \App\Db\Query())->select(['ticketid'])->from('vtiger_troubletickets')
175
			->where(['and', ['or', ['parent_id' => null], ['parent_id' => 0]], ['ticketid' => $entityIds]])
176
			->createCommand()->query();
177
		while ($ticketId = $dataReader->readColumn(0)) {
178
			$serviceContractsInfo = (new \App\Db\Query())->select(['vtiger_crmentity.setype', 'vtiger_servicecontracts.sc_related_to'])
179
				->from('vtiger_servicecontracts')
180
				->leftJoin('vtiger_crmentity', 'vtiger_crmentity.crmid = vtiger_servicecontracts.sc_related_to')
181
				->where(['vtiger_servicecontracts.servicecontractsid' => $focusId])->one();
182
			if ($serviceContractsInfo['setype'] === 'Accounts') {
183
				App\Db::getInstance()->createCommand()->update('vtiger_troubletickets', ['parent_id' => $serviceContractsInfo['sc_related_to']], ['ticketid' => $ticketId])->execute();
184
			}
185
		}
186
	}
187
188
	// Function to Compute and Update the Used Units and Progress of the Service Contract based on all the related Trouble tickets.
189 1
	public function updateServiceContractState($focusId)
190
	{
191 1
		$this->id = $focusId;
192 1
		$this->retrieveEntityInfo($focusId, 'ServiceContracts');
193 1
		$dataReader = (new App\Db\Query())->select(['relcrmid'])
194 1
			->from('vtiger_crmentityrel')
195 1
			->where(['module' => 'ServiceContracts', 'relmodule' => 'HelpDesk', 'crmid' => $focusId])
196 1
			->union((new App\Db\Query())->select(['crmid'])
197 1
			->from('vtiger_crmentityrel')
198 1
			->where(['relmodule' => 'ServiceContracts', 'module' => 'HelpDesk', 'relcrmid' => $focusId]))
199 1
			->createCommand()->query();
200 1
		$totalUsedUnits = 0;
201 1
		$ticketFocus = CRMEntity::getInstance('HelpDesk');
202 1
		while ($ticketId = $dataReader->readColumn(0)) {
203
			$ticketFocus->id = $ticketId;
204
			if (\App\Record::isExists($ticketId)) {
205
				$ticketFocus->retrieveEntityInfo($ticketId, 'HelpDesk');
206
				if (strtolower($ticketFocus->column_fields['ticketstatus']) === 'closed') {
207
					$totalUsedUnits += $this->computeUsedUnits($ticketFocus->column_fields);
208
				}
209
			}
210
		}
211 1
		$dataReader->close();
212 1
		$this->updateUsedUnits($totalUsedUnits);
213 1
		$this->calculateProgress();
214 1
	}
215
216
	// Function to Upate the Used Units of the Service Contract based on the given Ticket id.
217
	public function computeUsedUnits($ticketData, $operator = '+')
218
	{
219
		$trackingUnit = strtolower($this->column_fields['tracking_unit']);
220
		$workingHoursPerDay = 24;
221
222
		$usedUnits = 0;
223
		if ($trackingUnit == 'incidents') {
224
			$usedUnits = 1;
225
		} elseif ($trackingUnit == 'days') {
226
			if (!empty($ticketData['days'])) {
227
				$usedUnits = $ticketData['days'];
228
			} elseif (!empty($ticketData['hours'])) {
229
				$usedUnits = $ticketData['hours'] / $workingHoursPerDay;
230
			}
231
		} elseif ($trackingUnit == 'hours') {
232
			if (!empty($ticketData['hours'])) {
233
				$usedUnits = $ticketData['hours'];
234
			} elseif (!empty($ticketData['days'])) {
235
				$usedUnits = $ticketData['days'] * $workingHoursPerDay;
236
			}
237
		}
238
		return $usedUnits;
239
	}
240
241
	/**
242
	 * Function to Upate the Used Units of the Service Contract.
243
	 *
244
	 * @param float $usedUnits
245
	 */
246 1
	public function updateUsedUnits($usedUnits)
247
	{
248 1
		$this->column_fields['used_units'] = $usedUnits;
249 1
		\App\Db::getInstance()->createCommand()->update($this->table_name, ['used_units' => $usedUnits], ['servicecontractsid' => $this->id])->execute();
250 1
	}
251
252
	/**
253
	 * Function to Calculate the End Date, Planned Duration, Actual Duration and Progress of a Service Contract.
254
	 */
255 1
	public function calculateProgress()
256
	{
257 1
		$db = \App\Db::getInstance();
258 1
		$params = [];
259
260 1
		$startDate = $this->column_fields['start_date'];
261 1
		$dueDate = $this->column_fields['due_date'];
262 1
		$endDate = $this->column_fields['end_date'];
263
264 1
		$usedUnits = \vtlib\Functions::formatDecimal($this->column_fields['used_units']);
265 1
		$totalUnits = \vtlib\Functions::formatDecimal($this->column_fields['total_units']);
266
267 1
		$contractStatus = $this->column_fields['contract_status'];
268
269
		// Update the End date if the status is Complete or if the Used Units reaches/exceeds Total Units
270
		// We need to do this first to make sure Actual duration is computed properly
271 1
		if ($contractStatus === 'Complete' || (!empty($usedUnits) && !empty($totalUnits) && $usedUnits >= $totalUnits)) {
272 1 View Code Duplication
			if (empty($endDate)) {
273 1
				$endDate = date('Y-m-d');
274 1
				$db->createCommand()->update($this->table_name, ['end_date' => $endDate], ['servicecontractsid' => $this->id])->execute();
275
			}
276 View Code Duplication
		} else {
277 1
			$endDate = null;
278 1
			$db->createCommand()->update($this->table_name, ['end_date' => $endDate], ['servicecontractsid' => $this->id])->execute();
279
		}
280
281
		// Calculate the Planned Duration based on Due date and Start date. (in days)
282 1 View Code Duplication
		if (!empty($dueDate) && !empty($startDate)) {
283 1
			$params['planned_duration'] = \App\Fields\Date::getDiff($startDate, $dueDate, 'days');
284
		} else {
285
			$params['planned_duration'] = '';
286
		}
287
288
		// Calculate the Actual Duration based on End date and Start date. (in days)
289 1 View Code Duplication
		if (!empty($endDate) && !empty($startDate)) {
290 1
			$params['actual_duration'] = \App\Fields\Date::getDiff($startDate, $endDate, 'days');
291
		} else {
292 1
			$params['actual_duration'] = '';
293
		}
294
		// Update the Progress based on Used Units and Total Units (in percentage)
295 1
		if (!empty($usedUnits) && !empty($totalUnits)) {
296
			$params['progress'] = (float) (($usedUnits * 100) / $totalUnits);
297
		} else {
298 1
			$params['progress'] = null;
299
		}
300 1
		$db->createCommand()->update($this->table_name, $params, ['servicecontractsid' => $this->id])->execute();
301 1
	}
302
303
	/**
304
	 * Function to unlink an entity with given Id from another entity.
305
	 *
306
	 * @param int    $id
307
	 * @param string $returnModule
308
	 * @param int    $returnId
309
	 * @param bool   $relatedName
310
	 */
311
	public function unlinkRelationship($id, $returnModule, $returnId, $relatedName = false)
312
	{
313
		if ($relatedName === 'getManyToMany') {
314
			parent::unlinkRelationship($id, $returnModule, $returnId, $relatedName);
315
		} else {
316
			parent::deleteRelatedFromDB($id, $returnModule, $returnId);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (deleteRelatedFromDB() instead of unlinkRelationship()). Are you sure this is correct? If so, you might want to change this to $this->deleteRelatedFromDB().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
317
			$dataReader = (new \App\Db\Query())->select(['tabid', 'tablename', 'columnname'])
318
				->from('vtiger_field')
319
				->where(['fieldid' => (new \App\Db\Query())->select(['fieldid'])->from('vtiger_fieldmodulerel')->where(['module' => $this->moduleName, 'relmodule' => $returnModule])])
320
				->createCommand()->query();
321 View Code Duplication
			while ($row = $dataReader->read()) {
322
				App\Db::getInstance()->createCommand()
323
					->update($row['tablename'], [$row['columnname'] => null], [$row['columnname'] => $returnId, CRMEntity::getInstance(App\Module::getModuleName($row['tabid']))->table_index => $id])
324
					->execute();
325
			}
326
			$dataReader->close();
327
		}
328
	}
329
330
	/**
331
	 * Move the related records of the specified list of id's to the given record.
332
	 *
333
	 * @param string This module name
334
	 * @param array List of Entity Id's from which related records need to be transfered
335
	 * @param int Id of the the Record to which the related records are to be moved
336
	 */
337
	public function transferRelatedRecords($module, $transferEntityIds, $entityId)
338
	{
339
		$adb = PearDatabase::getInstance();
340
341
		\App\Log::trace("Entering function transferRelatedRecords ($module, $transferEntityIds, $entityId)");
342
343
		$rel_table_arr = ['Documents' => 'vtiger_senotesrel', 'Attachments' => 'vtiger_seattachmentsrel'];
344
345
		$tbl_field_arr = ['vtiger_senotesrel' => 'notesid', 'vtiger_seattachmentsrel' => 'attachmentsid'];
346
347
		$entity_tbl_field_arr = ['vtiger_senotesrel' => 'crmid', 'vtiger_seattachmentsrel' => 'crmid'];
348
349 View Code Duplication
		foreach ($transferEntityIds as $transferId) {
350
			foreach ($rel_table_arr as $rel_table) {
351
				$id_field = $tbl_field_arr[$rel_table];
352
				$entity_id_field = $entity_tbl_field_arr[$rel_table];
353
				// IN clause to avoid duplicate entries
354
				$sel_result = $adb->pquery("select $id_field from $rel_table where $entity_id_field=? " .
355
					" and $id_field not in (select $id_field from $rel_table where $entity_id_field=?)", [$transferId, $entityId]);
356
				$res_cnt = $adb->numRows($sel_result);
357
				if ($res_cnt > 0) {
358
					for ($i = 0; $i < $res_cnt; ++$i) {
359
						$id_field_value = $adb->queryResult($sel_result, $i, $id_field);
360
						$adb->pquery("update $rel_table set $entity_id_field=? where $entity_id_field=? and $id_field=?", [$entityId, $transferId, $id_field_value]);
361
					}
362
				}
363
			}
364
		}
365
		parent::transferRelatedRecords($module, $transferEntityIds, $entityId);
366
		\App\Log::trace('Exiting transferRelatedRecords...');
367
	}
368
}
369