1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# |
3
|
|
|
# This file is part of SENAITE.HEALTH. |
4
|
|
|
# |
5
|
|
|
# SENAITE.HEALTH is free software: you can redistribute it and/or modify it |
6
|
|
|
# under the terms of the GNU General Public License as published by the Free |
7
|
|
|
# Software Foundation, version 2. |
8
|
|
|
# |
9
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
10
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
11
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
12
|
|
|
# details. |
13
|
|
|
# |
14
|
|
|
# You should have received a copy of the GNU General Public License along with |
15
|
|
|
# this program; if not, write to the Free Software Foundation, Inc., 51 |
16
|
|
|
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
17
|
|
|
# |
18
|
|
|
# Copyright 2018-2019 by it's authors. |
19
|
|
|
# Some rights reserved, see README and LICENSE. |
20
|
|
|
|
21
|
|
|
from datetime import datetime |
22
|
|
|
|
23
|
|
|
from Products.ATContentTypes.utils import DT2dt |
24
|
|
|
from Products.ATExtensions.ateapi import RecordsField |
25
|
|
|
from Products.Archetypes import atapi |
26
|
|
|
from Products.Archetypes.public import * |
27
|
|
|
from Products.CMFCore.utils import getToolByName |
28
|
|
|
from Products.CMFPlone.utils import safe_unicode |
29
|
|
|
from zope.interface import implements |
30
|
|
|
|
31
|
|
|
from bika.health import bikaMessageFactory as _ |
32
|
|
|
from bika.health import logger |
33
|
|
|
from bika.health.config import * |
34
|
|
|
from bika.health.interfaces import IPatient |
35
|
|
|
from bika.health.utils import translate_i18n as t |
36
|
|
|
from bika.health.widgets import SplittedDateWidget |
37
|
|
|
from bika.health.widgets.patientmenstrualstatuswidget import \ |
38
|
|
|
PatientMenstrualStatusWidget |
39
|
|
|
from bika.lims import api |
40
|
|
|
from bika.lims import idserver |
41
|
|
|
from bika.lims.browser.fields import AddressField |
42
|
|
|
from bika.lims.browser.fields import DateTimeField as DateTimeField_bl |
43
|
|
|
from bika.lims.browser.fields.remarksfield import RemarksField |
44
|
|
|
from bika.lims.browser.widgets import AddressWidget |
45
|
|
|
from bika.lims.browser.widgets import DateTimeWidget as DateTimeWidget_bl |
46
|
|
|
from bika.lims.browser.widgets import RecordsWidget |
47
|
|
|
from bika.lims.browser.widgets import ReferenceWidget |
48
|
|
|
from bika.lims.browser.widgets.remarkswidget import RemarksWidget |
49
|
|
|
from bika.lims.catalog.analysisrequest_catalog import \ |
50
|
|
|
CATALOG_ANALYSIS_REQUEST_LISTING |
51
|
|
|
from bika.lims.catalog.bika_catalog import BIKA_CATALOG |
52
|
|
|
from bika.lims.content.person import Person |
53
|
|
|
from bika.lims.interfaces import IClient |
54
|
|
|
|
55
|
|
|
schema = Person.schema.copy() + Schema(( |
|
|
|
|
56
|
|
|
StringField( |
|
|
|
|
57
|
|
|
'PatientID', |
58
|
|
|
searchable=1, |
59
|
|
|
required=0, |
60
|
|
|
widget=StringWidget( |
|
|
|
|
61
|
|
|
visible={'view': 'visible', 'edit': 'hidden'}, |
62
|
|
|
label=_('Patient ID'), |
63
|
|
|
css='readonly-emphasize', |
64
|
|
|
), |
65
|
|
|
), |
66
|
|
|
ReferenceField( |
|
|
|
|
67
|
|
|
'PrimaryReferrer', |
68
|
|
|
allowed_types=('Client',), |
69
|
|
|
relationship='PatientClient', |
70
|
|
|
widget=ReferenceWidget( |
71
|
|
|
label=_("Client"), |
72
|
|
|
size=30, |
73
|
|
|
catalog_name="portal_catalog", |
74
|
|
|
base_query={"is_active": True, |
75
|
|
|
"sort_limit": 30, |
76
|
|
|
"sort_on": "sortable_title", |
77
|
|
|
"sort_order": "ascending"}, |
78
|
|
|
colModel=[ |
79
|
|
|
{"columnName": "Title", "label": _("Title"), |
80
|
|
|
"width": "30", "align": "left"}, |
81
|
|
|
{"columnName": "getProvince", "label": _("Province"), |
82
|
|
|
"width": "30", "align": "left"}, |
83
|
|
|
{"columnName": "getDistrict", "label": _("District"), |
84
|
|
|
"width": "30", "align": "left"}], |
85
|
|
|
showOn=True, |
86
|
|
|
), |
87
|
|
|
), |
88
|
|
|
ComputedField( |
|
|
|
|
89
|
|
|
'PrimaryReferrerID', |
90
|
|
|
expression="context.getClientID()", |
91
|
|
|
widget=ComputedWidget( |
|
|
|
|
92
|
|
|
), |
93
|
|
|
), |
94
|
|
|
ComputedField( |
95
|
|
|
'PrimaryReferrerTitle', |
96
|
|
|
expression="context.getClientTitle()", |
97
|
|
|
widget=ComputedWidget( |
98
|
|
|
), |
99
|
|
|
), |
100
|
|
|
ComputedField( |
101
|
|
|
'PrimaryReferrerUID', |
102
|
|
|
expression="context.getClientUID()", |
103
|
|
|
widget=ComputedWidget( |
104
|
|
|
), |
105
|
|
|
), |
106
|
|
|
ComputedField( |
107
|
|
|
'PrimaryReferrerURL', |
108
|
|
|
expression="context.getClientURL()", |
109
|
|
|
widget=ComputedWidget( |
110
|
|
|
visible=False |
111
|
|
|
), |
112
|
|
|
), |
113
|
|
|
StringField( |
114
|
|
|
'Gender', |
115
|
|
|
vocabulary=GENDERS, |
116
|
|
|
index='FieldIndex', |
117
|
|
|
default='dk', |
118
|
|
|
widget=SelectionWidget( |
|
|
|
|
119
|
|
|
format='select', |
120
|
|
|
label=_('Gender'), |
121
|
|
|
), |
122
|
|
|
), |
123
|
|
|
StringField( |
124
|
|
|
'Age', |
125
|
|
|
widget=StringWidget( |
126
|
|
|
label=_('Age'), |
127
|
|
|
visible=0, |
128
|
|
|
width=3, |
129
|
|
|
), |
130
|
|
|
), |
131
|
|
|
DateTimeField_bl( |
132
|
|
|
'BirthDate', |
133
|
|
|
required=1, |
134
|
|
|
validators=('isDateFormat',), |
135
|
|
|
widget=DateTimeWidget_bl( |
136
|
|
|
label=_('Birth date'), |
137
|
|
|
datepicker_nofuture=1, |
138
|
|
|
), |
139
|
|
|
), |
140
|
|
|
BooleanField( |
|
|
|
|
141
|
|
|
'BirthDateEstimated', |
142
|
|
|
default=False, |
143
|
|
|
widget=BooleanWidget( |
|
|
|
|
144
|
|
|
label=_('Birth date is estimated'), |
145
|
|
|
), |
146
|
|
|
), |
147
|
|
|
RecordsField( |
148
|
|
|
'AgeSplitted', |
149
|
|
|
required=1, |
150
|
|
|
widget=SplittedDateWidget( |
151
|
|
|
label=_('Age'), |
152
|
|
|
), |
153
|
|
|
), |
154
|
|
|
ComputedField( |
155
|
|
|
'AgeSplittedStr', |
156
|
|
|
expression="context.getAgeSplittedStr()", |
157
|
|
|
widget=ComputedWidget( |
158
|
|
|
visible=False |
159
|
|
|
), |
160
|
|
|
), |
161
|
|
|
AddressField( |
162
|
|
|
'CountryState', |
163
|
|
|
widget=AddressWidget( |
164
|
|
|
searchable=True, |
165
|
|
|
label=_("Country and state"), |
166
|
|
|
showLegend=True, |
167
|
|
|
showDistrict=True, |
168
|
|
|
showCopyFrom=False, |
169
|
|
|
showCity=False, |
170
|
|
|
showPostalCode=False, |
171
|
|
|
showAddress=False, |
172
|
|
|
), |
173
|
|
|
), |
174
|
|
|
RecordsField( |
175
|
|
|
'PatientIdentifiers', |
176
|
|
|
type='patientidentifiers', |
177
|
|
|
subfields=( |
178
|
|
|
'IdentifierType', |
179
|
|
|
'Identifier' |
180
|
|
|
), |
181
|
|
|
subfield_labels={ |
182
|
|
|
'IdentifierType': _('Identifier Type'), |
183
|
|
|
'Identifier': _('Identifier') |
184
|
|
|
}, |
185
|
|
|
subfield_sizes={ |
186
|
|
|
'Identifier': 15, |
187
|
|
|
'Identifier Type': 25 |
188
|
|
|
}, |
189
|
|
|
widget=RecordsWidget( |
190
|
|
|
label=_('Additional identifiers'), |
191
|
|
|
description=_('Patient additional identifiers'), |
192
|
|
|
combogrid_options={ |
193
|
|
|
'IdentifierType': { |
194
|
|
|
'colModel': [ |
195
|
|
|
{ |
196
|
|
|
'columnName': 'IdentifierType', |
197
|
|
|
'width': '30', |
198
|
|
|
'label': _('Title') |
199
|
|
|
}, |
200
|
|
|
{ |
201
|
|
|
'columnName': 'Description', |
202
|
|
|
'width': '70', |
203
|
|
|
'label': _('Description') |
204
|
|
|
} |
205
|
|
|
], |
206
|
|
|
'url': 'getidentifiertypes', |
207
|
|
|
'showOn': True, |
208
|
|
|
'width': '550px' |
209
|
|
|
}, |
210
|
|
|
}, |
211
|
|
|
), |
212
|
|
|
), |
213
|
|
|
ComputedField( |
214
|
|
|
'PatientIdentifiersStr', |
215
|
|
|
expression="context.getPatientIdentifiersStr()", |
216
|
|
|
widget=ComputedWidget( |
217
|
|
|
visible=False |
218
|
|
|
), |
219
|
|
|
), |
220
|
|
|
RemarksField( |
221
|
|
|
'Remarks', |
222
|
|
|
searchable=True, |
223
|
|
|
widget=RemarksWidget( |
224
|
|
|
label=_('Remarks'), |
225
|
|
|
), |
226
|
|
|
), |
227
|
|
|
RecordsField( |
228
|
|
|
'TreatmentHistory', |
229
|
|
|
type='treatmenthistory', |
230
|
|
|
subfields=( |
231
|
|
|
'Treatment', |
232
|
|
|
'Drug', |
233
|
|
|
'Start', |
234
|
|
|
'End' |
235
|
|
|
), |
236
|
|
|
required_subfields=( |
237
|
|
|
'Drug', |
238
|
|
|
'Start', |
239
|
|
|
'End' |
240
|
|
|
), |
241
|
|
|
subfield_labels={ |
242
|
|
|
'Drug': _('Drug'), |
243
|
|
|
'Start': _('Start'), |
244
|
|
|
'End': _('End') |
245
|
|
|
}, |
246
|
|
|
subfield_sizes={ |
247
|
|
|
'Drug': 40, |
248
|
|
|
'Start': 10, |
249
|
|
|
'End': 10 |
250
|
|
|
}, |
251
|
|
|
subfield_types={ |
252
|
|
|
'Start': 'datepicker_nofuture', |
253
|
|
|
'End': 'datepicker' |
254
|
|
|
}, |
255
|
|
|
widget=RecordsWidget( |
256
|
|
|
label='Drug History', |
257
|
|
|
description=_("A list of patient treatments and drugs administered."), |
258
|
|
|
combogrid_options={ |
259
|
|
|
'Treatment': { |
260
|
|
|
'colModel': [ |
261
|
|
|
{ |
262
|
|
|
'columnName': 'Treatment', |
263
|
|
|
'width': '30', |
264
|
|
|
'label': _('Title') |
265
|
|
|
}, |
266
|
|
|
{ |
267
|
|
|
'columnName': 'Description', |
268
|
|
|
'width': '70', |
269
|
|
|
'label': _('Description') |
270
|
|
|
} |
271
|
|
|
], |
272
|
|
|
'url': 'gettreatments', |
273
|
|
|
'showOn': True, |
274
|
|
|
'width': '550px' |
275
|
|
|
}, |
276
|
|
|
'Drug': { |
277
|
|
|
'colModel': [ |
278
|
|
|
{ |
279
|
|
|
'columnName': 'Drug', |
280
|
|
|
'width': '30', |
281
|
|
|
'label': _('Title') |
282
|
|
|
}, |
283
|
|
|
{ |
284
|
|
|
'columnName': 'Description', |
285
|
|
|
'width': '70', |
286
|
|
|
'label': _('Description') |
287
|
|
|
} |
288
|
|
|
], |
289
|
|
|
'url': 'getdrugs', |
290
|
|
|
'showOn': True, |
291
|
|
|
'width': '550px' |
292
|
|
|
}, |
293
|
|
|
}, |
294
|
|
|
), |
295
|
|
|
), |
296
|
|
|
RecordsField( |
297
|
|
|
'Allergies', |
298
|
|
|
type='allergies', |
299
|
|
|
subfields=( |
300
|
|
|
'DrugProhibition', |
301
|
|
|
'Drug', |
302
|
|
|
'Remarks' |
303
|
|
|
), |
304
|
|
|
required_subfields=( |
305
|
|
|
'DrugProhibition', |
306
|
|
|
'Drug' |
307
|
|
|
), |
308
|
|
|
subfield_labels={ |
309
|
|
|
'DrugProhibition': _('Drug Prohibition Explanation'), |
310
|
|
|
'Drug': _('Drug'), |
311
|
|
|
'Remarks': _('Remarks') |
312
|
|
|
}, |
313
|
|
|
subfield_sizes={ |
314
|
|
|
'DrugProhibition': 30, |
315
|
|
|
'Drug': 30, |
316
|
|
|
'Remarks': 30 |
317
|
|
|
}, |
318
|
|
|
widget=RecordsWidget( |
319
|
|
|
label='Allergies', |
320
|
|
|
description=_("Known Patient allergies to keep information that can aid drug reaction interpretation"), |
321
|
|
|
combogrid_options={ |
322
|
|
|
'Drug': { |
323
|
|
|
'colModel': [ |
324
|
|
|
{ |
325
|
|
|
'columnName': 'Title', |
326
|
|
|
'width': '30', |
327
|
|
|
'label': _('Title') |
328
|
|
|
}, |
329
|
|
|
{ |
330
|
|
|
'columnName': 'Description', |
331
|
|
|
'width': '70', |
332
|
|
|
'label': _('Description') |
333
|
|
|
} |
334
|
|
|
], |
335
|
|
|
'url': 'getdrugs', |
336
|
|
|
'showOn': True, |
337
|
|
|
'width': '550px' |
338
|
|
|
}, |
339
|
|
|
'DrugProhibition': { |
340
|
|
|
'colModel': [ |
341
|
|
|
{ |
342
|
|
|
'columnName': 'DrugProhibition', |
343
|
|
|
'width': '30', |
344
|
|
|
'label': _('Title') |
345
|
|
|
}, |
346
|
|
|
{ |
347
|
|
|
'columnName': 'Description', |
348
|
|
|
'width': '70', |
349
|
|
|
'label': _('Description') |
350
|
|
|
} |
351
|
|
|
], |
352
|
|
|
'url': 'getdrugprohibitions', |
353
|
|
|
'showOn': True, |
354
|
|
|
'width': '550px' |
355
|
|
|
}, |
356
|
|
|
}, |
357
|
|
|
), |
358
|
|
|
), |
359
|
|
|
RecordsField( |
360
|
|
|
'ImmunizationHistory', |
361
|
|
|
type='immunizationhistory', |
362
|
|
|
subfields=( |
363
|
|
|
'EPINumber', |
364
|
|
|
'Immunization', |
365
|
|
|
'VaccinationCenter', |
366
|
|
|
'Date', |
367
|
|
|
'Remarks' |
368
|
|
|
), |
369
|
|
|
required_subfields=( |
370
|
|
|
'EPINumber', |
371
|
|
|
'Immunization', |
372
|
|
|
'Date' |
373
|
|
|
), |
374
|
|
|
subfield_labels={ |
375
|
|
|
'EPINumber': _('EPI Number'), |
376
|
|
|
'Immunization': _('Immunization'), |
377
|
|
|
'VaccinationCenter': _('Vaccination Center'), |
378
|
|
|
'Date': _('Date'), |
379
|
|
|
'Remarks': _('Remarks') |
380
|
|
|
}, |
381
|
|
|
subfield_sizes={ |
382
|
|
|
'EPINumber': 12, |
383
|
|
|
'Immunization': 20, |
384
|
|
|
'VaccinationCenter': 10, |
385
|
|
|
'Date': 10, |
386
|
|
|
'Remarks': 25 |
387
|
|
|
}, |
388
|
|
|
subfield_types={ |
389
|
|
|
'Date': 'datepicker_nofuture' |
390
|
|
|
}, |
391
|
|
|
widget=RecordsWidget( |
392
|
|
|
label='Immunization History', |
393
|
|
|
description=_("A list of immunizations administered to the patient."), |
394
|
|
|
combogrid_options={ |
395
|
|
|
'Immunization': { |
396
|
|
|
'colModel': [ |
397
|
|
|
{ |
398
|
|
|
'columnName': 'Immunization', |
399
|
|
|
'width': '30', |
400
|
|
|
'label': _('Title') |
401
|
|
|
}, |
402
|
|
|
{ |
403
|
|
|
'columnName': 'Description', |
404
|
|
|
'width': '70', |
405
|
|
|
'label': _('Description') |
406
|
|
|
} |
407
|
|
|
], |
408
|
|
|
'url': 'getimmunizations', |
409
|
|
|
'showOn': True, |
410
|
|
|
'width': '550px' |
411
|
|
|
}, |
412
|
|
|
'VaccinationCenter': { |
413
|
|
|
'colModel': [ |
414
|
|
|
{ |
415
|
|
|
'columnName': 'VaccinationCenter', |
416
|
|
|
'width': '100', |
417
|
|
|
'label': _('Title') |
418
|
|
|
} |
419
|
|
|
], |
420
|
|
|
'url': 'getvaccinationcenters', |
421
|
|
|
'showOn': True, |
422
|
|
|
'width': '550px' |
423
|
|
|
}, |
424
|
|
|
}, |
425
|
|
|
), |
426
|
|
|
), |
427
|
|
|
RecordsField( |
428
|
|
|
'TravelHistory', |
429
|
|
|
type='travelhistory', |
430
|
|
|
subfields=( |
431
|
|
|
'TripStartDate', |
432
|
|
|
'TripEndDate', |
433
|
|
|
'Country', |
434
|
|
|
'Location', |
435
|
|
|
'Remarks' |
436
|
|
|
), |
437
|
|
|
required_subfields='Country', |
438
|
|
|
subfield_labels={ |
439
|
|
|
'TripStartDate': _('Trip Start Date', 'Start date'), |
440
|
|
|
'TripEndDate': _('Trip End Date', 'End date'), |
441
|
|
|
'Country': _('Country'), |
442
|
|
|
'Location': _('Location'), |
443
|
|
|
'Remarks': _('Remarks')}, |
444
|
|
|
subfield_sizes={ |
445
|
|
|
'TripStartDate': 10, |
446
|
|
|
'TripEndDate': 10, |
447
|
|
|
'Country': 20, |
448
|
|
|
'Location': 20, |
449
|
|
|
'Remarks': 25}, |
450
|
|
|
subfield_types={ |
451
|
|
|
'TripStartDate': 'datepicker_nofuture', |
452
|
|
|
'TripEndDate': 'datepicker' |
453
|
|
|
}, |
454
|
|
|
widget=RecordsWidget( |
455
|
|
|
label='Travel History', |
456
|
|
|
description=_("A list of places visited by the patient."), |
457
|
|
|
combogrid_options={ |
458
|
|
|
'Country': { |
459
|
|
|
'colModel': [ |
460
|
|
|
{ |
461
|
|
|
'columnName': 'Code', |
462
|
|
|
'width': '15', |
463
|
|
|
'label': _('Code') |
464
|
|
|
}, |
465
|
|
|
{ |
466
|
|
|
'columnName': 'Country', |
467
|
|
|
'width': '85', |
468
|
|
|
'label': _('Country') |
469
|
|
|
} |
470
|
|
|
], |
471
|
|
|
'url': 'getCountries', |
472
|
|
|
'showOn': True, |
473
|
|
|
'width': "450px", |
474
|
|
|
}, |
475
|
|
|
}, |
476
|
|
|
), |
477
|
|
|
), |
478
|
|
|
RecordsField( |
479
|
|
|
'ChronicConditions', |
480
|
|
|
type='chronicconditions', |
481
|
|
|
subfields=( |
482
|
|
|
'Code', |
483
|
|
|
'Title', |
484
|
|
|
'Description', |
485
|
|
|
'Onset', |
486
|
|
|
'End' |
487
|
|
|
), |
488
|
|
|
required_subfields=( |
489
|
|
|
'Title', |
490
|
|
|
'Onset' |
491
|
|
|
), |
492
|
|
|
subfield_sizes={ |
493
|
|
|
'Code': 7, |
494
|
|
|
'Title': 20, |
495
|
|
|
'Description': 35, |
496
|
|
|
'Onset': 10, |
497
|
|
|
'End': 10 |
498
|
|
|
}, |
499
|
|
|
subfield_types={ |
500
|
|
|
'Onset': 'datepicker_nofuture', |
501
|
|
|
'End': 'datepicker' |
502
|
|
|
}, |
503
|
|
|
widget=RecordsWidget( |
504
|
|
|
label='Past Medical History', |
505
|
|
|
description=_("Patient's past medical history."), |
506
|
|
|
combogrid_options={ |
507
|
|
|
'Title': { |
508
|
|
|
'colModel': [ |
509
|
|
|
{ |
510
|
|
|
'columnName': 'Code', |
511
|
|
|
'width': '10', |
512
|
|
|
'label': _('Code') |
513
|
|
|
}, |
514
|
|
|
{ |
515
|
|
|
'columnName': 'Title', |
516
|
|
|
'width': '30', |
517
|
|
|
'label': _('Title') |
518
|
|
|
}, |
519
|
|
|
{ |
520
|
|
|
'columnName': 'Description', |
521
|
|
|
'width': '60', |
522
|
|
|
'label': _('Description') |
523
|
|
|
} |
524
|
|
|
], |
525
|
|
|
'url': 'getdiseases', |
526
|
|
|
'showOn': True, |
527
|
|
|
'width': "650px", |
528
|
|
|
}, |
529
|
|
|
}, |
530
|
|
|
), |
531
|
|
|
), |
532
|
|
|
StringField( |
533
|
|
|
'BirthPlace', |
534
|
|
|
schemata='Personal', |
535
|
|
|
widget=StringWidget( |
536
|
|
|
label=_('Birth place'), |
537
|
|
|
), |
538
|
|
|
), |
539
|
|
|
# TODO This field will be removed on release 319. We maintain this field on release 318 |
540
|
|
|
# because of the transference between string field and content type data. |
541
|
|
|
StringField( |
542
|
|
|
'Ethnicity', |
543
|
|
|
schemata='Personal', |
544
|
|
|
index='FieldIndex', |
545
|
|
|
vocabulary=ETHNICITIES, |
546
|
|
|
widget=ReferenceWidget( |
547
|
|
|
label=_('Ethnicity'), |
548
|
|
|
description=_("Ethnicity eg. Asian, African, etc."), |
549
|
|
|
visible=False, |
550
|
|
|
), |
551
|
|
|
), |
552
|
|
|
# TODO This field will change its name on v319 and it'll be called Ethnicity |
553
|
|
|
ReferenceField( |
554
|
|
|
'Ethnicity_Obj', |
555
|
|
|
schemata='Personal', |
556
|
|
|
vocabulary='getEthnicitiesVocabulary', |
557
|
|
|
allowed_types=('Ethnicity',), |
558
|
|
|
relationship='PatientEthnicity', |
559
|
|
|
widget=SelectionWidget( |
560
|
|
|
format='select', |
561
|
|
|
label=_('Ethnicity'), |
562
|
|
|
description=_("Ethnicity eg. Asian, African, etc."), |
563
|
|
|
), |
564
|
|
|
), |
565
|
|
|
StringField( |
566
|
|
|
'Citizenship', |
567
|
|
|
schemata='Personal', |
568
|
|
|
widget=StringWidget( |
569
|
|
|
label=_('Citizenship'), |
570
|
|
|
), |
571
|
|
|
), |
572
|
|
|
StringField( |
573
|
|
|
'MothersName', |
574
|
|
|
schemata='Personal', |
575
|
|
|
widget=StringWidget( |
576
|
|
|
label=_('Mothers name'), |
577
|
|
|
), |
578
|
|
|
), |
579
|
|
|
StringField( |
580
|
|
|
'FathersName', |
581
|
|
|
schemata='Personal', |
582
|
|
|
widget=StringWidget( |
583
|
|
|
label=_('Fathers name'), |
584
|
|
|
), |
585
|
|
|
), |
586
|
|
|
StringField( |
587
|
|
|
'CivilStatus', |
588
|
|
|
schemata='Personal', |
589
|
|
|
widget=StringWidget( |
590
|
|
|
label=_('Civil status'), |
591
|
|
|
), |
592
|
|
|
), |
593
|
|
|
ImageField( |
|
|
|
|
594
|
|
|
'Photo', |
595
|
|
|
schemata='Identification', |
596
|
|
|
widget=ImageWidget( |
|
|
|
|
597
|
|
|
label=_('Photo'), |
598
|
|
|
), |
599
|
|
|
), |
600
|
|
|
ImageField( |
601
|
|
|
'Feature', |
602
|
|
|
schemata='Identification', |
603
|
|
|
multiValue=1, |
604
|
|
|
widget=ImageWidget( |
605
|
|
|
label=_('Feature'), |
606
|
|
|
), |
607
|
|
|
), |
608
|
|
|
RecordsField( |
609
|
|
|
'MenstrualStatus', |
610
|
|
|
type='menstrualstatus', |
611
|
|
|
widget=PatientMenstrualStatusWidget( |
612
|
|
|
label='Menstrual status', |
613
|
|
|
), |
614
|
|
|
), |
615
|
|
|
StringField( |
616
|
|
|
'ClientPatientID', |
617
|
|
|
searchable=1, |
618
|
|
|
validators=('unique_client_patient_ID_validator',), |
619
|
|
|
required=1, |
620
|
|
|
widget=StringWidget( |
621
|
|
|
label=_('Client Patient ID'), |
622
|
|
|
), |
623
|
|
|
), |
624
|
|
|
BooleanField( |
625
|
|
|
'Anonymous', |
626
|
|
|
default=False, |
627
|
|
|
widget=BooleanWidget( |
628
|
|
|
label=_("Anonymous") |
629
|
|
|
), |
630
|
|
|
), |
631
|
|
|
BooleanField( |
632
|
|
|
'DefaultResultsDistribution', |
633
|
|
|
schemata="Publication preference", |
634
|
|
|
default=True, |
635
|
|
|
widget=BooleanWidget( |
636
|
|
|
label=_("Inherit default settings"), |
637
|
|
|
description=_("If checked, settings will be inherited from " |
638
|
|
|
"the Client, so further changes in Client for this " |
639
|
|
|
"setting will be populated too.")) |
640
|
|
|
), |
641
|
|
|
BooleanField( |
642
|
|
|
'AllowResultsDistribution', |
643
|
|
|
schemata="Publication preference", |
644
|
|
|
default=False, |
645
|
|
|
widget=BooleanWidget( |
646
|
|
|
label=_("Allow results distribution to this patient"), |
647
|
|
|
description=_("If checked, results reports will also be sent " |
648
|
|
|
"to the Patient automatically.")) |
649
|
|
|
), |
650
|
|
|
BooleanField( |
651
|
|
|
'PublicationAttachmentsPermitted', |
652
|
|
|
default=False, |
653
|
|
|
schemata='Publication preference', |
654
|
|
|
widget=BooleanWidget( |
655
|
|
|
label=_("Results attachments permitted"), |
656
|
|
|
description=_("File attachments to results, e.g. microscope " |
657
|
|
|
"photos, will be included in emails to patient " |
658
|
|
|
"if this option is enabled")) |
659
|
|
|
), |
660
|
|
|
ReferenceField( |
661
|
|
|
'InsuranceCompany', |
662
|
|
|
vocabulary='get_insurancecompanies', |
663
|
|
|
allowed_types=('InsuranceCompany',), |
664
|
|
|
relationship='InsuranceCompany', |
665
|
|
|
required=False, |
666
|
|
|
widget=SelectionWidget( |
667
|
|
|
format='select', |
668
|
|
|
label=_('Insurance Company'), |
669
|
|
|
), |
670
|
|
|
), |
671
|
|
|
StringField( |
672
|
|
|
'InsuranceNumber', |
673
|
|
|
searchable=1, |
674
|
|
|
required=0, |
675
|
|
|
widget=StringWidget( |
676
|
|
|
label=_('Insurance Number'), |
677
|
|
|
), |
678
|
|
|
), |
679
|
|
|
BooleanField( |
680
|
|
|
'InvoiceToInsuranceCompany', |
681
|
|
|
default=False, |
682
|
|
|
widget=BooleanWidget( |
683
|
|
|
label=_("Send invoices to the insurance company."), |
684
|
|
|
description=_("If it is checked the invoices will be send to the insurance company." |
685
|
|
|
" In this case the insurance number will be mandatory.")) |
686
|
|
|
), |
687
|
|
|
BooleanField( |
688
|
|
|
'PatientAsGuarantor', |
689
|
|
|
schemata='Insurance', |
690
|
|
|
default=True, |
691
|
|
|
widget=BooleanWidget( |
692
|
|
|
label=_("The patient is the guarantor."), |
693
|
|
|
description=_("The patient and the guarantor are the same.")) |
694
|
|
|
), |
695
|
|
|
StringField( |
696
|
|
|
'GuarantorID', |
697
|
|
|
searchable=1, |
698
|
|
|
schemata='Insurance', |
699
|
|
|
required=0, |
700
|
|
|
widget=StringWidget( |
701
|
|
|
label=_('Guarantor ID'), |
702
|
|
|
description=_("The ID number (Insurance Number) from the person whose contract cover the current patient.") |
703
|
|
|
), |
704
|
|
|
), |
705
|
|
|
StringField( |
706
|
|
|
'GuarantorSurname', |
707
|
|
|
searchable=1, |
708
|
|
|
schemata='Insurance', |
709
|
|
|
required=0, |
710
|
|
|
widget=StringWidget( |
711
|
|
|
label=_("Guarantor's Surname"), |
712
|
|
|
), |
713
|
|
|
), |
714
|
|
|
StringField( |
715
|
|
|
'GuarantorFirstname', |
716
|
|
|
searchable=1, |
717
|
|
|
schemata='Insurance', |
718
|
|
|
required=0, |
719
|
|
|
widget=StringWidget( |
720
|
|
|
label=_("Guarantor's First Name"), |
721
|
|
|
), |
722
|
|
|
), |
723
|
|
|
AddressField( |
724
|
|
|
'GuarantorPostalAddress', |
725
|
|
|
searchable=1, |
726
|
|
|
schemata='Insurance', |
727
|
|
|
required=0, |
728
|
|
|
widget=AddressWidget( |
729
|
|
|
label=_("Guarantor's postal address"), |
730
|
|
|
), |
731
|
|
|
), |
732
|
|
|
StringField( |
733
|
|
|
'GuarantorBusinessPhone', |
734
|
|
|
schemata='Insurance', |
735
|
|
|
widget=StringWidget( |
736
|
|
|
label=_("Guarantor's Phone (business)"), |
737
|
|
|
), |
738
|
|
|
), |
739
|
|
|
StringField( |
740
|
|
|
'GuarantorHomePhone', |
741
|
|
|
schemata='Insurance', |
742
|
|
|
widget=StringWidget( |
743
|
|
|
label=_("Guarantor's Phone (home)"), |
744
|
|
|
), |
745
|
|
|
), |
746
|
|
|
StringField( |
747
|
|
|
'GuarantorMobilePhone', |
748
|
|
|
schemata='Insurance', |
749
|
|
|
widget=StringWidget( |
750
|
|
|
label=_("Guarantor's Phone (mobile)"), |
751
|
|
|
), |
752
|
|
|
), |
753
|
|
|
BooleanField( |
754
|
|
|
'ConsentSMS', |
755
|
|
|
default=False, |
756
|
|
|
widget=BooleanWidget( |
757
|
|
|
label=_('Consent to SMS'), |
758
|
|
|
), |
759
|
|
|
), |
760
|
|
|
ComputedField( |
761
|
|
|
'NumberOfSamples', |
762
|
|
|
expression="len(context.getSamples())", |
763
|
|
|
widget=ComputedWidget( |
764
|
|
|
visible=False |
765
|
|
|
), |
766
|
|
|
), |
767
|
|
|
ComputedField( |
768
|
|
|
'NumberOfSamplesCancelled', |
769
|
|
|
expression="len(context.getSamplesCancelled())", |
770
|
|
|
widget=ComputedWidget( |
771
|
|
|
visible=False |
772
|
|
|
), |
773
|
|
|
), |
774
|
|
|
ComputedField( |
775
|
|
|
'NumberOfSamplesOngoing', |
776
|
|
|
expression="len(context.getSamplesOngoing())", |
777
|
|
|
widget=ComputedWidget( |
778
|
|
|
visible=False |
779
|
|
|
), |
780
|
|
|
), |
781
|
|
|
ComputedField( |
782
|
|
|
'NumberOfSamplesPublished', |
783
|
|
|
expression="len(context.getSamplesPublished())", |
784
|
|
|
widget=ComputedWidget( |
785
|
|
|
visible=False |
786
|
|
|
), |
787
|
|
|
), |
788
|
|
|
ComputedField( |
789
|
|
|
'RatioOfSamplesOngoing', |
790
|
|
|
expression="context.getNumberOfSamplesOngoingRatio()", |
791
|
|
|
widget=ComputedWidget( |
792
|
|
|
visible=False |
793
|
|
|
), |
794
|
|
|
), |
795
|
|
|
)) |
796
|
|
|
|
797
|
|
|
schema['JobTitle'].widget.visible = False |
798
|
|
|
schema['Department'].widget.visible = False |
799
|
|
|
schema['BusinessPhone'].widget.visible = False |
800
|
|
|
schema['BusinessFax'].widget.visible = False |
801
|
|
|
# Don't make title required - it will be computed from the Person's Fullname |
802
|
|
|
schema['title'].required = 0 |
803
|
|
|
schema['title'].widget.visible = False |
804
|
|
|
schema['EmailAddress'].schemata = 'Personal' |
805
|
|
|
schema['HomePhone'].schemata = 'Personal' |
806
|
|
|
schema['MobilePhone'].schemata = 'Personal' |
807
|
|
|
schema['InsuranceCompany'].schemata = 'Insurance' |
808
|
|
|
schema['InsuranceNumber'].schemata = 'Insurance' |
809
|
|
|
schema['InvoiceToInsuranceCompany'].schemata = 'Insurance' |
810
|
|
|
schema.moveField('PrimaryReferrer', after='Surname') |
811
|
|
|
schema.moveField('PatientID', before='title') |
812
|
|
|
schema.moveField('ClientPatientID', after='PatientID') |
813
|
|
|
schema.moveField('Anonymous', before='ClientPatientID') |
814
|
|
|
schema.moveField('InsuranceCompany', after='PrimaryReferrer') |
815
|
|
|
schema.moveField('InsuranceNumber', after='InsuranceCompany') |
816
|
|
|
schema.moveField('PatientIdentifiers', after='InsuranceNumber') |
817
|
|
|
schema.moveField('Gender', after='PatientIdentifiers') |
818
|
|
|
schema.moveField('Age', after='Gender') |
819
|
|
|
schema.moveField('BirthDate', after='Age') |
820
|
|
|
schema.moveField('BirthDateEstimated', after='BirthDate') |
821
|
|
|
schema.moveField('AgeSplitted', after='BirthDateEstimated') |
822
|
|
|
schema.moveField('CountryState', after='AgeSplitted') |
823
|
|
|
schema.moveField('MenstrualStatus', after='AgeSplitted') |
824
|
|
|
schema.moveField('ConsentSMS', after='PrimaryReferrer') |
825
|
|
|
schema.moveField('PrimaryReferrer', before='ClientPatientID') |
826
|
|
|
|
827
|
|
|
|
828
|
|
|
class Patient(Person): |
829
|
|
|
implements(IPatient) |
830
|
|
|
_at_rename_after_creation = True |
831
|
|
|
displayContentsTab = False |
832
|
|
|
schema = schema |
|
|
|
|
833
|
|
|
|
834
|
|
|
def _renameAfterCreation(self, check_auto_id=False): |
835
|
|
|
"""Autogenerate the ID of the object based on core's ID formatting |
836
|
|
|
settings for this type |
837
|
|
|
""" |
838
|
|
|
idserver.renameAfterCreation(self) |
839
|
|
|
|
840
|
|
|
def Title(self): |
841
|
|
|
"""Return the Fullname of the patient |
842
|
|
|
""" |
843
|
|
|
return safe_unicode(self.getFullname()).encode('utf-8') |
844
|
|
|
|
845
|
|
|
def getPatientID(self): |
846
|
|
|
return self.getId() |
847
|
|
|
|
848
|
|
|
def getSamples(self, **kwargs): |
849
|
|
|
"""Return samples taken from this Patient |
850
|
|
|
""" |
851
|
|
|
catalog = api.get_tool(CATALOG_ANALYSIS_REQUEST_LISTING, context=self) |
852
|
|
|
query = dict([(k, v) for k, v in kwargs.items() |
853
|
|
|
if k in catalog.indexes()]) |
854
|
|
|
query["getPatientUID"] = api.get_uid(self) |
855
|
|
|
brains = api.search(query, CATALOG_ANALYSIS_REQUEST_LISTING) |
856
|
|
|
if not kwargs.get("full_objects", False): |
857
|
|
|
return brains |
858
|
|
|
return map(api.get_object, brains) |
859
|
|
|
|
860
|
|
|
def getSamplesCancelled(self, full_objects=False): |
861
|
|
|
"""Return samples taken from this Patient that are in cancelled state |
862
|
|
|
""" |
863
|
|
|
return self.getSamples(review_state="cancelled", |
864
|
|
|
full_objects=full_objects) |
865
|
|
|
|
866
|
|
|
def getSamplesPublished(self, full_objects=False): |
867
|
|
|
"""Return samples taken from this Patient that are in published state |
868
|
|
|
""" |
869
|
|
|
return self.getSamples(review_state="published", |
870
|
|
|
full_objects=full_objects) |
871
|
|
|
|
872
|
|
|
def getSamplesOngoing(self, full_objects=False): |
873
|
|
|
"""Return samples taken from this Patient that are ongoing |
874
|
|
|
""" |
875
|
|
|
ongoing_statuses = [ |
876
|
|
|
"to_be_sampled", |
877
|
|
|
"scheduled_sampling", |
878
|
|
|
"to_be_sampled", |
879
|
|
|
"sample_due", |
880
|
|
|
"sample_received", |
881
|
|
|
"attachment_due", |
882
|
|
|
"to_be_verified", |
883
|
|
|
"verified", |
884
|
|
|
"to_be_preserved"] |
885
|
|
|
return self.getSamples(review_state=ongoing_statuses, is_active=True, |
886
|
|
|
full_objects=full_objects) |
887
|
|
|
|
888
|
|
|
def getNumberOfSamplesOngoingRatio(self): |
889
|
|
|
""" |
890
|
|
|
Returns the ratio between NumberOfSamplesOngoing/NumberOfSamples |
891
|
|
|
""" |
892
|
|
|
samples = self.getSamples() |
893
|
|
|
if len(samples) > 0: |
894
|
|
|
return len(self.getSamplesOngoing())/len(samples) |
895
|
|
|
return 0 |
896
|
|
|
|
897
|
|
|
def get_insurancecompanies(self): |
898
|
|
|
""" |
899
|
|
|
Return all the registered insurance companies. |
900
|
|
|
""" |
901
|
|
|
bsc = getToolByName(self, 'bika_setup_catalog') |
902
|
|
|
# Void selection |
903
|
|
|
ret = [('', '')] |
904
|
|
|
# Other selections |
905
|
|
|
for ic in bsc(portal_type='InsuranceCompany', |
906
|
|
|
is_active=True, |
907
|
|
|
sort_on='sortable_title'): |
908
|
|
|
ret.append((ic.UID, ic.Title)) |
909
|
|
|
return DisplayList(ret) |
|
|
|
|
910
|
|
|
|
911
|
|
|
def getPatientIdentifiersList(self): |
912
|
|
|
"""Returns a list with the additional identifiers for this patient |
913
|
|
|
""" |
914
|
|
|
ids = self.getPatientIdentifiers() |
915
|
|
|
ids = map(lambda patient_id: patient_id.get("Identifier"), ids) |
916
|
|
|
return filter(None, ids) |
917
|
|
|
|
918
|
|
|
def getPatientIdentifiersStr(self): |
919
|
|
|
"""Returns a string representation of the additional identifiers for |
920
|
|
|
this patient |
921
|
|
|
""" |
922
|
|
|
ids = self.getPatientIdentifiersList() |
923
|
|
|
return " ".join(ids) |
924
|
|
|
|
925
|
|
|
def getAgeSplitted(self): |
926
|
|
|
|
927
|
|
|
if self.getBirthDate(): |
928
|
|
|
dob = DT2dt(self.getBirthDate()).replace(tzinfo=None) |
929
|
|
|
now = datetime.today() |
930
|
|
|
|
931
|
|
|
currentday = now.day |
932
|
|
|
currentmonth = now.month |
933
|
|
|
currentyear = now.year |
934
|
|
|
birthday = dob.day |
935
|
|
|
birthmonth = dob.month |
936
|
|
|
birthyear = dob.year |
937
|
|
|
ageday = currentday - birthday |
938
|
|
|
agemonth = 0 |
939
|
|
|
ageyear = 0 |
940
|
|
|
months31days = [1, 3, 5, 7, 8, 10, 12] |
941
|
|
|
|
942
|
|
|
if ageday < 0: |
943
|
|
|
currentmonth -= 1 |
944
|
|
|
if currentmonth < 1: |
945
|
|
|
currentyear -= 1 |
946
|
|
|
currentmonth = currentmonth + 12 |
947
|
|
|
|
948
|
|
|
dayspermonth = 30 |
949
|
|
|
if currentmonth in months31days: |
950
|
|
|
dayspermonth = 31 |
951
|
|
|
elif currentmonth == 2: |
952
|
|
|
dayspermonth = 28 |
953
|
|
|
if(currentyear % 4 == 0 |
954
|
|
|
and (currentyear % 100 > 0 or currentyear % 400 == 0)): |
955
|
|
|
dayspermonth += 1 |
956
|
|
|
|
957
|
|
|
ageday = ageday + dayspermonth |
958
|
|
|
|
959
|
|
|
agemonth = currentmonth - birthmonth |
960
|
|
|
if agemonth < 0: |
961
|
|
|
currentyear -= 1 |
962
|
|
|
agemonth = agemonth + 12 |
963
|
|
|
|
964
|
|
|
ageyear = currentyear - birthyear |
965
|
|
|
|
966
|
|
|
return [{'year': ageyear, |
967
|
|
|
'month': agemonth, |
968
|
|
|
'day': ageday}] |
969
|
|
|
else: |
970
|
|
|
return [{'year': '', |
971
|
|
|
'month': '', |
972
|
|
|
'day': ''}] |
973
|
|
|
|
974
|
|
|
def getAge(self): |
975
|
|
|
return self.getAgeSplitted()[0]['year'] |
976
|
|
|
|
977
|
|
|
def getAgeSplittedStr(self): |
978
|
|
|
splitted = self.getAgeSplitted()[0] |
979
|
|
|
arr = [] |
980
|
|
|
arr.append(splitted['year'] and str(splitted['year']) + 'y' or '') |
981
|
|
|
arr.append(splitted['month'] and str(splitted['month']) + 'm' or '') |
982
|
|
|
arr.append(splitted['day'] and str(splitted['day']) + 'd' or '') |
983
|
|
|
return ' '.join(arr) |
984
|
|
|
|
985
|
|
|
def getCountryState(self): |
986
|
|
|
return self.getField('CountryState').get(self) \ |
987
|
|
|
if self.getField('CountryState').get(self) \ |
988
|
|
|
else self.getPhysicalAddress() |
989
|
|
|
|
990
|
|
|
def getGuarantorID(self): |
991
|
|
|
""" |
992
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
993
|
|
|
the current patient fields. |
994
|
|
|
:return: The guarantor ID (insurance number) from |
995
|
|
|
""" |
996
|
|
|
return self.getInsuranceNumber() if self.getPatientAsGuarantor() else self.getField('GuarantorID').get(self) |
997
|
|
|
|
998
|
|
|
def getGuarantorSurname(self): |
999
|
|
|
""" |
1000
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
1001
|
|
|
the current patient fields. |
1002
|
|
|
""" |
1003
|
|
|
return self.getSurname() if self.getPatientAsGuarantor() else self.getField('GuarantorSurname').get(self) |
1004
|
|
|
|
1005
|
|
|
def getGuarantorFirstname(self): |
1006
|
|
|
""" |
1007
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
1008
|
|
|
the current patient fields. |
1009
|
|
|
""" |
1010
|
|
|
return self.getFirstname() if self.getPatientAsGuarantor() else self.getField('GuarantorFirstname').get(self) |
1011
|
|
|
|
1012
|
|
|
def getGuarantorPostalAddress(self): |
1013
|
|
|
""" |
1014
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
1015
|
|
|
the current patient fields. |
1016
|
|
|
""" |
1017
|
|
|
return self.getPostalAddress() \ |
1018
|
|
|
if self.getPatientAsGuarantor() \ |
1019
|
|
|
else self.getField('GuarantorPostalAddress').get(self) |
1020
|
|
|
|
1021
|
|
|
def getGuarantorBusinessPhone(self): |
1022
|
|
|
""" |
1023
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
1024
|
|
|
the current patient fields. |
1025
|
|
|
""" |
1026
|
|
|
return self.getBusinessPhone() \ |
1027
|
|
|
if self.getPatientAsGuarantor() \ |
1028
|
|
|
else self.getField('GuarantorBusinessPhone').get(self) |
1029
|
|
|
|
1030
|
|
|
def getGuarantorHomePhone(self): |
1031
|
|
|
""" |
1032
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
1033
|
|
|
the current patient fields. |
1034
|
|
|
""" |
1035
|
|
|
return self.getHomePhone() if self.getPatientAsGuarantor() else self.getField('GuarantorHomePhone').get(self) |
1036
|
|
|
|
1037
|
|
|
def getGuarantorMobilePhone(self): |
1038
|
|
|
""" |
1039
|
|
|
If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as |
1040
|
|
|
the current patient fields. |
1041
|
|
|
""" |
1042
|
|
|
return self.getMobilePhone() \ |
1043
|
|
|
if self.getPatientAsGuarantor() \ |
1044
|
|
|
else self.getField('GuarantorMobilePhone').get(self) |
1045
|
|
|
|
1046
|
|
|
def getEthnicitiesVocabulary(self, instance=None): |
1047
|
|
|
""" |
1048
|
|
|
Obtain all the ethnicities registered in the system and returns them as a list |
1049
|
|
|
""" |
1050
|
|
|
bsc = getToolByName(self, 'bika_setup_catalog') |
1051
|
|
|
items = [(c.UID, c.Title) |
1052
|
|
|
for c in bsc(portal_type='Ethnicity', |
1053
|
|
|
is_active=True)] |
1054
|
|
|
items.sort(lambda x, y: cmp(x[1], y[1])) |
|
|
|
|
1055
|
|
|
items.insert(0, ('', t(_('')))) |
1056
|
|
|
return DisplayList(items) |
|
|
|
|
1057
|
|
|
|
1058
|
|
|
# TODO This function will will be removed on v319 |
1059
|
|
|
def getEthnicity(self): |
1060
|
|
|
""" |
1061
|
|
|
This function exists because we are changing the construction of ethnicities. Until now, ethnicities options were |
1062
|
|
|
hand-coded but now they are a new content type. So we need to pass all patient's ethnicity values, but to do |
1063
|
|
|
such thing, we need to create new ethnicity types on upgrade step and edit patient ethnicity field to relate them |
1064
|
|
|
with its corresponding ethnicity content type. |
1065
|
|
|
:return: |
1066
|
|
|
""" |
1067
|
|
|
return self.getEthnicity_Obj() |
1068
|
|
|
|
1069
|
|
|
# TODO This function will be removed on v319 |
1070
|
|
|
def setEthnicity(self, value): |
1071
|
|
|
self.setEthnicity_Obj(value) |
1072
|
|
|
|
1073
|
|
|
def getDocuments(self): |
1074
|
|
|
""" |
1075
|
|
|
Return all the multifile objects related with the patient |
1076
|
|
|
""" |
1077
|
|
|
return self.objectValues('Multifile') |
1078
|
|
|
|
1079
|
|
|
def getPrimaryReferrer(self): |
1080
|
|
|
"""Returns the client the current Patient is assigned to. Delegates the |
1081
|
|
|
action to function getClient. |
1082
|
|
|
NOTE: This is kept for backwards compatibility |
1083
|
|
|
""" |
1084
|
|
|
logger.warn("Patient.getPrimaryReferrer: better use 'getClient'") |
1085
|
|
|
return self.getClient() |
1086
|
|
|
|
1087
|
|
|
def getClient(self): |
1088
|
|
|
"""Returns the client the current Patient is assigned to, if any |
1089
|
|
|
""" |
1090
|
|
|
# The schema's field PrimaryReferrer is only used to allow the user to |
1091
|
|
|
# assign the patient to a client in edit form. The entered value is used |
1092
|
|
|
# in ObjectModifiedEventHandler to move the patient to the Client's |
1093
|
|
|
# folder, so the value stored in the Schema's is not used anymore |
1094
|
|
|
# See https://github.com/senaite/senaite.core/pull/152 |
1095
|
|
|
client = self.aq_parent |
1096
|
|
|
if IClient.providedBy(client): |
1097
|
|
|
return client |
1098
|
|
|
return None |
1099
|
|
|
|
1100
|
|
|
def setClient(self, value): |
1101
|
|
|
"""Sets the client the current Patient has to be assigned to |
1102
|
|
|
""" |
1103
|
|
|
self.setPrimaryReferrer(value) |
1104
|
|
|
|
1105
|
|
|
def getClientID(self): |
1106
|
|
|
"""Returns the ID of the client this Patient belongs to or None |
1107
|
|
|
""" |
1108
|
|
|
client = self.getClient() |
1109
|
|
|
return client and api.get_id(client) or None |
1110
|
|
|
|
1111
|
|
|
def getClientUID(self): |
1112
|
|
|
"""Returns the UID of the client this Patient belongs to or None |
1113
|
|
|
""" |
1114
|
|
|
client = self.getClient() |
1115
|
|
|
return client and api.get_uid(client) or None |
1116
|
|
|
|
1117
|
|
|
def getClientURL(self): |
1118
|
|
|
"""Returns the URL of the client this Patient belongs to or None |
1119
|
|
|
""" |
1120
|
|
|
client = self.getClient() |
1121
|
|
|
return client and api.get_url(client) or None |
1122
|
|
|
|
1123
|
|
|
def getClientTitle(self): |
1124
|
|
|
"""Returns the title of the client this Patient belongs to or None |
1125
|
|
|
""" |
1126
|
|
|
client = self.getClient() |
1127
|
|
|
return client and api.get_title(client) or None |
1128
|
|
|
|
1129
|
|
|
def getBatches(self, full_objects=False): |
1130
|
|
|
"""Returns the Batches (Clinic Cases) this Patient is assigned to |
1131
|
|
|
""" |
1132
|
|
|
query = dict(portal_type="Batch", getPatientUID=api.get_uid(self)) |
1133
|
|
|
batches = api.search(query, BIKA_CATALOG) |
1134
|
|
|
if full_objects: |
1135
|
|
|
return map(api.get_object, batches) |
1136
|
|
|
return batches |
1137
|
|
|
|
1138
|
|
|
def SearchableText(self): |
1139
|
|
|
""" |
1140
|
|
|
Override searchable text logic based on the requirements. |
1141
|
|
|
|
1142
|
|
|
This method constructs a text blob which contains all full-text |
1143
|
|
|
searchable text for this content item. |
1144
|
|
|
https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching |
1145
|
|
|
""" |
1146
|
|
|
|
1147
|
|
|
# Speed up string concatenation ops by using a buffer |
1148
|
|
|
entries = [] |
1149
|
|
|
|
1150
|
|
|
# plain text fields we index from ourself, |
1151
|
|
|
# a list of accessor methods of the class |
1152
|
|
|
plain_text_fields = ("Title", "getFullname", "getId", |
1153
|
|
|
"getPrimaryReferrerID", "getPrimaryReferrerTitle", "getClientPatientID") |
1154
|
|
|
|
1155
|
|
|
def read(accessor): |
1156
|
|
|
""" |
1157
|
|
|
Call a class accessor method to give a value for certain Archetypes |
1158
|
|
|
field. |
1159
|
|
|
""" |
1160
|
|
|
try: |
1161
|
|
|
value = accessor() |
1162
|
|
|
except: |
1163
|
|
|
value = "" |
1164
|
|
|
|
1165
|
|
|
if value is None: |
1166
|
|
|
value = "" |
1167
|
|
|
|
1168
|
|
|
return value |
1169
|
|
|
|
1170
|
|
|
# Concatenate plain text fields as they are |
1171
|
|
|
for f in plain_text_fields: |
1172
|
|
|
accessor = getattr(self, f) |
1173
|
|
|
value = read(accessor) |
1174
|
|
|
entries.append(value) |
1175
|
|
|
|
1176
|
|
|
# Adding HTML Fields to SearchableText can be uncommented if necessary |
1177
|
|
|
# transforms = getToolByName(self, 'portal_transforms') |
1178
|
|
|
# |
1179
|
|
|
# # Run HTML valued fields through text/plain conversion |
1180
|
|
|
# for f in html_fields: |
1181
|
|
|
# accessor = getattr(self, f) |
1182
|
|
|
# value = read(accessor) |
1183
|
|
|
# |
1184
|
|
|
# if value != "": |
1185
|
|
|
# stream = transforms.convertTo('text/plain', value, mimetype='text/html') |
1186
|
|
|
# value = stream.getData() |
1187
|
|
|
# |
1188
|
|
|
# entries.append(value) |
1189
|
|
|
|
1190
|
|
|
# Plone accessor methods assume utf-8 |
1191
|
|
|
def convertToUTF8(text): |
1192
|
|
|
if type(text) == unicode: |
|
|
|
|
1193
|
|
|
return text.encode("utf-8") |
1194
|
|
|
return text |
1195
|
|
|
|
1196
|
|
|
entries = [convertToUTF8(entry) for entry in entries] |
1197
|
|
|
|
1198
|
|
|
# Concatenate all strings to one text blob |
1199
|
|
|
return " ".join(entries) |
1200
|
|
|
|
1201
|
|
|
|
1202
|
|
|
# schemata.finalizeATCTSchema(schema, folderish=True, moveDiscussion=False) |
1203
|
|
|
atapi.registerType(Patient, PROJECTNAME) |
1204
|
|
|
|