Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Multiple component analysis #2120

Draft
wants to merge 112 commits into
base: 2.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
c92dd65
Add MultiComponent and Analytes fields to AnalysisService type
xispa Aug 31, 2022
beb7b4a
Simplify validator
xispa Aug 31, 2022
d77d968
Add new func for the creation of a copy from a service
xispa Sep 1, 2022
46693af
Validate Service's Analytes on creation/edition
xispa Sep 1, 2022
2a73a2b
Add skip parameter to copy_service func
xispa Sep 1, 2022
7606fb2
Automatic creation of analytes (as Analysis objects) from Multicompon…
xispa Sep 2, 2022
1d5beac
Add fields for Analytes retrieval from Analysis content type
xispa Sep 2, 2022
b86f57b
Add isAnalyte index in analyses catalog
xispa Sep 2, 2022
8c78d05
Less intrusive addition of isAnalyte index
xispa Sep 2, 2022
9296fd1
Display analytes as children in analyses listing
xispa Sep 2, 2022
b0c272b
Auto-set result to MultiComponent on Analyte results capture
xispa Sep 2, 2022
dc49806
Do not allow to submit a multi-component unless all analytes submitted
xispa Sep 2, 2022
6a76ff1
Assign method and instrument from multi-component to analytes
xispa Sep 2, 2022
66d51a2
Bad spelling
xispa Sep 3, 2022
de05a78
Do not display the method in analytes
xispa Sep 3, 2022
45aa48d
Prevent max recursion depth
xispa Sep 3, 2022
819c96b
Rejection of Multi-component causes the rejection of all analytes
xispa Sep 3, 2022
f873c6a
Do not allow to retract analytes, but multi-component only
xispa Sep 3, 2022
0afa7a5
Create new analytes when a multi-component analysis is retracted
xispa Sep 3, 2022
7116829
Simplify and cleanup
xispa Sep 3, 2022
cfdcf81
Rely on IRetracted marker interface on guard_retract
xispa Sep 3, 2022
60cba4d
Cleanup
xispa Sep 3, 2022
121f7d0
Cleanup
xispa Sep 3, 2022
facd940
Retraction is only permitted for multi-analysis, but when analytes suit
xispa Sep 3, 2022
8d71297
Auto-reject multi-component when all its analytes are rejected
xispa Sep 3, 2022
6b3739f
Auto-verify analytes when multi-component is verified
xispa Sep 3, 2022
8538076
Do not display the result of a multi-component analysis in listing
xispa Sep 3, 2022
1a4b76f
Do not display the Submitter for multi-component analyses in listings
xispa Sep 3, 2022
61ef058
Allow to retest a multi-component analysis
xispa Sep 3, 2022
4cdda45
Cleanup
xispa Sep 3, 2022
34d263e
Automatically assign analytes when multi-component is assigned
xispa Sep 3, 2022
80a7f54
Automatically unassign analytes when a multi-analysis is unassigned
xispa Sep 3, 2022
69c78c9
Do not display analytes in the Add analyses view from inside Worksheet
xispa Sep 3, 2022
d990715
Render analytes as a flat list in worksheet results entry
xispa Sep 4, 2022
362d6b7
Set the analysis' default method on creation
xispa Sep 4, 2022
c5f0ab2
Performance: enhance retrieval of methods from analysis/service
xispa Sep 4, 2022
571a83d
Optimize and clean-up accessors from analysis/service types
xispa Sep 4, 2022
2a702d2
Added ico in services listing next to multi-component services
xispa Sep 4, 2022
a9c8659
Fix retract doctests
xispa Sep 4, 2022
27c9653
Fixture for tests that do not have a regular request
xispa Sep 4, 2022
b626a75
Add doctest for multi-analysis/service
xispa Sep 4, 2022
dfa1595
Changelog
xispa Sep 4, 2022
d473413
Fix retest guard
xispa Sep 4, 2022
b0f4e66
Complete doctest
xispa Sep 4, 2022
df5dd84
Fix bad message in test
xispa Sep 4, 2022
c28ec1a
Restore the check against calculation interim fields when validating …
xispa Sep 4, 2022
2d6d44b
Added Assignment section in multicomponent doctest
xispa Sep 4, 2022
0b4d1dc
Remove unused import (F401 'bika.lims.workflow as wf' imported but un…
xispa Sep 4, 2022
f13baa1
Allow to select Analytes on sample registration
xispa Sep 4, 2022
5ab4165
Do not display the "Hidden" checkbox for multi-component analyses
xispa Sep 5, 2022
5e487a8
Added copy_object func into the API
xispa Sep 6, 2022
7e51f70
Leave ShortTitle field empty when creating a copy
xispa Sep 6, 2022
fc81a35
Simplify keyword check (no need to filter by uid)
xispa Sep 6, 2022
199ef66
Wrap validator messages with messageFactory
xispa Sep 6, 2022
81aa786
Assume "reject" and "retract" are final statuses
xispa Sep 6, 2022
245354b
Do not show "Existing keyword" error when creating an Analysis Service
xispa Sep 8, 2022
2d22f21
Merge branch '2.x' into multi-analyses
ramonski Sep 8, 2022
fe06448
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Sep 27, 2022
326fb64
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Sep 27, 2022
3c84f6b
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Sep 28, 2022
7794394
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Oct 14, 2022
d4db8ea
Changelog
xispa Oct 14, 2022
79d701c
Restore getRawMethod original implementation
xispa Oct 14, 2022
794085c
Fix test
xispa Oct 14, 2022
cb0e41e
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Oct 15, 2022
9bcd61a
Remove unused function
xispa Oct 15, 2022
6deff67
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Oct 16, 2022
ca3d82e
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Oct 27, 2022
e948a13
Fix F821 undefined name 'serviceapi'
xispa Oct 27, 2022
c811877
Do not create analytes for analytes on retest
xispa Nov 11, 2022
88b9529
Do not apply (pre)Conditions to analytes
xispa Nov 11, 2022
53d0c0c
Cleanup after_retest transition
xispa Nov 11, 2022
2dfeaef
Fix doctest
xispa Nov 12, 2022
7453de7
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Nov 12, 2022
b6f1ef0
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Nov 18, 2022
d2945e0
Default result of a multi-component is set to analytes
xispa Nov 22, 2022
9223fa7
Drag retests across analytes when retracting a multi-analysis
xispa Nov 24, 2022
5d42811
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Nov 24, 2022
b83eeb5
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Nov 24, 2022
5525991
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Dec 21, 2022
216c5ca
Sort analytes as defined in the analysis servive
xispa Dec 21, 2022
ba95f72
Add leading zeros to the index used to sort analytes
xispa Dec 21, 2022
7986414
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Dec 22, 2022
1e35f06
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Dec 22, 2022
67827a2
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Dec 29, 2022
a7da180
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Dec 30, 2022
ea68ac4
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Jan 4, 2023
22b1b0d
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Jan 9, 2023
6e8273e
Merge branch '2.x' into multi-analyses
xispa Jan 10, 2023
b19c427
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Jan 12, 2023
c071851
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Jan 25, 2023
02321b7
Merge branch '2.x' into multi-analyses
xispa Jan 25, 2023
8d67b72
Merge branch '2.x' into multi-analyses
xispa Jan 29, 2023
f84cf03
Merge branch '2.x' into multi-analyses
xispa Jan 31, 2023
0271aa6
Merge branch '2.x' into multi-analyses
xispa Feb 9, 2023
32a92b6
Merge branch '2.x' into multi-analyses
xispa Feb 9, 2023
a37628c
Fix max-depth recursion error on verify or retract
xispa Feb 9, 2023
d2eb2d1
Merge branch '2.x' into multi-analyses
xispa Feb 14, 2023
cafbdbb
Merge branch '2.x' into multi-analyses
xispa Feb 17, 2023
24ebe2f
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Mar 29, 2023
3587f6e
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Mar 30, 2023
90a27a4
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Apr 17, 2023
449a84a
Remove unused imports
xispa Apr 17, 2023
245a872
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Apr 24, 2023
1c097a5
Fix analytes in sample retests point to original multi component
xispa May 3, 2023
58db4bc
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa May 3, 2023
82a1ae7
Fix doctest (seems sorting analyses does work differently)
xispa May 3, 2023
e3c7116
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa May 30, 2023
8393ccf
Compatibility with #2201
xispa Jul 11, 2023
a00600b
Allow DL selector and Manual DL entry for Analytes
xispa Jul 11, 2023
9df91d1
Merge branch '2.x' of github.com:senaite/senaite.core into multi-anal…
xispa Mar 18, 2024
e59c68e
Merge branch '2.x' into multi-analyses
ramonski Mar 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
2.6.0 (unreleased)
------------------

- #2120 Support for Multiple component analysis
- #2514 Added functions for easy update of workflows
- #2512 Skip rendering of empty record fiels
- #2510 Fix Add action of the Client located Analysis Profiles Listing
Expand Down
49 changes: 45 additions & 4 deletions src/bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def __init__(self, context, request, **kwargs):
"portal_type": "Analysis",
"sort_on": "sortable_title",
"sort_order": "ascending",
"isAnalyte": False,
})

