diff --git a/CHANGES.rst b/CHANGES.rst index e9234e2b20..d2e114a46e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ Changelog 2.4.0 (unreleased) ------------------ +- #2265 Change sample analysis column order for better results capturing - #2264 Collapsible analyses listings in sample view - #2262 Simplify attachment render in report options to single checkbox - #2259 Prevent string results from formatting and number conversion diff --git a/src/bika/lims/browser/analyses/view.py b/src/bika/lims/browser/analyses/view.py index a20a8f10ad..a93c54a2c6 100644 --- a/src/bika/lims/browser/analyses/view.py +++ b/src/bika/lims/browser/analyses/view.py @@ -26,7 +26,6 @@ from bika.lims import api from bika.lims import bikaMessageFactory as _ -from bika.lims import FieldEditAnalysisConditions from bika.lims import logger from bika.lims.api.analysis import get_formatted_interval from bika.lims.api.analysis import is_out_of_range @@ -35,10 +34,11 @@ from bika.lims.config import UDL from bika.lims.interfaces import IAnalysisRequest from bika.lims.interfaces import IFieldIcons -from bika.lims.interfaces import IRoutineAnalysis from bika.lims.interfaces import IReferenceAnalysis +from bika.lims.interfaces import IRoutineAnalysis from bika.lims.permissions import EditFieldResults from bika.lims.permissions import EditResults +from bika.lims.permissions import FieldEditAnalysisConditions from bika.lims.permissions import FieldEditAnalysisHidden from bika.lims.permissions import FieldEditAnalysisResult from bika.lims.permissions import TransitionVerify @@ -59,6 +59,7 @@ from senaite.app.listing import ListingView from senaite.core.catalog import ANALYSIS_CATALOG from senaite.core.catalog import SETUP_CATALOG +from senaite.core.registry import get_registry_record from zope.component import getAdapters from zope.component import getMultiAdapter @@ -120,29 +121,6 @@ def __init__(self, context, request, **kwargs): "attr": "Title", "index": "sortable_title", "sortable": False}), - ("Method", { - "title": _("Method"), - "sortable": False, - "ajax": True, - "on_change": "_on_method_change", - "toggle": True}), - ("Instrument", { - "title": _("Instrument"), - "ajax": True, - "sortable": False, - "toggle": True}), - ("Calculation", { - "title": _("Calculation"), - "sortable": False, - "toggle": False}), - ("Analyst", { - "title": _("Analyst"), - "sortable": False, - "ajax": True, - "toggle": True}), - ("state_title", { - "title": _("Status"), - "sortable": False}), ("DetectionLimitOperand", { "title": _("DL"), "sortable": False, @@ -172,20 +150,43 @@ def __init__(self, context, request, **kwargs): "title": _("Retested"), "type": "boolean", "sortable": False}), + ("Method", { + "title": _("Method"), + "sortable": False, + "ajax": True, + "on_change": "_on_method_change", + "toggle": True}), + ("Instrument", { + "title": _("Instrument"), + "ajax": True, + "sortable": False, + "toggle": True}), + ("Calculation", { + "title": _("Calculation"), + "sortable": False, + "toggle": False}), ("Attachments", { "title": _("Attachments"), "sortable": False}), + ("SubmittedBy", { + "title": _("Submitter"), + "sortable": False}), + ("Analyst", { + "title": _("Analyst"), + "sortable": False, + "ajax": True, + "toggle": True}), ("CaptureDate", { "title": _("Captured"), "index": "getResultCaptureDate", "sortable": False}), - ("SubmittedBy", { - "title": _("Submitter"), - "sortable": False}), ("DueDate", { "title": _("Due Date"), "index": "getDueDate", "sortable": False}), + ("state_title", { + "title": _("Status"), + "sortable": False}), ("Hidden", { "title": _("Hidden"), "toggle": True, @@ -255,6 +256,32 @@ def before_render(self): super(AnalysesView, self).before_render() self.request.set("disable_plone.rightcolumn", 1) + @viewcache.memoize + def get_default_columns_order(self): + """Return the default column order from the registry + + :returns: List of column keys + """ + name = "sampleview_analysis_columns_order" + return get_registry_record(name, default=[]) + + def reorder_analysis_columns(self): + """Reorder analysis columns based on registry configuration + """ + columns_order = self.get_default_columns_order() + if not columns_order: + return + # compute columns that are missing in the config + missing_columns = filter( + lambda col: col not in columns_order, self.columns.keys()) + # prepare the new sort order for the columns + ordered_columns = columns_order + missing_columns + + # set the order in each review state + for rs in self.review_states: + # set a copy of the new ordered columns list + rs["columns"] = ordered_columns[:] + @property @viewcache.memoize def senaite_theme(self): @@ -658,7 +685,6 @@ def folderitem(self, obj, item, index): item['Keyword'] = obj.getKeyword item['Unit'] = format_supsub(obj.getUnit) if obj.getUnit else '' item['retested'] = obj.getRetestOfUID and True or False - item['class']['retested'] = 'center' item['replace']['Service'] = '{}'.format(obj.Title) # Append info link before the service @@ -789,9 +815,6 @@ def folderitems(self): pos = "Result" in state["columns"] and \ state["columns"].index("Uncertainty") + 1 or len( state["columns"]) - if "retested" in state["columns"]: - state["columns"].remove("retested") - state["columns"].insert(pos, "retested") new_states.append(state) self.review_states = new_states # Allow selecting individual analyses @@ -805,20 +828,19 @@ def folderitems(self): # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) - self.items = items # Display method and instrument columns only if at least one of the # analyses requires them to be displayed for selection - show_method_column = self.is_method_column_required() + show_method_column = self.is_method_column_required(items) if "Method" in self.columns: self.columns["Method"]["toggle"] = show_method_column - show_instrument_column = self.is_instrument_column_required() + show_instrument_column = self.is_instrument_column_required(items) if "Instrument" in self.columns: self.columns["Instrument"]["toggle"] = show_instrument_column # show unit selection column only if required - show_unit_column = self.is_unit_selection_column_required() + show_unit_column = self.is_unit_selection_column_required(items) if "Unit" in self.columns: self.columns["Unit"]["toggle"] = show_unit_column @@ -1661,34 +1683,34 @@ def is_unit_choices_required(self, analysis): return True return False - def is_method_column_required(self): + def is_method_column_required(self, items): """Returns whether the method column has to be rendered or not. Returns True if at least one of the analyses from the listing requires the list for method selection to be rendered """ - for item in self.items: + for item in items: obj = item.get("obj") if self.is_method_required(obj): return True return False - def is_instrument_column_required(self): + def is_instrument_column_required(self, items): """Returns whether the instrument column has to be rendered or not. Returns True if at least one of the analyses from the listing requires the list for instrument selection to be rendered """ - for item in self.items: + for item in items: obj = item.get("obj") if self.is_instrument_required(obj): return True return False - def is_unit_selection_column_required(self): + def is_unit_selection_column_required(self, items): """Returns whether the unit column has to be rendered or not. Returns True if at least one of the analyses from the listing requires the list for unit selection to be rendered """ - for item in self.items: + for item in items: obj = item.get("obj") if self.is_unit_choices_required(obj): return True diff --git a/src/bika/lims/browser/analysisrequest/tables.py b/src/bika/lims/browser/analysisrequest/tables.py index 96dfd0a51d..5925eedae7 100644 --- a/src/bika/lims/browser/analysisrequest/tables.py +++ b/src/bika/lims/browser/analysisrequest/tables.py @@ -40,6 +40,7 @@ def __init__(self, context, request): self.show_workflow_action_buttons = True self.show_select_column = True self.show_search = False + self.reorder_analysis_columns() class FieldAnalysesTable(AnalysesView): @@ -59,6 +60,7 @@ def __init__(self, context, request): self.show_workflow_action_buttons = True self.show_select_column = True self.show_search = False + self.reorder_analysis_columns() class QCAnalysesTable(QCAnalysesView): @@ -73,3 +75,4 @@ def __init__(self, context, request): self.show_select_column = False self.show_workflow_action_buttons = False self.show_search = False + self.reorder_analysis_columns() diff --git a/src/senaite/core/profiles/default/metadata.xml b/src/senaite/core/profiles/default/metadata.xml index a12d76cc41..af18eb68da 100644 --- a/src/senaite/core/profiles/default/metadata.xml +++ b/src/senaite/core/profiles/default/metadata.xml @@ -1,6 +1,6 @@ - 2421 + 2422 profile-Products.ATContentTypes:base profile-Products.CMFEditions:CMFEditions diff --git a/src/senaite/core/registry/schema.py b/src/senaite/core/registry/schema.py index b32d676991..120be41b6e 100644 --- a/src/senaite/core/registry/schema.py +++ b/src/senaite/core/registry/schema.py @@ -21,6 +21,7 @@ class ISampleViewRegistry(ISenaiteRegistry): "sampleview_collapse_field_analysis_table", "sampleview_collapse_lab_analysis_table", "sampleview_collapse_qc_analysis_table", + "sampleview_analysis_columns_order", ], ) sampleview_collapse_field_analysis_table = schema.Bool( @@ -44,6 +45,35 @@ class ISampleViewRegistry(ISenaiteRegistry): required=False, ) + sampleview_analysis_columns_order = schema.List( + title=_(u"Analysis columns order"), + description=_( + u"Default column order for sample analysis listings" + ), + value_type=schema.ASCIILine(title=u"Column"), + required=False, + default=[ + "created", + "Service", + "DetectionLimitOperand", + "Result", + "Uncertainty", + "Unit", + "Specification", + "retested", + "Method", + "Instrument", + "Calculation", + "Attachments", + "SubmittedBy", + "Analyst", + "CaptureDate", + "DueDate", + "state_title", + "Hidden", + ] + ) + class ISampleHeaderRegistry(ISenaiteRegistry): """Registry settings for sample header configuration diff --git a/src/senaite/core/upgrade/v02_04_000.zcml b/src/senaite/core/upgrade/v02_04_000.zcml index de6b2bb4bc..0733a32610 100644 --- a/src/senaite/core/upgrade/v02_04_000.zcml +++ b/src/senaite/core/upgrade/v02_04_000.zcml @@ -194,4 +194,12 @@ handler="senaite.core.upgrade.v02_04_000.import_registry" profile="senaite.core:default"/> + +