Patient_Visit_Manager::determineVisitStatus()   B
last analyzed

Complexity

Conditions 10
Paths 128

Size

Total Lines 63
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 43
nc 128
nop 1
dl 0
loc 63
c 1
b 0
f 0
cc 10
rs 7.4333

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 Copyright (C) 2018-2020 KANOUN Salim
4
 This program is free software; you can redistribute it and/or modify
5
 it under the terms of the Affero GNU General Public v.3 License as published by
6
 the Free Software Foundation;
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
 Affero GNU General Public Public for more details.
11
 You should have received a copy of the Affero GNU General Public Public along
12
 with this program; if not, write to the Free Software Foundation, Inc.,
13
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14
 */
15
16
/**
17
 * Determine Visit permissions for creation and status for upload manager
18
 */
19
20
class Patient_Visit_Manager
21
{
22
23
	protected $patientCode;
24
	protected $linkpdo;
25
	protected $patientObject;
26
	protected $visitGroup;
27
28
	//Constants visit status available
29
	const DONE="Done";
30
	const NOT_DONE="Not Done";
31
	const SHOULD_BE_DONE="Should be done";
32
	const PENDING="Pending";
33
	const COMPLIANCY_YES="Yes";
34
	const COMPLIANCY_NO="No";
35
	const VISIT_WITHDRAWN="Visit Withdrawn";
36
	const VISIT_POSSIBLY_WITHDRAWN="Possibly Withdrawn";
37
	const OPTIONAL_VISIT="Optional";
38
	//Not needed status is no make custom choice to deactivate upload reminder
39
	const VISIT_NOT_NEEDED="Not Nedded";
40
41
	public function __construct(Patient $patientObject, Visit_Group $visitGroup, $linkpdo)
42
	{
43
		$this->linkpdo=$linkpdo;
44
		$this->patientCode=$patientObject->patientCode;
45
		$this->patientObject=$patientObject;
46
		$this->visitGroup=$visitGroup;
47
	}
48
49
50
	/**
51
	 * Return created visits of a given patient
52
	 * @param bool $deletedVisits
53
	 * @return Visit[]
54
	 */
55
	public function getCreatedPatientsVisits(bool $deletedVisits=false) : Array
56
	{
57
58
		$visitQuery=$this->linkpdo->prepare('SELECT id_visit FROM visits
59
                                                                INNER JOIN visit_type ON 
60
                                                                (visit_type.id=visits.visit_type_id 
61
                                                                AND visit_type.group_id = :visitGroupId)
62
													    WHERE patient_code = :patientCode
63
													    AND visits.deleted=:deleted
64
													    ORDER BY visit_type.visit_order');
65
66
67
		$visitQuery->execute(array(
68
			'patientCode' => $this->patientCode,
69
			'visitGroupId' => $this->visitGroup->groupId,
70
			'deleted' => $deletedVisits
71
		));
72
73
		$visitsResults=$visitQuery->fetchAll(PDO::FETCH_COLUMN);
74
75
		$visitsObjectArray=[];
76
		foreach ($visitsResults as $idVisit) {
77
			$visitsObjectArray[]=new Visit($idVisit, $this->linkpdo);
78
		}
79
80
		return $visitsObjectArray;
81
	}
82
83
	/**
84
	 * Return uploaded visits of a given patient
85
	 * @param bool $deletedVisits
86
	 * @return Visit[]
87
	 */
88
	public function getQcDonePatientsVisits(bool $deletedVisits=false) : Array
89
	{
90
91
		$visitQuery=$this->linkpdo->prepare('SELECT id_visit FROM visits
92
													            INNER JOIN visit_type ON 
93
                                                                (visit_type.id=visits.visit_type_id 
94
                                                                AND visit_type.group_id = :visitGroupId)
95
                                                    WHERE patient_code = :patientCode
96
                                                    AND state_quality_control = :qcStatus
97
													AND deleted=:deleted
98
													ORDER BY visit_order');
99
100
101
		$visitQuery->execute(array(
102
			'patientCode' => $this->patientCode,
103
			'qcStatus' => Visit::QC_ACCEPTED,
104
			'visitGroupId' => $this->visitGroup->groupId,
105
			'deleted' => $deletedVisits
106
		));
107
108
		$visitsResults=$visitQuery->fetchAll(PDO::FETCH_COLUMN);
109
110
		$visitsObjectArray=[];
111
		foreach ($visitsResults as $idVisit) {
112
			$visitsObjectArray[]=new Visit($idVisit, $this->linkpdo);
113
		}
114
115
		return $visitsObjectArray;
116
	}
117
118
	/**
119
	 * Return array of available visits to create
120
	 * Throw exception is patient withdraw or no possible visits
121
	 * Can be overriden for custom visit creation workflow
122
	 */
123
	public function getAvailableVisitsToCreate() : Array
124
	{
125
		$availableVisitName=[];
126
127
		// if withdraw disallow visit creation
128
		if ($this->patientObject->patientWithdraw) {
129
			throw new Exception(Patient::PATIENT_WITHDRAW);
130
		}
131
132
		$allPossibleVisits=$this->visitGroup->getAllVisitTypesOfGroup();
133
		$createdVisits=$this->getCreatedPatientsVisits();
134
		$createdVisitsNameArray=array_map(function(Visit $visit) {
135
			return $visit->visitType;
136
		},  $createdVisits);
137
138
		$createdVisitOrder=array_map(function(Visit $visit) {
139
			return $visit->getVisitCharacteristics()->visitOrder;
140
		},  $createdVisits);
141
142
		if (empty($createdVisitOrder)) {
143
			$lastCreatedVisitOrder=-1;
144
		}else {
145
			$lastCreatedVisitOrder=max($createdVisitOrder);
146
		}
147
148
		foreach ($allPossibleVisits as $possibleVisit) {
149
150
			if (in_array($possibleVisit->name, $createdVisitsNameArray)) {
151
				//Already created do not display it
152
				continue;
153
			}
154
155
			if ($possibleVisit->visitOrder < $lastCreatedVisitOrder) {
156
				$availableVisitName[]=$possibleVisit->name;
157
			}else if ($possibleVisit->visitOrder > $lastCreatedVisitOrder) {
158
				if ($possibleVisit->optionalVisit) {
159
					//If optional add optional visit and look for the next order
160
					$availableVisitName[]=$possibleVisit->name;
161
					$lastCreatedVisitOrder++;
162
				}else if ($possibleVisit->visitOrder > $lastCreatedVisitOrder) {
163
					$availableVisitName[]=$possibleVisit->name;
164
					break;
165
				}
166
			}
167
		}
168
169
		//Reverse to sort for the more advanced visit to create
170
		$availableVisitName=array_reverse($availableVisitName);
171
172
		if (empty($availableVisitName)) {
173
			throw new Exception('No possible visit');
174
		}
175
176
		return $availableVisitName;
177
	}
178
179
	/**
180
	 * Return if there are still visits that are awaiting to be created for this patient
181
	 * @return boolean
182
	 */
183
	public function isMissingVisit() : bool
184
	{
185
		try {
186
			if (!empty($this->getAvailableVisitsToCreate())) {
187
				return true;
188
			}
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 186 is false. This is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
189
		}catch (Exception $e) {
190
			//if exception happens no visits are missing
191
			return false;
192
		}
193
	}
194
195
	/**
196
	 * Determine Visit Status of a patient
197
	 * Theorical date are calculated from registration date and compared to
198
	 * acquisition date if visit created or actual date for non created visit
199
	 */
200
	public function determineVisitStatus(String $visitName)
201
	{
202
203
		$registrationDate=$this->patientObject->getImmutableRegistrationDate();
204
		$visitType=Visit_Type::getVisitTypeByName($this->visitGroup->groupId, $visitName, $this->linkpdo);
205
206
		$dateDownLimit=$registrationDate->modify($visitType->limitLowDays.'day');
207
		$dateUpLimit=$registrationDate->modify($visitType->limitUpDays.'day');
208
209
		$visitAnswer['status']=null;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$visitAnswer was never initialized. Although not strictly required by PHP, it is generally a good practice to add $visitAnswer = array(); before regardless.
Loading history...
210
		$visitAnswer['compliancy']=null;
211
		$visitAnswer['shouldBeDoneBefore']=$dateUpLimit->format('Y-m-d');
212
		$visitAnswer['shouldBeDoneAfter']=$dateDownLimit->format('Y-m-d');
213
		$visitAnswer['state_investigator_form']=null;
214
		$visitAnswer['state_quality_control']=null;
215
		$visitAnswer['acquisition_date']=null;
216
		$visitAnswer['upload_date']=null;
217
		$visitAnswer['upload_status']=null;
218
		$visitAnswer['id_visit']=null;
219
220
		try {
221
			//Visit Created check compliancy
222
			$visitObject=$this->getCreatedVisitForVisitTypeId($visitType->id);
223
			$visitAnswer['state_investigator_form']=$visitObject->stateInvestigatorForm;
224
			$visitAnswer['state_quality_control']=$visitObject->stateQualityControl;
225
			$visitAnswer['acquisition_date']=$visitObject->acquisitionDate;
226
			$visitAnswer['upload_date']=$visitObject->uploadDate;
227
			$visitAnswer['upload_status']=$visitObject->uploadStatus;
228
			$visitAnswer['id_visit']=$visitObject->id_visit;
229
			$testedDate=$visitObject->acquisitionDate;
230
			$visitAnswer['status']=Patient_Visit_Manager::DONE;
231
232
			if ($testedDate >= $dateDownLimit && $testedDate <= $dateDownLimit) {
233
				$visitAnswer['compliancy']=Patient_Visit_Manager::COMPLIANCY_YES;
234
			}else {
235
				$visitAnswer['compliancy']=Patient_Visit_Manager::COMPLIANCY_NO;
236
			}
237
		}catch (Exception $e) {
238
			//Visit Not Created
239
			//If optional visit no status determination
240
			if ($visitType->optionalVisit) {
241
				$visitAnswer['status']=Patient_Visit_Manager::OPTIONAL_VISIT;
242
			}else {
243
				//Compare actual time with theorical date to determine status
244
				$testedDate=new DateTime(date("Y-m-d"));
245
				if ($testedDate <= $dateUpLimit) {
246
					$visitAnswer['status']=Patient_Visit_Manager::PENDING;
247
				}else {
248
					$visitAnswer['status']=Patient_Visit_Manager::SHOULD_BE_DONE;
249
				}
250
			}
251
		}
252
253
		//Take account of possible withdrawal if not created
254
		if ($this->patientObject->patientWithdraw && $visitAnswer['acquisition_date'] == null) {
255
			if ($this->patientObject->patientWithdrawDate < $dateDownLimit) {
256
				$visitAnswer['status']=Patient_Visit_Manager::VISIT_WITHDRAWN;
257
			}else if ($this->patientObject->patientWithdrawDate > $dateDownLimit) {
258
				$visitAnswer['status']=Patient_Visit_Manager::VISIT_POSSIBLY_WITHDRAWN;
259
			}
260
		}
261
262
		return $visitAnswer;
263
	}
264
265
	/**
266
	 * Return visits of this patient available for review
267
	 */
268
	public function getAwaitingReviewVisits()
269
	{
270
271
		$createdVisits=$this->getCreatedPatientsVisits();
272
273
		$availableVisitsForReview=[];
274
275
		foreach ($createdVisits as $visit) {
276
			if ($visit->reviewAvailable) {
277
				$availableVisitsForReview[]=$visit;
278
			}
279
		}
280
281
		return $availableVisitsForReview;
282
	}
283
284
	public function isHavingAwaitingReviewVisit()
285
	{
286
		$awaitingReviews=$this->getAwaitingReviewVisits();
287
		return (!empty($awaitingReviews));
288
	}
289
290
	public function getCreatedVisitForVisitTypeId($visitTypeId) {
291
		$visitQuery=$this->linkpdo->prepare('SELECT id_visit FROM visits WHERE patient_code=:patientCode AND visit_type_id=:visitTypeId AND deleted=0 ');
292
        
293
		$visitQuery->execute(array('patientCode' => $this->patientCode, 'visitTypeId'=>$visitTypeId));
294
		$visitId=$visitQuery->fetch(PDO::FETCH_COLUMN);
295
296
		if (empty($visitId)) {
297
			throw new Exception("Visit Non Existing");
298
		}else {
299
			return new Visit($visitId, $this->linkpdo);
300
		}
301
        
302
	}
303
304
	public function getCreatedVisitByVisitName($visitName) {
305
306
		$visitQuery=$this->linkpdo->prepare('SELECT id_visit FROM visits, visit_type WHERE visits.visit_type_id = visit_type.id AND visits.patient_code=:patientCode AND visit_type.name=:visitName AND visits.deleted=0 ');
307
        
308
		$visitQuery->execute(array('patientCode' => $this->patientCode, 'visitName'=>$visitName));
309
		$visitId=$visitQuery->fetch(PDO::FETCH_COLUMN);
310
311
		if (empty($visitId)) {
312
			throw new Exception("Visit Non Existing");
313
		}else {
314
			return new Visit($visitId, $this->linkpdo);
315
		}
316
317
	}
318
}
319