# set the listing view config
Expand Down Expand Up @@ -387,6 +388,10 @@ def is_result_edition_allowed(self, analysis_brain):
# Get the ananylsis object
obj = self.get_object(analysis_brain)

if obj.isMultiComponent():
# The results entry cannot be done directly, but for its analytes
return False

if not obj.getDetectionLimitOperand():
# This is a regular result (not a detection limit)
return True
Expand Down Expand Up @@ -778,6 +783,11 @@ def folderitem(self, obj, item, index):
# Renders the analysis conditions
self._folder_item_conditions(obj, item)

# Analytes (if a multi component analysis)
obj = self.get_object(obj)
item["children"] = obj.getRawAnalytes()
item["parent"] = obj.getRawMultiComponentAnalysis()

return item

def folderitems(self):
Expand Down Expand Up @@ -940,6 +950,12 @@ def _folder_item_result(self, analysis_brain, item):
item["before"]["Result"] = img
return

# Get the analysis object
obj = self.get_object(analysis_brain)
if obj.isMultiComponent():
# Don't display the "NA" result of a multi-component analysis
return

result = analysis_brain.getResult
capture_date = analysis_brain.getResultCaptureDate
capture_date_str = self.ulocalized_time(capture_date, long_format=0)
Expand All @@ -953,9 +969,6 @@ def _folder_item_result(self, analysis_brain, item):
if unit:
item["after"]["Result"] = self.render_unit(unit)

