Skip to content

Commit

Permalink
Merge branch '1.x' of github.com:senaite/senaite.referral into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
xispa committed Jul 18, 2024
2 parents 425edee + 40d706b commit 97990b2
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 23 deletions.
11 changes: 11 additions & 0 deletions src/senaite/referral/adapters/guards/sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,14 @@ def guard_invalidate_at_reference(self):
if not lab_code:
return False
return True

def guard_receive_at_reference(self):
"""Returns true if current request contains a POST with the
'receive_at_reference' action to ensure this transition is not
performed manually,
"""
request = api.get_request()
lab_code = request.get("lab_code")
if not lab_code:
return False
return True
6 changes: 5 additions & 1 deletion src/senaite/referral/adapters/listing/samples.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ def add_review_states(self):
"id": "shipped",
"title": _("Referred"),
"contentFilter": {
"review_state": ("shipped", "rejected_at_reference"),
"review_state": (
"shipped",
"rejected_at_reference",
"received_at_reference",
),
"sort_on": "created",
"sort_order": "descending"},
"transitions": [],
Expand Down
24 changes: 24 additions & 0 deletions src/senaite/referral/content/inboundsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ class IInboundSampleSchema(model.Schema):
)
)

# TODO 2.x Replace schema.List by senaite.core.schema.UIDReferenceField
directives.omitted("services")
services = schema.List(
title=_(u"label_inboundsample_services", default=u"Services"),
)

