diff --git a/CHANGES.rst b/CHANGES.rst index 1a4f072e3a..271a3fd5cb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Changelog **Added** +- #1420 Allow to detach a partition from its primary sample - #1410 Email API diff --git a/bika/lims/browser/viewlets/analysisrequest.py b/bika/lims/browser/viewlets/analysisrequest.py index b1f5ce6057..dbd2afd941 100644 --- a/bika/lims/browser/viewlets/analysisrequest.py +++ b/bika/lims/browser/viewlets/analysisrequest.py @@ -66,3 +66,10 @@ class RejectedAnalysisRequestViewlet(ViewletBase): """Current ANalysis Request was rejected. Display the reasons """ template = ViewPageTemplateFile("templates/rejected_ar_viewlet.pt") + + +class DetachedPartitionViewlet(ViewletBase): + """Prints a viewlet that displays the Primary Sample the sample was + detached from + """ + template = ViewPageTemplateFile("templates/detached_partition_viewlet.pt") diff --git a/bika/lims/browser/viewlets/configure.zcml b/bika/lims/browser/viewlets/configure.zcml index 02e8f33fbe..180908b0c5 100644 --- a/bika/lims/browser/viewlets/configure.zcml +++ b/bika/lims/browser/viewlets/configure.zcml @@ -160,6 +160,17 @@ layer="bika.lims.interfaces.IBikaLIMS" /> + + + + +
+ +
+
+ + Info +

+ This is a detached partition from Sample + +