# Get the analysis object
obj = self.get_object(analysis_brain)

# Edit mode enabled of this Analysis
if self.is_analysis_edition_allowed(analysis_brain):
# Allow to set Remarks
Expand Down Expand Up @@ -1167,6 +1180,12 @@ def _folder_item_unit(self, analysis_brain, item):
if not self.is_analysis_edition_allowed(analysis_brain):
return

obj = self.get_object(analysis_brain)
if obj.isMultiComponent():
# Leave units empty
item["Unit"] = ""
return

# Edition allowed
voc = self.get_unit_vocabulary(analysis_brain)
if voc:
Expand All @@ -1179,7 +1198,11 @@ def _folder_item_method(self, analysis_brain, item):
:param analysis_brain: Brain that represents an analysis
:param item: analysis' dictionary counterpart that represents a row
"""
item["Method"] = ""
obj = self.get_object(analysis_brain)
if obj.isAnalyte():
return

is_editable = self.is_analysis_edition_allowed(analysis_brain)
if is_editable:
method_vocabulary = self.get_methods_vocabulary(analysis_brain)
Expand Down Expand Up @@ -1221,6 +1244,9 @@ def _folder_item_instrument(self, analysis_brain, item):
:param item: analysis' dictionary counterpart that represents a row
"""
item["Instrument"] = ""
obj = self.get_object(analysis_brain)
if obj.isAnalyte():
return