# TODO 2.x Replace schema.List by senaite.core.schema.UIDReferenceField
directives.omitted("sample")
sample = schema.List(
Expand Down Expand Up @@ -311,3 +317,21 @@ def getDateRejected(self):
"""Returns the datetime when this inbound sample was rejected or None
"""
return get_action_date(self, "reject_inbound_sample", default=None)

def setServices(self, value):
"""Set the services from the current instance that match with the
analyses that were requested by the referring laboratory
"""
set_uids_field_value(self, "services", value)

def getServices(self):
"""Returns the services from current instance that match with the
analyses that were requested by the referring laboratory
"""
return [api.get_object(uid) for uid in self.getRawServices()]

def getRawServices(self):
"""Returns the UIDs of the services from current instance that match
with the analyses that were requested by the referring laboratory
"""
return get_uids_field_value(self, "services") or []
13 changes: 11 additions & 2 deletions src/senaite/referral/jsonapi/outboundsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from bika.lims.interfaces import ISubmitted
from bika.lims.utils import changeWorkflowState
from bika.lims.workflow import doActionFor
from senaite.referral import logger


@implementer(IPushConsumer)
Expand Down Expand Up @@ -65,8 +66,16 @@ def process(self):
msg = "No retest found for '%s'" % sample_id
raise APIError(500, "ValueError: {}".format(msg))

# Do not allow to modify the sample if not referred
if api.get_review_status(sample) != "shipped":
# Do not allow to modify the sample if not received at reference
status = api.get_review_status(sample)
if status == "shipped":
# TODO Do not allow the modification of 'shipped' samples
logger.warn("Please upgrade reference instance to latest!!!. The "
"update of samples in 'shipped' statues won't be "
"supported in the near future, but only those in "
"'received_at_reference' status")

elif status != "received_at_reference":
# We don't rise an exception here because maybe the sample was
# updated earlier, but the reference lab got a timeout error and
# the remote user is now retrying the notification
Expand Down
3 changes: 1 addition & 2 deletions src/senaite/referral/profiles/default/metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
dependencies before installing this add-on own profile.
-->
<metadata>
<version>2003</version>
<version>2005</version>

<!-- Be sure to install the following dependencies if not yet installed -->
<dependencies>
<dependency>profile-senaite.lims:default</dependency>
</dependencies>

</metadata>
18 changes: 7 additions & 11 deletions src/senaite/referral/remotelab.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import math
from bika.lims import api
from bika.lims.interfaces import IAnalysisRequest
from bika.lims.interfaces import IInternalUse
from bika.lims.utils import format_supsub
from bika.lims.utils.analysis import format_uncertainty
from remotesession import RemoteSession
Expand Down Expand Up @@ -191,27 +190,24 @@ def update_analyses(self, sample, timeout=5):
"""

def get_valid_analyses(sample):
# Sort by id descending to prioritize newest results if retests
# Get the analyses from current sample that were requested by the
# referring laboratory, sorted by id descending to prioritize
# newest results if retests
inbound_sample = sample.getInboundSample()
services = inbound_sample.getRawServices()
kwargs = {
"full_objects": True,
"getServiceUID": services,
"sort_on": "id",
"sort_order": "ascending",
}

# Exclude old, but valid analyses with same keyword (e.g retests),
# exclude old, but valid analyses with same keyword (e.g retests),
# cause we want to update the referring lab with the newest result
analyses = {}
valid = ["verified", "published"]
for analysis in sample.getAnalyses(**kwargs):

# Skip analyses for internal use
if IInternalUse.providedBy(analysis):
continue

# Skip hidden, only interested in final results
if analysis.getHidden():
continue

# Skip analyses not in a suitable status
if api.get_review_status(analysis) not in valid:
continue
Expand Down
22 changes: 22 additions & 0 deletions src/senaite/referral/setuphandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,24 @@
"transitions": (
"verify",
"invalidate_at_reference",
"receive_at_reference",
"reject_at_reference",
"recall_from_shipment"
),
# Sample is read-only
"permissions_copy_from": "invalid",
},
"received_at_reference": {
"title": "Received at reference lab",
"description": "Sample received at reference laboratory",
"transitions": (
"verify",
"reject_at_reference",
"invalidate_at_reference",
),
# Sample is read-only
"permissions_copy_from": "invalid",
},
"rejected_at_reference": {
"title": "Rejected at reference lab",
"description": "Sample rejected at reference laboratory",
Expand All @@ -106,6 +118,16 @@
"guard_expr": "python:here.guard_handler('ship')",
}
},
"receive_at_reference": {
"title": "Receive sample (at reference lab)",
"new_state": "received_at_reference",
"action": "Received at reference lab",
"guard": {
"guard_permissions": "",
"guard_roles": "",
"guard_expr": "python:here.guard_handler('receive_at_reference')",
}
},
"reject_at_reference": {
"title": "Reject sample (at reference lab)",
"new_state": "rejected_at_reference",
Expand Down
3 changes: 1 addition & 2 deletions src/senaite/referral/upgrade/v01_00_000.zcml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="senaite.referral">
xmlns:genericsetup="http://namespaces.zope.org/genericsetup">

<!-- Setup chunk size for inbound sample reception -->
<genericsetup:upgradeStep
Expand Down
46 changes: 46 additions & 0 deletions src/senaite/referral/upgrade/v02_00_000.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
from senaite.core.upgrade.utils import UpgradeUtils
from senaite.referral import logger
from senaite.referral.catalog import SHIPMENT_CATALOG
from senaite.referral.catalog import INBOUND_SAMPLE_CATALOG
from senaite.referral.config import PRODUCT_NAME as product
from senaite.referral.setuphandlers import setup_ajax_transitions
from senaite.referral.setuphandlers import setup_workflows
from senaite.referral.utils import get_sample_types_mapping
from senaite.referral.utils import get_services_mapping

version = "2.0.0"
profile = "profile-{0}:default".format(product)
Expand Down Expand Up @@ -115,3 +117,47 @@ def setup_invalidate_at_reference(tool):
setup_workflows(portal)

logger.info("Setup transition 'invalidate_at_reference' [DONE]")


def setup_receive_at_reference(tool):
logger.info("Setup transition 'receive_at_reference' ...")
portal = tool.aq_inner.aq_parent

# Setup workflows
setup_workflows(portal)

logger.info("Setup transition 'receive_at_reference' [DONE]")


def setup_inbound_services(tool):
"""Updates the inbound samples with the service uids that were requested
by the referring laboratory
"""
logger.info("Setup inbound services ...")

# Get baseline objects mappings
services = get_services_mapping()

query = {
"portal_type": "InboundSample",
"review_state": "received",
}
brains = api.search(query, INBOUND_SAMPLE_CATALOG)
total = len(brains)
for num, brain in enumerate(brains):
if num and num % 100 == 0:
logger.info("Processed objects: {}/{}".format(num, total))

obj = api.get_object(brain)
if obj.getRawServices():
# processed already
continue

# Resolve the service uids that match with the requested analyses
keywords = obj.getAnalyses() or []
services_uids = map(lambda key: services.get(key), keywords)
services_uids = list(filter(api.is_uid, services_uids))
obj.setServices(services_uids)
obj._p_deactivate()

logger.info("Setup inbound services [DONE]")
16 changes: 16 additions & 0 deletions src/senaite/referral/upgrade/v02_00_000.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="senaite.referral">

<genericsetup:upgradeStep
title="SENAITE.REFERRAL 1.0.0: Store service uids in inbound sample"
description="Store service uids in inbound sample"
source="2004"
destination="2005"
handler=".v02_00_000.setup_inbound_services"
profile="senaite.referral:default"/>

<genericsetup:upgradeStep
title="SENAITE.REFERRAL 1.0.0: New transition: 'Receive at reference'"
description="Added the transition 'receive_at_reference'"
source="2003"
destination="2004"
handler=".v02_00_000.setup_receive_at_reference"
profile="senaite.referral:default"/>

<genericsetup:upgradeStep
title="SENAITE.REFERRAL 2.0.0: Invalidate at reference laboratory"
description="Added the transition 'invalidate_at_reference'"
Expand Down
8 changes: 3 additions & 5 deletions src/senaite/referral/workflow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,9 @@ def restore_referred_sample(sample):
sample.setOutboundShipment(None)

# Transition the sample and analyses to the state before sample was shipped
status = api.get_review_status(sample)
if status in ["shipped", "rejected_at_reference"]:
prev = get_previous_status(sample, before="shipped",
default="sample_received")
changeWorkflowState(sample, SAMPLE_WORKFLOW, prev)
previous = get_previous_status(sample, before="shipped")
if previous:
changeWorkflowState(sample, SAMPLE_WORKFLOW, previous)

# Restore status of referred analyses
wf_id = ANALYSIS_WORKFLOW
Expand Down
13 changes: 13 additions & 0 deletions src/senaite/referral/workflow/analysisrequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def AfterTransitionEventHandler(sample, event): # noqa lowercase
if event.transition.id == "recall_from_shipment":
restore_referred_sample(sample)

if event.transition.id == "receive":
after_receive(sample)


def after_no_sampling_workflow(sample):
"""Automatically receive and ship samples for which an outbound shipment
Expand Down Expand Up @@ -134,6 +137,16 @@ def after_invalidate(sample):
referring.do_action(sample, "invalidate_at_reference")


def after_receive(sample):
"""Actions to do when receiving a sample
"""
shipment = sample.getInboundShipment()
referring = get_remote_lab(shipment)
if referring:
# notify the referring laboratory
referring.do_action(sample, "receive_at_reference")


def after_invalidate_at_reference(sample):
"""Actions to do when a sample is invalidated at reference laboratory
"""
Expand Down
4 changes: 4 additions & 0 deletions src/senaite/referral/workflow/inboundsample/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,14 @@ def create_sample(inbound_sample):
if not sample_type_uid:
raise ValueError("No sample type found for '{}'".format(sample_type))

# Resolve the service uids that match with the requested analyses
keywords = inbound_sample.getAnalyses() or []
services_uids = map(lambda key: services.get(key), keywords)
services_uids = filter(api.is_uid, services_uids)

# Update the inbound sample with the service uids
inbound_sample.setServices(services_uids)

values = {
"Client": api.get_uid(client),
"Contact": None,
Expand Down

0 comments on commit 97990b2

Please sign in to comment.