+
+
+ diff --git a/bika/lims/content/analysisrequest.py b/bika/lims/content/analysisrequest.py index 58d8dae829..a64bfc7128 100644 --- a/bika/lims/content/analysisrequest.py +++ b/bika/lims/content/analysisrequest.py @@ -1294,6 +1294,20 @@ ), ), + # The Primary Sample the current sample was detached from + ReferenceField( + "DetachedFrom", + allowed_types=("AnalysisRequest",), + relationship="AnalysisRequestDetachedFrom", + referenceClass=HoldingReference, + mode="rw", + read_permission=View, + write_permission=ModifyPortalContent, + widget=ReferenceWidget( + visible=False, + ) + ), + # The Analysis Request the current Analysis Request comes from because of # an invalidation of the former ReferenceField( diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index 29b5815918..429a732aca 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -940,6 +940,12 @@ class IReceived(Interface): """Marker interface for received objects """ + class IInternalUse(Interface): """Marker interface for objects only lab personnel must have access """ + + +class IDetachedPartition(Interface): + """Marker interface for samples that have been detached from its primary + """ diff --git a/bika/lims/permissions.py b/bika/lims/permissions.py index f3a63bc47a..6dd568f1b3 100644 --- a/bika/lims/permissions.py +++ b/bika/lims/permissions.py @@ -106,6 +106,7 @@ # Transition permissions (Analysis Request) TransitionCancelAnalysisRequest = "senaite.core: Transition: Cancel Analysis Request" +TransitionDetachSamplePartition = "senaite.core: Transition: Detach Sample Partition" TransitionReinstateAnalysisRequest = "senaite.core: Transition: Reinstate Analysis Request" TransitionInvalidate = "senaite.core: Transition Invalidate" TransitionPreserveSample = "senaite.core: Transition: Preserve Sample" diff --git a/bika/lims/permissions.zcml b/bika/lims/permissions.zcml index a9b31acecd..fd54bde24f 100644 --- a/bika/lims/permissions.zcml +++ b/bika/lims/permissions.zcml @@ -78,6 +78,7 @@ # Transition Permissions (Analysis Request) + diff --git a/bika/lims/profiles/default/rolemap.xml b/bika/lims/profiles/default/rolemap.xml index 9bd281cf2a..79fa39d41c 100644 --- a/bika/lims/profiles/default/rolemap.xml +++ b/bika/lims/profiles/default/rolemap.xml @@ -444,6 +444,11 @@ + + + + + diff --git a/bika/lims/profiles/default/workflows/bika_ar_workflow/definition.xml b/bika/lims/profiles/default/workflows/bika_ar_workflow/definition.xml index 7b7a45ed6d..32d41eda92 100644 --- a/bika/lims/profiles/default/workflows/bika_ar_workflow/definition.xml +++ b/bika/lims/profiles/default/workflows/bika_ar_workflow/definition.xml @@ -20,6 +20,7 @@ senaite.core: Transition: Cancel Analysis Request + senaite.core: Transition: Detach Sample Partition senaite.core: Transition: Reinstate Analysis Request senaite.core: Transition: Invalidate senaite.core: Transition: Preserve Sample @@ -108,6 +109,7 @@ + @@ -194,6 +196,7 @@ + @@ -292,6 +295,7 @@ + @@ -367,6 +371,7 @@ + @@ -385,6 +390,7 @@ + @@ -454,6 +460,7 @@ + @@ -471,6 +478,7 @@ + @@ -626,6 +634,7 @@ + @@ -638,6 +647,7 @@ + @@ -714,6 +724,7 @@ + @@ -726,6 +737,7 @@ + @@ -796,6 +808,7 @@ + @@ -808,6 +821,7 @@ + @@ -887,6 +901,7 @@ + @@ -962,6 +977,7 @@ + @@ -1036,6 +1052,7 @@ + @@ -1115,6 +1132,7 @@ + @@ -1428,6 +1446,23 @@ + + + Detach + + python:here.guard_handler("detach") + senaite.core: Transition: Detach Sample Partition + + diff --git a/bika/lims/upgrade/v01_03_002.py b/bika/lims/upgrade/v01_03_002.py index 4a615a23ed..b38a6ad73d 100644 --- a/bika/lims/upgrade/v01_03_002.py +++ b/bika/lims/upgrade/v01_03_002.py @@ -18,7 +18,10 @@ # Copyright 2018-2019 by it's authors. # Some rights reserved, see README and LICENSE. +from bika.lims import api from bika.lims import logger +from bika.lims.catalog.analysisrequest_catalog import \ + CATALOG_ANALYSIS_REQUEST_LISTING from bika.lims.config import PROJECTNAME as product from bika.lims.upgrade import upgradestep from bika.lims.upgrade.utils import UpgradeUtils @@ -43,9 +46,45 @@ def upgrade(tool): # -------- ADD YOUR STUFF BELOW -------- - # Mixed permissions for transitions in client workflow - # https://github.com/senaite/senaite.core/pull/1419 + # Allow to detach a partition from its primary sample (#1420) + setup.runImportStepFromProfile(profile, "rolemap") + + # Mixed permissions for transitions in client workflow (#1419) + # Allow to detach a partition from its primary sample (#1420) setup.runImportStepFromProfile(profile, "workflow") + # Allow to detach a partition from its primary sample (#1420) + update_partitions_role_mappings(portal) + logger.info("{0} upgraded to version {1}".format(product, version)) return True + +def update_partitions_role_mappings(portal): + """Updates the rolemappings for existing partitions that are in a suitable + state, so they can be detached from the primary sample they belong to + """ + logger.info("Updating role mappings of partitions ...") + wf_tool = api.get_tool("portal_workflow") + workflow = wf_tool.getWorkflowById("bika_ar_workflow") + + # States that allow detach transition as defined in workflow definition in + # order to query and update role mappings of objects that matter + allowed_states = [ + "to_be_preserved", "sample_due", "sample_received", "to_be_verified", + "verified" + ] + query = dict(portal_type="AnalysisRequest", + isRootAncestor=False, + review_state=allowed_states) + + brains = api.search(query, CATALOG_ANALYSIS_REQUEST_LISTING) + total = len(brains) + for num, brain in enumerate(brains): + if num and num % 100 == 0: + logger.info("Updating role mappings of partitions: {}/{}" + .format(num, total)) + partition = api.get_object(brain) + workflow.updateRoleMappingsFor(partition) + partition.reindexObjectSecurity() + + logger.info("Updating role mappings of partitions [DONE]") diff --git a/bika/lims/utils/analysisrequest.py b/bika/lims/utils/analysisrequest.py index f321726c37..68d0a230f6 100644 --- a/bika/lims/utils/analysisrequest.py +++ b/bika/lims/utils/analysisrequest.py @@ -405,6 +405,7 @@ def create_partition(analysis_request, request, analyses, sample_type=None, "Analyses", "Attachment", "Client", + "DetachedFrom", "Profile", "Profiles", "RejectionReasons", diff --git a/bika/lims/workflow/analysisrequest/events.py b/bika/lims/workflow/analysisrequest/events.py index d383a4759b..6b902f01c5 100644 --- a/bika/lims/workflow/analysisrequest/events.py +++ b/bika/lims/workflow/analysisrequest/events.py @@ -20,7 +20,8 @@ from DateTime import DateTime from bika.lims import api -from bika.lims.interfaces import IReceived, IVerified +from bika.lims.interfaces import IDetachedPartition +from bika.lims.interfaces import IReceived, IVerified, IAnalysisRequestPartition from bika.lims.utils import changeWorkflowState from bika.lims.utils.analysisrequest import create_retest from bika.lims.workflow import get_prev_status_from_history @@ -153,3 +154,24 @@ def after_rollback_to_receive(analysis_request): """ if IVerified.providedBy(analysis_request): noLongerProvides(analysis_request, IVerified) + + +def after_detach(analysis_request): + """Function triggered after "detach" transition is performed + """ + # Unbind the sample from its parent (the primary) + parent = analysis_request.getParentAnalysisRequest() + analysis_request.setParentAnalysisRequest(None) + + # Assign the primary from which the sample has been detached + analysis_request.setDetachedFrom(parent) + + # This sample is no longer a partition + noLongerProvides(analysis_request, IAnalysisRequestPartition) + + # And we mark the sample with IDetachedPartition + alsoProvides(analysis_request, IDetachedPartition) + + # Reindex both the parent and the detached one + analysis_request.reindexObject() + parent.reindexObject() diff --git a/bika/lims/workflow/analysisrequest/guards.py b/bika/lims/workflow/analysisrequest/guards.py index bc9ddcabc2..b0f9c30f00 100644 --- a/bika/lims/workflow/analysisrequest/guards.py +++ b/bika/lims/workflow/analysisrequest/guards.py @@ -223,3 +223,11 @@ def guard_reject(analysis_request): True only if setup's isRejectionWorkflowEnabled is True """ return analysis_request.bika_setup.isRejectionWorkflowEnabled() + + +def guard_detach(analysis_request): + """Returns whether 'detach' transition can be performed or not. Returns True + only if the sample passed in is a partition + """ + # Detach transition can only be done to partitions + return analysis_request.isPartition()