# Instrument can be assigned to this analysis
is_editable = self.is_analysis_edition_allowed(analysis_brain)
Expand Down Expand Up @@ -1261,7 +1287,13 @@ def _folder_item_analyst(self, obj, item):
item["Analyst"] = self.get_user_name(analyst)

def _folder_item_submitted_by(self, obj, item):
item["SubmittedBy"] = ""

obj = self.get_object(obj)
if obj.isMultiComponent():
# Do not display submitter for multi-component analyses
return

submitted_by = obj.getSubmittedBy()
item["SubmittedBy"] = self.get_user_name(submitted_by)

Expand Down Expand Up @@ -1357,10 +1389,16 @@ def _folder_item_detection_limits(self, analysis_brain, item):
if not obj.getDetectionLimitSelector():
return None

# Display the column for the selector
self.columns["DetectionLimitOperand"]["toggle"] = True

# If multicomponent only render the selector for analytes
if obj.isMultiComponent():
return

# Show Detection Limit Operand Selector
item["DetectionLimitOperand"] = obj.getDetectionLimitOperand()
item["allow_edit"].append("DetectionLimitOperand")
self.columns["DetectionLimitOperand"]["toggle"] = True

# Prepare selection list for LDL/UDL
choices = [
Expand Down Expand Up @@ -1583,6 +1621,9 @@ def _folder_item_report_visibility(self, analysis_brain, item):
return

full_obj = self.get_object(analysis_brain)
if full_obj.isMultiComponent():
return

item['Hidden'] = full_obj.getHidden()

# Hidden checkbox is not reachable by tabbing
Expand Down
1 change: 1 addition & 0 deletions src/bika/lims/browser/analysisrequest/add2.py
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,7 @@ def get_service_info(self, obj):
"category": obj.getCategoryTitle(),
"poc": obj.getPointOfCapture(),
"conditions": self.get_conditions_info(obj),
"analytes": obj.getAnalytes(),
})

dependencies = get_calculation_dependencies_for(obj).values()
Expand Down
43 changes: 43 additions & 0 deletions src/bika/lims/browser/analysisrequest/templates/ar_add2.pt
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,44 @@
</div>
</script>

