Passed
Push — 2.x ( 3056d4...8bc3ab )
by Ramon
07:33
created

Client.getContactUIDForUser()   A

Complexity

Conditions 2

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 12
rs 9.95
c 0
b 0
f 0
cc 2
nop 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# 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-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import six
22
23
from AccessControl import ClassSecurityInfo
24
from AccessControl import Unauthorized
25
from Products.ATContentTypes.content import schemata
26
from Products.Archetypes.public import BooleanField
27
from Products.Archetypes.public import BooleanWidget
28
from Products.Archetypes.public import ReferenceField
29
from Products.Archetypes.public import Schema
30
from Products.Archetypes.public import SelectionWidget
31
from Products.Archetypes.public import StringField
32
from Products.Archetypes.public import StringWidget
33
from Products.Archetypes.public import registerType
34
from Products.CMFCore import permissions
35
from Products.CMFCore.PortalFolder import PortalFolderBase as PortalFolder
36
from Products.CMFCore.utils import _checkPermission
37
from zope.interface import implements
38
39
from bika.lims import _
40
from bika.lims import api
41
from bika.lims.browser.fields import EmailsField
42
from bika.lims.browser.widgets import ReferenceWidget
43
from bika.lims.catalog.bikasetup_catalog import SETUP_CATALOG
44
from bika.lims.config import DECIMAL_MARKS
45
from bika.lims.config import PROJECTNAME
46
from bika.lims.content.attachment import Attachment
47
from bika.lims.content.organisation import Organisation
48
from bika.lims.interfaces import IClient
49
from bika.lims.interfaces import IDeactivable
50
51
schema = Organisation.schema.copy() + Schema((
52
    StringField(
53
        "ClientID",
54
        required=1,
55
        searchable=True,
56
        validators=("uniquefieldvalidator", "standard_id_validator"),
57
        widget=StringWidget(
58
            label=_("Client ID"),
59
            description=_(
60
                "Short and unique identifier of this client. Besides fast "
61
                "searches by client in Samples listings, the purposes of this "
62
                "field depend on the laboratory needs. For instance, the "
63
                "Client ID can be included as part of the Sample identifier, "
64
                "so the lab can easily know the client a given sample belongs "
65
                "to by just looking to its ID.")
66
        ),
67
    ),
68
69
    BooleanField(
70
        "BulkDiscount",
71
        default=False,
72
        widget=BooleanWidget(
73
            label=_("Bulk discount applies"),
74
        ),
75
    ),
76
77
    BooleanField(
78
        "MemberDiscountApplies",
79
        default=False,
80
        widget=BooleanWidget(
81
            label=_("Member discount applies"),
82
        ),
83
    ),
84
85
    EmailsField(
86
        "CCEmails",
87
        schemata="Preferences",
88
        mode="rw",
89
        widget=StringWidget(
90
            label=_("CC Emails"),
91
            description=_(
92
                "Default Emails to CC all published Samples for this client"),
93
            visible={
94
                "edit": "visible",
95
                "view": "visible",
96
            },
97
        ),
98
    ),
99
100
    ReferenceField(
101
        "DefaultCategories",
102
        schemata="Preferences",
103
        required=0,
104
        multiValued=1,
105
        allowed_types=("AnalysisCategory",),
106
        relationship="ClientDefaultCategories",
107
        widget=ReferenceWidget(
108
            label=_("Default categories"),
109
            description=_(
110
                "Always expand the selected categories in client views"),
111
            showOn=True,
112
            catalog_name=SETUP_CATALOG,
113
            base_query=dict(
114
                is_active=True,
115
                sort_on="sortable_title",
116
                sort_order="ascending",
117
            ),
118
        ),
119
    ),
120
121
    ReferenceField(
122
        "RestrictedCategories",
123
        schemata="Preferences",
124
        required=0,
125
        multiValued=1,
126
        validators=("restrictedcategoriesvalidator",),
127
        allowed_types=("AnalysisCategory",),
128
        relationship="ClientRestrictedCategories",
129
        widget=ReferenceWidget(
130
            label=_("Restrict categories"),
131
            description=_("Show only selected categories in client views"),
132
            showOn=True,
133
            catalog_name=SETUP_CATALOG,
134
            base_query=dict(
135
                is_active=True,
136
                sort_on="sortable_title",
137
                sort_order="ascending",
138
            ),
139
        ),
140
    ),
141
142
    BooleanField(
143
        "DefaultDecimalMark",
144
        schemata="Preferences",
145
        default=True,
146
        widget=BooleanWidget(
147
            label=_("Default decimal mark"),
148
            description=_(
149
                "The decimal mark selected in Bika Setup will be used."),
150
        )
151
    ),
152
153
    StringField(
154
        "DecimalMark",
155
        schemata="Preferences",
156
        vocabulary=DECIMAL_MARKS,
157
        default=".",
158
        widget=SelectionWidget(
159
            label=_("Custom decimal mark"),
160
            description=_(
161
                "Decimal mark to use in the reports from this Client."),
162
            format="select",
163
        )
164
    ),
165
))
166
167
schema["title"].widget.visible = False
168
schema["description"].widget.visible = False
169
schema["EmailAddress"].schemata = "default"
170
171
schema.moveField("ClientID", after="Name")
172
173
174
class Client(Organisation):
175
    implements(IClient, IDeactivable)
