Passed
Pull Request — master (#152)
by Pau
04:10
created

Patient.getClientTitle()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nop 1
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((
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Schema does not seem to be defined.
Loading history...
56
    StringField(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable StringField does not seem to be defined.
Loading history...
57
        'PatientID',
58
        searchable=1,
59
        required=0,
60
        widget=StringWidget(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable StringWidget does not seem to be defined.
Loading history...
61
            visible={'view': 'visible', 'edit': 'hidden'},
62
            label=_('Patient ID'),
63
            css='readonly-emphasize',
64
        ),
65
    ),
66
    ReferenceField(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ReferenceField does not seem to be defined.
Loading history...
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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ComputedField does not seem to be defined.
Loading history...
89
        'PrimaryReferrerID',
90
        expression="context.getClientID()",
91
        widget=ComputedWidget(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ComputedWidget does not seem to be defined.
Loading history...
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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable SelectionWidget does not seem to be defined.
Loading history...
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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable BooleanField does not seem to be defined.
Loading history...
141
        'BirthDateEstimated',
142
        default=False,
143
        widget=BooleanWidget(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable BooleanWidget does not seem to be defined.
Loading history...
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(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ImageField does not seem to be defined.
Loading history...
594
        'Photo',
595
        schemata='Identification',
596
        widget=ImageWidget(
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable ImageWidget does not seem to be defined.
Loading history...
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
826
827
class Patient(Person):
828
    implements(IPatient)
829
    _at_rename_after_creation = True
830
    displayContentsTab = False
831
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
832
833
    def _renameAfterCreation(self, check_auto_id=False):
834
        """Autogenerate the ID of the object based on core's ID formatting
835
        settings for this type
836
        """
837
        idserver.renameAfterCreation(self)
838
839
    def Title(self):
840
        """Return the Fullname of the patient
841
        """
842
        return safe_unicode(self.getFullname()).encode('utf-8')
843
844
    def getPatientID(self):
845
        return self.getId()
846
847
    def getSamples(self, **kwargs):
848
        """Return samples taken from this Patient
849
        """
850
        catalog = api.get_tool(CATALOG_ANALYSIS_REQUEST_LISTING, context=self)
851
        query = dict([(k, v) for k, v in kwargs.items()
852
                      if k in catalog.indexes()])
853
        query["getPatientUID"] = api.get_uid(self)
854
        brains = api.search(query, CATALOG_ANALYSIS_REQUEST_LISTING)
855
        if not kwargs.get("full_objects", False):
856
            return brains
857
        return map(api.get_object, brains)
858
859
    def getSamplesCancelled(self, full_objects=False):
860
        """Return samples taken from this Patient that are in cancelled state
861
        """
862
        return self.getSamples(review_state="cancelled",
863
                               full_objects=full_objects)
864
865
    def getSamplesPublished(self, full_objects=False):
866
        """Return samples taken from this Patient that are in published state
867
        """
868
        return self.getSamples(review_state="published",
869
                               full_objects=full_objects)
870
871
    def getSamplesOngoing(self, full_objects=False):
872
        """Return samples taken from this Patient that are ongoing
873
        """
874
        ongoing_statuses = [
875
            "to_be_sampled",
876
            "scheduled_sampling",
877
            "to_be_sampled",
878
            "sample_due",
879
            "sample_received",
880
            "attachment_due",
881
            "to_be_verified",
882
            "verified",
883
            "to_be_preserved"]
884
        return self.getSamples(review_state=ongoing_statuses, is_active=True,
885
                               full_objects=full_objects)
886
887
    def getNumberOfSamplesOngoingRatio(self):
888
        """
889
        Returns the ratio between NumberOfSamplesOngoing/NumberOfSamples
890
        """
891
        samples = self.getSamples()
892
        if len(samples) > 0:
893
            return len(self.getSamplesOngoing())/len(samples)
894
        return 0
895
896
    def get_insurancecompanies(self):
897
        """
898
        Return all the registered insurance companies.
899
        """
900
        bsc = getToolByName(self, 'bika_setup_catalog')
901
        # Void selection
902
        ret = [('', '')]
903
        # Other selections
904
        for ic in bsc(portal_type='InsuranceCompany',
905
                      is_active=True,
906
                      sort_on='sortable_title'):
907
            ret.append((ic.UID, ic.Title))
908
        return DisplayList(ret)
909
910
    def getPatientIdentifiersList(self):
911
        """Returns a list with the additional identifiers for this patient
912
        """
913
        ids = self.getPatientIdentifiers()
914
        ids = map(lambda patient_id: patient_id.get("Identifier"), ids)
915
        return filter(None, ids)
916
917
    def getPatientIdentifiersStr(self):
918
        """Returns a string representation of the additional identifiers for
919
        this patient
920
        """
921
        ids = self.getPatientIdentifiersList()
922
        return " ".join(ids)
923
924
    def getAgeSplitted(self):
925
926
        if self.getBirthDate():
927
            dob = DT2dt(self.getBirthDate()).replace(tzinfo=None)
928
            now = datetime.today()
929
930
            currentday = now.day
931
            currentmonth = now.month
932
            currentyear = now.year
933
            birthday = dob.day
934
            birthmonth = dob.month
935
            birthyear = dob.year
936
            ageday = currentday - birthday
937
            agemonth = 0
938
            ageyear = 0
939
            months31days = [1, 3, 5, 7, 8, 10, 12]
940
941
            if ageday < 0:
942
                currentmonth -= 1
943
                if currentmonth < 1:
944
                    currentyear -= 1
945
                    currentmonth = currentmonth + 12
946
947
                dayspermonth = 30
948
                if currentmonth in months31days:
949
                    dayspermonth = 31
950
                elif currentmonth == 2:
951
                    dayspermonth = 28
952
                    if(currentyear % 4 == 0
953
                       and (currentyear % 100 > 0 or currentyear % 400 == 0)):
954
                        dayspermonth += 1
955
956
                ageday = ageday + dayspermonth
957
958
            agemonth = currentmonth - birthmonth
959
            if agemonth < 0:
960
                currentyear -= 1
961
                agemonth = agemonth + 12
962
963
            ageyear = currentyear - birthyear
964
965
            return [{'year': ageyear,
966
                     'month': agemonth,
967
                     'day': ageday}]
968
        else:
969
            return [{'year': '',
970
                     'month': '',
971
                     'day': ''}]
972
973
    def getAge(self):
974
        return self.getAgeSplitted()[0]['year']
975
976
    def getAgeSplittedStr(self):
977
        splitted = self.getAgeSplitted()[0]
978
        arr = []
979
        arr.append(splitted['year'] and str(splitted['year']) + 'y' or '')
980
        arr.append(splitted['month'] and str(splitted['month']) + 'm' or '')
981
        arr.append(splitted['day'] and str(splitted['day']) + 'd' or '')
982
        return ' '.join(arr)
983
984
    def getCountryState(self):
985
        return self.getField('CountryState').get(self) \
986
            if self.getField('CountryState').get(self) \
987
            else self.getPhysicalAddress()
988
989
    def getGuarantorID(self):
990
        """
991
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
992
        the current patient fields.
993
        :return: The guarantor ID (insurance number) from
994
        """
995
        return self.getInsuranceNumber() if self.getPatientAsGuarantor() else self.getField('GuarantorID').get(self)
996
997
    def getGuarantorSurname(self):
998
        """
999
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
1000
        the current patient fields.
1001
        """
1002
        return self.getSurname() if self.getPatientAsGuarantor() else self.getField('GuarantorSurname').get(self)
1003
1004
    def getGuarantorFirstname(self):
1005
        """
1006
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
1007
        the current patient fields.
1008
        """
1009
        return self.getFirstname() if self.getPatientAsGuarantor() else self.getField('GuarantorFirstname').get(self)
1010
1011
    def getGuarantorPostalAddress(self):
1012
        """
1013
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
1014
        the current patient fields.
1015
        """
1016
        return self.getPostalAddress() \
1017
            if self.getPatientAsGuarantor() \
1018
            else self.getField('GuarantorPostalAddress').get(self)
1019
1020
    def getGuarantorBusinessPhone(self):
1021
        """
1022
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
1023
        the current patient fields.
1024
        """
1025
        return self.getBusinessPhone() \
1026
            if self.getPatientAsGuarantor() \
1027
            else self.getField('GuarantorBusinessPhone').get(self)
1028
1029
    def getGuarantorHomePhone(self):
1030
        """
1031
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
1032
        the current patient fields.
1033
        """
1034
        return self.getHomePhone() if self.getPatientAsGuarantor() else self.getField('GuarantorHomePhone').get(self)
1035
1036
    def getGuarantorMobilePhone(self):
1037
        """
1038
        If the patient is the guarantor, all the fields related with the guarantor are going to have the same value as
1039
        the current patient fields.
1040
        """
1041
        return self.getMobilePhone() \
1042
            if self.getPatientAsGuarantor() \
1043
            else self.getField('GuarantorMobilePhone').get(self)
1044
1045
    def getEthnicitiesVocabulary(self, instance=None):
1046
        """
1047
        Obtain all the ethnicities registered in the system and returns them as a list
1048
        """
1049
        bsc = getToolByName(self, 'bika_setup_catalog')
1050
        items = [(c.UID, c.Title)
1051
                 for c in bsc(portal_type='Ethnicity',
1052
                              is_active=True)]
1053
        items.sort(lambda x, y: cmp(x[1], y[1]))
1054
        items.insert(0, ('', t(_(''))))
1055
        return DisplayList(items)
1056
1057
    # TODO This function will will be removed on v319
1058
    def getEthnicity(self):
1059
        """
1060
        This function exists because we are changing the construction of ethnicities. Until now, ethnicities options were
1061
        hand-coded but now they are a new content type. So we need to pass all patient's ethnicity values, but to do
1062
        such thing, we need to create new ethnicity types on upgrade step and edit patient ethnicity field to relate them
1063
        with its corresponding ethnicity content type.
1064
        :return:
1065
        """
1066
        return self.getEthnicity_Obj()
1067
1068
    # TODO This function will be removed on v319
1069
    def setEthnicity(self, value):
1070
        self.setEthnicity_Obj(value)
1071
1072
    def getDocuments(self):
1073
        """
1074
        Return all the multifile objects related with the patient
1075
        """
1076
        return self.objectValues('Multifile')
1077
1078
    def getPrimaryReferrer(self):
1079
        """Returns the client the current Patient is assigned to. Delegates the
1080
        action to function getClient.
1081
        NOTE: This is kept for backwards compatibility
1082
        """
1083
        logger.warn("Patient.getPrimaryReferrer: better use 'getClient'")
1084
        return self.getClient()
1085
1086
    def getClient(self):
1087
        """Returns the client the current Patient is assigned to, if any
1088
        """
1089
        # The schema's field PrimaryReferrer is only used to allow the user to
1090
        # assign the patient to a client in edit form. The entered value is used
1091
        # in ObjectModifiedEventHandler to move the patient to the Client's
1092
        # folder, so the value stored in the Schema's is not used anymore
1093
        # See https://github.com/senaite/senaite.core/pull/152
1094
        client = self.aq_parent
1095
        if IClient.providedBy(client):
1096
            return client
1097
        return None
1098
1099
    def setClient(self, value):
1100
        """Sets the client the current Patient has to be assigned to
1101
        """
1102
        self.setPrimaryReferrer(value)
1103
1104
    def getClientID(self):
1105
        """Returns the ID of the client this Patient belongs to or None
1106
        """
1107
        client = self.getClient()
1108
        return client and api.get_id(client) or None
1109
1110
    def getClientUID(self):
1111
        """Returns the UID of the client this Patient belongs to or None
1112
        """
1113
        client = self.getClient()
1114
        return client and api.get_uid(client) or None
1115
1116
    def getClientURL(self):
1117
        """Returns the URL of the client this Patient belongs to or None
1118
        """
1119
        client = self.getClient()
1120
        return client and api.get_url(client) or None
1121
1122
    def getClientTitle(self):
1123
        """Returns the title of the client this Patient belongs to or None
1124
        """
1125
        client = self.getClient()
1126
        return client and api.get_title(client) or None
1127
1128
    def getBatches(self, full_objects=False):
1129
        """Returns the Batches (Clinic Cases) this Patient is assigned to
1130
        """
1131
        query = dict(portal_type="Batch", getPatientUID=api.get_uid(self))
1132
        batches = api.search(query, BIKA_CATALOG)
1133
        if full_objects:
1134
            batches = map(api.get_object_by_uid, batches)
1135
        return batches
1136
1137
    def SearchableText(self):
1138
        """
1139
        Override searchable text logic based on the requirements.
1140
1141
        This method constructs a text blob which contains all full-text
1142
        searchable text for this content item.
1143
        https://docs.plone.org/develop/plone/searching_and_indexing/indexing.html#full-text-searching
1144
        """
1145
1146
        # Speed up string concatenation ops by using a buffer
1147
        entries = []
1148
1149
        # plain text fields we index from ourself,
1150
        # a list of accessor methods of the class
1151
        plain_text_fields = ("Title", "getFullname", "getId",
1152
                             "getPrimaryReferrerID", "getPrimaryReferrerTitle", "getClientPatientID")
1153
1154
        def read(accessor):
1155
            """
1156
            Call a class accessor method to give a value for certain Archetypes
1157
            field.
1158
            """
1159
            try:
1160
                value = accessor()
1161
            except:
1162
                value = ""
1163
1164
            if value is None:
1165
                value = ""
1166
1167
            return value
1168
1169
        # Concatenate plain text fields as they are
1170
        for f in plain_text_fields:
1171
            accessor = getattr(self, f)
1172
            value = read(accessor)
1173
            entries.append(value)
1174
1175
        # Adding HTML Fields to SearchableText can be uncommented if necessary
1176
        # transforms = getToolByName(self, 'portal_transforms')
1177
        #
1178
        # # Run HTML valued fields through text/plain conversion
1179
        # for f in html_fields:
1180
        #     accessor = getattr(self, f)
1181
        #     value = read(accessor)
1182
        #
1183
        #     if value != "":
1184
        #         stream = transforms.convertTo('text/plain', value, mimetype='text/html')
1185
        #         value = stream.getData()
1186
        #
1187
        #     entries.append(value)
1188
1189
        # Plone accessor methods assume utf-8
1190
        def convertToUTF8(text):
1191
            if type(text) == unicode:
1192
                return text.encode("utf-8")
1193
            return text
1194
1195
        entries = [convertToUTF8(entry) for entry in entries]
1196
1197
        # Concatenate all strings to one text blob
1198
        return " ".join(entries)
1199
1200
1201
# schemata.finalizeATCTSchema(schema, folderish=True, moveDiscussion=False)
1202
atapi.registerType(Patient, PROJECTNAME)
1203