<!-- Service Analytes template -->
<script id="service-analytes" type="text/x-handlebars-template">
<div data-fieldname="ServiceAnalytes-{{arnum}}">
<table class="service-analytes-table table table-condensed table-bordered">
{{#each analytes}}
<tr data-subfield="{{title}}">
<td class="service-analytes-label">
<label class="formQuestion">
<span class="">{{{title}}} ({{{keyword}}})</span>
</label>
</td>
<td class="service-analytes-value">
<input type='hidden'
name="ServiceAnalytes-{{../arnum}}.uid:records"
value="{{../uid}}"/>
<input type="hidden"
name="ServiceAnalytes-{{../arnum}}.title:records"
value="{{title}}"/>
<input type="hidden"
name="ServiceAnalytes-{{../arnum}}.keyword:records"
value="{{keyword}}"/>
<input type="hidden"
name="ServiceAnalytes-{{../arnum}}.selected:records"
value="{{selected}}"/>
{{# if selected}}
<input type="checkbox"
name="ServiceAnalytes-{{../arnum}}.value:records"
checked="checked"/>
{{else}}
<input type="checkbox"
name="ServiceAnalytes-{{../arnum}}.value:records"/>
{{/if}}
</td>
</tr>
{{/each}}
</table>
</div>
</script>
</metal:block>
</head>

Expand Down Expand Up @@ -571,6 +609,11 @@
class python:'{}-conditions service-conditions'.format(service_uid);">
</div>

<!-- Service analytes -->
<div tal:attributes="id python:'{}-analytes'.format(service_uid);
class python:'{}-analytes service-analytes'.format(service_uid);">
</div>

</td>
</tal:columns>
</tr>
Expand Down
41 changes: 35 additions & 6 deletions src/bika/lims/browser/fields/aranalysesfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from bika.lims.interfaces import ISubmitted
from senaite.core.permissions import AddAnalysis
from bika.lims.utils.analysis import create_analysis
from bika.lims.utils.analysis import create_analytes
from Products.Archetypes.public import Field
from Products.Archetypes.public import ObjectField
from Products.Archetypes.Registry import registerField
Expand Down Expand Up @@ -277,6 +278,10 @@ def add_analysis(self, instance, service, **kwargs):
analysis = create_analysis(instance, service)
analyses.append(analysis)

# Create the analytes if multi-component analysis
analytes = create_analytes(analysis)
analyses.extend(analytes)

for analysis in analyses:
# Set the hidden status
analysis.setHidden(hidden)
Expand All @@ -288,18 +293,17 @@ def add_analysis(self, instance, service, **kwargs):
parent_sample = analysis.getRequest()
analysis.setInternalUse(parent_sample.getInternalUse())

# Set the default result to the analysis
if not analysis.getResult() and default_result:
analysis.setResult(default_result)
analysis.setResultCaptureDate(None)
# Set default result, but only if not a multi-component
self.set_default_result(analysis, default_result)

# Set the result range to the analysis
analysis_rr = specs.get(service_uid) or analysis.getResultsRange()
analysis.setResultsRange(analysis_rr)

# Set default (pre)conditions
conditions = self.resolve_conditions(analysis)
analysis.setConditions(conditions)
if not analysis.isAnalyte():
conditions = self.resolve_conditions(analysis)
analysis.setConditions(conditions)

analysis.reindexObject()

Expand Down Expand Up @@ -368,6 +372,31 @@ def resolve_analyses(self, instance, service):

return analyses

def set_default_result(self, analysis, default_result):
"""Sets the default result to the analysis w/o updating the results
capture date. It does nothing if the instance is a multi-component
analysis or if the analysis has a result already set
"""
if not default_result:
return
if analysis.getResult():
return
if analysis.isMultiComponent():
return

# keep track of original capture date of the multi-component the
# analysis belongs to
multi = analysis.getMultiComponentAnalysis()
multi_capture = multi.getResultCaptureDate() if multi else None

# set the default result and reset capture date
analysis.setResult(default_result)
analysis.setResultCaptureDate(None)

# if multi, restore the original capture date
if multi:
multi.setResultCaptureDate(multi_capture)

def get_analyses_from_descendants(self, instance):
"""Returns all the analyses from descendants
"""
Expand Down
1 change: 1 addition & 0 deletions src/bika/lims/browser/worksheet/views/add_analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def __init__(self, context, request):
self.contentFilter = {
"portal_type": "Analysis",
"review_state": "unassigned",
"isAnalyte": False,
"isSampleReceived": True,
"sort_on": "getPrioritySortkey",
}
Expand Down
4 changes: 4 additions & 0 deletions src/bika/lims/browser/worksheet/views/analyses.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ def folderitem(self, obj, item, index):
item_obj = api.get_object(obj)
uid = item["uid"]

# Analytes are rendered like the rest, as a flat list
item["parent"] = ""
item["children"] = ""

# Slot is the row position where all analyses sharing the same parent
# (eg. AnalysisRequest, SampleReference), will be displayed as a group
slot = self.get_item_slot(uid)
Expand Down
Loading
Loading