176
177
    security = ClassSecurityInfo()
178
    displayContentsTab = False
179
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
180
    _at_rename_after_creation = True
181
182
    def _renameAfterCreation(self, check_auto_id=False):
183
        from bika.lims.idserver import renameAfterCreation
184
        renameAfterCreation(self)
185
186
    security.declarePublic("getContactFromUsername")
187
188
    def getContactFromUsername(self, username):
189
        for contact in self.objectValues("Contact"):
190
            if contact.getUsername() == username:
191
                return contact.UID()
192
193
    def getContacts(self, only_active=True):
194
        """Return an array containing the contacts from this Client
195
        """
196
        contacts = self.objectValues("Contact")
197
        if only_active:
198
            contacts = filter(api.is_active, contacts)
199
        return contacts
200
201
    def getDecimalMark(self):
202
        """Return the decimal mark to be used on reports for this client
203
204
        If the client has DefaultDecimalMark selected, the Default value from
205
        the LIMS Setup will be returned.
206
207
        Otherwise, will return the value of DecimalMark.
208
        """
209
        if self.getDefaultDecimalMark() is False:
210
            return self.Schema()["DecimalMark"].get(self)
211
        return self.bika_setup.getDecimalMark()
212
213
    def getCountry(self, default=None):
214
        """Return the Country from the Physical or Postal Address
215
        """
216
        physical_address = self.getPhysicalAddress().get("country", default)
217
        postal_address = self.getPostalAddress().get("country", default)
218
        return physical_address or postal_address
219
220
    def getProvince(self, default=None):
221
        """Return the Province from the Physical or Postal Address
222
        """
223
        physical_address = self.getPhysicalAddress().get("state", default)
224
        postal_address = self.getPostalAddress().get("state", default)
225
        return physical_address or postal_address
226
227
    def getDistrict(self, default=None):
228
        """Return the Province from the Physical or Postal Address
229
        """
230
        physical_address = self.getPhysicalAddress().get("district", default)
231
        postal_address = self.getPostalAddress().get("district", default)
232
        return physical_address or postal_address
233
234
    # TODO Security Make Attachments live inside ARs (instead of Client)
235
    # Since the Attachments live inside Client, we are forced here to overcome
236
    # the DeleteObjects permission when objects to delete are from Attachment
237
    # type. And we want to keep the DeleteObjects permission at Client level
238
    # because is the main container for Samples!
239
    # For some statuses of the AnalysisRequest type (e.g. received), the
240
    # permission "DeleteObjects" is granted, allowing the user to remove e.g.
241
    # analyses. Attachments are closely bound to Analysis and Samples, so they
242
    # should live inside Analysis Request.
243
    # Then, we will be able to remove this function from here
244
    def manage_delObjects(self, ids=None, REQUEST=None):
245
        """Overrides parent function. If the ids passed in are from Attachment
246
        types, the function ignores the DeleteObjects permission. For the rest
247
        of types, it works as usual (checks the permission)
248
        """
249
        if ids is None:
250
            ids = []
251
        if isinstance(ids, six.string_types):
252
            ids = [ids]
253
254
        for id in ids:
255
            item = self._getOb(id)
256
            if isinstance(item, Attachment):
257
                # Ignore DeleteObjects permission check
258
                continue
259
            if not _checkPermission(permissions.DeleteObjects, item):
260
                msg = "Do not have permissions to remove this object"
261
                raise Unauthorized(msg)
262
263
        return PortalFolder.manage_delObjects(self, ids, REQUEST=REQUEST)
264
265
266
schemata.finalizeATCTSchema(schema, folderish=True, moveDiscussion=False)
267
268
registerType(Client, PROJECTNAME)
269