From b6ed0e0371866d1a67a0baef8bed2076132c6861 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 26 Jun 2026 07:32:14 +0200 Subject: [PATCH 1/3] Show analysis service keyword as code across service listings Render the analysis service keyword consistently as a chip in the service/analysis listings so analyses that share a title (e.g. several "Cs-137" services) can be told apart by their keyword. - Add a Keyword column to the AR "Manage Analyses" view - Wrap the keyword in in the Analysis Services control panel, the specification, profile, services and AR template analyses widgets - In the partition "Manage Partitions" view, show the keyword in parentheses next to the analysis name and add the service info icon - Fix the partition row click handler so clicking the info icon no longer toggles the analysis checkbox - Fix the partition caret icon staying stuck after an info overlay re-dispatches DOMContentLoaded by binding the collapse handlers once --- .../analysisrequest/manage_analyses.py | 7 ++++- .../widgets/analysisprofileanalyseswidget.py | 4 ++- .../widgets/analysisspecificationwidget.py | 1 + .../widgets/artemplateanalyseswidget.py | 4 +++ .../lims/browser/widgets/serviceswidget.py | 4 +++ .../controlpanel/bika_analysisservices.py | 5 ++++ .../core/browser/samples/partition_magic.py | 1 + .../samples/templates/partition_magic.pt | 30 ++++++++++++------- .../static/js/senaite.core.partitionmagic.js | 10 +++++-- 9 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/bika/lims/browser/analysisrequest/manage_analyses.py b/src/bika/lims/browser/analysisrequest/manage_analyses.py index da29b3a67c..c5ddd221a6 100644 --- a/src/bika/lims/browser/analysisrequest/manage_analyses.py +++ b/src/bika/lims/browser/analysisrequest/manage_analyses.py @@ -74,6 +74,9 @@ def __init__(self, context, request): "title": _("Service"), "index": "sortable_title", "sortable": False}), + ("Keyword", { + "title": _("Keyword"), + "sortable": False}), ("ResultUnit", { "title": _("Unit"), "sortable": False}), @@ -94,7 +97,7 @@ def __init__(self, context, request): "title": _("Max")}), )) - columns = ["Title", "Unit", "Hidden", ] + columns = ["Title", "Keyword", "Unit", "Hidden", ] if self.show_prices(): columns.append("Price") if self.show_ar_specs(): @@ -238,6 +241,8 @@ def folderitem(self, obj, item, index): spec = rr.get(keyword, ResultsRangeDict()) item["Title"] = obj.Title() + item["Keyword"] = keyword + item["replace"]["Keyword"] = "{}".format(keyword) item["ResultUnit"] = obj.getUnit() item["Price"] = price item["before"]["Price"] = self.get_currency_symbol() diff --git a/src/bika/lims/browser/widgets/analysisprofileanalyseswidget.py b/src/bika/lims/browser/widgets/analysisprofileanalyseswidget.py index fd3cb9ff21..5e1b00a47e 100644 --- a/src/bika/lims/browser/widgets/analysisprofileanalyseswidget.py +++ b/src/bika/lims/browser/widgets/analysisprofileanalyseswidget.py @@ -225,7 +225,9 @@ def folderitem(self, obj, item, index): item["selected"] = False item["Hidden"] = hidden item["selected"] = uid in self.configuration - item["Keyword"] = obj.getKeyword() + keyword = obj.getKeyword() + item["Keyword"] = keyword + item["replace"]["Keyword"] = "{}".format(keyword) # Add methods methods = obj.getMethods() diff --git a/src/bika/lims/browser/widgets/analysisspecificationwidget.py b/src/bika/lims/browser/widgets/analysisspecificationwidget.py index c1d1c71e94..a310621f2f 100644 --- a/src/bika/lims/browser/widgets/analysisspecificationwidget.py +++ b/src/bika/lims/browser/widgets/analysisspecificationwidget.py @@ -248,6 +248,7 @@ def folderitem(self, obj, item, index): item["Title"] = title item["Keyword"] = keyword + item["replace"]["Keyword"] = "{}".format(keyword) item["replace"]["Title"] = get_link(url, value=title) item["choices"]["min_operator"] = self.min_operator_choices item["choices"]["max_operator"] = self.max_operator_choices diff --git a/src/bika/lims/browser/widgets/artemplateanalyseswidget.py b/src/bika/lims/browser/widgets/artemplateanalyseswidget.py index eda2e167a6..886a128265 100644 --- a/src/bika/lims/browser/widgets/artemplateanalyseswidget.py +++ b/src/bika/lims/browser/widgets/artemplateanalyseswidget.py @@ -236,6 +236,10 @@ def folderitem(self, obj, item, index): item["Hidden"] = hidden item["selected"] = uid in self.configuration + keyword = obj.getKeyword() + item["Keyword"] = keyword + item["replace"]["Keyword"] = "{}".format(keyword) + # Make partition a required field item.setdefault("required", []).append("Partition") diff --git a/src/bika/lims/browser/widgets/serviceswidget.py b/src/bika/lims/browser/widgets/serviceswidget.py index 6b1e0f008e..4d56ad8d57 100644 --- a/src/bika/lims/browser/widgets/serviceswidget.py +++ b/src/bika/lims/browser/widgets/serviceswidget.py @@ -152,6 +152,10 @@ def folderitem(self, obj, item, index): item["selected"] = False item["selected"] = uid in self.selected_services_uids + keyword = obj.getKeyword() + item["Keyword"] = keyword + item["replace"]["Keyword"] = "{}".format(keyword) + # Add methods methods = obj.getMethods() if methods: diff --git a/src/bika/lims/controlpanel/bika_analysisservices.py b/src/bika/lims/controlpanel/bika_analysisservices.py index ae8279c96a..b4784a7172 100644 --- a/src/bika/lims/controlpanel/bika_analysisservices.py +++ b/src/bika/lims/controlpanel/bika_analysisservices.py @@ -359,6 +359,11 @@ def folderitem(self, obj, item, index): item["Department"] = title item["replace"]["Department"] = get_link(url, title) + # Keyword + keyword = obj.getKeyword() + if keyword: + item["replace"]["Keyword"] = "{}".format(keyword) + # Unit unit = obj.getUnit() item["Unit"] = unit and format_supsub(unit) or "" diff --git a/src/senaite/core/browser/samples/partition_magic.py b/src/senaite/core/browser/samples/partition_magic.py index aadaea7bbe..0bcd23fae4 100644 --- a/src/senaite/core/browser/samples/partition_magic.py +++ b/src/senaite/core/browser/samples/partition_magic.py @@ -224,6 +224,7 @@ def get_analysis_data_for(self, ar): info = self.get_base_info(an) info.update({ "service_uid": an.getServiceUID(), + "keyword": an.getKeyword(), }) out.append(info) return out diff --git a/src/senaite/core/browser/samples/templates/partition_magic.pt b/src/senaite/core/browser/samples/templates/partition_magic.pt index 9c8b5881c6..5c5941f9f6 100644 --- a/src/senaite/core/browser/samples/templates/partition_magic.pt +++ b/src/senaite/core/browser/samples/templates/partition_magic.pt @@ -228,7 +228,15 @@ checked python:service_uid in template.get('analyses', {}).get(partition, [])"/> + + + + + () + @@ -272,16 +280,18 @@ diff --git a/src/senaite/core/browser/static/js/senaite.core.partitionmagic.js b/src/senaite/core/browser/static/js/senaite.core.partitionmagic.js index 920c307e43..f036de8092 100644 --- a/src/senaite/core/browser/static/js/senaite.core.partitionmagic.js +++ b/src/senaite/core/browser/static/js/senaite.core.partitionmagic.js @@ -25,13 +25,17 @@ window.PartitionController = class PartitionController { /** * Handles click on analysis row - * If user clicks on anything other than a checkbox, the row's checkbox is toggled + * If the user clicks on an empty area of the row, the row's checkbox is + * toggled. Clicks on interactive elements (links, inputs, labels, e.g. the + * service info icon) are ignored, so they keep their own behavior. */ on_analysis_click(event) { const $row = $(event.currentTarget); - if (event.target.type !== "checkbox") { - $row.find("input[type=checkbox]").trigger("click"); + if ($(event.target).closest("a, input, button, select, label").length) { + return; } + + $row.find("input[type=checkbox]").trigger("click"); } } From 7fbf1553de224051b9e21e41feb03d1b32fde7db Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 26 Jun 2026 07:32:56 +0200 Subject: [PATCH 2/3] Add changelog entry for #2963 --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 65377711e8..9feea25da0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ Changelog 2.7.0 (unreleased) ------------------ +- #2963 Show analysis service keyword as code across service listings - #2957 Harmonize form input widths via tunable CSS variables - #2956 Restrict client discount fields to lab staff - #2958 Add labels with colors, filtering, and bulk-manage modal for samples From 059558186ff8239c7dee63f3b95ef5d146fbc929 Mon Sep 17 00:00:00 2001 From: Ramon Bartl Date: Fri, 26 Jun 2026 07:45:52 +0200 Subject: [PATCH 3/3] Allow to search for the keyword in sample add form --- .../lims/browser/analysisrequest/templates/ar_add2.pt | 10 ++++++++++ .../static/js/senaite.core.analysisrequest.add.coffee | 7 +++++-- .../static/js/senaite.core.analysisrequest.add.js | 8 +++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/bika/lims/browser/analysisrequest/templates/ar_add2.pt b/src/bika/lims/browser/analysisrequest/templates/ar_add2.pt index 9bc456ac6e..9ed898da23 100644 --- a/src/bika/lims/browser/analysisrequest/templates/ar_add2.pt +++ b/src/bika/lims/browser/analysisrequest/templates/ar_add2.pt @@ -104,6 +104,15 @@ {{title}} + {{#if keyword}} + + Keyword + + {{keyword}} + + + {{/if}} {{#if unit}} diff --git a/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.coffee b/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.coffee index 7b10803ea4..35d0e7c6be 100644 --- a/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.coffee +++ b/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.coffee @@ -1343,8 +1343,11 @@ class window.AnalysisRequestAdd name_el = $("div.service-title", $service) name = name_el.html().toLowerCase() - # hide service if no match found and not checked for any sample - if name.indexOf(term) isnt -1 + # get the service keyword + keyword = ($service.data("keyword") or "").toString().toLowerCase() + + # match against the human name or the keyword + if name.indexOf(term) isnt -1 or keyword.indexOf(term) isnt -1 matches.push service_uid return matches diff --git a/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.js b/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.js index 43b3e6add8..15af9e547b 100644 --- a/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.js +++ b/src/senaite/core/browser/static/js/senaite.core.analysisrequest.add.js @@ -1590,7 +1590,7 @@ window.AnalysisRequestAdd = class AnalysisRequestAdd { // iterate through all registered services to find matches services = $(`tr.${poc}.service`); $.each(services, function(num, service) { - var $service, category_id, name, name_el, service_uid; + var $service, category_id, keyword, name, name_el, service_uid; // get the service basic info $service = $(service); category_id = $service.data("category"); @@ -1598,8 +1598,10 @@ window.AnalysisRequestAdd = class AnalysisRequestAdd { // get the service human name name_el = $("div.service-title", $service); name = name_el.html().toLowerCase(); - // hide service if no match found and not checked for any sample - if (name.indexOf(term) !== -1) { + // get the service keyword + keyword = ($service.data("keyword") || "").toString().toLowerCase(); + // match against the human name or the keyword + if (name.indexOf(term) !== -1 || keyword.indexOf(term) !== -1) { return matches.push(service_uid); } });