def __call__(self): form = self.request.form plone.protect.CheckAuthenticator(form) workflow = getToolByName(self.context, 'portal_workflow') if self.destination_url == "": self.destination_url = self.request.get_header("referer", self.context.absolute_url()) skiplist = self.request.get('workflow_skiplist', []) action, came_from = self._get_form_workflow_action() # transition the context object. if came_from == "workflow_action": obj = self.context # the only actions allowed on inactive/cancelled # items are "reinstate" and "activate" if not isActive(obj) and action not in ('reinstate', 'activate'): message = self.context.translate(_('Item is inactive.')) self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.destination_url) return else: if obj.UID() not in skiplist: allowed_transitions = [] for t in workflow.getTransitionsFor(obj): allowed_transitions.append(t['id']) if action in allowed_transitions: workflow.doActionFor(obj, action) self.request.response.redirect(self.destination_url) return # transition selected items from the bika_listing/Table. transitioned = [] selected_items = self._get_selected_items() for uid, item in selected_items.items(): # the only action allowed on inactive items is "activate" if not isActive(item) and action not in ('reinstate', 'activate'): continue if uid not in skiplist: allowed_transitions = [] for t in workflow.getTransitionsFor(item): allowed_transitions.append(t['id']) if action in allowed_transitions: try: workflow.doActionFor(item, action) transitioned.append(item.Title()) except WorkflowException: pass if len(transitioned) > 0: message = self.context.translate(PMF('Changes saved.')) self.context.plone_utils.addPortalMessage(message, 'info') # automatic label printing if action == 'receive' and 'receive' in self.context.bika_setup.getAutoPrintLabels(): q = "/sticker?size=%s&items=" % (self.context.bika_setup.getAutoLabelSize()) # selected_items is a list of UIDs (stickers for AR_add use IDs) q += ",".join([i.getId() for i in selected_items.values()]) self.request.response.redirect(self.context.absolute_url() + q) else: self.request.response.redirect(self.destination_url)
def __call__(self): form = self.request.form plone.protect.CheckAuthenticator(form) workflow = getToolByName(self.context, 'portal_workflow') if self.destination_url == "": self.destination_url = self.request.get_header( "referer", self.context.absolute_url()) action, came_from = self._get_form_workflow_action() # transition the context object (plone edit bar dropdown) if came_from == "workflow_action": obj = self.context # the only actions allowed on inactive/cancelled # items are "reinstate" and "activate" if not isActive(obj) and action not in ('reinstate', 'activate'): message = self.context.translate(_('Item is inactive.')) self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.destination_url) return if not skip(obj, action, peek=True): allowed_transitions = [] for t in workflow.getTransitionsFor(obj): allowed_transitions.append(t['id']) if action in allowed_transitions: workflow.doActionFor(obj, action) self.request.response.redirect(self.destination_url) return # transition selected items from the bika_listing/Table. transitioned = [] selected_items = self._get_selected_items() for uid, item in selected_items.items(): # the only actions allowed on inactive/cancelled # items are "reinstate" and "activate" if not isActive(item) and action not in ('reinstate', 'activate'): continue if not skip(item, action, peek=True): allowed_transitions = [] for t in workflow.getTransitionsFor(item): allowed_transitions.append(t['id']) if action in allowed_transitions: doActionFor(item, action) transitioned.append(item.Title()) if len(transitioned) > 0: message = self.context.translate(PMF('Changes saved.')) self.context.plone_utils.addPortalMessage(message, 'info') # automatic label printing if action == 'receive' and 'receive' in self.portal.bika_setup.getAutoPrintLabels( ): q = "/sticker?size=%s&items=" % ( self.portal.bika_setup.getAutoLabelSize()) # selected_items is a list of UIDs (stickers for AR_add use IDs) q += ",".join([i.getId() for i in selected_items.values()]) self.request.response.redirect(self.context.absolute_url() + q) else: self.request.response.redirect(self.destination_url)
def getWSTemplates(self, contentFilter={}): istate = contentFilter.get("inactive_state", None) if istate == 'active': templates = [p for p in self.context.bika_setup.objectValues("WSTemplate") if isActive(p)] elif istate == 'inactive': templates = [p for p in self.context.objectValues("WorksheetTemplate") if not isActive(p)] else: templates = [p for p in self.context.objectValues("WorksheetTemplate")] return templates
def getARProfiles(self, contentFilter={}): istate = contentFilter.get("inactive_state", None) if istate == 'active': profiles = [p for p in self.context.objectValues("ARProfile") if isActive(p)] elif istate == 'inactive': profiles = [p for p in self.context.objectValues("ARProfile") if not isActive(p)] else: profiles = [p for p in self.context.objectValues("ARProfile")] return profiles
def getARProfiles(self, contentFilter={}): istate = contentFilter.get("inactive_state", None) if istate == 'active': profiles = [p for p in self.context.objectValues("ARProfile") if isActive(p)] elif istate == 'inactive': profiles = [p for p in self.context.objectValues("ARProfile") if not isActive(p)] else: profiles = [p for p in self.context.objectValues("ARProfile")] profiles.sort(lambda a,b:cmp(a.Title().lower(), b.Title().lower())) return profiles
def getARTemplates(self, contentFilter={}): istate = contentFilter.get("inactive_state", None) if istate == 'active': templates = [ p for p in self.context.objectValues("ARTemplate") if isActive(p) ] elif istate == 'inactive': templates = [ p for p in self.context.objectValues("ARTemplate") if not isActive(p) ] else: templates = [p for p in self.context.objectValues("ARTemplate")] return templates
def __call__(self): self.context_actions = {} wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage translate = self.context.translate # client contact required active_contacts = [c for c in self.context.objectValues('Contact') if wf.getInfoFor(c, 'inactive_state', '') == 'active'] if isActive(self.context): if self.context.portal_type == "Client" and not active_contacts: msg = _("Client contact required before request may be submitted") addPortalMessage(msg) else: if mtool.checkPermission(AddAnalysisRequest, self.context): self.context_actions[t(_('Add'))] = { 'url': self.context.absolute_url() + "/portal_factory/" "AnalysisRequest/Request new analyses/ar_add", 'icon': '++resource++bika.lims.images/add.png'} # in client context we can use a permission check for this transition # in multi-client listings, we must rather check against user roles. if mtool.checkPermission(ModifyPortalContent, self.context): review_states = [] for review_state in self.review_states: review_state['custom_actions'].extend( [{'id': 'copy_to_new', 'title': _('Copy to new'), 'url': 'workflow_action?action=copy_to_new'}, ]) review_states.append(review_state) self.review_states = review_states return super(ClientAnalysisRequestsView, self).__call__()
def submitTransition(self, action, came_from, items): """ Performs the action's transition for the specified items Returns (numtransitions, destination), where: - numtransitions: the number of objects successfully transitioned. If no objects have been successfully transitioned, gets 0 value - destination: the destination url to be loaded immediately """ dest = None transitioned = [] workflow = getToolByName(self.context, 'portal_workflow') # transition selected items from the bika_listing/Table. for item in items: # the only actions allowed on inactive/cancelled # items are "reinstate" and "activate" if not isActive(item) and action not in ('reinstate', 'activate'): continue if not skip(item, action, peek=True): allowed_transitions = [it['id'] for it in \ workflow.getTransitionsFor(item)] if action in allowed_transitions: success, message = doActionFor(item, action) if success: transitioned.append(item.id) else: self.context.plone_utils.addPortalMessage(message, 'error') # automatic label printing if transitioned and action == 'receive' \ and 'receive' in self.portal.bika_setup.getAutoPrintStickers(): q = "/sticker?template=%s&items=" % (self.portal.bika_setup.getAutoStickerTemplate()) # selected_items is a list of UIDs (stickers for AR_add use IDs) q += ",".join(transitioned) dest = self.context.absolute_url() + q return len(transitioned), dest
def __call__(self): self.context_actions = {} wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage PR = self.context.getPrimaryReferrer() if isActive(self.context): if mtool.checkPermission(AddAnalysisRequest, PR): #client contact required contacts = [ c for c in PR.objectValues('Contact') if wf.getInfoFor(c, 'inactive_state', '') == 'active' ] if contacts: self.context_actions[self.context.translate(_('Add'))] = { 'url': PR.absolute_url() + "/ar_add", 'icon': '++resource++bika.lims.images/add.png' } else: msg = _( "Client contact required before request may be submitted" ) addPortalMessage(self.context.translate(msg)) return super(AnalysisRequestsView, self).__call__()
def __call__(self): self.context_actions = {} wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage translate = self.context.translate # client contact required active_contacts = [ c for c in self.context.objectValues('Contact') if wf.getInfoFor(c, 'inactive_state', '') == 'active' ] if isActive(self.context): if self.context.portal_type == "Client" and not active_contacts: msg = _( "Client contact required before request may be submitted") addPortalMessage(msg) else: if mtool.checkPermission(AddAnalysisRequest, self.context): self.context_actions[t(_('Add'))] = { 'url': self.context.absolute_url() + "/portal_factory/" "AnalysisRequest/Request new analyses/ar_add", 'icon': '++resource++bika.lims.images/add.png' } return super(ClientAnalysisRequestsView, self).__call__()
def workflow_action_publish(self): action, came_from = WorkflowAction._get_form_workflow_action(self) if not isActive(self.context): message = _('Item is inactive.') self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.context.absolute_url()) return # AR publish preview self.request.response.redirect(self.context.absolute_url() + "/publish")
def __call__(self): form = self.request.form plone.protect.CheckAuthenticator(form) workflow = getToolByName(self.context, "portal_workflow") if self.destination_url == "": self.destination_url = self.request.get_header("referer", self.context.absolute_url()) skiplist = self.request.get("workflow_skiplist", []) action, came_from = self._get_form_workflow_action() # transition the context object. if came_from == "workflow_action": obj = self.context # the only actions allowed on inactive/cancelled # items are "reinstate" and "activate" if not isActive(obj) and action not in ("reinstate", "activate"): message = _("Item is inactive.") self.context.plone_utils.addPortalMessage(message, "info") self.request.response.redirect(self.destination_url) return else: if obj.UID() not in skiplist: workflow.doActionFor(obj, action) self.request.response.redirect(self.destination_url) return # transition selected items from the bika_listing/Table. transitioned = [] selected_items = self._get_selected_items() for uid, item in selected_items.items(): # the only action allowed on inactive items is "activate" if not isActive(item) and action not in ("reinstate", "activate"): continue if uid not in skiplist: try: workflow.doActionFor(item, action) transitioned.append(item.Title()) except WorkflowException: pass if len(transitioned) > 0: message = _("Changes saved.") self.context.plone_utils.addPortalMessage(message, "info") self.request.response.redirect(self.destination_url)
def __call__(self): mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if isActive(self.context): if checkPermission(AddAnalysisSpec, self.context): self.context_actions[_('Add')] = \ {'url': 'createObject?type_name=AnalysisSpec', 'permission': 'Add portal content', 'icon': '++resource++bika.lims.images/add.png'} return super(ClientAnalysisSpecsView, self).__call__()
def getContacts(self, dl=True): pairs = [] objects = [] for contact in self.aq_parent.objectValues('Contact'): if isActive(contact) and contact.UID() != self.UID(): pairs.append((contact.UID(), contact.Title())) if not dl: objects.append(contact) pairs.sort(lambda x, y: cmp(x[1].lower(), y[1].lower())) return dl and DisplayList(pairs) or objects
def getCCContacts(self): """Return a JSON value, containing all Contacts and their default CCs for this client. This function is used to set form values for javascript. """ contact_data = [] for contact in self.objectValues('Contact'): if isActive(contact): this_contact_data = {'title': contact.Title(), 'uid': contact.UID(), } ccs = [] for cc in contact.getCCContact(): if isActive(cc): ccs.append({'title': cc.Title(), 'uid': cc.UID(),}) this_contact_data['ccs_json'] = json.dumps(ccs) this_contact_data['ccs'] = ccs contact_data.append(this_contact_data) contact_data.sort(lambda x, y:cmp(x['title'].lower(), y['title'].lower())) return contact_data
def artemplates(self): """ Return applicable client and Lab ARTemplate records """ res = [] templates = [] client = self.context.portal_type == 'AnalysisRequest' \ and self.context.aq_parent or self.context for template in client.objectValues("ARTemplate"): if isActive(template): templates.append((template.Title(), template)) templates.sort(lambda x, y: cmp(x[0], y[0])) res += templates templates = [] for template in self.context.bika_setup.bika_artemplates.objectValues("ARTemplate"): if isActive(template): lab = t(_('Lab')) title = to_utf8(template.Title()) templates.append(("%s: %s" % (lab, title), template)) templates.sort(lambda x, y: cmp(x[0], y[0])) res += templates return res
def analysisprofiles(self): """ Return applicable client and Lab AnalysisProfile records """ res = [] profiles = [] client = self.context.portal_type == 'AnalysisRequest' \ and self.context.aq_parent or self.context for profile in client.objectValues("AnalysisProfile"): if isActive(profile): profiles.append((profile.Title(), profile)) profiles.sort(lambda x, y: cmp(x[0], y[0])) res += profiles profiles = [] for profile in self.context.bika_setup.bika_analysisprofiles.objectValues("AnalysisProfile"): if isActive(profile): lab = t(_('Lab')) title = to_utf8(profile.Title()) profiles.append(("%s: %s" % (lab, title), profile)) profiles.sort(lambda x, y: cmp(x[0], y[0])) res += profiles return res
def __call__(self): self.context_actions = {} bc = getToolByName(self.context, 'bika_catalog') wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage if isActive(self.context): if mtool.checkPermission(AddAnalysisRequest, self.portal): self.context_actions[self.context.translate(_('Add new'))] = { 'url':self.context.absolute_url() + '/ar_add?col_count=1', 'icon': '++resource++bika.lims.images/add.png'} return super(BatchAnalysisRequestsView, self).__call__()
def __call__(self): mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage w_tool = getToolByName(self.context, 'portal_workflow') active_contacts = [ c for c in self.context.objectValues('Contact') if w_tool.getInfoFor(c, 'inactive_state', '') == 'active' ] if isActive(self.context): if self.context.portal_type == "Client": if not active_contacts: msg = _( "Client contact required before request may be submitted" ) addPortalMessage(msg) else: if mtool.checkPermission(AddProject, self.context): self.context_actions[_('Add')] = { 'url': 'createObject?type_name=Project', 'icon': '++resource++bika.lims.images/add.png' } if mtool.checkPermission(ManageProjects, self.context): self.review_states[0]['transitions'].append({'id': 'deactivate'}) self.review_states.append({ 'id': 'inactive', 'title': _('Dormant'), 'contentFilter': { 'inactive_state': 'inactive' }, 'transitions': [ { 'id': 'activate' }, ], 'columns': ['Title', 'getClient', 'getStudyType'] }) self.review_states.append({ 'id': 'all', 'title': _('All'), 'contentFilter': {}, 'transitions': [{ 'id': 'empty' }], 'columns': ['Title', 'getClient', 'getStudyType'] }) stat = self.request.get("%s_review_state" % self.form_id, 'default') self.show_select_column = stat != 'all' return BikaListingView.__call__(self)
def folderitems(self, full_objects=False): items = BikaListingView.folderitems(self) bsc = getToolByName(self.context, 'bika_setup_catalog') brains = bsc(portal_type='SampleType', inactive_state='active') biospecimen_types = [{ 'ResultValue': brain.UID, 'ResultText': brain.title } for brain in brains] ret = [] for x, item in enumerate(items): if not items[x].has_key('obj'): continue obj = items[x]['obj'] if not ISample.providedBy(obj): continue items[x]['Type'] = obj.getSampleType() and obj.getSampleType( ).Title() or '' items[x]['Volume'] = obj.getField('Volume').get(obj) items[x]['Unit'] = VOLUME_UNITS[0]['ResultText'] items[x]['SubjectID'] = obj.getField('SubjectID').get(obj) kit = obj.getField('Kit').get(obj) project = obj.getField('Project').get(obj) items[x]['Kit'] = kit items[x]['Project'] = project if project: items[x]['replace']['Project'] = \ '<a href="%s">%s</a>' % (project.absolute_url(), project.Title()) if kit: items[x]['replace']['Kit'] = \ '<a href="%s">%s</a>' % (kit.absolute_url(), kit.Title()) # TODO: IF STATUS IS RECEIVED EXECUTE THIS # items[x]['replace']['Type'] = \ # '<a href="%s">%s</a>' % (obj.getSampleType().absolute_url(), # obj.getSampleType().Title()) items[x]['Barcode'] = obj.getField('Barcode').get(obj) items[x]['replace']['Title'] = "<a href='%s'>%s</a>" % \ (items[x]['url'], items[x]['Title']) # TODO: SPECIFY OBJ STATES WHERE USER CAN EDIT BARCODE if self.allow_edit and isActive(self.context) and \ getSecurityManager().checkPermission(ManageProjects, obj): if items[x]['review_state'] == "sample_registered": items[x]['allow_edit'] = ['Type', 'Barcode'] items[x]['choices']['Type'] = biospecimen_types elif items[x]['review_state'] == "sample_due": items[x]['allow_edit'] = ['SubjectID', 'Volume', 'Unit'] if not items[x]['Unit']: items[x]['choices']['Unit'] = VOLUME_UNITS ret.append(item) return ret
def __call__(self): self.context_actions = {} bc = getToolByName(self.context, 'bika_catalog') wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage if isActive(self.context): if mtool.checkPermission(AddAnalysisRequest, self.portal): self.context_actions[self.context.translate(_('Add new'))] = { 'url': self.context.absolute_url() + '/ar_add?col_count=1', 'icon': '++resource++bika.lims.images/add.png' } return super(BatchAnalysisRequestsView, self).__call__()
def __call__(self): self.context_actions = {} bc = getToolByName(self.context, "bika_catalog") wf = getToolByName(self.context, "portal_workflow") mtool = getToolByName(self.context, "portal_membership") addPortalMessage = self.context.plone_utils.addPortalMessage if isActive(self.context): if mtool.checkPermission(AddAnalysisRequest, self.portal): self.context_actions[self.context.translate(_("Add new"))] = { "url": self.context.absolute_url() + "/ar_add?col_count=1", "icon": "++resource++bika.lims.images/add.png", } return super(BatchAnalysisRequestsView, self).__call__()
def getContacts(self, dl=True): pc = getToolByName(self, 'portal_catalog') bc = getToolByName(self, 'bika_catalog') bsc = getToolByName(self, 'bika_setup_catalog') pairs = [] objects = [] for contact in self.objectValues('Contact'): if isActive(contact): pairs.append((contact.UID(), contact.Title())) if not dl: objects.append(contact) pairs.sort(lambda x, y:cmp(x[1].lower(), y[1].lower())) return dl and DisplayList(pairs) or objects
def workflow_action_publish(self): action, came_from = WorkflowAction._get_form_workflow_action(self) if not isActive(self.context): message = _('Item is inactive.') self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.context.absolute_url()) return # AR publish preview uids = self.request.form.get('uids', [self.context.UID()]) items = ",".join(uids) self.request.response.redirect(self.context.portal_url() + "/analysisrequests/publish?items=" + items)
def __call__(self): mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if isActive(self.context): if checkPermission(AddAnalysisSpec, self.context): self.context_actions[_('Add')] = \ {'url': 'createObject?type_name=AnalysisSpec', 'icon': '++resource++bika.lims.images/add.png'} if checkPermission("Modify portal content", self.context): self.context_actions[_('Set to lab defaults')] = \ {'url': 'set_to_lab_defaults', 'icon': '++resource++bika.lims.images/analysisspec.png'} return super(ClientAnalysisSpecsView, self).__call__()
def getCCs(self): items = [] for contact in self.getContacts(dl=False): item = {"uid": contact.UID(), "title": contact.Title()} ccs = [] if hasattr(contact, "getCCContact"): for cc in contact.getCCContact(): if isActive(cc): ccs.append({"title": cc.Title(), "uid": cc.UID()}) item["ccs_json"] = json.dumps(ccs) item["ccs"] = ccs items.append(item) items.sort(lambda x, y: cmp(x["title"].lower(), y["title"].lower())) return items
def __call__(self): mtool = getToolByName(self.context, "portal_membership") checkPermission = mtool.checkPermission if isActive(self.context): if checkPermission(AddAnalysisSpec, self.context): self.context_actions[_("Add")] = { "url": "createObject?type_name=AnalysisSpec", "icon": "++resource++bika.lims.images/add.png", } if checkPermission(ManageClients, self.context): self.context_actions[_("Set to lab defaults")] = { "url": "set_to_lab_defaults", "icon": "++resource++bika.lims.images/analysisspec.png", } return super(ClientAnalysisSpecsView, self).__call__()
def getCCContacts(self): """Return a JSON value, containing all Contacts and their default CCs for this client. This function is used to set form values for javascript. """ contact_data = [] for contact in self.objectValues('Contact'): if isActive(contact): this_contact_data = { 'title': contact.Title(), 'uid': contact.UID(), } ccs = [] for cc in contact.getCCContact(): if isActive(cc): ccs.append({ 'title': cc.Title(), 'uid': cc.UID(), }) this_contact_data['ccs_json'] = json.dumps(ccs) this_contact_data['ccs'] = ccs contact_data.append(this_contact_data) contact_data.sort( lambda x, y: cmp(x['title'].lower(), y['title'].lower())) return contact_data
def getCCs(self): items = [] for contact in self.getContacts(dl=False): item = {'uid': contact.UID(), 'title': contact.Title()} ccs = [] if hasattr(contact, 'getCCContact'): for cc in contact.getCCContact(): if isActive(cc): ccs.append({'title': cc.Title(), 'uid': cc.UID(),}) item['ccs_json'] = json.dumps(ccs) item['ccs'] = ccs items.append(item) items.sort(lambda x, y:cmp(x['title'].lower(), y['title'].lower())) return items
def __call__(self): mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if isActive(self.context): if checkPermission(AddAnalysisSpec, self.context): self.context_actions[_('Add')] = \ {'url': 'createObject?type_name=AnalysisSpec', 'icon': '++resource++bika.lims.images/add.png'} # # @lemoene with the changes made in AR-specs, I dont know how much # sense this makes anymore. # if checkPermission("Modify portal content", self.context): # self.context_actions[_('Set to lab defaults')] = \ # {'url': 'set_to_lab_defaults', # 'icon': '++resource++bika.lims.images/analysisspec.png'} return super(ClientAnalysisSpecsView, self).__call__()
def __init__(self, context, request): super(ClientAnalysisSpecsView, self).__init__(context, request) bsc = getToolByName(context, 'bika_setup_catalog') wf = getToolByName(context, 'portal_workflow') self.contentFilter = { 'portal_type': 'AnalysisSpec', 'getClientUID': context.UID(), 'path': { "query": "/".join(context.getPhysicalPath()), "level": 0 } } self.contentsMethod = bsc checkPermission = self.context.portal_membership.checkPermission self.context_actions = {} if isActive(self.context): if checkPermission(AddAnalysisSpec, self.context): self.context_actions[translate(_('Add'))] = \ {'url': 'createObject?type_name=AnalysisSpec', 'icon': '++resource++bika.lims.images/add.png'} if checkPermission(ManageClients, self.context): self.context_actions[translate(_('Set to lab defaults'))] = \ {'url': 'set_to_lab_defaults', 'icon': '++resource++bika.lims.images/analysisspec.png'} self.show_sort_column = False self.show_select_row = False self.show_select_column = True self.pagesize = 50 self.icon = "++resource++bika.lims.images/analysisspec_big.png" self.title = _("Analysis Specifications") self.columns = { 'SampleType': { 'title': _('Sample Type'), 'index': 'getSampleTypeTitle' }, } self.review_states = [ { 'id': 'all', 'title': _('All'), 'columns': ['SampleType'], }, ]
def __call__(self): plone.protect.CheckAuthenticator(self.request.form) rc = getToolByName(self.context, REFERENCE_CATALOG) uid = self.request.form.keys() and self.request.form.keys()[0] or None if not uid: return contact = rc.lookupObject(uid) cc_uids = [] cc_titles = [] for cc in contact.getCCContact(): active = isActive(contact) if not active: continue cc_uids.append(cc.UID()) cc_titles.append(cc.Title()) return json.dumps([",".join(cc_uids), ",".join(cc_titles)])
def __call__(self): self.context_actions = {} wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage # client contact required active_contacts = [c for c in self.context.objectValues('Contact') if wf.getInfoFor(c, 'inactive_state', '') == 'active'] if isActive(self.context): if self.context.portal_type == "Client" and not active_contacts: msg = _("Client contact required before request may be submitted") addPortalMessage(self.context.translate(msg)) else: if mtool.checkPermission(AddAnalysisRequest, self.context): self.context_actions[self.context.translate(_('Add'))] = { 'url':'ar_add', 'icon': '++resource++bika.lims.images/add.png'} return super(ClientAnalysisRequestsView, self).__call__()
def folderitems(self, full_objects=False): items = BikaListingView.folderitems(self) bsc = getToolByName(self.context, 'bika_setup_catalog') brains = bsc(portal_type='SampleType', inactive_state='active') biospecimen_types = [ { 'ResultValue': brain.UID, 'ResultText': brain.title } for brain in brains ] ret = [] for x, item in enumerate(items): if not items[x].has_key('obj'): continue obj = items[x]['obj'] if not IBiospecimen.providedBy(obj): continue items[x]['Type'] = obj.getSampleType() and obj.getSampleType().Title() or '' items[x]['Volume'] = obj.getField('Volume').get(obj) items[x]['Unit'] = VOLUME_UNITS[0]['ResultText'] items[x]['SubjectID'] = obj.getField('SubjectID').get(obj) kit = obj.getField('Kit').get(obj) items[x]['Kit'] = kit items[x]['Project'] = obj.aq_parent if kit: items[x]['replace']['Kit'] = \ '<a href="%s">%s</a>' % (kit.absolute_url(), kit.Title()) items[x]['replace']['Project'] = \ '<a href="%s">%s</a>' % (kit.aq_parent.absolute_url(), kit.aq_parent.Title()) items[x]['Barcode'] = obj.getField('Barcode').get(obj) items[x]['replace']['Title'] = "<a href='%s'>%s</a>" % \ (items[x]['url'], items[x]['Title']) # TODO: SPECIFY OBJ STATES WHERE USER CAN EDIT BARCODE if self.allow_edit and isActive(self.context) and \ getSecurityManager().checkPermission(ManageAliquots, obj) and \ items[x]['review_state'] == "sample_due": items[x]['allow_edit'] = ['Type', 'SubjectID', 'Barcode', 'Volume', 'Unit'] items[x]['choices']['Type'] = biospecimen_types items[x]['choices']['Unit'] = VOLUME_UNITS ret.append(item) return ret
def getCCs(self): """Return a JSON value, containing all Contacts and their default CCs. This function is used to set form values for javascript. """ items = [] for contact in self.getContacts(dl=False): item = {'uid': contact.UID(), 'title': contact.Title()} ccs = [] if hasattr(contact, 'getCCContact'): for cc in contact.getCCContact(): if isActive(cc): ccs.append({ 'title': cc.Title(), 'uid': cc.UID(), }) item['ccs_json'] = json.dumps(ccs) item['ccs'] = ccs items.append(item) items.sort(lambda x, y: cmp(x['title'].lower(), y['title'].lower())) return items
def __call__(self): self.context_actions = {} wf = getToolByName(self.context, "portal_workflow") mtool = getToolByName(self.context, "portal_membership") addPortalMessage = self.context.plone_utils.addPortalMessage # client contact required active_contacts = [ c for c in self.context.objectValues("Contact") if wf.getInfoFor(c, "inactive_state", "") == "active" ] if isActive(self.context): if self.context.portal_type == "Client" and not active_contacts: msg = _("Client contact required before request may be submitted") addPortalMessage(self.context.translate(msg)) else: if mtool.checkPermission(AddAnalysisRequest, self.context): self.context_actions[self.context.translate(_("Add"))] = { "url": "ar_add", "icon": "++resource++bika.lims.images/add.png", } return super(ClientAnalysisRequestsView, self).__call__()
def __call__(self): self.context_actions = {} wf = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage PR = self.context.getPrimaryReferrer() if isActive(self.context): if mtool.checkPermission(AddAnalysisRequest, PR): #client contact required contacts = [c for c in PR.objectValues('Contact') if wf.getInfoFor(c, 'inactive_state', '') == 'active'] if contacts: self.context_actions[self.context.translate(_('Add'))] = { 'url': PR.absolute_url() + "/portal_factory/" "AnalysisRequest/Request new analyses/ar_add", 'icon': '++resource++bika.lims.images/add.png'} else: msg = _("Client contact required before request may be submitted") addPortalMessage(self.context.translate(msg)) return super(AnalysisRequestsView, self).__call__()
def folderitems(self, full_objects=False): items = BikaListingView.folderitems(self) bsc = getToolByName(self.context, 'bika_setup_catalog') brains = bsc(portal_type='SampleType', inactive_state='active') aliquot_types = [ { 'ResultValue': brain.UID, 'ResultText': brain.title } for brain in brains ] ret = [] for x, item in enumerate(items): if not items[x].has_key('obj'): continue obj = items[x]['obj'] if not IAliquot.providedBy(obj): continue items[x]['replace']['Biospecimen'] = \ "<a href='%s'>%s</a>" % (obj.getField('LinkedSample').get(obj).absolute_url(), obj.getField('LinkedSample').get(obj).Title()) items[x]['AliquotType'] = obj.getSampleType() and obj.getSampleType().Title() or '' items[x]['Volume'] = items[x]['Volume'] = obj.getField('Volume').get(obj) items[x]['Unit'] = VOLUME_UNITS[0]['ResultText'] items[x]['replace']['Title'] = "<a href='%s'>%s</a>" % \ (items[x]['url'], items[x]['Title']) if self.context.portal_type == 'Aliquots': items[x]['replace']['Project'] = \ '<a href="%s">%s</a>' % (obj.aq_parent.absolute_url(), obj.aq_parent.Title()) if self.allow_edit and isActive(self.context) and \ getSecurityManager().checkPermission("Modify portal content", obj) and \ items[x]['review_state'] == "sample_due": items[x]['allow_edit'] = ['AliquotType', 'Volume', 'Unit'] items[x]['choices']['AliquotType'] = aliquot_types items[x]['choices']['Unit'] = VOLUME_UNITS ret.append(item) return ret
def folderitems(self, full_objects=False): items = BikaListingView.folderitems(self) bsc = getToolByName(self.context, 'bika_setup_catalog') brains = bsc(portal_type='SampleType', inactive_state='active') aliquot_types = [{ 'ResultValue': brain.UID, 'ResultText': brain.title } for brain in brains] ret = [] for x, item in enumerate(items): if not items[x].has_key('obj'): continue obj = items[x]['obj'] if not IAliquot.providedBy(obj): continue items[x]['replace']['Biospecimen'] = \ "<a href='%s'>%s</a>" % (obj.getField('LinkedSample').get(obj).absolute_url(), obj.getField('LinkedSample').get(obj).Title()) items[x]['AliquotType'] = obj.getSampleType( ) and obj.getSampleType().Title() or '' items[x]['Volume'] = items[x]['Volume'] = obj.getField( 'Volume').get(obj) items[x]['Unit'] = VOLUME_UNITS[0]['ResultText'] items[x]['replace']['Title'] = "<a href='%s'>%s</a>" % \ (items[x]['url'], items[x]['Title']) if self.context.portal_type == 'Aliquots': items[x]['replace']['Project'] = \ '<a href="%s">%s</a>' % (obj.aq_parent.absolute_url(), obj.aq_parent.Title()) if self.allow_edit and isActive(self.context) and \ getSecurityManager().checkPermission("Modify portal content", obj) and \ items[x]['review_state'] == "sample_due": items[x]['allow_edit'] = ['AliquotType', 'Volume', 'Unit'] items[x]['choices']['AliquotType'] = aliquot_types items[x]['choices']['Unit'] = VOLUME_UNITS ret.append(item) return ret
def __call__(self): mtool = getToolByName(self.context, 'portal_membership') addPortalMessage = self.context.plone_utils.addPortalMessage w_tool = getToolByName(self.context, 'portal_workflow') active_contacts = [c for c in self.context.objectValues('Contact') if w_tool.getInfoFor(c, 'inactive_state', '') == 'active'] if isActive(self.context): if self.context.portal_type == "Client": if not active_contacts: msg = _("Client contact required before request may be submitted") addPortalMessage(msg) else: if mtool.checkPermission(AddProject, self.context): self.context_actions[_('Add')] = { 'url': 'createObject?type_name=Project', 'icon': '++resource++bika.lims.images/add.png' } if mtool.checkPermission(ManageProjects, self.context): self.review_states[0]['transitions'].append({'id': 'deactivate'}) self.review_states.append( {'id': 'inactive', 'title': _('Dormant'), 'contentFilter': {'inactive_state': 'inactive'}, 'transitions': [{'id': 'activate'}, ], 'columns': ['Title', 'getClient', 'getStudyType']}) self.review_states.append( {'id': 'all', 'title': _('All'), 'contentFilter': {}, 'transitions': [{'id': 'empty'}], 'columns': ['Title', 'getClient', 'getStudyType']}) stat = self.request.get("%s_review_state" % self.form_id, 'default') self.show_select_column = stat != 'all' return super(ProjectsView, self).__call__()
def workflow_action_publish(self): action, came_from = WorkflowAction._get_form_workflow_action(self) if not isActive(self.context): message = _('Item is inactive.') self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.context.absolute_url()) return # publish entire AR. self.context.setDatePublished(DateTime()) transitioned = self.doPublish(self.context, self.request, action, [self.context, ])() if len(transitioned) == 1: message = _( '${items} published.', mapping={'items': safe_unicode(', '.join(transitioned))}) else: message = _("No items were published") self.context.plone_utils.addPortalMessage(message, 'info') self.destination_url = self.request.get_header("referer", self.context.absolute_url()) self.request.response.redirect(self.destination_url)
def workflow_action_submit(self): form = self.request.form rc = getToolByName(self.context, REFERENCE_CATALOG) action, came_from = WorkflowAction._get_form_workflow_action(self) checkPermission = self.context.portal_membership.checkPermission if not isActive(self.context): message = _('Item is inactive.') self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.context.absolute_url()) return # calcs.js has kept item_data and form input interim values synced, # so the json strings from item_data will be the same as the form values item_data = {} if 'item_data' in form: if isinstance(form['item_data'], list): for i_d in form['item_data']: for i, d in json.loads(i_d).items(): item_data[i] = d else: item_data = json.loads(form['item_data']) selected_analyses = WorkflowAction._get_selected_items(self) results = {} hasInterims = {} # check that the form values match the database # save them if not. for uid, result in self.request.form.get('Result', [{}])[0].items(): # Do not save data for analyses that are not selected. if uid not in selected_analyses: continue analysis = selected_analyses[uid] # never save any part of rows with empty result values. # https://jira.bikalabs.com/browse/LIMS-1944: if not result: continue # ignore result if analysis object no longer exists if not analysis: continue # Prevent saving data if the analysis is already transitioned if not (checkPermission(EditResults, analysis) or checkPermission(EditFieldResults, analysis)): title = safe_unicode(analysis.getService().Title()) msgid = _('Result for ${analysis} could not be saved because ' 'it was already submitted by another user.', mapping={'analysis': title}) message = safe_unicode(t(msgid)) self.context.plone_utils.addPortalMessage(message) continue # if the AR has ReportDryMatter set, get dry_result from form. dry_result = '' if hasattr(self.context, 'getReportDryMatter') \ and self.context.getReportDryMatter(): for k, v in self.request.form['ResultDM'][0].items(): if uid == k: dry_result = v break results[uid] = result interimFields = item_data[uid] if len(interimFields) > 0: hasInterims[uid] = True else: hasInterims[uid] = False retested = 'retested' in form and uid in form['retested'] remarks = form.get('Remarks', [{}, ])[0].get(uid, '') # Don't save uneccessary things # https://github.com/bikalabs/Bika-LIMS/issues/766: # Somehow, using analysis.edit() fails silently when # logged in as Analyst. if analysis.getInterimFields() != interimFields or \ analysis.getRetested() != retested or \ analysis.getRemarks() != remarks: analysis.setInterimFields(interimFields) analysis.setRetested(retested) analysis.setRemarks(remarks) # save results separately, otherwise capture date is rewritten if analysis.getResult() != result or \ analysis.getResultDM() != dry_result: analysis.setResultDM(dry_result) analysis.setResult(result) methods = self.request.form.get('Method', [{}])[0] instruments = self.request.form.get('Instrument', [{}])[0] analysts = self.request.form.get('Analyst', [{}])[0] uncertainties = self.request.form.get('Uncertainty', [{}])[0] dlimits = self.request.form.get('DetectionLimit', [{}])[0] # discover which items may be submitted submissable = [] for uid, analysis in selected_analyses.items(): analysis_active = isActive(analysis) # Need to save the instrument? if uid in instruments and analysis_active: # TODO: Add SetAnalysisInstrument permission # allow_setinstrument = sm.checkPermission(SetAnalysisInstrument) allow_setinstrument = True # ---8<----- if allow_setinstrument == True: # The current analysis allows the instrument regards # to its analysis service and method? if (instruments[uid]==''): previnstr = analysis.getInstrument() if previnstr: previnstr.removeAnalysis(analysis) analysis.setInstrument(None); elif analysis.isInstrumentAllowed(instruments[uid]): previnstr = analysis.getInstrument() if previnstr: previnstr.removeAnalysis(analysis) analysis.setInstrument(instruments[uid]) instrument = analysis.getInstrument() instrument.addAnalysis(analysis) # Need to save the method? if uid in methods and analysis_active: # TODO: Add SetAnalysisMethod permission # allow_setmethod = sm.checkPermission(SetAnalysisMethod) allow_setmethod = True # ---8<----- if allow_setmethod == True and analysis.isMethodAllowed(methods[uid]): analysis.setMethod(methods[uid]) # Need to save the analyst? if uid in analysts and analysis_active: analysis.setAnalyst(analysts[uid]) # Need to save the uncertainty? if uid in uncertainties and analysis_active: analysis.setUncertainty(uncertainties[uid]) # Need to save the detection limit? if analysis_active: analysis.setDetectionLimitOperand(dlimits.get(uid, None)) if uid not in results or not results[uid]: continue can_submit = True # guard_submit does a lot of the same stuff, too. # the code there has also been commented. # we must find a better way to allow dependencies to control # this process. # for dependency in analysis.getDependencies(): # dep_state = workflow.getInfoFor(dependency, 'review_state') # if hasInterims[uid]: # if dep_state in ('to_be_sampled', 'to_be_preserved', # 'sample_due', 'sample_received', # 'attachment_due', 'to_be_verified',): # can_submit = False # break # else: # if dep_state in ('to_be_sampled', 'to_be_preserved', # 'sample_due', 'sample_received',): # can_submit = False # break if can_submit and analysis not in submissable: submissable.append(analysis) # and then submit them. for analysis in submissable: doActionFor(analysis, 'submit') # LIMS-2366: Finally, when we are done processing all applicable # analyses, we must attempt to initiate the submit transition on the # AR itself. This is for the case where "general retraction" has been # done, or where the last "received" analysis has been removed, and # the AR is in state "received" while there are no "received" analyses # left to trigger the parent transition. if self.context.portal_type == 'Sample': ar = self.context.getAnalysisRequests()[0] elif self.context.portal_type == 'Analysis': ar = self.context.aq_parent else: ar = self.context doActionFor(ar, 'submit') message = PMF("Changes saved.") self.context.plone_utils.addPortalMessage(message, 'info') if checkPermission(EditResults, self.context): self.destination_url = self.context.absolute_url() + "/manage_results" else: self.destination_url = self.context.absolute_url() self.request.response.redirect(self.destination_url)
def folderitems(self): bsc = getToolByName(self.context, 'bika_setup_catalog') analysis_categories = bsc(portal_type="AnalysisCategory", sort_on="sortable_title") analysis_categories_order = dict([ (b.Title, "{:04}".format(a)) for a, b in enumerate(analysis_categories) ]) workflow = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) self.allow_edit = can_edit_analyses self.show_select_column = self.allow_edit context_active = isActive(self.context) self.categories = [] items = super(AnalysesView, self).folderitems(full_objects=True) member = mtool.getAuthenticatedMember() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False dmk = self.context.bika_setup.getResultsDecimalMark() for item in items: if 'obj' not in item: logger.warn( "Missing 'obj' key in Analysis item '{}'".format(item)) continue # self.contentsMethod may return brains or objects. obj = api.get_object(item["obj"]) if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): logger.info("Skipping retracted analysis {}".format( obj.getId())) continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() cat_order = analysis_categories_order.get(cat) item['category'] = cat if (cat, cat_order) not in self.categories: self.categories.append((cat, cat_order)) # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] # kick some pretty display values in. for x in range(len(interim_fields)): interim_fields[x]['formatted_value'] = \ formatDecimalMark(interim_fields[x]['value'], dmk) self.interim_fields[obj.UID()] = interim_fields item['service_uid'] = service.UID() item['Service'] = service.Title() item['Keyword'] = keyword item['Unit'] = format_supsub(unit) if unit else '' item['Result'] = '' item['formatted_result'] = '' item['interim_fields'] = interim_fields item['Remarks'] = obj.getRemarks() item['Uncertainty'] = '' item['DetectionLimit'] = '' item['retested'] = obj.getRetested() item['class']['retested'] = 'center' item['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) item['calculation'] = calculation and True or False try: item['Partition'] = obj.getSamplePartition().getId() except AttributeError: item['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": item['DueDate'] = self.ulocalized_time( obj.aq_parent.getExpiryDate(), long_format=0) else: item['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() item['CaptureDate'] = cd and self.ulocalized_time( cd, long_format=1) or '' item['Attachments'] = '' item['allow_edit'] = [] tblrowclass = item.get('table_row_class') if obj.portal_type == 'ReferenceAnalysis': item['st_uid'] = obj.aq_parent.UID() item['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': item['st_uid'] = obj.aq_parent.UID() item['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) else: sample = None if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() elif self.context.portal_type == 'Worksheet': if obj.portal_type in ('DuplicateAnalysis', 'RejectAnalysis'): sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() elif self.context.portal_type == 'Sample': sample = self.context st_uid = sample.getSampleType().UID() if sample else '' item['st_uid'] = st_uid if checkPermission(ManageBika, self.context): # service_uid = service.UID() # latest = rc.lookupObject(service_uid).version_id item['Service'] = service.Title() item['class']['Service'] = "service_title" # Show version number of out-of-date objects # No: This should be done in another column, if at all. # The (vX) value confuses some more fragile forms. # if hasattr(obj, 'reference_versions') and \ # service_uid in obj.reference_versions and \ # latest != obj.reference_versions[service_uid]: # items[i]['after']['Service'] = "(v%s)" % \ # (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: item['choices']['Result'] = choices # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ((poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj))) allowed_method_states = [ 'to_be_sampled', 'to_be_preserved', 'sample_received', 'sample_registered', 'sampled', 'assigned', ] # Prevent from being edited if the instrument assigned # is not valid (out-of-date or uncalibrated), except if # the analysis is a QC with assigned status can_edit_analysis = can_edit_analysis \ and (obj.isInstrumentValid() or (obj.portal_type == 'ReferenceAnalysis' and item['review_state'] in allowed_method_states)) if can_edit_analysis: item['allow_edit'].extend(['Analyst', 'Result', 'Remarks']) # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: item['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not item['calculation'] or \ (item['calculation'] and self.interim_fields[obj.UID()]): item['allow_edit'].append('retested') # TODO: Only the labmanager must be able to change the method # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj) can_set_method = can_edit_analysis \ and item['review_state'] in allowed_method_states method = obj.getMethod() \ if hasattr(obj, 'getMethod') and obj.getMethod() else service.getMethod() # Display the methods selector if the AS has at least one # method assigned item['Method'] = '' item['replace']['Method'] = '' if can_set_method: voc = self.get_methods_vocabulary(obj) if voc: # The service has at least one method available item['Method'] = method.UID() if method else '' item['choices']['Method'] = voc item['allow_edit'].append('Method') show_methodinstr_columns = True elif method: # This should never happen # The analysis has set a method, but its parent # service hasn't any method available O_o item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True elif method: # Edition not allowed, but method set item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True # TODO: Instrument selector dynamic behavior in worksheet Results # Only the labmanager must be able to change the instrument to be used. Also, # the instrument selection should be done in accordance with the method selected # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj) can_set_instrument = service.getInstrumentEntryOfResults() \ and can_edit_analysis \ and item['review_state'] in allowed_method_states item['Instrument'] = '' item['replace']['Instrument'] = '' if service.getInstrumentEntryOfResults(): instrument = None # If the analysis has an instrument already assigned, use it if service.getInstrumentEntryOfResults() \ and hasattr(obj, 'getInstrument') \ and obj.getInstrument(): instrument = obj.getInstrument() # Otherwise, use the Service's default instrument elif service.getInstrumentEntryOfResults(): instrument = service.getInstrument() if can_set_instrument: # Edition allowed voc = self.get_instruments_vocabulary(obj) if voc: # The service has at least one instrument available item['Instrument'] = instrument.UID( ) if instrument else '' item['choices']['Instrument'] = voc item['allow_edit'].append('Instrument') show_methodinstr_columns = True elif instrument: # This should never happen # The analysis has an instrument set, but the # service hasn't any available instrument item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True elif instrument: # Edition not allowed, but instrument set item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True else: # Manual entry of results, instrument not allowed item['Instrument'] = _('Manual') msgtitle = t( _( "Instrument entry of results not allowed for ${service}", mapping={"service": safe_unicode(service.Title())}, )) item['replace']['Instrument'] = \ '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual'))) # Sets the analyst assigned to this analysis if can_edit_analysis: analyst = obj.getAnalyst() # widget default: current user if not analyst: analyst = mtool.getAuthenticatedMember().getUserName() item['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: item['Analyst'] = obj.getAnalystName() # If the user can attach files to analyses, show the attachment col can_add_attachment = \ getSecurityManager().checkPermission(AddAttachment, obj) if can_add_attachment or can_view_result: attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.icon attachments += "<span class='attachment' attachment_uid='%s'>" % ( attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % ( self.portal_url, icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % ( attachment.absolute_url(), af.filename) if can_edit_analysis: attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % ( attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" item['replace']['Attachments'] = attachments[:-12] + "</span>" # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: item['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() item['formatted_result'] = obj.getFormattedResult( sciformat=int(scinot), decimalmark=dmk) # LIMS-1379 Allow manual uncertainty value input # https://jira.bikalabs.com/browse/LIMS-1379 fu = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot)) fu = fu if fu else '' if can_edit_analysis and service.getAllowManualUncertainty( ) is True: unc = obj.getUncertainty(result) item['allow_edit'].append('Uncertainty') item['Uncertainty'] = unc if unc else '' item['before']['Uncertainty'] = '± ' item['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % item[ 'Unit'] item['structure'] = False elif fu: item['Uncertainty'] = fu item['before']['Uncertainty'] = '± ' item['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % item[ 'Unit'] item['structure'] = True # LIMS-1700. Allow manual input of Detection Limits # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode # https://jira.bikalabs.com/browse/LIMS-1700 # https://jira.bikalabs.com/browse/LIMS-1775 if can_edit_analysis and \ hasattr(obj, 'getDetectionLimitOperand') and \ hasattr(service, 'getDetectionLimitSelector') and \ service.getDetectionLimitSelector() is True: isldl = obj.isBelowLowerDetectionLimit() isudl = obj.isAboveUpperDetectionLimit() dlval = '' if isldl or isudl: dlval = '<' if isldl else '>' item['allow_edit'].append('DetectionLimit') item['DetectionLimit'] = dlval choices = [{ 'ResultValue': '<', 'ResultText': '<' }, { 'ResultValue': '>', 'ResultText': '>' }] item['choices']['DetectionLimit'] = choices self.columns['DetectionLimit']['toggle'] = True srv = obj.getService() defdls = { 'min': srv.getLowerDetectionLimit(), 'max': srv.getUpperDetectionLimit(), 'manual': srv.getAllowManualDetectionLimit() } defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>' defin = defin % (obj.UID(), json.dumps(defdls)) item['after']['DetectionLimit'] = defin # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 # Since LDL, UDL, etc. are wildcards that can be used # in calculations, these fields must be loaded always # for 'live' calculations. if can_edit_analysis: dls = { 'default_ldl': 'none', 'default_udl': 'none', 'below_ldl': False, 'above_udl': False, 'is_ldl': False, 'is_udl': False, 'manual_allowed': False, 'dlselect_allowed': False } if hasattr(obj, 'getDetectionLimits'): dls['below_ldl'] = obj.isBelowLowerDetectionLimit() dls['above_udl'] = obj.isBelowLowerDetectionLimit() dls['is_ldl'] = obj.isLowerDetectionLimit() dls['is_udl'] = obj.isUpperDetectionLimit() dls['default_ldl'] = service.getLowerDetectionLimit() dls['default_udl'] = service.getUpperDetectionLimit() dls['manual_allowed'] = service.getAllowManualDetectionLimit( ) dls['dlselect_allowed'] = service.getDetectionLimitSelector( ) dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>' dlsin = dlsin % (obj.UID(), json.dumps(dls)) item['after']['Result'] = dlsin else: item['Specification'] = "" if 'Result' in item['allow_edit']: item['allow_edit'].remove('Result') item['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (self.portal_url) # Everyone can see valid-ranges spec = self.get_analysis_spec(obj) if spec: min_val = spec.get('min', '') min_str = ">{0}".format(min_val) if min_val else '' max_val = spec.get('max', '') max_str = "<{0}".format(max_val) if max_val else '' error_val = spec.get('error', '') error_str = "{0}%".format(error_val) if error_val else '' rngstr = ",".join( [x for x in [min_str, max_str, error_str] if x]) else: rngstr = "" item['Specification'] = rngstr # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns and not f.get( 'hidden', False): self.interim_columns[f['keyword']] = f['title'] # and to the item itself item[f['keyword']] = f item['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue resultdate = obj.aq_parent.getDateSampled() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getResultCaptureDate() duedate = obj.aq_parent.getExpiryDate() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getDueDate() item['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if item['review_state'] not in [ 'to_be_sampled', 'to_be_preserved', 'sample_due', 'published' ]: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): item['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (self.ulocalized_time(duedate, long_format=1), self.portal_url, t(_("Late Analysis"))) after_icons = [] # Submitting user may not verify results unless the user is labman # or manager and the AS has isSelfVerificationEnabled set to True if item['review_state'] == 'to_be_verified': # If multi-verification required, place an informative icon numverifications = obj.getNumberOfRequiredVerifications() if numverifications > 1: # More than one verification required, place an icon # Get the number of verifications already done: done = obj.getNumberOfVerifications() pending = numverifications - done ratio = float(done) / float(numverifications) \ if done > 0 else 0 scale = '' if ratio < 0.25 else '25' \ if ratio < 0.50 else '50' \ if ratio < 0.75 else '75' anchor = "<a href='#' title='%s %s %s' " \ "class='multi-verification scale-%s'>%s/%s</a>" anchor = anchor % (t( _("Multi-verification required")), str(pending), t(_("verification(s) pending")), scale, str(done), str(numverifications)) after_icons.append(anchor) username = member.getUserName() allowed = ploneapi.user.has_permission(VerifyPermission, username=username) if allowed and not obj.isUserAllowedToVerify(member): after_icons.append( "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % (t( _("Cannot verify, submitted or verified by current user before" )))) elif allowed: if obj.getSubmittedBy() == member.getUser().getId(): after_icons.append( "<img src='++resource++bika.lims.images/warning.png' title='%s'/>" % (t(_("Can verify, but submitted by current user"))) ) # If analysis Submitted and Verified by the same person, then warning icon will appear. submitter = obj.getSubmittedBy() if submitter and obj.wasVerifiedByUser(submitter): after_icons.append( "<img src='++resource++bika.lims.images/warning.png' title='%s'/>" % (t( _("Submited and verified by the same user- " + submitter)))) # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = item['obj'] if obj.portal_type in ['ReferenceAnalysis', 'DuplicateAnalysis'] or \ workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned': br = obj.getBackReferences('WorksheetAnalysis') if len(br) > 0: ws = br[0] after_icons.append( "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % (ws.absolute_url(), t( _("Assigned to: ${worksheet_id}", mapping={ 'worksheet_id': safe_unicode(ws.id) })))) item['after']['state_title'] = ' '.join(after_icons) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = { 'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False } if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. # In case of hidden fields, the calcs.py should check calcs/services # for additional InterimFields!! pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is never enabled for reference sample contexts # and refers to getReportDryMatter in ARs. if items and \ (hasattr(self.context, 'getReportDryMatter') and self.context.getReportDryMatter()): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states if self.show_categories: self.categories = map(lambda x: x[0], sorted(self.categories, key=lambda x: x[1])) else: self.categories.sort() # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) self.items = items # Method and Instrument columns must be shown or hidden at the # same time, because the value assigned to one causes # a value reassignment to the other (one method can be performed # by different instruments) self.columns['Method']['toggle'] = show_methodinstr_columns self.columns['Instrument']['toggle'] = show_methodinstr_columns return items
def folderitems(self, full_objects=False): # Show only ISharable samples for EMS. Skip others. pm = getToolByName(self.context, 'portal_membership') roles = pm.getAuthenticatedMember().getRoles() #print roles if 'EMS' in roles: self.contentFilter['object_provides'] = ISharableSample.__identifier__ items = BikaListingView.folderitems(self) bsc = getToolByName(self.context, 'bika_setup_catalog') brains = bsc(portal_type='SampleType', inactive_state='active') biospecimen_types = [ { 'ResultValue': brain.UID, 'ResultText': brain.title } for brain in brains ] ret = [] for x, item in enumerate(items): if not items[x].has_key('obj'): continue obj = items[x]['obj'] if not ISample.providedBy(obj): continue items[x]['Type'] = obj.getSampleType() and obj.getSampleType().Title() or '' items[x]['Volume'] = obj.getField('Volume').get(obj) items[x]['Unit'] = obj.getField('Unit').get(obj) items[x]['SubjectID'] = obj.getField('SubjectID').get(obj) project = obj.getField('Project').get(obj) if not project: project = obj.aq_parent items[x]['Project'] = project storage_location = obj.getField('StorageLocation').get(obj) if storage_location: items[x]['StorageLocation'] = storage_location.Title() if project: items[x]['replace']['Project'] = \ '<a href="%s">%s</a>' % (project.absolute_url(), project.Title()) items[x]['Barcode'] = obj.getField('Barcode').get(obj) items[x]['replace']['Title'] = "<a href='%s'>%s</a>" % \ (items[x]['url'], items[x]['Title']) frozen_time = obj.getField('FrozenTime').get(obj) if frozen_time: try: items[x]['FrozenTime'] = frozen_time.strftime("%Y-%m-%d %H:%M") except: items[x]['FrozenTime'] = str(frozen_time) batch = obj.getField('Batch').get(obj) try: items[x]['CFGTime'] = batch.getField('CfgDateTime').get(batch).strftime("%Y/%m/%d %H:%M") except: items[x]['CFGTime'] = '' try: items[x]['SamplingTime'] = obj.getField('SamplingDate').get(obj).strftime("%Y/%m/%d %H:%M") except: items[x]['SamplingTime'] = '' if self.allow_edit and isActive(self.context) and \ getSecurityManager().checkPermission(ModifyPortalContent, obj): if items[x]['review_state'] == "sample_registered": items[x]['allow_edit'] = ['Type', 'Barcode', 'FrozenTime'] items[x]['choices']['Type'] = biospecimen_types elif items[x]['review_state'] == "sample_due": # items[x]['allow_edit'] = ['SubjectID', 'Volume', 'Unit'] items[x]['allow_edit'] = ['Volume', 'Unit'] if not items[x]['Unit']: items[x]['choices']['Unit'] = VOLUME_UNITS elif items[x]['review_state'] == "sample_shipped": # items[x]['allow_edit'] = ['SubjectID', 'Volume'] items[x]['allow_edit'] = ['Volume'] ret.append(item) return ret
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') workflow = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) self.allow_edit = can_edit_analyses self.show_select_column = self.allow_edit context_active = isActive(self.context) self.categories = [] items = super(AnalysesView, self).folderitems(full_objects = True) # manually skim retracted analyses from the list new_items = [] for i,item in enumerate(items): # self.contentsMethod may return brains or objects. if not ('obj' in items[i]): continue obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue new_items.append(item) items = new_items methods = self.get_methods_vocabulary() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False for i, item in enumerate(items): # self.contentsMethod may return brains or objects. obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() items[i]['category'] = cat if cat not in self.categories: self.categories.append(cat) # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] # kick some pretty display values in. for x in range(len(interim_fields)): interim_fields[x]['formatted_value'] = \ format_numeric_result(obj, interim_fields[x]['value']) self.interim_fields[obj.UID()] = interim_fields items[i]['service_uid'] = service.UID() items[i]['Service'] = service.Title() items[i]['Keyword'] = keyword items[i]['Unit'] = format_supsub(unit) if unit else '' items[i]['Result'] = '' items[i]['formatted_result'] = '' items[i]['interim_fields'] = interim_fields items[i]['Remarks'] = obj.getRemarks() items[i]['Uncertainty'] = '' items[i]['retested'] = obj.getRetested() items[i]['class']['retested'] = 'center' items[i]['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) items[i]['calculation'] = calculation and True or False try: items[i]['Partition'] = obj.getSamplePartition().getId() except AttributeError: items[i]['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": items[i]['DueDate'] = self.ulocalized_time(obj.aq_parent.getExpiryDate(), long_format=0) else: items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() items[i]['CaptureDate'] = cd and self.ulocalized_time(cd, long_format=1) or '' items[i]['Attachments'] = '' item['allow_edit'] = [] client_or_lab = "" tblrowclass = items[i].get('table_row_class'); if obj.portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']); elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']); else: if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() st_uid = sample.getSampleType().UID() items[i]['st_uid'] = st_uid if st_uid not in self.specs: proxies = bsc(portal_type = 'AnalysisSpec', getSampleTypeUID = st_uid) elif self.context.portal_type == "Worksheet": if obj.portal_type == "DuplicateAnalysis": sample = obj.getAnalysis().getSample() elif obj.portal_type == "RejectAnalysis": sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() st_uid = sample.getSampleType().UID() items[i]['st_uid'] = st_uid if st_uid not in self.specs: proxies = bsc(portal_type = 'AnalysisSpec', getSampleTypeUID = st_uid) elif self.context.portal_type == 'Sample': st_uid = self.context.getSampleType().UID() items[i]['st_uid'] = st_uid if st_uid not in self.specs: proxies = bsc(portal_type = 'AnalysisSpec', getSampleTypeUID = st_uid) else: proxies = [] if st_uid not in self.specs: for spec in (p.getObject() for p in proxies): if spec.getClientUID() == obj.getClientUID(): client_or_lab = 'client' elif spec.getClientUID() == self.context.bika_setup.bika_analysisspecs.UID(): client_or_lab = 'lab' else: continue for keyword, results_range in \ spec.getResultsRangeDict().items(): # hidden form field 'specs' keyed by sampletype uid: # {st_uid: {'lab/client':{keyword:{min,max,error}}}} if st_uid in self.specs: if client_or_lab in self.specs[st_uid]: self.specs[st_uid][client_or_lab][keyword] = results_range else: self.specs[st_uid][client_or_lab] = {keyword: results_range} else: self.specs[st_uid] = {client_or_lab: {keyword: results_range}} if checkPermission(ManageBika, self.context): service_uid = service.UID() latest = rc.lookupObject(service_uid).version_id items[i]['Service'] = service.Title() items[i]['class']['Service'] = "service_title" # Show version number of out-of-date objects # No: This should be done in another column, if at all. # The (vX) value confuses some more fragile forms. # if hasattr(obj, 'reference_versions') and \ # service_uid in obj.reference_versions and \ # latest != obj.reference_versions[service_uid]: # items[i]['after']['Service'] = "(v%s)" % \ # (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: item['choices']['Result'] = choices # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) ) allowed_method_states = ['to_be_sampled', 'to_be_preserved', 'sample_received', 'sample_registered', 'sampled', 'assigned'] # Prevent from being edited if the instrument assigned # is not valid (out-of-date or uncalibrated), except if # the analysis is a QC with assigned status can_edit_analysis = can_edit_analysis \ and (obj.isInstrumentValid() \ or (obj.portal_type == 'ReferenceAnalysis' \ and item['review_state'] in allowed_method_states)) if can_edit_analysis: items[i]['allow_edit'].extend(['Analyst', 'Result', 'Remarks']) # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: items[i]['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not items[i]['calculation'] or \ (items[i]['calculation'] and self.interim_fields[obj.UID()]): items[i]['allow_edit'].append('retested') # TODO: Only the labmanager must be able to change the method # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj) can_set_method = can_edit_analysis \ and item['review_state'] in allowed_method_states method = obj.getMethod() \ if hasattr(obj, 'getMethod') and obj.getMethod() \ else service.getMethod() # Display the methods selector if the AS has at least one # method assigned item['Method'] = '' item['replace']['Method'] = '' if can_set_method: voc = self.get_methods_vocabulary(obj) if voc: # The service has at least one method available item['Method'] = method.UID() if method else '' item['choices']['Method'] = voc item['allow_edit'].append('Method') show_methodinstr_columns = True elif method: # This should never happen # The analysis has set a method, but its parent # service hasn't any method available O_o item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True elif method: # Edition not allowed, but method set item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True # TODO: Instrument selector dynamic behavior in worksheet Results # Only the labmanager must be able to change the instrument to be used. Also, # the instrument selection should be done in accordance with the method selected # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj) can_set_instrument = service.getInstrumentEntryOfResults() \ and can_edit_analysis \ and item['review_state'] in allowed_method_states item['Instrument'] = '' item['replace']['Instrument'] = '' if service.getInstrumentEntryOfResults(): instrument = None # If the analysis has an instrument already assigned, use it if service.getInstrumentEntryOfResults() \ and hasattr(obj, 'getInstrument') \ and obj.getInstrument(): instrument = obj.getInstrument() # Otherwise, use the Service's default instrument elif service.getInstrumentEntryOfResults(): instrument = service.getInstrument() if can_set_instrument: # Edition allowed voc = self.get_instruments_vocabulary(obj) if voc: # The service has at least one instrument available item['Instrument'] = instrument.UID() if instrument else '' item['choices']['Instrument'] = voc item['allow_edit'].append('Instrument') show_methodinstr_columns = True elif instrument: # This should never happen # The analysis has an instrument set, but the # service hasn't any available instrument item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True elif instrument: # Edition not allowed, but instrument set item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True else: # Manual entry of results, instrument not allowed item['Instrument'] = _('Manual') msgtitle = t(_( "Instrument entry of results not allowed for ${service}", mapping={"service": safe_unicode(service.Title())}, )) item['replace']['Instrument'] = \ '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual'))) # Sets the analyst assigned to this analysis if can_edit_analysis: analyst = obj.getAnalyst() # widget default: current user if not analyst: analyst = mtool.getAuthenticatedMember().getUserName() items[i]['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: items[i]['Analyst'] = obj.getAnalystName() # If the user can attach files to analyses, show the attachment col can_add_attachment = \ getSecurityManager().checkPermission(AddAttachment, obj) if can_add_attachment or can_view_result: attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.getBestIcon() attachments += "<span class='attachment' attachment_uid='%s'>" % (attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % (self.portal_url, icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % (attachment.absolute_url(), af.filename) if can_edit_analysis: attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % (attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" items[i]['replace']['Attachments'] = attachments[:-12] + "</span>" # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: items[i]['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() dmk = self.context.bika_setup.getResultsDecimalMark() items[i]['formatted_result'] = obj.getFormattedResult(sciformat=int(scinot),decimalmark=dmk) items[i]['Uncertainty'] = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot)) else: items[i]['Specification'] = "" if 'Result' in items[i]['allow_edit']: items[i]['allow_edit'].remove('Result') items[i]['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (self.portal_url) # Everyone can see valid-ranges spec = self.get_analysis_spec(obj) if spec: min_val = spec.get('min', '') min_str = ">{0}".format(min_val) if min_val else '' max_val = spec.get('max', '') max_str = "<{0}".format(max_val) if max_val else '' error_val = spec.get('error', '') error_str = "{0}%".format(error_val) if error_val else '' rngstr = ",".join([x for x in [min_str, max_str, error_str] if x]) else: rngstr = "" items[i]['Specification'] = rngstr # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns and not f.get('hidden', False): self.interim_columns[f['keyword']] = f['title'] # and to the item itself items[i][f['keyword']] = f items[i]['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue resultdate = obj.aq_parent.getDateSampled() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getResultCaptureDate() duedate = obj.aq_parent.getExpiryDate() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getDueDate() items[i]['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if items[i]['review_state'] not in ['to_be_sampled', 'to_be_preserved', 'sample_due', 'published']: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (self.ulocalized_time(duedate, long_format=1), self.portal_url, t(_("Late Analysis"))) # Submitting user may not verify results (admin can though) if items[i]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): user_id = getSecurityManager().getUser().getId() self_submitted = False try: review_history = list(workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == user_id: self_submitted = True break if self_submitted: items[i]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ (t(_("Cannot verify: Submitted by current user"))) except WorkflowException: pass # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = items[i]['obj'] if obj.portal_type in ['ReferenceAnalysis', 'DuplicateAnalysis'] or \ workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned': br = obj.getBackReferences('WorksheetAnalysis') if len(br) > 0: ws = br[0] items[i]['after']['state_title'] = \ "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \ (ws.absolute_url(), t(_("Assigned to: ${worksheet_id}", mapping={'worksheet_id': safe_unicode(ws.id)}))) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = {'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False} if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. # In case of hidden fields, the calcs.py should check calcs/services # for additional InterimFields!! pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is never enabled for reference sample contexts # and refers to getReportDryMatter in ARs. if items and \ (hasattr(self.context, 'getReportDryMatter') and \ self.context.getReportDryMatter()): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states self.categories.sort() # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) self.items = items # Method and Instrument columns must be shown or hidden at the # same time, because the value assigned to one causes # a value reassignment to the other (one method can be performed # by different instruments) self.columns['Method']['toggle'] = show_methodinstr_columns self.columns['Instrument']['toggle'] = show_methodinstr_columns return items
def __call__(self): form = self.request.form plone.protect.CheckAuthenticator(form) self.context = aq_inner(self.context) workflow = getToolByName(self.context, 'portal_workflow') checkPermission = self.context.portal_membership.checkPermission context = self.context context_url = context.absolute_url() # use came_from to decide which UI action was clicked. # "workflow_action" is the action name specified in the # portal_workflow transition url. came_from = "workflow_action" action = form.get(came_from, '') if not action and not form.get('bika_listing_filter_bar_submit', ''): # workflow_action_button is the action name specified in # the bika_listing_view table buttons. came_from = "workflow_action_button" action = form.get('workflow_action_id', '') if not action: if self.destination_url == "": self.destination_url = self.request.get_header("referer", context_url) self.request.response.redirect(self.destination_url) return if action == "sample": objects = AnalysisRequestWorkflowAction._get_selected_items(self) transitioned = {'to_be_preserved': [], 'sample_due': []} dsfn = 'getDateSampled' for obj_uid, obj in objects.items(): if obj.portal_type == "AnalysisRequest": ar = obj sample = obj.getSample() else: # If it is a Sample, then fieldname is DateSampled dsfn = 'DateSampled' sample = obj ar = sample.aq_parent # can't transition inactive items if workflow.getInfoFor(sample, 'inactive_state', '') == 'inactive': continue # grab this object's Sampler and DateSampled from the form # (if the columns are available and edit controls exist) if 'getSampler' in form: try: Sampler = form['getSampler'][0][obj_uid].strip() except KeyError: continue Sampler = Sampler and Sampler or '' sample.setSampler(Sampler) sample.reindexObject() if dsfn in form: try: DateSampled = form[dsfn][0][obj_uid].strip() except KeyError: continue DateSampled = DateSampled and DateTime(DateSampled) or '' sample.setDateSampled(DateSampled) sample.reindexObject() # write them to the sample if not sample.getSampler(): # Make the message more specific if the reason for not # transitioning is that no Sampler has been selected message = _('Sampler is required for the Sampling transition of ${sample}', mapping={'sample': sample.Title()}) self.context.plone_utils.addPortalMessage(message, 'info') continue if not sample.getDateSampled(): continue Sampler = sample.getSampler() DateSampled = sample.getDateSampled() sample.reindexObject() ars = sample.getAnalysisRequests() # Analyses and AnalysisRequets have calculated fields # that are indexed; re-index all these objects. for ar in ars: ar.reindexObject() analyses = sample.getAnalyses({'review_state': 'to_be_sampled'}) for a in analyses: a.getObject().reindexObject() # transition the object if both values are present if Sampler and DateSampled: workflow.doActionFor(sample, action) new_state = workflow.getInfoFor(sample, 'review_state') doActionFor(ar, action) transitioned[new_state].append(sample.Title()) message = None for state in transitioned: tlist = transitioned[state] if len(tlist) > 1: if state == 'to_be_preserved': message = _('${items} are waiting for preservation.', mapping={'items': ', '.join(tlist)}) else: message = _('${items} are waiting to be received.', mapping={'items': ', '.join(tlist)}) self.context.plone_utils.addPortalMessage(message, 'info') elif len(tlist) == 1: if state == 'to_be_preserved': message = _('${item} is waiting for preservation.', mapping={'item': ', '.join(tlist)}) else: message = _('${item} is waiting to be received.', mapping={'item': ', '.join(tlist)}) self.context.plone_utils.addPortalMessage(message, 'info') if not message: message = _('No changes made.') self.context.plone_utils.addPortalMessage(message, 'info') self.destination_url = self.request.get_header("referer", context_url) self.request.response.redirect(self.destination_url) elif action == "preserve": objects = AnalysisRequestWorkflowAction._get_selected_items(self) transitioned = {} not_transitioned = [] Preserver = str() DatePreserved = str() for obj_uid, obj in objects.items(): if obj.portal_type == "AnalysisRequest": ar = obj sample = obj.getSample() else: sample = obj ar = sample.aq_parent # can't transition inactive items if workflow.getInfoFor(sample, 'inactive_state', '') == 'inactive': continue if not checkPermission(PreserveSample, sample): continue # grab this object's Preserver and DatePreserved from the form # (if the columns are available and edit controls exist) if 'getPreserver' in form and 'getDatePreserved' in form: try: Preserver = form['getPreserver'][0][obj_uid].strip() DatePreserved = form['getDatePreserved'][0][obj_uid].strip() except KeyError: continue Preserver = Preserver and Preserver or '' DatePreserved = DatePreserved and DateTime(DatePreserved) or '' else: continue for sp in sample.objectValues("SamplePartition"): if workflow.getInfoFor(sp, 'review_state') == 'to_be_preserved': sp.setDatePreserved(DatePreserved) sp.setPreserver(Preserver) for sp in sample.objectValues("SamplePartition"): if workflow.getInfoFor(sp, 'review_state') == 'to_be_preserved': if Preserver and DatePreserved: doActionFor(sp, action) transitioned[sp.aq_parent.Title()] = sp.Title() else: not_transitioned.append(sp) if len(transitioned.keys()) > 1: message = _('${items}: partitions are waiting to be received.', mapping={'items': ', '.join(transitioned.keys())}) else: message = _('${item}: ${part} is waiting to be received.', mapping={'item': ', '.join(transitioned.keys()), 'part': ', '.join(transitioned.values()), }) self.context.plone_utils.addPortalMessage(message, 'info') # And then the sample itself if Preserver and DatePreserved and not not_transitioned: doActionFor(sample, action) # message = _('${item} is waiting to be received.', # mapping = {'item': sample.Title()}) # message = t(message) # self.context.plone_utils.addPortalMessage(message, 'info') self.destination_url = self.request.get_header( "referer", self.context.absolute_url()) self.request.response.redirect(self.destination_url) elif action in ('prepublish', 'publish', 'republish'): # We pass a list of AR objects to Publish. # it returns a list of AR IDs which were actually published. objects = AnalysisRequestWorkflowAction._get_selected_items(self) its = [] for uid, obj in objects.items(): if isActive(obj): its.append(uid) its = ",".join(its) q = "/publish?items=" + its self.destination_url = self.context.absolute_url() + q self.request.response.redirect(self.destination_url) else: AnalysisRequestWorkflowAction.__call__(self)
def workflow_action_retract_ar(self): workflow = getToolByName(self.context, 'portal_workflow') # AR should be retracted # Can't transition inactive ARs if not isActive(self.context): message = _('Item is inactive.') self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.context.absolute_url()) return # 1. Copies the AR linking the original one and viceversa ar = self.context newar = self.cloneAR(ar) # 2. The old AR gets a status of 'invalid' workflow.doActionFor(ar, 'retract_ar') # 3. The new AR copy opens in status 'to be verified' changeWorkflowState(newar, 'bika_ar_workflow', 'to_be_verified') # 4. The system immediately alerts the client contacts who ordered # the results, per email and SMS, that a possible mistake has been # picked up and is under investigation. # A much possible information is provided in the email, linking # to the AR online. laboratory = self.context.bika_setup.laboratory lab_address = "<br/>".join(laboratory.getPrintAddress()) mime_msg = MIMEMultipart('related') mime_msg['Subject'] = t(_("Erroneus result publication from ${request_id}", mapping={"request_id": ar.getRequestID()})) mime_msg['From'] = formataddr( (encode_header(laboratory.getName()), laboratory.getEmailAddress())) to = [] contact = ar.getContact() if contact: to.append(formataddr((encode_header(contact.Title()), contact.getEmailAddress()))) for cc in ar.getCCContact(): formatted = formataddr((encode_header(cc.Title()), cc.getEmailAddress())) if formatted not in to: to.append(formatted) managers = self.context.portal_groups.getGroupMembers('LabManagers') for bcc in managers: user = self.portal.acl_users.getUser(bcc) if user: uemail = user.getProperty('email') ufull = user.getProperty('fullname') formatted = formataddr((encode_header(ufull), uemail)) if formatted not in to: to.append(formatted) mime_msg['To'] = ','.join(to) aranchor = "<a href='%s'>%s</a>" % (ar.absolute_url(), ar.getRequestID()) naranchor = "<a href='%s'>%s</a>" % (newar.absolute_url(), newar.getRequestID()) addremarks = ('addremarks' in self.request and ar.getRemarks()) \ and ("<br/><br/>" + _("Additional remarks:") + "<br/>" + ar.getRemarks().split("===")[1].strip() + "<br/><br/>") \ or '' sub_d = dict(request_link=aranchor, new_request_link=naranchor, remarks=addremarks, lab_address=lab_address) body = Template("Some errors have been detected in the results report " "published from the Analysis Request $request_link. The Analysis " "Request $new_request_link has been created automatically and the " "previous has been invalidated.<br/>The possible mistake " "has been picked up and is under investigation.<br/><br/>" "$remarks $lab_address").safe_substitute(sub_d) msg_txt = MIMEText(safe_unicode(body).encode('utf-8'), _subtype='html') mime_msg.preamble = 'This is a multi-part MIME message.' mime_msg.attach(msg_txt) try: host = getToolByName(self.context, 'MailHost') host.send(mime_msg.as_string(), immediate=True) except Exception as msg: message = _('Unable to send an email to alert lab ' 'client contacts that the Analysis Request has been ' 'retracted: ${error}', mapping={'error': safe_unicode(msg)}) self.context.plone_utils.addPortalMessage(message, 'warning') message = _('${items} invalidated.', mapping={'items': ar.getRequestID()}) self.context.plone_utils.addPortalMessage(message, 'warning') self.request.response.redirect(newar.absolute_url())
def submit(self): """ Saves the form """ form = self.request.form remarks = form.get('Remarks', [{}])[0] results = form.get('Result', [{}])[0] retested = form.get('retested', {}) methods = form.get('Method', [{}])[0] instruments = form.get('Instrument', [{}])[0] analysts = self.request.form.get('Analyst', [{}])[0] uncertainties = self.request.form.get('Uncertainty', [{}])[0] dlimits = self.request.form.get('DetectionLimit', [{}])[0] selected = WorkflowAction._get_selected_items(self) workflow = getToolByName(self.context, 'portal_workflow') rc = getToolByName(self.context, REFERENCE_CATALOG) sm = getSecurityManager() hasInterims = {} # XXX combine data from multiple bika listing tables. item_data = {} if 'item_data' in form: if type(form['item_data']) == list: for i_d in form['item_data']: for i, d in json.loads(i_d).items(): item_data[i] = d else: item_data = json.loads(form['item_data']) # Iterate for each selected analysis and save its data as needed for uid, analysis in selected.items(): allow_edit = sm.checkPermission(EditResults, analysis) analysis_active = isActive(analysis) # Need to save remarks? if uid in remarks and allow_edit and analysis_active: analysis.setRemarks(remarks[uid]) # Retested? if uid in retested and allow_edit and analysis_active: analysis.setRetested(retested[uid]) # Need to save the instrument? if uid in instruments and analysis_active: # TODO: Add SetAnalysisInstrument permission # allow_setinstrument = sm.checkPermission(SetAnalysisInstrument) allow_setinstrument = True # ---8<----- if allow_setinstrument == True: # The current analysis allows the instrument regards # to its analysis service and method? if (instruments[uid] == ''): previnstr = analysis.getInstrument() if previnstr: previnstr.removeAnalysis(analysis) analysis.setInstrument(None) elif analysis.isInstrumentAllowed(instruments[uid]): previnstr = analysis.getInstrument() if previnstr: previnstr.removeAnalysis(analysis) analysis.setInstrument(instruments[uid]) instrument = analysis.getInstrument() instrument.addAnalysis(analysis) if analysis.meta_type == 'ReferenceAnalysis': instrument.setDisposeUntilNextCalibrationTest( False) # Need to save the method? if uid in methods and analysis_active: # TODO: Add SetAnalysisMethod permission # allow_setmethod = sm.checkPermission(SetAnalysisMethod) allow_setmethod = True # ---8<----- if allow_setmethod == True and analysis.isMethodAllowed( methods[uid]): analysis.setMethod(methods[uid]) # Need to save the analyst? if uid in analysts and analysis_active: analysis.setAnalyst(analysts[uid]) # Need to save the uncertainty? if uid in uncertainties and analysis_active: analysis.setUncertainty(uncertainties[uid]) # Need to save the detection limit? if analysis_active and uid in dlimits and dlimits[uid]: analysis.setDetectionLimitOperand(dlimits[uid]) # Need to save results? if uid in results and results[uid] and allow_edit \ and analysis_active: interims = item_data.get(uid, []) analysis.setInterimFields(interims) analysis.setResult(results[uid]) analysis.reindexObject() can_submit = True deps = analysis.getDependencies() \ if hasattr(analysis, 'getDependencies') else [] for dependency in deps: if workflow.getInfoFor(dependency, 'review_state') in \ ('to_be_sampled', 'to_be_preserved', 'sample_due', 'sample_received'): can_submit = False break if can_submit: # doActionFor transitions the analysis to verif pending, # so must only be done when results are submitted. doActionFor(analysis, 'submit') # Maybe some analyses need to be retracted due to a QC failure # Done here because don't know if the last selected analysis is # a valid QC for the instrument used in previous analyses. # If we add this logic in subscribers.analyses, there's the # possibility to retract analyses before the QC being reached. self.retractInvalidAnalyses() message = PMF("Changes saved.") self.context.plone_utils.addPortalMessage(message, 'info') self.destination_url = self.request.get_header( "referer", self.context.absolute_url()) self.request.response.redirect(self.destination_url)
def workflow_action_retract_ar(self): workflow = getToolByName(self.context, 'portal_workflow') # AR should be retracted # Can't transition inactive ARs if not isActive(self.context): message = _('Item is inactive.') self.context.plone_utils.addPortalMessage(message, 'info') self.request.response.redirect(self.context.absolute_url()) return # 1. Copies the AR linking the original one and viceversa ar = self.context newar = self.cloneAR(ar) # 2. The old AR gets a status of 'invalid' workflow.doActionFor(ar, 'retract_ar') # 3. The new AR copy opens in status 'to be verified' changeWorkflowState(newar, 'bika_ar_workflow', 'to_be_verified') # 4. The system immediately alerts the client contacts who ordered # the results, per email and SMS, that a possible mistake has been # picked up and is under investigation. # A much possible information is provided in the email, linking # to the AR online. laboratory = self.context.bika_setup.laboratory lab_address = "<br/>".join(laboratory.getPrintAddress()) mime_msg = MIMEMultipart('related') mime_msg['Subject'] = t(_("Erroneus result publication from ${request_id}", mapping={"request_id": ar.getRequestID()})) mime_msg['From'] = formataddr( (encode_header(laboratory.getName()), laboratory.getEmailAddress())) to = [] contact = ar.getContact() if contact: to.append(formataddr((encode_header(contact.Title()), contact.getEmailAddress()))) for cc in ar.getCCContact(): formatted = formataddr((encode_header(cc.Title()), cc.getEmailAddress())) if formatted not in to: to.append(formatted) managers = self.context.portal_groups.getGroupMembers('LabManagers') for bcc in managers: user = self.portal.acl_users.getUser(bcc) if user: uemail = user.getProperty('email') ufull = user.getProperty('fullname') formatted = formataddr((encode_header(ufull), uemail)) if formatted not in to: to.append(formatted) mime_msg['To'] = ','.join(to) aranchor = "<a href='%s'>%s</a>" % (ar.absolute_url(), ar.getRequestID()) naranchor = "<a href='%s'>%s</a>" % (newar.absolute_url(), newar.getRequestID()) addremarks = ('addremarks' in self.request and ar.getRemarks()) \ and ("<br/><br/>" + _("Additional remarks:") + "<br/>" + ar.getRemarks().split("===")[1].strip() + "<br/><br/>") \ or '' body = _("Some errors have been detected in the results report " "published from the Analysis Request ${request_link}. The Analysis " "Request ${new_request_link} has been created automatically and the " "previous has been invalidated.<br/>The possible mistake " "has been picked up and is under investigation.<br/><br/>" "${remarks}${lab_address}", mapping={"request_link":aranchor, "new_request_link":naranchor, "remarks": addremarks, "lab_address": lab_address}) msg_txt = MIMEText(safe_unicode(body).encode('utf-8'), _subtype='html') mime_msg.preamble = 'This is a multi-part MIME message.' mime_msg.attach(msg_txt) try: host = getToolByName(self.context, 'MailHost') host.send(mime_msg.as_string(), immediate=True) except Exception as msg: message = _('Unable to send an email to alert lab ' 'client contacts that the Analysis Request has been ' 'retracted: ${error}', mapping={'error': safe_unicode(msg)}) self.context.plone_utils.addPortalMessage(message, 'warning') message = _('${items} invalidated.', mapping={'items': ar.getRequestID()}) self.context.plone_utils.addPortalMessage(message, 'warning') self.request.response.redirect(newar.absolute_url())
def __call__(self): # Do generic bika.lims stuff _AnalysisRequestWorkflowAction.__call__(self) # Do bika-health specific actions when submit action = _AnalysisRequestWorkflowAction._get_form_workflow_action(self) addPortalMessage = self.context.plone_utils.addPortalMessage if action[0] == 'submit' and isActive(self.context): inpanicanalyses = [] workflow = getToolByName(self.context, 'portal_workflow') translate = self.context.translate rc = getToolByName(self.context, REFERENCE_CATALOG) uc = getToolByName(self.context, 'uid_catalog') # retrieve the results from database and check if # the values are exceeding panic levels alerts = {} for uid in self.request.form.get('Result', [{}])[0].keys(): analysis = rc.lookupObject(uid) analysis = analysis.getObject() if hasattr(analysis, 'getObject') else analysis if not analysis: continue astate = workflow.getInfoFor(analysis, 'review_state') if astate == 'retracted': continue alerts.update(ResultOutOfRange(analysis)()) if alerts: message = translate(_('Some results exceeded the ' 'panic levels that may ' 'indicate an imminent ' 'life-threatening condition' )) addPortalMessage(message, 'warning') self.request.response.redirect(self.context.absolute_url()) # If panic levels alert email enabled, send an email to # labmanagers bs = self.context.bika_setup if hasattr(bs, 'getEnablePanicAlert') \ and bs.getEnablePanicAlert(): laboratory = self.context.bika_setup.laboratory lab_address = "<br/>".join(laboratory.getPrintAddress()) managers = self.context.portal_groups.getGroupMembers('LabManagers') mime_msg = MIMEMultipart('related') mime_msg['Subject'] = _("Panic alert") mime_msg['From'] = formataddr( (encode_header(laboratory.getName()), laboratory.getEmailAddress())) to = [] for manager in managers: user = self.portal.acl_users.getUser(manager) uemail = user.getProperty('email') ufull = user.getProperty('fullname') to.append(formataddr((encode_header(ufull), uemail))) mime_msg['To'] = ','.join(to) strans = [] ars = {} for analysis_uid, alertlist in alerts: analysis = uc(analysis_uid).getObject() for alert in alertlist: ars[analysis.aq_parent.Title()] = 1 strans.append("- {0}, {1}: {2}".format( analysis.Title(), translate(_("Result")), analysis.getResult())) ars = ", ".join(ars.keys()) stran = "<br/>".join(strans) text = translate(_( "Some results from ${items} exceeded the panic levels " "that may indicate an imminent life-threatening " "condition: <br/><br/>{analysisresults}<br/><br/>" "<b>Please, check the Analysis Request if you " "want to re-test the analysis or immediately " "alert the client.</b><br/><br/>{lab_address}", mapping={'items': ars, 'analysisresults': stran, 'lab_address': lab_address})) msg_txt = MIMEText(safe_unicode(text).encode('utf-8'), _subtype='html') mime_msg.preamble = 'This is a multi-part MIME message.' mime_msg.attach(msg_txt) try: host = getToolByName(self.context, 'MailHost') host.send(mime_msg.as_string(), immediate=True) except Exception as msg: ar = inpanicanalyses[0].getRequestID() logger.error("Panic level email %s: %s" % (ar, str(msg))) message = translate( _('Unable to send an email to alert lab ' 'managers that some analyses exceeded the ' 'panic levels') + (": %s" % str(msg))) addPortalMessage(message, 'warning')
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') workflow = getToolByName(self.context, 'portal_workflow') portal = getToolByName(self.context, 'portal_url').getPortalObject() translate = self.context.translation_service.translate mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) context_active = isActive(self.context) items = super(AnalysesView, self).folderitems(full_objects=True) self.interim_fields = {} self.interim_columns = {} self.specs = {} for i, item in enumerate(items): # self.contentsMethod may return brains or objects. obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() precision = service.getPrecision() # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] self.interim_fields[obj.UID()] = interim_fields items[i]['Keyword'] = keyword items[i]['Unit'] = unit and unit or '' items[i]['Result'] = '' items[i]['formatted_result'] = '' items[i]['interim_fields'] = interim_fields items[i]['Uncertainty'] = '' items[i]['retested'] = obj.getRetested() items[i]['class']['retested'] = 'center' items[i]['calculation'] = calculation and True or False items[i]['Partition'] = obj.getSamplePartition().Title() if obj.portal_type == "ReferenceAnalysis": items[i]['DueDate'] = '' items[i]['CaptureDate'] = '' else: items[i]['DueDate'] = obj.getDueDate() cd = obj.getResultCaptureDate() items[i]['CaptureDate'] = cd and TimeOrDate(self.context, cd) or '' items[i]['Attachments'] = '' # calculate specs if obj.portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() else: if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() st_uid = sample.getSampleType().UID() items[i]['st_uid'] = st_uid if st_uid not in self.specs: proxies = bsc(portal_type='AnalysisSpec', getSampleTypeUID=st_uid) elif self.context.portal_type == "Worksheet": if obj.portal_type == "DuplicateAnalysis": sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() st_uid = sample.getSampleType().UID() items[i]['st_uid'] = st_uid if st_uid not in self.specs: proxies = bsc(portal_type='AnalysisSpec', getSampleTypeUID=st_uid) elif self.context.portal_type == 'Sample': st_uid = self.context.getSampleType().UID() items[i]['st_uid'] = st_uid if st_uid not in self.specs: proxies = bsc(portal_type='AnalysisSpec', getSampleTypeUID=st_uid) else: proxies = [] if st_uid not in self.specs: for spec in (p.getObject() for p in proxies): client_or_lab = "" if spec.getClientUID() == obj.getClientUID(): client_or_lab = 'client' elif spec.getClientUID( ) == self.context.bika_setup.bika_analysisspecs.UID(): client_or_lab = 'lab' else: continue for keyword, results_range in \ spec.getResultsRangeDict().items(): # hidden form field 'specs' keyed by sampletype uid: # {st_uid: {'lab/client':{keyword:{min,max,error}}}} if st_uid in self.specs: if client_or_lab in self.specs[st_uid]: self.specs[st_uid][client_or_lab][ keyword] = results_range else: self.specs[st_uid][client_or_lab] = { keyword: results_range } else: self.specs[st_uid] = { client_or_lab: { keyword: results_range } } method = service.getMethod() items[i]['Method'] = method and method.Title() or '' if method: items[i]['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) # Show version number of out-of-date objects if checkPermission(ManageBika, self.context): service_uid = service.UID() latest = rc.lookupObject(service_uid).version_id items[i]['Service'] = service.Title() items[i]['class']['Service'] = "service_title" if hasattr(obj, 'reference_versions') and \ service_uid in obj.reference_versions and \ latest != obj.reference_versions[service_uid]: items[i]['after']['Service'] = "(v%s)" % \ (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: items[i]['choices'] = {'Result': choices} # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) ) if can_edit_analysis: items[i]['allow_edit'] = [ 'Result', ] # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: items[i]['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not items[i]['calculation'] or \ (items[i]['calculation'] and self.interim_fields[obj.UID()]): items[i]['allow_edit'].append('retested') # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: items[i]['Result'] = result items[i]['formatted_result'] = result if result != '': if 'Result' in items[i]['choices'] and items[i]['choices'][ 'Result']: items[i]['formatted_result'] = \ [r['ResultText'] for r in items[i]['choices']['Result'] \ if str(r['ResultValue']) == str(result)][0] else: try: items[i]['formatted_result'] = precision and \ str("%%.%sf" % precision) % float(result) or result except: items[i]['formatted_result'] = result indet = translate(_('Indet')) if result == indet: # 'Indeterminate' results flag a specific error Indet = translate(_("Indeterminate result")) items[i]['after']['Result'] = \ '<img width="16" height="16" title="%s"' % Indet + \ 'src="%s/++resource++bika.lims.images/exclamation.png"/>' % \ (portal.absolute_url()) else: # result being un-floatable, is an error. msg = translate(_("Invalid result")) items[i]['after']['Result'] = \ '<img width="16" height="16" title="%s"' % msg + \ 'src="%s/++resource++bika.lims.images/exclamation.png"/>' % \ (portal.absolute_url()) items[i]['Uncertainty'] = obj.getUncertainty(result) attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.getBestIcon() attachments += "<span class='attachment' attachment_uid='%s'>" % ( attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % ( portal.absolute_url(), icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % ( attachment.absolute_url(), af.filename) attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % ( attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" items[i]['replace'][ 'Attachments'] = attachments[:-12] + "</span>" if hasattr(obj, 'result_in_range'): items[i]['result_in_range'] = obj.result_in_range( result, self.chosen_spec) else: items[i]['result_in_range'] = (True, None) if not can_view_result or \ (not items[i]['Result'] and not can_edit_analysis): if 'Result' in items[i]['allow_edit']: items[i]['allow_edit'].remove('Result') items[i]['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (portal.absolute_url()) # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns: self.interim_columns[f['keyword']] = f['title'] # and to the item itself items[i][f['keyword']] = f items[i]['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue if items[i]['obj'].portal_type != "DuplicateAnalysis": if (not calculation or (calculation and not calculation.getDependentServices())) and \ items[i]['review_state'] not in ['to_be_sampled', 'to_be_preserved', 'sample_due', 'published'] and \ items[i]['DueDate'] < DateTime(): DueDate = TimeOrDate(self.context, item['DueDate'], long_format=0) if self.context.portal_type == 'AnalysisRequest': items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (DueDate, portal.absolute_url(), translate(_("Due Date")) + ": %s"%DueDate) else: items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (DueDate, portal.absolute_url(), translate(_("Late Analysis"))) else: items[i]['replace']['DueDate'] = TimeOrDate( self.context, item['DueDate']) # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest' and \ workflow.getInfoFor(items[i]['obj'], 'worksheetanalysis_review_state') == 'assigned': ws = items[i]['obj'].getBackReferences('WorksheetAnalysis')[0] items[i]['after']['state_title'] = \ "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='Assigned: %s'/></a>" % \ (ws.absolute_url(), ws.id) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = { 'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False } if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is always enabled for worksheets, # never enabled for reference sample contexts, and refers to # getReportDryMatter in ARs. # XXX It should be enabled only if any of the ARs present asked for DM. if items and \ (self.context.portal_type == 'Worksheet' or \ (hasattr(self.context, 'getReportDryMatter') and \ self.context.getReportDryMatter())): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) return items
def __call__(self): # Do generic bika.lims stuff BaseClass.__call__(self) # Do bika-health specific actions when submit action = BaseClass._get_form_workflow_action(self) addPortalMessage = self.context.plone_utils.addPortalMessage if action[0] == 'submit' and isActive(self.context): inpanicanalyses = [] workflow = getToolByName(self.context, 'portal_workflow') translate = self.context.translate rc = getToolByName(self.context, REFERENCE_CATALOG) uc = getToolByName(self.context, 'uid_catalog') # retrieve the results from database and check if # the values are exceeding panic levels alerts = {} for uid in self.request.form.get('Result', [{}])[0].keys(): analysis = rc.lookupObject(uid) analysis = analysis.getObject() if hasattr(analysis, 'getObject') else analysis if not analysis: continue astate = workflow.getInfoFor(analysis, 'review_state') if astate == 'retracted': continue alerts.update(ResultOutOfRange(analysis)()) if alerts: message = translate(_('Some results exceeded the ' 'panic levels that may ' 'indicate an imminent ' 'life-threatening condition' )) addPortalMessage(message, 'warning') self.request.response.redirect(self.context.absolute_url()) # If panic levels alert email enabled, send an email to # labmanagers bs = self.context.bika_setup if hasattr(bs, 'getEnablePanicAlert') \ and bs.getEnablePanicAlert(): laboratory = self.context.bika_setup.laboratory lab_address = "<br/>".join(laboratory.getPrintAddress()) managers = self.context.portal_groups.getGroupMembers('LabManagers') mime_msg = MIMEMultipart('related') mime_msg['Subject'] = _("Panic alert") mime_msg['From'] = formataddr( (encode_header(laboratory.getName()), laboratory.getEmailAddress())) to = [] for manager in managers: user = self.portal.acl_users.getUser(manager) uemail = user.getProperty('email') ufull = user.getProperty('fullname') to.append(formataddr((encode_header(ufull), uemail))) mime_msg['To'] = ','.join(to) strans = [] ars = {} for analysis_uid, alertlist in alerts: analysis = uc(analysis_uid).getObject() for alert in alertlist: ars[analysis.aq_parent.Title()] = 1 strans.append("- {0}, {1}: {2}".format( analysis.getService().Title(), translate(_("Result")), analysis.getResult())) ars = ", ".join(ars.keys()) stran = "<br/>".join(strans) text = translate(_( "Some results from ${items} exceeded the panic levels " "that may indicate an imminent life-threatening " "condition: <br/><br/>{analysisresults}<br/><br/>" "<b>Please, check the Analysis Request if you " "want to re-test the analysis or immediately " "alert the client.</b><br/><br/>{lab_address}", mapping={'items': ars, 'analysisresults': stran, 'lab_address': lab_address})) msg_txt = MIMEText(safe_unicode(text).encode('utf-8'), _subtype='html') mime_msg.preamble = 'This is a multi-part MIME message.' mime_msg.attach(msg_txt) try: host = getToolByName(self.context, 'MailHost') host.send(mime_msg.as_string(), immediate=True) except Exception as msg: ar = inpanicanalyses[0].getRequestID() logger.error("Panic level email %s: %s" % (ar, str(msg))) message = translate( _('Unable to send an email to alert lab ' 'managers that some analyses exceeded the ' 'panic levels') + (": %s" % str(msg))) addPortalMessage(message, 'warning')
def __call__(self): form = self.request.form plone.protect.CheckAuthenticator(form) workflow = getToolByName(self.context, 'portal_workflow') rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') bac = getToolByName(self.context, 'bika_analysis_catalog') action, came_from = WorkflowAction._get_form_workflow_action(self) # XXX combine data from multiple bika listing tables. item_data = {} if 'item_data' in form: if type(form['item_data']) == list: for i_d in form['item_data']: for i, d in json.loads(i_d).items(): item_data[i] = d else: item_data = json.loads(form['item_data']) if action == 'submit' and self.request.form.has_key("Result"): selected_analyses = WorkflowAction._get_selected_items(self) results = {} hasInterims = {} # first save results for entire form for uid, result in self.request.form['Result'][0].items(): if uid in selected_analyses: analysis = selected_analyses[uid] else: analysis = rc.lookupObject(uid) if not analysis: # ignore result if analysis object no longer exists continue if not(getSecurityManager().checkPermission(EditResults, analysis)): # or changes no longer allowed continue if not isActive(analysis): # or it's cancelled continue results[uid] = result service = analysis.getService() interimFields = item_data[uid] if len(interimFields) > 0: hasInterims[uid] = True else: hasInterims[uid] = False unit = service.getUnit() analysis.edit( Result = result, InterimFields = interimFields, Retested = form.has_key('retested') and \ form['retested'].has_key(uid), Unit = unit and unit or '') # discover which items may be submitted submissable = [] for uid, analysis in selected_analyses.items(): if uid not in results: continue can_submit = True if hasattr(analysis, 'getDependencies'): dependencies = analysis.getDependencies() for dependency in dependencies: dep_state = workflow.getInfoFor(dependency, 'review_state') if hasInterims[uid]: if dep_state in ('to_be_sampled', 'to_be_preserved', 'sample_due', 'sample_received', 'attachment_due', 'to_be_verified',): can_submit = False break else: if dep_state in ('to_be_sampled', 'to_be_preserved', 'sample_due', 'sample_received',): can_submit = False break for dependency in dependencies: if workflow.getInfoFor(dependency, 'review_state') in \ ('to_be_sampled', 'to_be_preserved', 'sample_due', 'sample_received'): can_submit = False if can_submit: submissable.append(analysis) # and then submit them. for analysis in submissable: doActionFor(analysis, 'submit') message = PMF("Changes saved.") self.context.plone_utils.addPortalMessage(message, 'info') self.destination_url = self.request.get_header("referer", self.context.absolute_url()) self.request.response.redirect(self.destination_url) ## assign elif action == 'assign': if not(getSecurityManager().checkPermission(EditWorksheet, self.context)): self.request.response.redirect(self.context.absolute_url()) return selected_analyses = WorkflowAction._get_selected_items(self) selected_analysis_uids = selected_analyses.keys() if selected_analyses: for uid in selected_analysis_uids: analysis = rc.lookupObject(uid) # Double-check the state first if (workflow.getInfoFor(analysis, 'worksheetanalysis_review_state') == 'unassigned' and workflow.getInfoFor(analysis, 'review_state') == 'sample_received' and workflow.getInfoFor(analysis, 'cancellation_state') == 'active'): self.context.addAnalysis(analysis) self.destination_url = self.context.absolute_url() self.request.response.redirect(self.destination_url) ## unassign elif action == 'unassign': if not(getSecurityManager().checkPermission(EditWorksheet, self.context)): self.request.response.redirect(self.context.absolute_url()) return selected_analyses = WorkflowAction._get_selected_items(self) selected_analysis_uids = selected_analyses.keys() for analysis_uid in selected_analysis_uids: try: analysis = bac(UID=analysis_uid)[0].getObject() except IndexError: # Duplicate analyses are removed when their analyses # get removed, so indexerror is expected. continue if skip(analysis, action, peek=True): continue self.context.removeAnalysis(analysis) self.destination_url = self.context.absolute_url() self.request.response.redirect(self.destination_url) ## verify elif action == 'verify': # default bika_listing.py/WorkflowAction, but then go to view screen. self.destination_url = self.context.absolute_url() WorkflowAction.__call__(self) else: # default bika_listing.py/WorkflowAction for other transitions WorkflowAction.__call__(self)