From 05c3919c89079e9ec31b876b6f8c811c94259956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Wed, 16 Oct 2019 22:55:25 +0200 Subject: [PATCH 01/25] Adapters support for Client field filter in Add2 --- bika/lims/browser/analysisrequest/add2.py | 30 +++++++---- .../js/bika.lims.analysisrequest.add.js | 38 +++---------- .../bika.lims.analysisrequest.add.coffee | 54 ++----------------- bika/lims/interfaces/__init__.py | 10 ++++ 4 files changed, 41 insertions(+), 91 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index d5f7f8e37f..831293d376 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -28,7 +28,8 @@ from bika.lims import logger from bika.lims.api.analysisservice import get_calculation_dependencies_for from bika.lims.api.analysisservice import get_service_dependencies_for -from bika.lims.interfaces import IGetDefaultFieldValueARAddHook +from bika.lims.interfaces import IGetDefaultFieldValueARAddHook, \ + IAddSampleFieldFilter from bika.lims.utils import tmpID from bika.lims.utils.analysisrequest import create_analysisrequest as crar from bika.lims.workflow import ActionHandlerPool @@ -42,6 +43,7 @@ from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from zope.annotation.interfaces import IAnnotations +from zope.component import getAdapters from zope.component import queryAdapter from zope.i18n.locales import locales from zope.interface import implements @@ -845,39 +847,45 @@ def get_client_info(self, obj): # catalog queries for UI field filtering filter_queries = { - "contact": { + "Contact": { "getParentUID": [uid] }, - "cc_contact": { + "CCContact": { "getParentUID": [uid] }, - "invoice_contact": { + "InvoiceContact": { "getParentUID": [uid] }, - "samplepoint": { + "SamplePoint": { "getClientUID": [uid, bika_samplepoints_uid], }, - "artemplates": { + "Template": { "getClientUID": [uid, bika_artemplates_uid], }, - "analysisprofiles": { + "Profiles": { "getClientUID": [uid, bika_analysisprofiles_uid], }, - "analysisspecs": { + "Specification": { "getClientUID": [uid, bika_analysisspecs_uid], }, - "samplinground": { + "SamplingRound": { "getParentUID": [uid], }, - "sample": { + "Sample": { "getClientUID": [uid], }, - "batch": { + "Batch": { "getClientUID": [uid, ""], } } info["filter_queries"] = filter_queries + # Maybe other add-ons have additional fields that require filtering too + import pdb;pdb.set_trace() + for name, ad in getAdapters((obj,), IAddSampleFieldFilter): + additional_filters = ad.get_info() + info["filter_queries"].update(additional_filters) + return info @cache(cache_key) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index e46d9ccd7a..7f563babf9 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -486,10 +486,8 @@ * Filter SamplingRound * Filter Batch */ - var contact_title, contact_uid, field, query; - field = $("#Contact-" + arnum); - query = client.filter_queries.contact; - this.set_reference_field_query(field, query); + var contact_title, contact_uid, me; + me = this; if (document.URL.indexOf("analysisrequests") > -1) { contact_title = client.default_contact.title; contact_uid = client.default_contact.uid; @@ -497,33 +495,11 @@ this.set_reference_field(field, contact_uid, contact_title); } } - field = $("#CCContact-" + arnum); - query = client.filter_queries.cc_contact; - this.set_reference_field_query(field, query); - field = $("#InvoiceContact-" + arnum); - query = client.filter_queries.invoice_contact; - this.set_reference_field_query(field, query); - field = $("#SamplePoint-" + arnum); - query = client.filter_queries.samplepoint; - this.set_reference_field_query(field, query); - field = $("#Template-" + arnum); - query = client.filter_queries.artemplates; - this.set_reference_field_query(field, query); - field = $("#Profiles-" + arnum); - query = client.filter_queries.analysisprofiles; - this.set_reference_field_query(field, query); - field = $("#Specification-" + arnum); - query = client.filter_queries.analysisspecs; - this.set_reference_field_query(field, query); - field = $("#SamplingRound-" + arnum); - query = client.filter_queries.samplinground; - this.set_reference_field_query(field, query); - field = $("#PrimaryAnalysisRequest-" + arnum); - query = client.filter_queries.sample; - this.set_reference_field_query(field, query); - field = $("#Batch-" + arnum); - query = client.filter_queries.batch; - return this.set_reference_field_query(field, query); + return $.each(client.filter_queries, function(field_name, query) { + var field; + field = $("#" + field_name + ("-" + arnum)); + return me.set_reference_field_query(field, query); + }); }; AnalysisRequestAdd.prototype.set_contact = function(arnum, contact) { diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 519e22375b..5dff3e3284 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -495,10 +495,7 @@ class window.AnalysisRequestAdd * Filter Batch ### - # filter Contacts - field = $("#Contact-#{arnum}") - query = client.filter_queries.contact - @set_reference_field_query field, query + me = this # handle default contact for /analysisrequests listing # https://github.com/senaite/senaite.core/issues/705 @@ -508,51 +505,10 @@ class window.AnalysisRequestAdd if contact_title and contact_uid @set_reference_field field, contact_uid, contact_title - # filter CCContacts - field = $("#CCContact-#{arnum}") - query = client.filter_queries.cc_contact - @set_reference_field_query field, query - - # filter InvoiceContact - # XXX Where is this field? - field = $("#InvoiceContact-#{arnum}") - query = client.filter_queries.invoice_contact - @set_reference_field_query field, query - - # filter Sample Points - field = $("#SamplePoint-#{arnum}") - query = client.filter_queries.samplepoint - @set_reference_field_query field, query - - # filter AR Templates - field = $("#Template-#{arnum}") - query = client.filter_queries.artemplates - @set_reference_field_query field, query - - # filter Analysis Profiles - field = $("#Profiles-#{arnum}") - query = client.filter_queries.analysisprofiles - @set_reference_field_query field, query - - # filter Analysis Specs - field = $("#Specification-#{arnum}") - query = client.filter_queries.analysisspecs - @set_reference_field_query field, query - - # filter Samplinground - field = $("#SamplingRound-#{arnum}") - query = client.filter_queries.samplinground - @set_reference_field_query field, query - - # filter Sample - field = $("#PrimaryAnalysisRequest-#{arnum}") - query = client.filter_queries.sample - @set_reference_field_query field, query - - # filter Batch - field = $("#Batch-#{arnum}") - query = client.filter_queries.batch - @set_reference_field_query field, query + # Apply search filters to other fields + $.each client.filter_queries, (field_name, query) -> + field = $("#" + field_name + "-#{arnum}") + me.set_reference_field_query field, query set_contact: (arnum, contact) => diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index 9e80020f2e..e1e3da1444 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -961,3 +961,13 @@ class IGuardAdapter(Interface): def guard(self, transition): """Return False if you want to block the transition """ + + +class IAddSampleFieldFilter(Interface): + """Marker interface for field filters for Add Sample form + """ + + def get_info(self): + """Returns a dict where the key is the name of the field to be filtered + and the value is a dict with the search criteria + """ From 5798d6cdae907d218e2ce4f0ad60ddb7fce668d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Wed, 16 Oct 2019 22:58:44 +0200 Subject: [PATCH 02/25] Remove pdb --- bika/lims/browser/analysisrequest/add2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 831293d376..4480fc1eab 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -881,7 +881,6 @@ def get_client_info(self, obj): info["filter_queries"] = filter_queries # Maybe other add-ons have additional fields that require filtering too - import pdb;pdb.set_trace() for name, ad in getAdapters((obj,), IAddSampleFieldFilter): additional_filters = ad.get_info() info["filter_queries"].update(additional_filters) From 76226c9d7391ff018aa1c7859cc5c7b52571f44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 09:19:06 +0200 Subject: [PATCH 03/25] Support in Add Sample view for fields flush --- bika/lims/browser/analysisrequest/add2.py | 37 +++++++++++- .../js/bika.lims.analysisrequest.add.js | 58 +++++++++++------- .../bika.lims.analysisrequest.add.coffee | 60 ++++++++++--------- bika/lims/interfaces/__init__.py | 10 ++++ 4 files changed, 115 insertions(+), 50 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 4480fc1eab..8786846c53 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -29,7 +29,7 @@ from bika.lims.api.analysisservice import get_calculation_dependencies_for from bika.lims.api.analysisservice import get_service_dependencies_for from bika.lims.interfaces import IGetDefaultFieldValueARAddHook, \ - IAddSampleFieldFilter + IAddSampleFieldFilter, IAddSampleFieldsFlush from bika.lims.utils import tmpID from bika.lims.utils.analysisrequest import create_analysisrequest as crar from bika.lims.workflow import ActionHandlerPool @@ -882,6 +882,7 @@ def get_client_info(self, obj): # Maybe other add-ons have additional fields that require filtering too for name, ad in getAdapters((obj,), IAddSampleFieldFilter): + logger.info("Additional field filters from {}".format(name)) additional_filters = ad.get_info() info["filter_queries"].update(additional_filters) @@ -1212,6 +1213,40 @@ def ajax_get_global_settings(self): } return settings + def ajax_get_flush_settings(self): + """Returns the settings for fields flush + """ + flush_settings = { + "Client": [ + "Contact", + "CCContact", + "InvoiceContact", + "SamplePoint", + "Template", + "Profiles", + "PrimaryAnalysisRequest", + "Specification", + "Batch" + ], + "Contact": [ + "CCContact" + ], + "SampleType": [ + "SamplePoint", + "Specification" + ] + } + + # Maybe other add-ons have additional fields that require flushing + for name, ad in getAdapters((self.context,), IAddSampleFieldsFlush): + logger.info("Additional flush settings from {}".format(name)) + additional_settings = ad.get_flush_settings() + for key, values in additional_settings.items(): + new_values = flush_settings.get(key, []) + values + flush_settings[key] = list(set(new_values)) + + return flush_settings + def ajax_get_service(self): """Returns the services information """ diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 7f563babf9..f3d28eb7a8 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -51,6 +51,7 @@ this.update_form = bind(this.update_form, this); this.recalculate_prices = bind(this.recalculate_prices, this); this.recalculate_records = bind(this.recalculate_records, this); + this.get_flush_settings = bind(this.get_flush_settings, this); this.get_global_settings = bind(this.get_global_settings, this); this.render_template = bind(this.render_template, this); this.template_dialog = bind(this.template_dialog, this); @@ -65,12 +66,14 @@ this._ = window.jarn.i18n.MessageFactory("senaite.core"); $('input[type=text]').prop('autocomplete', 'off'); this.global_settings = {}; + this.flush_settings = {}; this.records_snapshot = {}; this.applied_templates = {}; $(".blurrable").removeClass("blurrable"); this.bind_eventhandler(); this.init_file_fields(); this.get_global_settings(); + this.get_flush_settings(); return this.recalculate_records(); }; @@ -203,6 +206,18 @@ }); }; + AnalysisRequestAdd.prototype.get_flush_settings = function() { + + /* + * Retrieve the flush settings + */ + return this.ajax_post_form("get_flush_settings").done(function(settings) { + console.debug("Flush settings:", settings); + this.flush_settings = settings; + return $(this).trigger("flush_settings:updated", settings); + }); + }; + AnalysisRequestAdd.prototype.recalculate_records = function() { /* @@ -372,6 +387,22 @@ return $(field_id); }; + AnalysisRequestAdd.prototype.flush_fields_for = function(field_name, arnum) { + + /* + * Flush dependant fields + */ + var field_ids, me; + me = this; + field_ids = this.flush_settings[field_name]; + return $.each(this.flush_settings[field_name], function(index, id) { + var field; + console.debug("flushing: id=" + id); + field = $("#" + id + "-" + arnum); + return me.flush_reference_field(field); + }); + }; + AnalysisRequestAdd.prototype.flush_reference_field = function(field) { /* @@ -759,19 +790,14 @@ /* * Eventhandler when the client changed (happens on Batches) */ - var $el, arnum, el, field_ids, me, uid; + var $el, arnum, el, me, uid; me = this; el = event.currentTarget; $el = $(el); uid = $el.attr("uid"); arnum = $el.closest("[arnum]").attr("arnum"); console.debug("°°° on_client_changed: arnum=" + arnum + " °°°"); - field_ids = ["Contact", "CCContact", "InvoiceContact", "SamplePoint", "Template", "Profiles", "PrimaryAnalysisRequest", "Specification", "Batch"]; - $.each(field_ids, function(index, id) { - var field; - field = me.get_field_by_id(id, arnum); - return me.flush_reference_field(field); - }); + me.flush_fields_for("Client", arnum); return $(me).trigger("form:changed"); }; @@ -780,19 +806,14 @@ /* * Eventhandler when the contact changed */ - var $el, arnum, el, field_ids, me, uid; + var $el, arnum, el, me, uid; me = this; el = event.currentTarget; $el = $(el); uid = $el.attr("uid"); arnum = $el.closest("[arnum]").attr("arnum"); console.debug("°°° on_contact_changed: arnum=" + arnum + " °°°"); - field_ids = ["CCContact"]; - $.each(field_ids, function(index, id) { - var field; - field = me.get_field_by_id(id, arnum); - return me.flush_reference_field(field); - }); + me.flush_fields_for("Contact", arnum); return $(me).trigger("form:changed"); }; @@ -921,7 +942,7 @@ * Eventhandler when the SampleType was changed. * Fires form:changed event */ - var $el, arnum, el, field_ids, has_sampletype_selected, me, uid, val; + var $el, arnum, el, has_sampletype_selected, me, uid, val; me = this; el = event.currentTarget; $el = $(el); @@ -933,12 +954,7 @@ if (!has_sampletype_selected) { $("input[type=hidden]", $el.parent()).val(""); } - field_ids = ["SamplePoint", "Specification"]; - $.each(field_ids, function(index, id) { - var field; - field = me.get_field_by_id(id, arnum); - return me.flush_reference_field(field); - }); + me.flush_fields_for("SampleType", arnum); return $(me).trigger("form:changed"); }; diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 5dff3e3284..396482783a 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -18,6 +18,9 @@ class window.AnalysisRequestAdd # storage for global Bika settings @global_settings = {} + # storage for mapping of fields to flush on_change + @flush_settings = {} + # services data snapshot from recalculate_records # returns a mapping of arnum -> services data @records_snapshot = {} @@ -40,6 +43,9 @@ class window.AnalysisRequestAdd # get the global settings on load @get_global_settings() + # get the flush settings + @get_flush_settings() + # recalculate records on load (needed for AR copies) @recalculate_records() @@ -189,6 +195,16 @@ class window.AnalysisRequestAdd $(@).trigger "settings:updated", settings + get_flush_settings: => + ### + * Retrieve the flush settings + ### + @ajax_post_form("get_flush_settings").done (settings) -> + console.debug "Flush settings:", settings + @flush_settings = settings + $(@).trigger "flush_settings:updated", settings + + recalculate_records: => ### * Submit all form values to the server to recalculate the records @@ -369,6 +385,18 @@ class window.AnalysisRequestAdd return $(field_id) + flush_fields_for: (field_name, arnum) -> + ### + * Flush dependant fields + ### + me = this + field_ids = @flush_settings[field_name] + $.each @flush_settings[field_name], (index, id) -> + console.debug "flushing: id=#{id}" + field = $("##{id}-#{arnum}") + me.flush_reference_field field + + flush_reference_field: (field) -> ### * Empty the reference field @@ -815,20 +843,7 @@ class window.AnalysisRequestAdd console.debug "°°° on_client_changed: arnum=#{arnum} °°°" # Flush client depending fields - field_ids = [ - "Contact" - "CCContact" - "InvoiceContact" - "SamplePoint" - "Template" - "Profiles" - "PrimaryAnalysisRequest" - "Specification" - "Batch" - ] - $.each field_ids, (index, id) -> - field = me.get_field_by_id id, arnum - me.flush_reference_field field + me.flush_fields_for "Client", arnum # trigger form:changed event $(me).trigger "form:changed" @@ -847,13 +862,8 @@ class window.AnalysisRequestAdd console.debug "°°° on_contact_changed: arnum=#{arnum} °°°" - # Flush client depending fields - field_ids = [ - "CCContact" - ] - $.each field_ids, (index, id) -> - field = me.get_field_by_id id, arnum - me.flush_reference_field field + # Flush contact depending fields + me.flush_fields_for "Contact", arnum # trigger form:changed event $(me).trigger "form:changed" @@ -1008,13 +1018,7 @@ class window.AnalysisRequestAdd $("input[type=hidden]", $el.parent()).val("") # Flush sampletype depending fields - field_ids = [ - "SamplePoint" - "Specification" - ] - $.each field_ids, (index, id) -> - field = me.get_field_by_id id, arnum - me.flush_reference_field field + me.flush_fields_for "SampleType", arnum # trigger form:changed event $(me).trigger "form:changed" diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index e1e3da1444..caea8ff3d8 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -971,3 +971,13 @@ def get_info(self): """Returns a dict where the key is the name of the field to be filtered and the value is a dict with the search criteria """ + + +class IAddSampleFieldsFlush(Interface): + """Marker interface for field dependencies flush for Add Sample form + """ + + def get_flush_settings(self): + """Returns a dict where the key is the name of the field and the value + is an array dependencies as field names + """ From d9b50baf06869f7d40f1233c0ce8bc4015d1791f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 13:24:38 +0200 Subject: [PATCH 04/25] Generalize on_changed event for reference input fields in Add Sample --- .../js/bika.lims.analysisrequest.add.js | 34 ++++----------- .../bika.lims.analysisrequest.add.coffee | 42 ++++--------------- 2 files changed, 17 insertions(+), 59 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index f3d28eb7a8..7a685397f3 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -29,8 +29,7 @@ this.on_analysis_lock_button_click = bind(this.on_analysis_lock_button_click, this); this.on_analysis_details_click = bind(this.on_analysis_details_click, this); this.on_analysis_specification_changed = bind(this.on_analysis_specification_changed, this); - this.on_contact_changed = bind(this.on_contact_changed, this); - this.on_client_changed = bind(this.on_client_changed, this); + this.on_referencefield_value_changed = bind(this.on_referencefield_value_changed, this); this.hide_all_service_info = bind(this.hide_all_service_info, this); this.get_service = bind(this.get_service, this); this.set_service_spec = bind(this.set_service_spec, this); @@ -96,8 +95,7 @@ $("body").on("click", "tr[fieldname=Composite] input[type='checkbox']", this.recalculate_records); $("body").on("click", "tr[fieldname=InvoiceExclude] input[type='checkbox']", this.recalculate_records); $("body").on("click", "tr[fieldname=Analyses] input[type='checkbox']", this.on_analysis_checkbox_click); - $("body").on("selected change", "tr[fieldname=Client] input[type='text']", this.on_client_changed); - $("body").on("selected change", "tr[fieldname=Contact] input[type='text']", this.on_contact_changed); + $("body").on("selected change", "input[type='text'].referencewidget", this.on_referencefield_value_changed); $("body").on("change", "input.min", this.on_analysis_specification_changed); $("body").on("change", "input.max", this.on_analysis_specification_changed); $("body").on("change", "input.warn_min", this.on_analysis_specification_changed); @@ -785,35 +783,20 @@ /* EVENT HANDLER */ - AnalysisRequestAdd.prototype.on_client_changed = function(event) { + AnalysisRequestAdd.prototype.on_referencefield_value_changed = function(event) { /* - * Eventhandler when the client changed (happens on Batches) + * Generic event handler for when a field value changes */ - var $el, arnum, el, me, uid; + var $el, arnum, el, field_name, me, uid; me = this; el = event.currentTarget; $el = $(el); uid = $el.attr("uid"); + field_name = $el.closest("tr[fieldname]").attr("fieldname"); arnum = $el.closest("[arnum]").attr("arnum"); - console.debug("°°° on_client_changed: arnum=" + arnum + " °°°"); - me.flush_fields_for("Client", arnum); - return $(me).trigger("form:changed"); - }; - - AnalysisRequestAdd.prototype.on_contact_changed = function(event) { - - /* - * Eventhandler when the contact changed - */ - var $el, arnum, el, me, uid; - me = this; - el = event.currentTarget; - $el = $(el); - uid = $el.attr("uid"); - arnum = $el.closest("[arnum]").attr("arnum"); - console.debug("°°° on_contact_changed: arnum=" + arnum + " °°°"); - me.flush_fields_for("Contact", arnum); + console.debug("°°° on_field_value_changed: field_name=" + field_name + " arnum=" + arnum + " °°°"); + me.flush_fields_for(field_name, arnum); return $(me).trigger("form:changed"); }; @@ -954,7 +937,6 @@ if (!has_sampletype_selected) { $("input[type=hidden]", $el.parent()).val(""); } - me.flush_fields_for("SampleType", arnum); return $(me).trigger("form:changed"); }; diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 396482783a..85156e7a04 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -73,10 +73,9 @@ class window.AnalysisRequestAdd $("body").on "click", "tr[fieldname=InvoiceExclude] input[type='checkbox']", @recalculate_records # Analysis Checkbox clicked $("body").on "click", "tr[fieldname=Analyses] input[type='checkbox']", @on_analysis_checkbox_click - # Client changed - $("body").on "selected change", "tr[fieldname=Client] input[type='text']", @on_client_changed - # Contact changed - $("body").on "selected change", "tr[fieldname=Contact] input[type='text']", @on_contact_changed + # Generic onchange event handler for reference fields + $("body").on "selected change" , "input[type='text'].referencewidget", @on_referencefield_value_changed + # Analysis Specification changed $("body").on "change", "input.min", @on_analysis_specification_changed $("body").on "change", "input.max", @on_analysis_specification_changed @@ -829,41 +828,21 @@ class window.AnalysisRequestAdd ### EVENT HANDLER ### - on_client_changed: (event) => - ### - * Eventhandler when the client changed (happens on Batches) - ### - - me = this - el = event.currentTarget - $el = $(el) - uid = $el.attr "uid" - arnum = $el.closest("[arnum]").attr "arnum" - - console.debug "°°° on_client_changed: arnum=#{arnum} °°°" - - # Flush client depending fields - me.flush_fields_for "Client", arnum - - # trigger form:changed event - $(me).trigger "form:changed" - - - on_contact_changed: (event) => + on_referencefield_value_changed: (event) => ### - * Eventhandler when the contact changed + * Generic event handler for when a field value changes ### - me = this el = event.currentTarget $el = $(el) uid = $el.attr "uid" + field_name = $el.closest("tr[fieldname]").attr "fieldname" arnum = $el.closest("[arnum]").attr "arnum" - console.debug "°°° on_contact_changed: arnum=#{arnum} °°°" + console.debug "°°° on_field_value_changed: field_name=#{field_name} arnum=#{arnum} °°°" - # Flush contact depending fields - me.flush_fields_for "Contact", arnum + # Flush depending fields + me.flush_fields_for field_name, arnum # trigger form:changed event $(me).trigger "form:changed" @@ -1017,9 +996,6 @@ class window.AnalysisRequestAdd # XXX manually flush UID field $("input[type=hidden]", $el.parent()).val("") - # Flush sampletype depending fields - me.flush_fields_for "SampleType", arnum - # trigger form:changed event $(me).trigger "form:changed" From 53f3a80e9a834bbb86eb91c00152be03c351e80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 14:08:21 +0200 Subject: [PATCH 05/25] Flush the uid of reference fields when empty in generic on_change handler --- .../js/bika.lims.analysisrequest.add.js | 53 ++--------------- .../bika.lims.analysisrequest.add.coffee | 58 ++----------------- 2 files changed, 12 insertions(+), 99 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 7a685397f3..f37c981088 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -23,8 +23,6 @@ this.on_analysis_profile_removed = bind(this.on_analysis_profile_removed, this); this.on_analysis_profile_selected = bind(this.on_analysis_profile_selected, this); this.on_analysis_template_changed = bind(this.on_analysis_template_changed, this); - this.on_specification_changed = bind(this.on_specification_changed, this); - this.on_sampletype_changed = bind(this.on_sampletype_changed, this); this.on_sample_changed = bind(this.on_sample_changed, this); this.on_analysis_lock_button_click = bind(this.on_analysis_lock_button_click, this); this.on_analysis_details_click = bind(this.on_analysis_details_click, this); @@ -103,8 +101,6 @@ $("body").on("click", ".service-lockbtn", this.on_analysis_lock_button_click); $("body").on("click", ".service-infobtn", this.on_analysis_details_click); $("body").on("selected change", "tr[fieldname=PrimaryAnalysisRequest] input[type='text']", this.on_sample_changed); - $("body").on("selected change", "tr[fieldname=SampleType] input[type='text']", this.on_sampletype_changed); - $("body").on("selected change", "tr[fieldname=Specification] input[type='text']", this.on_specification_changed); $("body").on("selected change", "tr[fieldname=Template] input[type='text']", this.on_analysis_template_changed); $("body").on("selected", "tr[fieldname=Profiles] input[type='text']", this.on_analysis_profile_selected); $("body").on("click", "tr[fieldname=Profiles] img.deletebtn", this.on_analysis_profile_removed); @@ -788,15 +784,19 @@ /* * Generic event handler for when a field value changes */ - var $el, arnum, el, field_name, me, uid; + var $el, arnum, el, field_name, has_value, me, uid; me = this; el = event.currentTarget; $el = $(el); + has_value = $el.val(); uid = $el.attr("uid"); field_name = $el.closest("tr[fieldname]").attr("fieldname"); arnum = $el.closest("[arnum]").attr("arnum"); - console.debug("°°° on_field_value_changed: field_name=" + field_name + " arnum=" + arnum + " °°°"); + console.debug("°°° on_referencefield_value_changed: field_name=" + field_name + " arnum=" + arnum + " °°°"); me.flush_fields_for(field_name, arnum); + if (!has_value) { + $("input[type=hidden]", $el.parent()).val(""); + } return $(me).trigger("form:changed"); }; @@ -919,47 +919,6 @@ return $(me).trigger("form:changed"); }; - AnalysisRequestAdd.prototype.on_sampletype_changed = function(event) { - - /* - * Eventhandler when the SampleType was changed. - * Fires form:changed event - */ - var $el, arnum, el, has_sampletype_selected, me, uid, val; - me = this; - el = event.currentTarget; - $el = $(el); - uid = $(el).attr("uid"); - val = $el.val(); - arnum = $el.closest("[arnum]").attr("arnum"); - has_sampletype_selected = $el.val(); - console.debug("°°° on_sampletype_change::UID=" + uid + " SampleType=" + val + "°°°"); - if (!has_sampletype_selected) { - $("input[type=hidden]", $el.parent()).val(""); - } - return $(me).trigger("form:changed"); - }; - - AnalysisRequestAdd.prototype.on_specification_changed = function(event) { - - /* - * Eventhandler when the Specification was changed. - */ - var $el, arnum, el, has_specification_selected, me, uid, val; - me = this; - el = event.currentTarget; - $el = $(el); - uid = $(el).attr("uid"); - val = $el.val(); - arnum = $el.closest("[arnum]").attr("arnum"); - has_specification_selected = $el.val(); - console.debug("°°° on_specification_change::UID=" + uid + " Specification=" + val + "°°°"); - if (!has_specification_selected) { - $("input[type=hidden]", $el.parent()).val(""); - } - return $(me).trigger("form:changed"); - }; - AnalysisRequestAdd.prototype.on_analysis_template_changed = function(event) { /* diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 85156e7a04..186f66c49c 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -87,10 +87,6 @@ class window.AnalysisRequestAdd $("body").on "click", ".service-infobtn", @on_analysis_details_click # Sample changed $("body").on "selected change", "tr[fieldname=PrimaryAnalysisRequest] input[type='text']", @on_sample_changed - # SampleType changed - $("body").on "selected change", "tr[fieldname=SampleType] input[type='text']", @on_sampletype_changed - # Specification changed - $("body").on "selected change", "tr[fieldname=Specification] input[type='text']", @on_specification_changed # Analysis Template changed $("body").on "selected change", "tr[fieldname=Template] input[type='text']", @on_analysis_template_changed # Analysis Profile selected @@ -835,15 +831,20 @@ class window.AnalysisRequestAdd me = this el = event.currentTarget $el = $(el) + has_value = $el.val() uid = $el.attr "uid" field_name = $el.closest("tr[fieldname]").attr "fieldname" arnum = $el.closest("[arnum]").attr "arnum" - console.debug "°°° on_field_value_changed: field_name=#{field_name} arnum=#{arnum} °°°" + console.debug "°°° on_referencefield_value_changed: field_name=#{field_name} arnum=#{arnum} °°°" # Flush depending fields me.flush_fields_for field_name, arnum + # Manually flush UID field if the field does not have a selected value + if not has_value + $("input[type=hidden]", $el.parent()).val("") + # trigger form:changed event $(me).trigger "form:changed" @@ -976,53 +977,6 @@ class window.AnalysisRequestAdd $(me).trigger "form:changed" - on_sampletype_changed: (event) => - ### - * Eventhandler when the SampleType was changed. - * Fires form:changed event - ### - - me = this - el = event.currentTarget - $el = $(el) - uid = $(el).attr "uid" - val = $el.val() - arnum = $el.closest("[arnum]").attr "arnum" - has_sampletype_selected = $el.val() - console.debug "°°° on_sampletype_change::UID=#{uid} SampleType=#{val}°°°" - - # deselect the sampletype if the field is empty - if not has_sampletype_selected - # XXX manually flush UID field - $("input[type=hidden]", $el.parent()).val("") - - # trigger form:changed event - $(me).trigger "form:changed" - - - on_specification_changed: (event) => - ### - * Eventhandler when the Specification was changed. - ### - - me = this - el = event.currentTarget - $el = $(el) - uid = $(el).attr "uid" - val = $el.val() - arnum = $el.closest("[arnum]").attr "arnum" - has_specification_selected = $el.val() - console.debug "°°° on_specification_change::UID=#{uid} Specification=#{val}°°°" - - # deselect the specification if the field is empty - if not has_specification_selected - # XXX manually flush UID field - $("input[type=hidden]", $el.parent()).val("") - - # trigger form:changed event - $(me).trigger "form:changed" - - on_analysis_template_changed: (event) => ### * Eventhandler when an Analysis Template was changed. From b6275bcce69730f4889e605b6c577b05f1c943e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 16:00:31 +0200 Subject: [PATCH 06/25] Generic function for applying search filters --- .../js/bika.lims.analysisrequest.add.js | 23 +++++++++++++++---- .../bika.lims.analysisrequest.add.coffee | 17 +++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index f37c981088..5738348f61 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -381,6 +381,20 @@ return $(field_id); }; + AnalysisRequestAdd.prototype.apply_field_filters = function(record, arnum) { + + /* + * Apply search filters to dependendents + */ + var me; + me = this; + return $.each(record.filter_queries, function(field_name, query) { + var field; + field = $("#" + field_name + ("-" + arnum)); + return me.set_reference_field_query(field, query); + }); + }; + AnalysisRequestAdd.prototype.flush_fields_for = function(field_name, arnum) { /* @@ -520,11 +534,7 @@ this.set_reference_field(field, contact_uid, contact_title); } } - return $.each(client.filter_queries, function(field_name, query) { - var field; - field = $("#" + field_name + ("-" + arnum)); - return me.set_reference_field_query(field, query); - }); + return me.apply_field_filters(client, arnum); }; AnalysisRequestAdd.prototype.set_contact = function(arnum, contact) { @@ -792,6 +802,9 @@ uid = $el.attr("uid"); field_name = $el.closest("tr[fieldname]").attr("fieldname"); arnum = $el.closest("[arnum]").attr("arnum"); + if (field_name === "Template" || field_name === "Profiles") { + return; + } console.debug("°°° on_referencefield_value_changed: field_name=" + field_name + " arnum=" + arnum + " °°°"); me.flush_fields_for(field_name, arnum); if (!has_value) { diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 186f66c49c..3d1d704f03 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -380,6 +380,16 @@ class window.AnalysisRequestAdd return $(field_id) + apply_field_filters: (record, arnum) -> + ### + * Apply search filters to dependendents + ### + me = this + $.each record.filter_queries, (field_name, query) -> + field = $("#" + field_name + "-#{arnum}") + me.set_reference_field_query field, query + + flush_fields_for: (field_name, arnum) -> ### * Flush dependant fields @@ -529,9 +539,7 @@ class window.AnalysisRequestAdd @set_reference_field field, contact_uid, contact_title # Apply search filters to other fields - $.each client.filter_queries, (field_name, query) -> - field = $("#" + field_name + "-#{arnum}") - me.set_reference_field_query field, query + me.apply_field_filters client, arnum set_contact: (arnum, contact) => @@ -835,6 +843,9 @@ class window.AnalysisRequestAdd uid = $el.attr "uid" field_name = $el.closest("tr[fieldname]").attr "fieldname" arnum = $el.closest("[arnum]").attr "arnum" + if field_name in ["Template", "Profiles"] + # These fields have it's own event handler + return console.debug "°°° on_referencefield_value_changed: field_name=#{field_name} arnum=#{arnum} °°°" From 1afd3bd09ab95253ee905f540b4a181d1fec51e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 18:54:27 +0200 Subject: [PATCH 07/25] Generalize set_value function (e.g. set_client) to apply_field_value --- bika/lims/browser/analysisrequest/add2.py | 12 ++-- .../js/bika.lims.analysisrequest.add.js | 61 +++++++++++-------- .../bika.lims.analysisrequest.add.coffee | 59 +++++++++--------- 3 files changed, 71 insertions(+), 61 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 8786846c53..9431e92fa7 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -435,7 +435,7 @@ def get_default_contact(self, client=None): "query": path, "depth": 1 }, - "incactive_state": "active", + "is_active": True, } contacts = catalog(query) if len(contacts) == 1: @@ -804,6 +804,8 @@ def get_base_info(self, obj): "title": obj.Title(), "description": obj.Description(), "url": obj.absolute_url(), + "field_values": {}, + "filter_queries": {}, } return info @@ -817,11 +819,9 @@ def get_client_info(self, obj): default_contact_info = {} default_contact = self.get_default_contact(client=obj) if default_contact: - default_contact_info = self.get_contact_info(default_contact) - - info.update({ - "default_contact": default_contact_info - }) + info["field_values"].update({ + "Contact": self.get_contact_info(default_contact) + }) # UID of the client uid = api.get_uid(obj) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 5738348f61..0cd162ae02 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -36,7 +36,6 @@ this.set_sampletype = bind(this.set_sampletype, this); this.set_sample = bind(this.set_sample, this); this.set_contact = bind(this.set_contact, this); - this.set_client = bind(this.set_client, this); this.set_reference_field = bind(this.set_reference_field, this); this.set_reference_field_query = bind(this.set_reference_field_query, this); this.get_field_by_id = bind(this.get_field_by_id, this); @@ -259,7 +258,7 @@ $(".service-lockbtn").hide(); return $.each(records, function(arnum, record) { $.each(record.client_metadata, function(uid, client) { - return me.set_client(arnum, client); + return me.apply_field_value(arnum, client); }); $.each(record.contact_metadata, function(uid, contact) { return me.set_contact(arnum, contact); @@ -381,7 +380,39 @@ return $(field_id); }; - AnalysisRequestAdd.prototype.apply_field_filters = function(record, arnum) { + AnalysisRequestAdd.prototype.apply_field_value = function(arnum, record) { + + /* + * Applies the value for the given record, by setting values and applying + * search filters to dependents + */ + var me; + me = this; + me.apply_dependent_values(arnum, record); + return me.apply_dependent_filter_queries(record, arnum); + }; + + AnalysisRequestAdd.prototype.apply_dependent_values = function(arnum, record) { + + /* + * Sets default field values to dependents + */ + var me; + me = this; + return $.each(record.field_values, function(field_name, values) { + var field, values_json; + values_json = $.toJSON(values); + field = $("#" + field_name + ("-" + arnum)); + console.debug("set_field_values: field_name=" + field_name + " field_values=" + values_json); + if ((values.uid != null) && (values.title != null)) { + return me.set_reference_field(field, values.uid, values.title); + } else if (values.value != null) { + return field.val(values.value); + } + }); + }; + + AnalysisRequestAdd.prototype.apply_dependent_filter_queries = function(record, arnum) { /* * Apply search filters to dependendents @@ -513,30 +544,6 @@ } }; - AnalysisRequestAdd.prototype.set_client = function(arnum, client) { - - /* - * Filter Contacts - * Filter CCContacts - * Filter InvoiceContacts - * Filter SamplePoints - * Filter ARTemplates - * Filter Specification - * Filter SamplingRound - * Filter Batch - */ - var contact_title, contact_uid, me; - me = this; - if (document.URL.indexOf("analysisrequests") > -1) { - contact_title = client.default_contact.title; - contact_uid = client.default_contact.uid; - if (contact_title && contact_uid) { - this.set_reference_field(field, contact_uid, contact_title); - } - } - return me.apply_field_filters(client, arnum); - }; - AnalysisRequestAdd.prototype.set_contact = function(arnum, contact) { /* diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 3d1d704f03..f007fb40e1 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -248,7 +248,7 @@ class window.AnalysisRequestAdd # set client $.each record.client_metadata, (uid, client) -> - me.set_client arnum, client + me.apply_field_value arnum, client # set contact $.each record.contact_metadata, (uid, contact) -> @@ -380,7 +380,36 @@ class window.AnalysisRequestAdd return $(field_id) - apply_field_filters: (record, arnum) -> + apply_field_value: (arnum, record) -> + ### + * Applies the value for the given record, by setting values and applying + * search filters to dependents + ### + me = this + + # Set default values to dependents + me.apply_dependent_values arnum, record + + # Apply search filters to other fields + me.apply_dependent_filter_queries record, arnum + + + apply_dependent_values: (arnum, record) -> + ### + * Sets default field values to dependents + ### + me = this + $.each record.field_values, (field_name, values) -> + values_json = $.toJSON values + field = $("#" + field_name + "-#{arnum}") + console.debug "set_field_values: field_name=#{field_name} field_values=#{values_json}" + if values.uid? and values.title? + me.set_reference_field field, values.uid, values.title + else if values.value? + field.val values.value + + + apply_dependent_filter_queries: (record, arnum) -> ### * Apply search filters to dependendents ### @@ -516,32 +545,6 @@ class window.AnalysisRequestAdd $field.val("") - set_client: (arnum, client) => - ### - * Filter Contacts - * Filter CCContacts - * Filter InvoiceContacts - * Filter SamplePoints - * Filter ARTemplates - * Filter Specification - * Filter SamplingRound - * Filter Batch - ### - - me = this - - # handle default contact for /analysisrequests listing - # https://github.com/senaite/senaite.core/issues/705 - if document.URL.indexOf("analysisrequests") > -1 - contact_title = client.default_contact.title - contact_uid = client.default_contact.uid - if contact_title and contact_uid - @set_reference_field field, contact_uid, contact_title - - # Apply search filters to other fields - me.apply_field_filters client, arnum - - set_contact: (arnum, contact) => ### * Set CC Contacts From bc72f9d811cfb38f47126ebb8c350447a5f9a394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 19:53:52 +0200 Subject: [PATCH 08/25] set_contact --> apply_field_value --- bika/lims/browser/analysisrequest/add2.py | 14 ++-- .../js/bika.lims.analysisrequest.add.js | 67 +++++++++++-------- .../bika.lims.analysisrequest.add.coffee | 60 ++++++++++------- 3 files changed, 84 insertions(+), 57 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 9431e92fa7..d869e13dc4 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -815,8 +815,6 @@ def get_client_info(self, obj): """Returns the client info of an object """ info = self.get_base_info(obj) - - default_contact_info = {} default_contact = self.get_default_contact(client=obj) if default_contact: info["field_values"].update({ @@ -899,20 +897,24 @@ def get_contact_info(self, obj): # Note: It might get a circular dependency when calling: # map(self.get_contact_info, obj.getCCContact()) - cccontacts = {} + cccontacts = [] for contact in obj.getCCContact(): uid = api.get_uid(contact) fullname = contact.getFullname() email = contact.getEmailAddress() - cccontacts[uid] = { + cccontacts.append({ + "uid": uid, + "title": fullname, "fullname": fullname, "email": email - } + }) info.update({ "fullname": fullname, "email": email, - "cccontacts": cccontacts, + "field_values": { + "CCContact": cccontacts + }, }) return info diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 0cd162ae02..11bf8adf56 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -10,6 +10,8 @@ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; window.AnalysisRequestAdd = (function() { + var typeIsArray; + function AnalysisRequestAdd() { this.init_file_fields = bind(this.init_file_fields, this); this.on_form_submit = bind(this.on_form_submit, this); @@ -35,7 +37,6 @@ this.set_template = bind(this.set_template, this); this.set_sampletype = bind(this.set_sampletype, this); this.set_sample = bind(this.set_sample, this); - this.set_contact = bind(this.set_contact, this); this.set_reference_field = bind(this.set_reference_field, this); this.set_reference_field_query = bind(this.set_reference_field_query, this); this.get_field_by_id = bind(this.get_field_by_id, this); @@ -261,7 +262,7 @@ return me.apply_field_value(arnum, client); }); $.each(record.contact_metadata, function(uid, contact) { - return me.set_contact(arnum, contact); + return me.apply_field_value(arnum, contact); }); $.each(record.service_metadata, function(uid, metadata) { var lock; @@ -380,6 +381,15 @@ return $(field_id); }; + typeIsArray = Array.isArray || function(value) { + + /* + * Returns if the given value is an array + * Taken from: https://coffeescript-cookbook.github.io/chapters/arrays/check-type-is-array + */ + return {}.toString.call(value) === '[object Array]'; + }; + AnalysisRequestAdd.prototype.apply_field_value = function(arnum, record) { /* @@ -400,23 +410,39 @@ var me; me = this; return $.each(record.field_values, function(field_name, values) { - var field, values_json; - values_json = $.toJSON(values); - field = $("#" + field_name + ("-" + arnum)); - console.debug("set_field_values: field_name=" + field_name + " field_values=" + values_json); - if ((values.uid != null) && (values.title != null)) { - return me.set_reference_field(field, values.uid, values.title); - } else if (values.value != null) { - return field.val(values.value); - } + return me.apply_dependent_value(arnum, field_name, values); }); }; - AnalysisRequestAdd.prototype.apply_dependent_filter_queries = function(record, arnum) { + AnalysisRequestAdd.prototype.apply_dependent_value = function(arnum, field_name, values) { /* * Apply search filters to dependendents */ + var field, me, values_json; + me = this; + values_json = $.toJSON(values); + field = $("#" + field_name + ("-" + arnum)); + console.debug("apply_dependent_value: field_name=" + field_name + " field_values=" + values_json); + if ((values.uid != null) && (values.title != null)) { + return me.set_reference_field(field, values.uid, values.title); + } else if (values.value != null) { + return field.val(values.value); + } else if (typeIsArray(values)) { + return $.each(values, function(index, item) { + var item_json; + item_json = $.toJSON(item); + console.debug("" + item_json); + return me.apply_dependent_value(arnum, field_name, item); + }); + } + }; + + AnalysisRequestAdd.prototype.apply_dependent_filter_queries = function(record, arnum) { + + /* + * Apply search filters to dependents + */ var me; me = this; return $.each(record.filter_queries, function(field_name, query) { @@ -544,21 +570,6 @@ } }; - AnalysisRequestAdd.prototype.set_contact = function(arnum, contact) { - - /* - * Set CC Contacts - */ - var field, me; - me = this; - field = $("#CCContact-" + arnum); - return $.each(contact.cccontacts, function(uid, cccontact) { - var fullname; - fullname = cccontact.fullname; - return me.set_reference_field(field, uid, fullname); - }); - }; - AnalysisRequestAdd.prototype.set_sample = function(arnum, sample) { /* @@ -574,7 +585,7 @@ uid = contact.uid; fullname = contact.fullname; this.set_reference_field(field, uid, fullname); - this.set_contact(arnum, contact); + this.apply_field_value(arnum, contact); field = $("#SamplingDate-" + arnum); value = sample.sampling_date; field.val(value); diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index f007fb40e1..a47d77546b 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -252,7 +252,7 @@ class window.AnalysisRequestAdd # set contact $.each record.contact_metadata, (uid, contact) -> - me.set_contact arnum, contact + me.apply_field_value arnum, contact # set services $.each record.service_metadata, (uid, metadata) -> @@ -380,6 +380,14 @@ class window.AnalysisRequestAdd return $(field_id) + typeIsArray = Array.isArray || (value) -> + ### + * Returns if the given value is an array + * Taken from: https://coffeescript-cookbook.github.io/chapters/arrays/check-type-is-array + ### + return {}.toString.call( value ) is '[object Array]' + + apply_field_value: (arnum, record) -> ### * Applies the value for the given record, by setting values and applying @@ -400,20 +408,39 @@ class window.AnalysisRequestAdd ### me = this $.each record.field_values, (field_name, values) -> - values_json = $.toJSON values - field = $("#" + field_name + "-#{arnum}") - console.debug "set_field_values: field_name=#{field_name} field_values=#{values_json}" - if values.uid? and values.title? - me.set_reference_field field, values.uid, values.title - else if values.value? - field.val values.value + me.apply_dependent_value arnum, field_name, values - apply_dependent_filter_queries: (record, arnum) -> + apply_dependent_value: (arnum, field_name, values) -> ### * Apply search filters to dependendents ### me = this + values_json = $.toJSON values + field = $("#" + field_name + "-#{arnum}") + console.debug "apply_dependent_value: field_name=#{field_name} field_values=#{values_json}" + + if values.uid? and values.title? + # This is a reference field + me.set_reference_field field, values.uid, values.title + + else if values.value? + # This is a normal input field + field.val values.value + + else if typeIsArray values + # This is a multi field (e.g. CCContact) + $.each values, (index, item) -> + item_json = $.toJSON item + console.debug "#{item_json}" + me.apply_dependent_value arnum, field_name, item + + + apply_dependent_filter_queries: (record, arnum) -> + ### + * Apply search filters to dependents + ### + me = this $.each record.filter_queries, (field_name, query) -> field = $("#" + field_name + "-#{arnum}") me.set_reference_field_query field, query @@ -545,19 +572,6 @@ class window.AnalysisRequestAdd $field.val("") - set_contact: (arnum, contact) => - ### - * Set CC Contacts - ### - me = this - - field = $("#CCContact-#{arnum}") - - $.each contact.cccontacts, (uid, cccontact) -> - fullname = cccontact.fullname - me.set_reference_field field, uid, fullname - - set_sample: (arnum, sample) => ### * Apply the sample data to all fields of arnum @@ -575,7 +589,7 @@ class window.AnalysisRequestAdd uid = contact.uid fullname = contact.fullname @set_reference_field field, uid, fullname - @set_contact(arnum, contact) + @apply_field_value(arnum, contact) # set the sampling date field = $("#SamplingDate-#{arnum}") From f3a474fb4675fef1fc4fb7108b35ce0ee6c3c01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 21:05:00 +0200 Subject: [PATCH 09/25] set_sampletype --> apply_field_value --- bika/lims/browser/analysisrequest/add2.py | 25 ++------------ .../js/bika.lims.analysisrequest.add.js | 30 ++++------------ .../bika.lims.analysisrequest.add.coffee | 34 ++++--------------- 3 files changed, 15 insertions(+), 74 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index d869e13dc4..c67314806c 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -1034,42 +1034,21 @@ def get_sampletype_info(self, obj): client = self.get_client() client_uid = client and api.get_uid(client) or "" - # sample matrix - sample_matrix = obj.getSampleMatrix() - sample_matrix_uid = sample_matrix and sample_matrix.UID() or "" - sample_matrix_title = sample_matrix and sample_matrix.Title() or "" - - # container type - container_type = obj.getContainerType() - container_type_uid = container_type and container_type.UID() or "" - container_type_title = container_type and container_type.Title() or "" - - # sample points - sample_points = obj.getSamplePoints() - sample_point_uids = map(lambda sp: sp.UID(), sample_points) - sample_point_titles = map(lambda sp: sp.Title(), sample_points) - info.update({ "prefix": obj.getPrefix(), "minimum_volume": obj.getMinimumVolume(), "hazardous": obj.getHazardous(), "retention_period": obj.getRetentionPeriod(), - "sample_matrix_uid": sample_matrix_uid, - "sample_matrix_title": sample_matrix_title, - "container_type_uid": container_type_uid, - "container_type_title": container_type_title, - "sample_point_uids": sample_point_uids, - "sample_point_titles": sample_point_titles, }) # catalog queries for UI field filtering filter_queries = { - "samplepoint": { + "SamplePoint": { "getSampleTypeTitles": [obj.Title(), ''], "getClientUID": [client_uid, bika_samplepoints_uid], "sort_order": "descending", }, - "specification": { + "Specification": { "getSampleTypeTitle": obj.Title(), "getClientUID": [client_uid, bika_analysisspecs_uid], "sort_order": "descending", diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 11bf8adf56..b47bb9a33c 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -35,7 +35,6 @@ this.set_service_spec = bind(this.set_service_spec, this); this.set_service = bind(this.set_service, this); this.set_template = bind(this.set_template, this); - this.set_sampletype = bind(this.set_sampletype, this); this.set_sample = bind(this.set_sample, this); this.set_reference_field = bind(this.set_reference_field, this); this.set_reference_field_query = bind(this.set_reference_field_query, this); @@ -284,7 +283,7 @@ return me.set_sample(arnum, sample); }); $.each(record.sampletype_metadata, function(uid, sampletype) { - return me.set_sampletype(arnum, sampletype); + return me.apply_field_value(arnum, sampletype); }); return $.each(record.unmet_dependencies, function(uid, dependencies) { var context, dialog, service; @@ -423,6 +422,11 @@ me = this; values_json = $.toJSON(values); field = $("#" + field_name + ("-" + arnum)); + if ((values.if_empty != null) && values.if_empty === true) { + if (!field.val()) { + return; + } + } console.debug("apply_dependent_value: field_name=" + field_name + " field_values=" + values_json); if ((values.uid != null) && (values.title != null)) { return me.set_reference_field(field, values.uid, values.title); @@ -632,28 +636,6 @@ return this.set_reference_field(field, uid, title); }; - AnalysisRequestAdd.prototype.set_sampletype = function(arnum, sampletype) { - - /* - * Recalculate partitions - * Filter Sample Points - */ - var field, query, title, uid; - field = $("#SamplePoint-" + arnum); - query = sampletype.filter_queries.samplepoint; - this.set_reference_field_query(field, query); - field = $("#DefaultContainerType-" + arnum); - if (!field.val()) { - uid = sampletype.container_type_uid; - title = sampletype.container_type_title; - this.flush_reference_field(field); - this.set_reference_field(field, uid, title); - } - field = $("#Specification-" + arnum); - query = sampletype.filter_queries.specification; - return this.set_reference_field_query(field, query); - }; - AnalysisRequestAdd.prototype.set_template = function(arnum, template) { /* diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index a47d77546b..d5ab24d2f0 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -283,7 +283,7 @@ class window.AnalysisRequestAdd # set sampletype $.each record.sampletype_metadata, (uid, sampletype) -> - me.set_sampletype arnum, sampletype + me.apply_field_value arnum, sampletype # handle unmet dependencies, one at a time $.each record.unmet_dependencies, (uid, dependencies) -> @@ -418,6 +418,12 @@ class window.AnalysisRequestAdd me = this values_json = $.toJSON values field = $("#" + field_name + "-#{arnum}") + + if values.if_empty? and values.if_empty is true + # Set the value if the field is empty only + if not field.val() + return + console.debug "apply_dependent_value: field_name=#{field_name} field_values=#{values_json}" if values.uid? and values.title? @@ -662,32 +668,6 @@ class window.AnalysisRequestAdd @set_reference_field field, uid, title - set_sampletype: (arnum, sampletype) => - ### - * Recalculate partitions - * Filter Sample Points - ### - - # restrict the sample points - field = $("#SamplePoint-#{arnum}") - query = sampletype.filter_queries.samplepoint - @set_reference_field_query field, query - - # set the default container - field = $("#DefaultContainerType-#{arnum}") - # apply default container if the field is empty - if not field.val() - uid = sampletype.container_type_uid - title = sampletype.container_type_title - @flush_reference_field field - @set_reference_field field, uid, title - - # restrict the specifications - field = $("#Specification-#{arnum}") - query = sampletype.filter_queries.specification - @set_reference_field_query field, query - - set_template: (arnum, template) => ### * Apply the template data to all fields of arnum From b2715f1c071f1ae486ba31012eb266aa23e75a68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Thu, 17 Oct 2019 22:41:49 +0200 Subject: [PATCH 10/25] Refactor set_sample --> apply_field_value --- bika/lims/browser/analysisrequest/add2.py | 120 +++++++++-------- .../js/bika.lims.analysisrequest.add.js | 96 +------------- .../bika.lims.analysisrequest.add.coffee | 124 +----------------- 3 files changed, 78 insertions(+), 262 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index c67314806c..993094e419 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -1064,70 +1064,60 @@ def get_sample_info(self, obj): """ info = self.get_base_info(obj) - # sample type + batch = obj.getBatch() + client = obj.getClient() sample_type = obj.getSampleType() - sample_type_uid = sample_type and sample_type.UID() or "" - sample_type_title = sample_type and sample_type.Title() or "" - - # sample condition sample_condition = obj.getSampleCondition() - sample_condition_uid = sample_condition \ - and sample_condition.UID() or "" - sample_condition_title = sample_condition \ - and sample_condition.Title() or "" - - # storage location storage_location = obj.getStorageLocation() - storage_location_uid = storage_location \ - and storage_location.UID() or "" - storage_location_title = storage_location \ - and storage_location.Title() or "" - - # sample point sample_point = obj.getSamplePoint() - sample_point_uid = sample_point and sample_point.UID() or "" - sample_point_title = sample_point and sample_point.Title() or "" - - # container type - container_type = sample_type and sample_type.getContainerType() or None - container_type_uid = container_type and container_type.UID() or "" - container_type_title = container_type and container_type.Title() or "" - - # Sampling deviation + container = obj.getContainer() deviation = obj.getSamplingDeviation() - deviation_uid = deviation and deviation.UID() or "" - deviation_title = deviation and deviation.Title() or "" + preservation = obj.getPreservation() + specification = obj.getSpecification() + sample_template = obj.getTemplate() + profiles = obj.getProfiles() or [] + cccontacts = obj.getCCContact() or [] + contact = obj.getContact() info.update({ - "sample_id": obj.getId(), - "batch_uid": obj.getBatchUID() or None, - "date_sampled": self.to_iso_date(obj.getDateSampled()), - "sampling_date": self.to_iso_date(obj.getSamplingDate()), - "sample_type_uid": sample_type_uid, - "sample_type_title": sample_type_title, - "container_type_uid": container_type_uid, - "container_type_title": container_type_title, - "sample_condition_uid": sample_condition_uid, - "sample_condition_title": sample_condition_title, - "storage_location_uid": storage_location_uid, - "storage_location_title": storage_location_title, - "sample_point_uid": sample_point_uid, - "sample_point_title": sample_point_title, - "environmental_conditions": obj.getEnvironmentalConditions(), "composite": obj.getComposite(), - "client_uid": obj.getClientUID(), - "client_title": obj.getClientTitle(), - "contact": self.get_contact_info(obj.getContact()), - "client_order_number": obj.getClientOrderNumber(), - "client_sample_id": obj.getClientSampleID(), - "client_reference": obj.getClientReference(), - "sampling_deviation_uid": deviation_uid, - "sampling_deviation_title": deviation_title, - "sampling_workflow_enabled": obj.getSamplingWorkflowEnabled(), - "remarks": obj.getRemarks(), }) + + # Set the fields for which we want the value to be set automatically + # when the primary sample is selected + info["field_values"].update({ + "Client": self.to_field_value(client), + "Contact": self.to_field_value(contact), + "CCContact": map(self.to_field_value, cccontacts), + "CCEmails": obj.getCCEmails() or [], + "Batch": self.to_field_value(batch), + "DateSampled": {"value": self.to_iso_date(obj.getDateSampled())}, + "SamplingDate": {"value": self.to_iso_date(obj.getSamplingDate())}, + "SampleType": self.to_field_value(sample_type), + "EnvironmentalConditions": {"value": obj.getEnvironmentalConditions()}, + "ClientSampleID": {"value": obj.getClientSampleID()}, + "ClientReference": {"value": obj.getClientReference()}, + "ClientOrderNumber": {"value": obj.getClientOrderNumber()}, + "SampleCondition": self.to_field_value(sample_condition), + "SamplePoint": self.to_field_value(sample_point), + "StorageLocation": self.to_field_value(storage_location), + "Container": self.to_field_value(container), + "SamplingDeviation": self.to_field_value(deviation), + "Preservation": self.to_field_value(preservation), + "Specification": self.to_field_value(specification), + "Template": self.to_field_value(sample_template), + "Profiles": map(self.to_field_value, profiles), + "Composite": {"value": obj.getComposite()} + }) + return info + def to_field_value(self, obj): + return { + "uid": obj and api.get_uid(obj) or "", + "title": obj and api.get_title(obj) or "" + } + @cache(cache_key) def get_specification_info(self, obj): """Returns the info for a Specification @@ -1215,6 +1205,30 @@ def ajax_get_flush_settings(self): "SampleType": [ "SamplePoint", "Specification" + ], + "PrimarySample": [ + "Batch" + "Client", + "Contact", + "CCContact", + "CCEmails", + "ClientOrderNumber", + "ClientReference", + "ClientSampleID", + "ContainerType", + "DateSampled", + "EnvironmentalConditions", + "InvoiceContact", + "Preservation", + "Profiles", + "SampleCondition", + "SamplePoint", + "SampleType", + "SamplingDate", + "SamplingDeviation", + "StorageLocation", + "Specification", + "Template", ] } diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index b47bb9a33c..53e82cf7f5 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -25,7 +25,6 @@ this.on_analysis_profile_removed = bind(this.on_analysis_profile_removed, this); this.on_analysis_profile_selected = bind(this.on_analysis_profile_selected, this); this.on_analysis_template_changed = bind(this.on_analysis_template_changed, this); - this.on_sample_changed = bind(this.on_sample_changed, this); this.on_analysis_lock_button_click = bind(this.on_analysis_lock_button_click, this); this.on_analysis_details_click = bind(this.on_analysis_details_click, this); this.on_analysis_specification_changed = bind(this.on_analysis_specification_changed, this); @@ -35,7 +34,6 @@ this.set_service_spec = bind(this.set_service_spec, this); this.set_service = bind(this.set_service, this); this.set_template = bind(this.set_template, this); - this.set_sample = bind(this.set_sample, this); this.set_reference_field = bind(this.set_reference_field, this); this.set_reference_field_query = bind(this.set_reference_field_query, this); this.get_field_by_id = bind(this.get_field_by_id, this); @@ -99,7 +97,6 @@ $("body").on("change", "input.warn_max", this.on_analysis_specification_changed); $("body").on("click", ".service-lockbtn", this.on_analysis_lock_button_click); $("body").on("click", ".service-infobtn", this.on_analysis_details_click); - $("body").on("selected change", "tr[fieldname=PrimaryAnalysisRequest] input[type='text']", this.on_sample_changed); $("body").on("selected change", "tr[fieldname=Template] input[type='text']", this.on_analysis_template_changed); $("body").on("selected", "tr[fieldname=Profiles] input[type='text']", this.on_analysis_profile_selected); $("body").on("click", "tr[fieldname=Profiles] img.deletebtn", this.on_analysis_profile_removed); @@ -280,7 +277,7 @@ }); }); $.each(record.sample_metadata, function(uid, sample) { - return me.set_sample(arnum, sample); + return me.apply_field_value(arnum, sample); }); $.each(record.sampletype_metadata, function(uid, sampletype) { return me.apply_field_value(arnum, sampletype); @@ -431,12 +428,13 @@ if ((values.uid != null) && (values.title != null)) { return me.set_reference_field(field, values.uid, values.title); } else if (values.value != null) { - return field.val(values.value); + if (typeof values.value === "boolean") { + return field.prop("checked", values.value); + } else { + return field.val(values.value); + } } else if (typeIsArray(values)) { return $.each(values, function(index, item) { - var item_json; - item_json = $.toJSON(item); - console.debug("" + item_json); return me.apply_dependent_value(arnum, field_name, item); }); } @@ -574,68 +572,6 @@ } }; - AnalysisRequestAdd.prototype.set_sample = function(arnum, sample) { - - /* - * Apply the sample data to all fields of arnum - */ - var contact, field, fullname, title, uid, value; - field = $("#Client-" + arnum); - uid = sample.client_uid; - title = sample.client_title; - this.set_reference_field(field, uid, title); - field = $("#Contact-" + arnum); - contact = sample.contact; - uid = contact.uid; - fullname = contact.fullname; - this.set_reference_field(field, uid, fullname); - this.apply_field_value(arnum, contact); - field = $("#SamplingDate-" + arnum); - value = sample.sampling_date; - field.val(value); - field = $("#DateSampled-" + arnum); - value = sample.date_sampled; - field.val(value); - field = $("#SampleType-" + arnum); - uid = sample.sample_type_uid; - title = sample.sample_type_title; - this.set_reference_field(field, uid, title); - field = $("#EnvironmentalConditions-" + arnum); - value = sample.environmental_conditions; - field.val(value); - field = $("#ClientSampleID-" + arnum); - value = sample.client_sample_id; - field.val(value); - field = $("#ClientReference-" + arnum); - value = sample.client_reference; - field.val(value); - field = $("#ClientOrderNumber-" + arnum); - value = sample.client_order_number; - field.val(value); - field = $("#Composite-" + arnum); - field.prop("checked", sample.composite); - field = $("#SampleCondition-" + arnum); - uid = sample.sample_condition_uid; - title = sample.sample_condition_title; - this.set_reference_field(field, uid, title); - field = $("#SamplePoint-" + arnum); - uid = sample.sample_point_uid; - title = sample.sample_point_title; - this.set_reference_field(field, uid, title); - field = $("#StorageLocation-" + arnum); - uid = sample.storage_location_uid; - title = sample.storage_location_title; - this.set_reference_field(field, uid, title); - field = $("#DefaultContainerType-" + arnum); - uid = sample.container_type_uid; - title = sample.container_type_title; - this.set_reference_field(field, uid, title); - field = $("#SamplingDeviation-" + arnum); - uid = sample.sampling_deviation_uid; - title = sample.sampling_deviation_title; - return this.set_reference_field(field, uid, title); - }; - AnalysisRequestAdd.prototype.set_template = function(arnum, template) { /* @@ -912,26 +848,6 @@ return dialog = this.template_dialog("service-dependant-template", context, buttons); }; - AnalysisRequestAdd.prototype.on_sample_changed = function(event) { - - /* - * Eventhandler when the Sample was changed. - */ - var $el, arnum, el, has_sample_selected, me, uid, val; - me = this; - el = event.currentTarget; - $el = $(el); - uid = $(el).attr("uid"); - val = $el.val(); - arnum = $el.closest("[arnum]").attr("arnum"); - has_sample_selected = $el.val(); - console.debug("°°° on_sample_change::UID=" + uid + " PrimaryAnalysisRequest=" + val + "°°°"); - if (!has_sample_selected) { - $("input[type=hidden]", $el.parent()).val(""); - } - return $(me).trigger("form:changed"); - }; - AnalysisRequestAdd.prototype.on_analysis_template_changed = function(event) { /* diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index d5ab24d2f0..a6ddb47d22 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -85,8 +85,6 @@ class window.AnalysisRequestAdd $("body").on "click", ".service-lockbtn", @on_analysis_lock_button_click # Analysis info button clicked $("body").on "click", ".service-infobtn", @on_analysis_details_click - # Sample changed - $("body").on "selected change", "tr[fieldname=PrimaryAnalysisRequest] input[type='text']", @on_sample_changed # Analysis Template changed $("body").on "selected change", "tr[fieldname=Template] input[type='text']", @on_analysis_template_changed # Analysis Profile selected @@ -279,7 +277,7 @@ class window.AnalysisRequestAdd # set sample $.each record.sample_metadata, (uid, sample) -> - me.set_sample arnum, sample + me.apply_field_value arnum, sample # set sampletype $.each record.sampletype_metadata, (uid, sampletype) -> @@ -432,13 +430,14 @@ class window.AnalysisRequestAdd else if values.value? # This is a normal input field - field.val values.value + if typeof values.value == "boolean" + field.prop "checked", values.value + else + field.val values.value else if typeIsArray values # This is a multi field (e.g. CCContact) $.each values, (index, item) -> - item_json = $.toJSON item - console.debug "#{item_json}" me.apply_dependent_value arnum, field_name, item @@ -578,96 +577,6 @@ class window.AnalysisRequestAdd $field.val("") - set_sample: (arnum, sample) => - ### - * Apply the sample data to all fields of arnum - ### - - # set the client - field = $("#Client-#{arnum}") - uid = sample.client_uid - title = sample.client_title - @set_reference_field field, uid, title - - # set the client contact - field = $("#Contact-#{arnum}") - contact = sample.contact - uid = contact.uid - fullname = contact.fullname - @set_reference_field field, uid, fullname - @apply_field_value(arnum, contact) - - # set the sampling date - field = $("#SamplingDate-#{arnum}") - value = sample.sampling_date - field.val value - - # set the date sampled - field = $("#DateSampled-#{arnum}") - value = sample.date_sampled - field.val value - - # set the sample type (required) - field = $("#SampleType-#{arnum}") - uid = sample.sample_type_uid - title = sample.sample_type_title - @set_reference_field field, uid, title - - # set environmental conditions - field = $("#EnvironmentalConditions-#{arnum}") - value = sample.environmental_conditions - field.val value - - # set client sample ID - field = $("#ClientSampleID-#{arnum}") - value = sample.client_sample_id - field.val value - - # set client reference - field = $("#ClientReference-#{arnum}") - value = sample.client_reference - field.val value - - # set the client order number - field = $("#ClientOrderNumber-#{arnum}") - value = sample.client_order_number - field.val value - - # set composite - field = $("#Composite-#{arnum}") - field.prop "checked", sample.composite - - # set the sample condition - field = $("#SampleCondition-#{arnum}") - uid = sample.sample_condition_uid - title = sample.sample_condition_title - @set_reference_field field, uid, title - - # set the sample point - field = $("#SamplePoint-#{arnum}") - uid = sample.sample_point_uid - title = sample.sample_point_title - @set_reference_field field, uid, title - - # set the storage location - field = $("#StorageLocation-#{arnum}") - uid = sample.storage_location_uid - title = sample.storage_location_title - @set_reference_field field, uid, title - - # set the default container type - field = $("#DefaultContainerType-#{arnum}") - uid = sample.container_type_uid - title = sample.container_type_title - @set_reference_field field, uid, title - - # set the sampling deviation - field = $("#SamplingDeviation-#{arnum}") - uid = sample.sampling_deviation_uid - title = sample.sampling_deviation_title - @set_reference_field field, uid, title - - set_template: (arnum, template) => ### * Apply the template data to all fields of arnum @@ -962,29 +871,6 @@ class window.AnalysisRequestAdd dialog = @template_dialog "service-dependant-template", context, buttons - on_sample_changed: (event) => - ### - * Eventhandler when the Sample was changed. - ### - - me = this - el = event.currentTarget - $el = $(el) - uid = $(el).attr "uid" - val = $el.val() - arnum = $el.closest("[arnum]").attr "arnum" - has_sample_selected = $el.val() - console.debug "°°° on_sample_change::UID=#{uid} PrimaryAnalysisRequest=#{val}°°°" - - # deselect the sample if the field is empty - if not has_sample_selected - # XXX manually flush UID field - $("input[type=hidden]", $el.parent()).val("") - - # trigger form:changed event - $(me).trigger "form:changed" - - on_analysis_template_changed: (event) => ### * Eventhandler when an Analysis Template was changed. From 200667a2f24c222d4c4831f3e76fac5396a56cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Fri, 18 Oct 2019 00:30:00 +0200 Subject: [PATCH 11/25] Support for field-specific metadata in recalculate (Add Sample) --- bika/lims/browser/analysisrequest/add2.py | 17 +++++++++++++++- .../js/bika.lims.analysisrequest.add.js | 5 +++++ .../bika.lims.analysisrequest.add.coffee | 5 +++++ bika/lims/interfaces/__init__.py | 20 +++++++++++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 993094e419..a4608c94e2 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -29,7 +29,7 @@ from bika.lims.api.analysisservice import get_calculation_dependencies_for from bika.lims.api.analysisservice import get_service_dependencies_for from bika.lims.interfaces import IGetDefaultFieldValueARAddHook, \ - IAddSampleFieldFilter, IAddSampleFieldsFlush + IAddSampleFieldFilter, IAddSampleFieldsFlush, IAddSampleMetadata from bika.lims.utils import tmpID from bika.lims.utils.analysisrequest import create_analysisrequest as crar from bika.lims.workflow import ActionHandlerPool @@ -1110,6 +1110,12 @@ def get_sample_info(self, obj): "Composite": {"value": obj.getComposite()} }) + # Maybe other add-ons have additional fields that require filtering too + for name, ad in getAdapters((obj,), IAddSampleFieldFilter): + logger.info("Additional field filters from {}".format(name)) + additional_filters = ad.get_info() + info["filter_queries"].update(additional_filters) + return info def to_field_value(self, obj): @@ -1472,8 +1478,17 @@ def ajax_recalculate_records(self): "service_to_profiles": service_to_profiles, "service_metadata": service_metadata, "unmet_dependencies": unmet_dependencies, + "additional": {} } + # Maybe other add-ons require other fields to be recalculated, + # either from core but not specifically recalculated (e.g Batch) + # or new add-on specific fields + for name, ad in getAdapters((self.context,), IAddSampleMetadata): + logger.info("Additional field records from {}".format(name)) + additional_metadata = ad.get_metadata(record) + out[n]["additional"].update(additional_metadata) + return out def show_recalculate_prices(self): diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 53e82cf7f5..ea1b9ba1cc 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -282,6 +282,11 @@ $.each(record.sampletype_metadata, function(uid, sampletype) { return me.apply_field_value(arnum, sampletype); }); + $.each(record.additional, function(field_name, item) { + return $.each(item, function(uid, metadata) { + return me.apply_field_value(arnum, metadata); + }); + }); return $.each(record.unmet_dependencies, function(uid, dependencies) { var context, dialog, service; service = record.service_metadata[uid]; diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index a6ddb47d22..93f89bf107 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -283,6 +283,11 @@ class window.AnalysisRequestAdd $.each record.sampletype_metadata, (uid, sampletype) -> me.apply_field_value arnum, sampletype + # set additional records + $.each record.additional, (field_name, item) -> + $.each item, (uid, metadata) -> + me.apply_field_value arnum, metadata + # handle unmet dependencies, one at a time $.each record.unmet_dependencies, (uid, dependencies) -> service = record.service_metadata[uid] diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index caea8ff3d8..cf15f9ad2e 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -981,3 +981,23 @@ def get_flush_settings(self): """Returns a dict where the key is the name of the field and the value is an array dependencies as field names """ + + +class IAddSampleMetadata(Interface): + """Marker interface for additional fields that need to be taken into + account in recalculation of metadata from Add Sample form + """ + + def get_metadata(self, record): + """Returns a dict where the key is the fieldname (e.g. Batch) and the + value is another dict where the key is the UID of the object and the + value is its metadata representation as a dict: + + {Batch: { + : { + "id": , + "title", , + ... + }, + } + """ From 46be897f4a735b911fe0f9bdc11c895d5f81c16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sat, 19 Oct 2019 21:04:14 +0200 Subject: [PATCH 12/25] Refactor ajax_recalculate_records function --- CHANGES.rst | 1 + bika/lims/browser/analysisrequest/add2.py | 456 +++++++++--------- .../js/bika.lims.analysisrequest.add.js | 12 +- .../bika.lims.analysisrequest.add.coffee | 13 +- bika/lims/interfaces/__init__.py | 5 + 5 files changed, 243 insertions(+), 244 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 53a59012bd..fc3e97bd77 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Changelog **Added** +- #1462 Allow to extend Add Sample behavior with adapters - #1455 Added support for adapters in guard handler - #1436 Setting in setup for auto-reception of samples upon creation - #1433 Added Submitter column in Sample's analyses listing diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index a4608c94e2..5107eda624 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -29,7 +29,8 @@ from bika.lims.api.analysisservice import get_calculation_dependencies_for from bika.lims.api.analysisservice import get_service_dependencies_for from bika.lims.interfaces import IGetDefaultFieldValueARAddHook, \ - IAddSampleFieldFilter, IAddSampleFieldsFlush, IAddSampleMetadata + IAddSampleFieldFilter, IAddSampleFieldsFlush, IAddSampleMetadata, \ + IAddSampleObjectInfo from bika.lims.utils import tmpID from bika.lims.utils.analysisrequest import create_analysisrequest as crar from bika.lims.workflow import ActionHandlerPool @@ -799,11 +800,9 @@ def get_base_info(self, obj): return {} info = { - "id": obj.getId(), - "uid": obj.UID(), - "title": obj.Title(), - "description": obj.Description(), - "url": obj.absolute_url(), + "id": api.get_id(obj), + "uid": api.get_uid(obj), + "title": api.get_title(obj), "field_values": {}, "filter_queries": {}, } @@ -1059,8 +1058,8 @@ def get_sampletype_info(self, obj): return info @cache(cache_key) - def get_sample_info(self, obj): - """Returns the info for a Sample + def get_primaryanalysisrequest_info(self, obj): + """Returns the info for a Primary Sample """ info = self.get_base_info(obj) @@ -1110,14 +1109,9 @@ def get_sample_info(self, obj): "Composite": {"value": obj.getComposite()} }) - # Maybe other add-ons have additional fields that require filtering too - for name, ad in getAdapters((obj,), IAddSampleFieldFilter): - logger.info("Additional field filters from {}".format(name)) - additional_filters = ad.get_info() - info["filter_queries"].update(additional_filters) - return info + @cache(cache_key) def to_field_value(self, obj): return { "uid": obj and api.get_uid(obj) or "", @@ -1264,232 +1258,232 @@ def ajax_get_service(self): return info def ajax_recalculate_records(self): - """Recalculate all AR records and dependencies + out = {} + records = self.get_records() + for num_sample, record in enumerate(records): + # Get reference fields metadata + metadata = self.get_record_metadata(record) - - samples - - templates - - profiles - - services - - dependecies + # Extract additional metadata from this record + # service_to_specs + service_to_specs = self.get_service_to_specs_info(metadata) + metadata.update(service_to_specs) - XXX: This function has grown too much and needs refactoring! - """ - out = {} + # service_to_templates, template_to_services + templates_additional = self.get_template_additional_info(metadata) + metadata.update(templates_additional) - # The sorted records from the request - records = self.get_records() + # service_to_profiles, profiles_to_services + profiles_additional = self.get_profiles_additional_info(metadata) + metadata.update(profiles_additional) - for n, record in enumerate(records): + # dependencies + dependencies = self.get_unmet_dependencies_info(metadata) + metadata.update(dependencies) - # Mapping of client UID -> client object info - client_metadata = {} - # Mapping of contact UID -> contact object info - contact_metadata = {} - # Mapping of sample UID -> sample object info - sample_metadata = {} - # Mapping of sampletype UID -> sampletype object info - sampletype_metadata = {} - # Mapping of specification UID -> specification object info - specification_metadata = {} - # Mapping of specification UID -> list of service UIDs - specification_to_services = {} - # Mapping of service UID -> list of specification UIDs - service_to_specifications = {} - # Mapping of template UID -> template object info - template_metadata = {} - # Mapping of template UID -> list of service UIDs - template_to_services = {} - # Mapping of service UID -> list of template UIDs - service_to_templates = {} - # Mapping of profile UID -> list of service UIDs - profile_to_services = {} - # Mapping of service UID -> list of profile UIDs - service_to_profiles = {} - # Profile metadata for UI purposes - profile_metadata = {} - # Mapping of service UID -> service object info - service_metadata = {} - # mapping of service UID -> unmet service dependency UIDs - unmet_dependencies = {} - - # Mappings of UID -> object of selected items in this record - _clients = self.get_objs_from_record(record, "Client_uid") - _contacts = self.get_objs_from_record(record, "Contact_uid") - _specifications = self.get_objs_from_record( - record, "Specification_uid") - _templates = self.get_objs_from_record(record, "Template_uid") - _samples = self.get_objs_from_record(record, "PrimaryAnalysisRequest_uid") - _profiles = self.get_objs_from_record(record, "Profiles_uid") - _services = self.get_objs_from_record(record, "Analyses") - _sampletypes = self.get_objs_from_record(record, "SampleType_uid") - - # CLIENTS - for uid, obj in _clients.iteritems(): - # get the client metadata - metadata = self.get_client_info(obj) - # remember the sampletype metadata - client_metadata[uid] = metadata - - # CONTACTS - for uid, obj in _contacts.iteritems(): - # get the client metadata - metadata = self.get_contact_info(obj) - # remember the sampletype metadata - contact_metadata[uid] = metadata - - # SPECIFICATIONS - for uid, obj in _specifications.iteritems(): - # get the specification metadata - metadata = self.get_specification_info(obj) - # remember the metadata of this specification - specification_metadata[uid] = metadata - # get the spec'd service UIDs - service_uids = metadata["service_uids"] - # remember a mapping of specification uid -> spec'd services - specification_to_services[uid] = service_uids - # remember a mapping of service uid -> specifications - for service_uid in service_uids: - if service_uid in service_to_specifications: - service_to_specifications[service_uid].append(uid) - else: - service_to_specifications[service_uid] = [uid] - - # AR TEMPLATES - for uid, obj in _templates.iteritems(): - # get the template metadata - metadata = self.get_template_info(obj) - # remember the template metadata - template_metadata[uid] = metadata - - # profile from the template - profile = obj.getAnalysisProfile() - # add the profile to the other profiles - if profile is not None: - profile_uid = api.get_uid(profile) - _profiles[profile_uid] = profile - - # get the template analyses - # [{'partition': 'part-1', 'service_uid': '...'}, - # {'partition': 'part-1', 'service_uid': '...'}] - analyses = obj.getAnalyses() or [] - # get all UIDs of the template records - service_uids = map( - lambda rec: rec.get("service_uid"), analyses) - # remember a mapping of template uid -> service - template_to_services[uid] = service_uids - # remember a mapping of service uid -> templates - for service_uid in service_uids: - # append service to services mapping + # Set the metadata for current sample number (column) + out[num_sample] = metadata + + return out + + def get_record_metadata(self, record): + metadata = {} + for key, value in record.items(): + if not key.endswith("_uid"): + continue + + # This is a reference field (ends with _uid), so we add the + # metadata key, even if there is no way to handle objects this + # field refers to + metadata_key = key.replace("_uid", "") + metadata_key = "{}_metadata".format(metadata_key.lower()) + metadata[metadata_key] = {} + + if not value: + continue + + # Get objects information (metadata) + objs_info = self.get_objects_info(record, key) + objs_uids = map(lambda obj: obj["uid"], objs_info) + metadata[metadata_key] = dict(zip(objs_uids, objs_info)) + + return metadata + + def get_service_to_specs_info(self, metadata): + service_to_specs = {} + specifications = metadata.get("specification_metadata", {}) + for uid, obj_info in specifications.items(): + service_uids = obj_info["service_uids"] + for service_uid in service_uids: + if service_uid in service_to_specs: + service_to_specs[service_uid].append(uid) + else: + service_to_specs[service_uid] = [uid] + return {"service_to_specifications": service_to_specs} + + def get_template_additional_info(self, metadata): + template_to_services = {} + service_to_templates = {} + service_metadata = metadata.get("service_metadata", {}) + profiles_metadata = metadata.get("profiles_metadata", {}) + template = metadata.get("template_metadata", {}) + # We don't expect more than one template, but who knows about future? + for uid, obj_info in template.items(): + obj = api.get_object_by_uid(uid) + # profile from the template + profile = obj.getAnalysisProfile() + # add the profile to the other profiles + if profile is not None: + profile_uid = api.get_uid(profile) + if profile_uid not in profiles_metadata: + profile = self.get_object_by_uid(profile_uid) + profile_info = self.get_profile_info(profile) + profiles_metadata[profile_uid] = profile_info + + # get the template analyses + # [{'partition': 'part-1', 'service_uid': '...'}, + # {'partition': 'part-1', 'service_uid': '...'}] + analyses = obj.getAnalyses() or [] + # get all UIDs of the template records + service_uids = map(lambda rec: rec.get("service_uid"), analyses) + # remember a mapping of template uid -> service + template_to_services[uid] = service_uids + # remember a mapping of service uid -> templates + for service_uid in service_uids: + # remember the template of all services + if service_uid in service_to_templates: + service_to_templates[service_uid].append(uid) + else: + service_to_templates[service_uid] = [uid] + # remember the service metadata + if service_uid not in service_metadata: service = self.get_object_by_uid(service_uid) - # remember the template of all services - if service_uid in service_to_templates: - service_to_templates[service_uid].append(uid) - else: - service_to_templates[service_uid] = [uid] - - # remember the service metadata - if service_uid not in service_metadata: - metadata = self.get_service_info(service) - service_metadata[service_uid] = metadata - - # PROFILES - for uid, obj in _profiles.iteritems(): - # get the profile metadata - metadata = self.get_profile_info(obj) - # remember the profile metadata - profile_metadata[uid] = metadata - # get all services of this profile - services = obj.getService() - # get all UIDs of the profile services - service_uids = map(api.get_uid, services) - # remember all services of this profile - profile_to_services[uid] = service_uids - # remember a mapping of service uid -> profiles - for service in services: - # get the UID of this service - service_uid = api.get_uid(service) - # add the service to the other services - _services[service_uid] = service - # remember the profiles of this service - if service_uid in service_to_profiles: - service_to_profiles[service_uid].append(uid) + service_info = self.get_service_info(service) + service_metadata[service_uid] = service_info + + return { + "service_to_templates": service_to_templates, + "template_to_services": template_to_services, + "service_metadata": service_metadata, + "profiles_metadata": profiles_metadata, + } + + def get_profiles_additional_info(self, metadata): + profile_to_services = {} + service_to_profiles = metadata.get("service_to_profiles", {}) + service_metadata = metadata.get("service_metadata", {}) + profiles = metadata.get("profiles_metadata", {}) + for uid, obj_info in profiles.items(): + obj = api.get_object_by_uid(uid) + # get all services of this profile + services = obj.getService() + # get all UIDs of the profile services + service_uids = map(api.get_uid, services) + # remember all services of this profile + profile_to_services[uid] = service_uids + # remember a mapping of service uid -> profiles + for service in services: + # get the UID of this service + service_uid = api.get_uid(service) + # remember the profiles of this service + if service_uid in service_to_profiles: + service_to_profiles[service_uid].append(uid) + else: + service_to_profiles[service_uid] = [uid] + # remember the service metadata + if service_uid not in service_metadata: + service_info = self.get_service_info(service) + service_metadata[service_uid] = service_info + + return { + "profile_to_services": profile_to_services, + "service_to_profiles": service_to_profiles, + "service_metadata": service_metadata, + } + + def get_unmet_dependencies_info(self, metadata): + # mapping of service UID -> unmet service dependency UIDs + unmet_dependencies = {} + services = metadata.get("service_metadata", {}).copy() + for uid, obj_info in services.items(): + obj = api.get_object_by_uid(uid) + # get the dependencies of this service + deps = get_service_dependencies_for(obj) + + # check for unmet dependencies + for dep in deps["dependencies"]: + # we use the UID to test for equality + dep_uid = api.get_uid(dep) + if dep_uid not in services: + if uid in unmet_dependencies: + unmet_dependencies[uid].append(self.get_base_info(dep)) else: - service_to_profiles[service_uid] = [uid] - - # PRIMARY ANALYSIS REQUESTS - for uid, obj in _samples.iteritems(): - # get the sample metadata - metadata = self.get_sample_info(obj) - # remember the sample metadata - sample_metadata[uid] = metadata - - # SAMPLETYPES - for uid, obj in _sampletypes.iteritems(): - # get the sampletype metadata - metadata = self.get_sampletype_info(obj) - # remember the sampletype metadata - sampletype_metadata[uid] = metadata - - # SERVICES - for uid, obj in _services.iteritems(): - # get the service metadata - metadata = self.get_service_info(obj) - - # remember the services' metadata - service_metadata[uid] = metadata - - # DEPENDENCIES - for uid, obj in _services.iteritems(): - # get the dependencies of this service - deps = get_service_dependencies_for(obj) - - # check for unmet dependencies - for dep in deps["dependencies"]: - # we use the UID to test for equality - dep_uid = api.get_uid(dep) - if dep_uid not in _services.keys(): - if uid in unmet_dependencies: - unmet_dependencies[uid].append( - self.get_base_info(dep)) - else: - unmet_dependencies[uid] = [self.get_base_info(dep)] - # remember the dependencies in the service metadata - service_metadata[uid].update({ - "dependencies": map( - self.get_base_info, deps["dependencies"]), - }) - - # Each key `n` (1,2,3...) contains the form data for one AR Add - # column in the UI. - # All relevant form data will be set accoriding to this data. - out[n] = { - "client_metadata": client_metadata, - "contact_metadata": contact_metadata, - "sample_metadata": sample_metadata, - "sampletype_metadata": sampletype_metadata, - "specification_metadata": specification_metadata, - "specification_to_services": specification_to_services, - "service_to_specifications": service_to_specifications, - "template_metadata": template_metadata, - "template_to_services": template_to_services, - "service_to_templates": service_to_templates, - "profile_metadata": profile_metadata, - "profile_to_services": profile_to_services, - "service_to_profiles": service_to_profiles, - "service_metadata": service_metadata, - "unmet_dependencies": unmet_dependencies, - "additional": {} - } + unmet_dependencies[uid] = [self.get_base_info(dep)] + # remember the dependencies in the service metadata + metadata["service_metadata"][uid].update({ + "dependencies": map( + self.get_base_info, deps["dependencies"]), + }) + return { + "unmet_dependencies": unmet_dependencies + } - # Maybe other add-ons require other fields to be recalculated, - # either from core but not specifically recalculated (e.g Batch) - # or new add-on specific fields - for name, ad in getAdapters((self.context,), IAddSampleMetadata): - logger.info("Additional field records from {}".format(name)) - additional_metadata = ad.get_metadata(record) - out[n]["additional"].update(additional_metadata) + def get_objects_info(self, record, key): + """ + Returns a dictionary with the metadata for the objects the field with + field_name passed in refers to. Returns empty list if the field is not + a reference field or the record for this key cannot be handled + :param record: a record for a single sample (column) + :param key: The key of the field from the record (e.g. Client_uid) + :return: + """ + if not key.endswith("_uid"): + return [] - return out + # Check if there is a function to handle objects for this field + field_name = key.replace("_uid", "") + func_name = "get_{}_info".format(field_name.lower()) + func = getattr(self, func_name, None) + + # Check if there is any adapter to handle objects for this field + adapters = [] + #for name, ad in getAdapters((obj, ), IAddSampleObjectInfo): + # adapters.append((name, ad)) + + if not callable(func): + logger.info("Function '{}' not found".format(func)) + + if not callable(func) and not adapters: + # Nobody can handle objects for this field + return [] + + # Get the objects from this record. Returns a list because the field + # can be multivalued + objects = self.get_objects_from_record(record, key) + return map(lambda obj: self.get_object_info(obj, func, adapters), objects) + + def get_objects_from_record(self, record, key): + """Returns a list of objects that referenced by the key and from the + record passed in + """ + objects = self.get_objs_from_record(record, key) + # We only want the objects, not the uids + return objects.values() + + def get_object_info(self, obj, func, adapters): + # Get the info for each object + if callable(func): + info = func(obj) + + for name, adapter in adapters: + ad_info = adapter.get_object_info(obj) + info = self.merge_object_info(info, ad_info) + + return info + + def merge_object_info(self, base_info, additional_info): + # TODO: merge it correctly, giving priority to base_info! + return base_info.update(additional_info) def show_recalculate_prices(self): bika_setup = api.get_bika_setup() diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index ea1b9ba1cc..1c974a0c7a 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -254,6 +254,9 @@ me = this; $(".service-lockbtn").hide(); return $.each(records, function(arnum, record) { + $.each(record.primaryanalysisrequest_metadata, function(uid, sample) { + return me.apply_field_value(arnum, sample); + }); $.each(record.client_metadata, function(uid, client) { return me.apply_field_value(arnum, client); }); @@ -276,9 +279,6 @@ return me.set_service_spec(arnum, uid, service_spec); }); }); - $.each(record.sample_metadata, function(uid, sample) { - return me.apply_field_value(arnum, sample); - }); $.each(record.sampletype_metadata, function(uid, sampletype) { return me.apply_field_value(arnum, sampletype); }); @@ -788,7 +788,7 @@ if (uid in record.service_to_profiles) { profiles = record.service_to_profiles[uid]; $.each(profiles, function(index, uid) { - return extra["profiles"].push(record.profile_metadata[uid]); + return extra["profiles"].push(record.profiles_metadata[uid]); }); } if (uid in record.service_to_templates) { @@ -839,7 +839,7 @@ context["templates"] = []; if (uid in record.service_to_profiles) { profile_uid = record.service_to_profiles[uid]; - context["profiles"].push(record.profile_metadata[profile_uid]); + context["profiles"].push(record.profiles_metadata[profile_uid]); } if (uid in record.service_to_templates) { template_uid = record.service_to_templates[uid]; @@ -962,7 +962,7 @@ uid = $el.attr("uid"); arnum = $el.closest("[arnum]").attr("arnum"); record = this.records_snapshot[arnum]; - profile_metadata = record.profile_metadata[uid]; + profile_metadata = record.profiles_metadata[uid]; profile_services = []; $.each(record.profile_to_services[uid], function(index, uid) { return profile_services.push(record.service_metadata[uid]); diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 93f89bf107..cb9405e54c 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -243,6 +243,9 @@ class window.AnalysisRequestAdd # set all values for one record (a single column in the AR Add form) $.each records, (arnum, record) -> + # set sample + $.each record.primaryanalysisrequest_metadata, (uid, sample) -> + me.apply_field_value arnum, sample # set client $.each record.client_metadata, (uid, client) -> @@ -275,10 +278,6 @@ class window.AnalysisRequestAdd $.each spec.specifications, (uid, service_spec) -> me.set_service_spec arnum, uid, service_spec - # set sample - $.each record.sample_metadata, (uid, sample) -> - me.apply_field_value arnum, sample - # set sampletype $.each record.sampletype_metadata, (uid, sampletype) -> me.apply_field_value arnum, sampletype @@ -812,7 +811,7 @@ class window.AnalysisRequestAdd if uid of record.service_to_profiles profiles = record.service_to_profiles[uid] $.each profiles, (index, uid) -> - extra["profiles"].push record.profile_metadata[uid] + extra["profiles"].push record.profiles_metadata[uid] # inject template info if uid of record.service_to_templates @@ -862,7 +861,7 @@ class window.AnalysisRequestAdd # collect profiles if uid of record.service_to_profiles profile_uid = record.service_to_profiles[uid] - context["profiles"].push record.profile_metadata[profile_uid] + context["profiles"].push record.profiles_metadata[profile_uid] # collect templates if uid of record.service_to_templates @@ -1007,7 +1006,7 @@ class window.AnalysisRequestAdd arnum = $el.closest("[arnum]").attr "arnum" record = @records_snapshot[arnum] - profile_metadata = record.profile_metadata[uid] + profile_metadata = record.profiles_metadata[uid] profile_services = [] # prepare a list of services used by the profile with the given UID diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index cf15f9ad2e..1e3a6e3933 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -1001,3 +1001,8 @@ def get_metadata(self, record): }, } """ + + +class IAddSampleObjectInfo(Interface): + """Marker interface for objects metadata mapping + """ From 99f90cf49314d1aa7327067388a0dca44e4a9e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sat, 19 Oct 2019 23:25:59 +0200 Subject: [PATCH 13/25] Apply values generically on form updated after records recalculation --- bika/lims/browser/analysisrequest/add2.py | 104 +++++++++--------- .../js/bika.lims.analysisrequest.add.js | 32 ++---- .../bika.lims.analysisrequest.add.coffee | 32 ++---- 3 files changed, 75 insertions(+), 93 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 5107eda624..34ba69979e 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -44,6 +44,7 @@ from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from zope.annotation.interfaces import IAnnotations +from zope.component import adapts from zope.component import getAdapters from zope.component import queryAdapter from zope.i18n.locales import locales @@ -785,13 +786,6 @@ def get_uids_from_record(self, record, key): value = value.split(",") return filter(lambda uid: uid, value) - def get_objs_from_record(self, record, key): - """Returns a mapping of UID -> object - """ - uids = self.get_uids_from_record(record, key) - objs = map(self.get_object_by_uid, uids) - return dict(zip(uids, objs)) - @cache(cache_key) def get_base_info(self, obj): """Returns the base info of an object @@ -876,13 +870,6 @@ def get_client_info(self, obj): } } info["filter_queries"] = filter_queries - - # Maybe other add-ons have additional fields that require filtering too - for name, ad in getAdapters((obj,), IAddSampleFieldFilter): - logger.info("Additional field filters from {}".format(name)) - additional_filters = ad.get_info() - info["filter_queries"].update(additional_filters) - return info @cache(cache_key) @@ -1430,60 +1417,58 @@ def get_unmet_dependencies_info(self, metadata): def get_objects_info(self, record, key): """ - Returns a dictionary with the metadata for the objects the field with + Returns a list with the metadata for the objects the field with field_name passed in refers to. Returns empty list if the field is not a reference field or the record for this key cannot be handled :param record: a record for a single sample (column) :param key: The key of the field from the record (e.g. Client_uid) - :return: + :return: list of info objects """ - if not key.endswith("_uid"): - return [] + # Get the objects from this record. Returns a list because the field + # can be multivalued + uids = self.get_uids_from_record(record, key) + objects = map(self.get_object_by_uid, uids) + objects = map(lambda obj: self.get_object_info(obj, key), objects) + return filter(None, objects) + def get_object_info(self, obj, key): + """Returns the object info metadata for the passed in object and key + :param obj: the object from which extract the info from + :param key: The key of the field from the record (e.g. Client_uid) + :return: dict that represents the object + """ # Check if there is a function to handle objects for this field field_name = key.replace("_uid", "") func_name = "get_{}_info".format(field_name.lower()) func = getattr(self, func_name, None) - # Check if there is any adapter to handle objects for this field - adapters = [] - #for name, ad in getAdapters((obj, ), IAddSampleObjectInfo): - # adapters.append((name, ad)) - - if not callable(func): - logger.info("Function '{}' not found".format(func)) - - if not callable(func) and not adapters: - # Nobody can handle objects for this field - return [] - - # Get the objects from this record. Returns a list because the field - # can be multivalued - objects = self.get_objects_from_record(record, key) - return map(lambda obj: self.get_object_info(obj, func, adapters), objects) - - def get_objects_from_record(self, record, key): - """Returns a list of objects that referenced by the key and from the - record passed in - """ - objects = self.get_objs_from_record(record, key) - # We only want the objects, not the uids - return objects.values() - - def get_object_info(self, obj, func, adapters): # Get the info for each object + info = {} if callable(func): info = func(obj) - for name, adapter in adapters: - ad_info = adapter.get_object_info(obj) - info = self.merge_object_info(info, ad_info) + # Check if there is any adapter to handle objects for this field + for name, adapter in getAdapters((obj, ), IAddSampleObjectInfo): + logger.info("adapter for '{}': {}".format(field_name, name)) + ad_info = adapter.get_object_info() + self.update_object_info(info, ad_info) return info - def merge_object_info(self, base_info, additional_info): - # TODO: merge it correctly, giving priority to base_info! - return base_info.update(additional_info) + def update_object_info(self, base_info, additional_info): + if not base_info: + base_info.update(additional_info) + return + + # Merge field_values info + field_values = base_info.get("field_values", {}) + field_values.update(additional_info.get("field_values", {})) + base_info["field_values"] = field_values + + # Merge filter_queries info + filter_queries = base_info.get("filter_queries", {}) + filter_queries.update(additional_info.get("filter_queries", {})) + base_info["filter_queries"] = filter_queries def show_recalculate_prices(self): bika_setup = api.get_bika_setup() @@ -1720,3 +1705,22 @@ def ajax_submit(self): } else: return {'success': message} + + +class AddSampleObjectInfoAdapter(object): + adapts(IAddSampleObjectInfo) + + def __init__(self, context): + self.context = context + + def get_base_info(self): + return { + "id": api.get_id(self.context), + "uid": api.get_uid(self.context), + "title": api.get_title(self.context), + "field_values": {}, + "filter_queries": {}, + } + + def get_object_info(self): + raise NotImplementedError("get_object_info not implemented") \ No newline at end of file diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 1c974a0c7a..38f7eda072 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -254,14 +254,15 @@ me = this; $(".service-lockbtn").hide(); return $.each(records, function(arnum, record) { - $.each(record.primaryanalysisrequest_metadata, function(uid, sample) { - return me.apply_field_value(arnum, sample); - }); - $.each(record.client_metadata, function(uid, client) { - return me.apply_field_value(arnum, client); - }); - $.each(record.contact_metadata, function(uid, contact) { - return me.apply_field_value(arnum, contact); + var discard; + discard = ["service_metadata", "specification_metadata"]; + $.each(record, function(name, metadata) { + if (indexOf.call(discard, name) >= 0 || !name.endsWith("_metadata")) { + return; + } + return $.each(metadata, function(uid, obj_info) { + return me.apply_field_value(arnum, obj_info); + }); }); $.each(record.service_metadata, function(uid, metadata) { var lock; @@ -271,22 +272,11 @@ } return me.set_service(arnum, uid, true); }); - $.each(record.template_metadata, function(uid, template) { - return me.set_template(arnum, template); - }); $.each(record.specification_metadata, function(uid, spec) { return $.each(spec.specifications, function(uid, service_spec) { return me.set_service_spec(arnum, uid, service_spec); }); }); - $.each(record.sampletype_metadata, function(uid, sampletype) { - return me.apply_field_value(arnum, sampletype); - }); - $.each(record.additional, function(field_name, item) { - return $.each(item, function(uid, metadata) { - return me.apply_field_value(arnum, metadata); - }); - }); return $.each(record.unmet_dependencies, function(uid, dependencies) { var context, dialog, service; service = record.service_metadata[uid]; @@ -397,8 +387,10 @@ * Applies the value for the given record, by setting values and applying * search filters to dependents */ - var me; + var me, title; me = this; + title = record.title; + console.debug("apply_field_value: arnum=" + arnum + " record=" + title); me.apply_dependent_values(arnum, record); return me.apply_dependent_filter_queries(record, arnum); }; diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index cb9405e54c..029648f696 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -243,17 +243,14 @@ class window.AnalysisRequestAdd # set all values for one record (a single column in the AR Add form) $.each records, (arnum, record) -> - # set sample - $.each record.primaryanalysisrequest_metadata, (uid, sample) -> - me.apply_field_value arnum, sample - # set client - $.each record.client_metadata, (uid, client) -> - me.apply_field_value arnum, client - - # set contact - $.each record.contact_metadata, (uid, contact) -> - me.apply_field_value arnum, contact + # Apply the values generically, but those to be handled differently + discard = ["service_metadata", "specification_metadata"] + $.each record, (name, metadata) -> + if name in discard or !name.endsWith("_metadata") + return + $.each metadata, (uid, obj_info) -> + me.apply_field_value arnum, obj_info # set services $.each record.service_metadata, (uid, metadata) -> @@ -269,24 +266,11 @@ class window.AnalysisRequestAdd # select the service me.set_service arnum, uid, yes - # set template - $.each record.template_metadata, (uid, template) -> - me.set_template arnum, template - # set specification $.each record.specification_metadata, (uid, spec) -> $.each spec.specifications, (uid, service_spec) -> me.set_service_spec arnum, uid, service_spec - # set sampletype - $.each record.sampletype_metadata, (uid, sampletype) -> - me.apply_field_value arnum, sampletype - - # set additional records - $.each record.additional, (field_name, item) -> - $.each item, (uid, metadata) -> - me.apply_field_value arnum, metadata - # handle unmet dependencies, one at a time $.each record.unmet_dependencies, (uid, dependencies) -> service = record.service_metadata[uid] @@ -396,6 +380,8 @@ class window.AnalysisRequestAdd * search filters to dependents ### me = this + title = record.title + console.debug "apply_field_value: arnum=#{arnum} record=#{title}" # Set default values to dependents me.apply_dependent_values arnum, record From f4d9d11449364da76036e193fba2f77ce2aa7aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sun, 20 Oct 2019 01:00:29 +0200 Subject: [PATCH 14/25] autofill dependent default values when a field changes --- bika/lims/browser/analysisrequest/add2.py | 26 +++++++++++++++++++ .../bika.lims.analysisrequest.add.coffee | 2 ++ 2 files changed, 28 insertions(+) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 34ba69979e..a8e51ea18e 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -19,6 +19,7 @@ # Some rights reserved, see README and LICENSE. import json +import itertools from collections import OrderedDict from datetime import datetime @@ -1275,6 +1276,7 @@ def ajax_recalculate_records(self): def get_record_metadata(self, record): metadata = {} + extra_fields = {} for key, value in record.items(): if not key.endswith("_uid"): continue @@ -1294,6 +1296,30 @@ def get_record_metadata(self, record): objs_uids = map(lambda obj: obj["uid"], objs_info) metadata[metadata_key] = dict(zip(objs_uids, objs_info)) + # Grab 'autofill' fields to be recalculated too + for obj_info in objs_info: + field_values = obj_info.get("field_values", {}) + for field_name, field_value in field_values.items(): + if not isinstance(field_value, dict): + # this is probably a list, e.g. "Profiles" field + continue + uids = self.get_uids_from_record(field_value, "uid") + if len(uids) == 1: + extra_fields[field_name] = uids[0] + + # Get object info from extra fields + for field_name, uid in extra_fields.items(): + key = "{}_metadata".format(field_name.lower()) + if metadata.get(key): + continue + obj = self.get_object_by_uid(uid) + if not obj: + continue + obj_info = self.get_object_info(obj, field_name) + if not obj_info or "uid" not in obj_info: + continue + metadata[key] = {obj_info["uid"]: obj_info} + return metadata def get_service_to_specs_info(self, metadata): diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 029648f696..8dd53821e3 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -247,6 +247,8 @@ class window.AnalysisRequestAdd # Apply the values generically, but those to be handled differently discard = ["service_metadata", "specification_metadata"] $.each record, (name, metadata) -> + # Discard those fields that will be handled differently and those that + # do not contain explicit object metadata (e.g service_to_specification) if name in discard or !name.endsWith("_metadata") return $.each metadata, (uid, obj_info) -> From 228661c3350a3d7595f90105633d774f95788908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sun, 20 Oct 2019 01:02:15 +0200 Subject: [PATCH 15/25] Imports cleanup --- bika/lims/browser/analysisrequest/add2.py | 31 +++++++++++------------ 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index a8e51ea18e..86888b9ec5 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -19,31 +19,18 @@ # Some rights reserved, see README and LICENSE. import json -import itertools from collections import OrderedDict from datetime import datetime -from bika.lims import POINTS_OF_CAPTURE -from bika.lims import api -from bika.lims import bikaMessageFactory as _ -from bika.lims import logger -from bika.lims.api.analysisservice import get_calculation_dependencies_for -from bika.lims.api.analysisservice import get_service_dependencies_for -from bika.lims.interfaces import IGetDefaultFieldValueARAddHook, \ - IAddSampleFieldFilter, IAddSampleFieldsFlush, IAddSampleMetadata, \ - IAddSampleObjectInfo -from bika.lims.utils import tmpID -from bika.lims.utils.analysisrequest import create_analysisrequest as crar -from bika.lims.workflow import ActionHandlerPool from BTrees.OOBTree import OOBTree from DateTime import DateTime -from plone import protect -from plone.memoize.volatile import DontCache -from plone.memoize.volatile import cache from Products.CMFPlone.utils import _createObjectByType from Products.CMFPlone.utils import safe_unicode from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile +from plone import protect +from plone.memoize.volatile import DontCache +from plone.memoize.volatile import cache from zope.annotation.interfaces import IAnnotations from zope.component import adapts from zope.component import getAdapters @@ -52,6 +39,18 @@ from zope.interface import implements from zope.publisher.interfaces import IPublishTraverse +from bika.lims import POINTS_OF_CAPTURE +from bika.lims import api +from bika.lims import bikaMessageFactory as _ +from bika.lims import logger +from bika.lims.api.analysisservice import get_calculation_dependencies_for +from bika.lims.api.analysisservice import get_service_dependencies_for +from bika.lims.interfaces import IGetDefaultFieldValueARAddHook, \ + IAddSampleFieldsFlush, IAddSampleObjectInfo +from bika.lims.utils import tmpID +from bika.lims.utils.analysisrequest import create_analysisrequest as crar +from bika.lims.workflow import ActionHandlerPool + AR_CONFIGURATION_STORAGE = "bika.lims.browser.analysisrequest.manage.add" SKIP_FIELD_ON_COPY = ["Sample", "PrimaryAnalysisRequest", "Remarks"] From 708dc807c77eeb159886487123e788a80a6ba8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sun, 20 Oct 2019 11:10:23 +0200 Subject: [PATCH 16/25] Restore the search query of reference field on flush --- .../js/bika.lims.analysisrequest.add.js | 20 +++++++++++++++++-- .../bika.lims.analysisrequest.add.coffee | 13 +++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index 38f7eda072..fdf30b4cb5 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -36,6 +36,7 @@ this.set_template = bind(this.set_template, this); this.set_reference_field = bind(this.set_reference_field, this); this.set_reference_field_query = bind(this.set_reference_field_query, this); + this.reset_reference_field_query = bind(this.reset_reference_field_query, this); this.get_field_by_id = bind(this.get_field_by_id, this); this.get_fields = bind(this.get_fields, this); this.get_form = bind(this.get_form, this); @@ -470,7 +471,7 @@ AnalysisRequestAdd.prototype.flush_reference_field = function(field) { /* - * Empty the reference field + * Empty the reference field and restore the search query */ var catalog_name; catalog_name = field.attr("catalog_name"); @@ -479,7 +480,22 @@ } field.val(""); $("input[type=hidden]", field.parent()).val(""); - return $(".multiValued-listing", field.parent()).empty(); + $(".multiValued-listing", field.parent()).empty(); + return this.reset_reference_field_query(field); + }; + + AnalysisRequestAdd.prototype.reset_reference_field_query = function(field) { + + /* + * Restores the catalog search query for the given reference field + */ + var catalog_name, query; + catalog_name = field.attr("catalog_name"); + if (!catalog_name) { + return; + } + query = $.parseJSON(field.attr("base_query")); + return this.set_reference_field_query(field, query); }; AnalysisRequestAdd.prototype.set_reference_field_query = function(field, query, type) { diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 8dd53821e3..9ee60b2a0f 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -457,7 +457,7 @@ class window.AnalysisRequestAdd flush_reference_field: (field) -> ### - * Empty the reference field + * Empty the reference field and restore the search query ### catalog_name = field.attr "catalog_name" @@ -468,6 +468,17 @@ class window.AnalysisRequestAdd $("input[type=hidden]", field.parent()).val("") $(".multiValued-listing", field.parent()).empty() + # restore the original search query + @reset_reference_field_query field + + reset_reference_field_query: (field) => + ### + * Restores the catalog search query for the given reference field + ### + catalog_name = field.attr "catalog_name" + return unless catalog_name + query = $.parseJSON field.attr "base_query" + @set_reference_field_query field, query set_reference_field_query: (field, query, type="base_query") => ### From b3ca1af5864f0cb41793a5eff0452fa5e0e74f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Sun, 20 Oct 2019 11:30:37 +0200 Subject: [PATCH 17/25] Cleanup --- bika/lims/adapters/addsample.py | 52 +++++++++++++++++++++ bika/lims/browser/analysisrequest/add2.py | 31 ++++-------- bika/lims/interfaces/__init__.py | 57 +++++++++++------------ 3 files changed, 88 insertions(+), 52 deletions(-) create mode 100644 bika/lims/adapters/addsample.py diff --git a/bika/lims/adapters/addsample.py b/bika/lims/adapters/addsample.py new file mode 100644 index 0000000000..c9db15c9f9 --- /dev/null +++ b/bika/lims/adapters/addsample.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SENAITE.CORE. +# +# SENAITE.CORE is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright 2018-2019 by it's authors. +# Some rights reserved, see README and LICENSE. + +from zope.component import adapts + +from bika.lims import api +from bika.lims.interfaces import IAddSampleObjectInfo + + +class AddSampleObjectInfoAdapter(object): + """Base implementation of an adapter for reference fields used in Sample + Add form + """ + adapts(IAddSampleObjectInfo) + + def __init__(self, context): + self.context = context + + def get_base_info(self): + """Returns the basic dictionary structure for the current object + """ + return { + "id": api.get_id(self.context), + "uid": api.get_uid(self.context), + "title": api.get_title(self.context), + "field_values": {}, + "filter_queries": {}, + } + + def get_object_info(self): + """Returns the dict representation of the context object for its + correct consumption by Sample Add form. + See IAddSampleObjectInfo for further details + """ + raise NotImplementedError("get_object_info not implemented") diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 86888b9ec5..7ab78c00f8 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -32,7 +32,6 @@ from plone.memoize.volatile import DontCache from plone.memoize.volatile import cache from zope.annotation.interfaces import IAnnotations -from zope.component import adapts from zope.component import getAdapters from zope.component import queryAdapter from zope.i18n.locales import locales @@ -1274,6 +1273,8 @@ def ajax_recalculate_records(self): return out def get_record_metadata(self, record): + """Returns the metadata for the record passed in + """ metadata = {} extra_fields = {} for key, value in record.items(): @@ -1295,7 +1296,7 @@ def get_record_metadata(self, record): objs_uids = map(lambda obj: obj["uid"], objs_info) metadata[metadata_key] = dict(zip(objs_uids, objs_info)) - # Grab 'autofill' fields to be recalculated too + # Grab 'field_values' fields to be recalculated too for obj_info in objs_info: field_values = obj_info.get("field_values", {}) for field_name, field_value in field_values.items(): @@ -1306,10 +1307,11 @@ def get_record_metadata(self, record): if len(uids) == 1: extra_fields[field_name] = uids[0] - # Get object info from extra fields + # Populate metadata with object info from extra fields for field_name, uid in extra_fields.items(): key = "{}_metadata".format(field_name.lower()) if metadata.get(key): + # This object has been processed already, skip continue obj = self.get_object_by_uid(uid) if not obj: @@ -1481,6 +1483,10 @@ def get_object_info(self, obj, key): return info def update_object_info(self, base_info, additional_info): + """Updates the dictionaries for keys 'field_values' and 'filter_queries' + from base_info with those defined in additional_info. If base_info is + empty or None, updates the whole base_info dict with additional_info + """ if not base_info: base_info.update(additional_info) return @@ -1730,22 +1736,3 @@ def ajax_submit(self): } else: return {'success': message} - - -class AddSampleObjectInfoAdapter(object): - adapts(IAddSampleObjectInfo) - - def __init__(self, context): - self.context = context - - def get_base_info(self): - return { - "id": api.get_id(self.context), - "uid": api.get_uid(self.context), - "title": api.get_title(self.context), - "field_values": {}, - "filter_queries": {}, - } - - def get_object_info(self): - raise NotImplementedError("get_object_info not implemented") \ No newline at end of file diff --git a/bika/lims/interfaces/__init__.py b/bika/lims/interfaces/__init__.py index 1e3a6e3933..684d0afaf5 100644 --- a/bika/lims/interfaces/__init__.py +++ b/bika/lims/interfaces/__init__.py @@ -963,16 +963,6 @@ def guard(self, transition): """ -class IAddSampleFieldFilter(Interface): - """Marker interface for field filters for Add Sample form - """ - - def get_info(self): - """Returns a dict where the key is the name of the field to be filtered - and the value is a dict with the search criteria - """ - - class IAddSampleFieldsFlush(Interface): """Marker interface for field dependencies flush for Add Sample form """ @@ -983,26 +973,33 @@ def get_flush_settings(self): """ -class IAddSampleMetadata(Interface): - """Marker interface for additional fields that need to be taken into - account in recalculation of metadata from Add Sample form - """ - - def get_metadata(self, record): - """Returns a dict where the key is the fieldname (e.g. Batch) and the - value is another dict where the key is the UID of the object and the - value is its metadata representation as a dict: - - {Batch: { - : { - "id": , - "title", , - ... - }, - } - """ - - class IAddSampleObjectInfo(Interface): """Marker interface for objects metadata mapping """ + + def get_object_info(self): + """Returns the dict representation of the context object for its + correct consumption by Sample Add form: + + {'id': , + 'uid': , + 'title': , + 'filter_queries': { + : { + : + } + }, + 'field_values': { + : { + : , + : <dependent_title> + } + } + + Besides the basic keys (id, uid, title), two additional keys can be + provided: + - filter_queries: contains the filter queries for other fields to be + applied when the value of current field changes. + - field_values: contains default values for other fields to be applied + when the value of the current field changes. + """ \ No newline at end of file From e7fac4552f6a5ca20cb03247059bd738de45670f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 11:32:01 +0200 Subject: [PATCH 18/25] Update changelog --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index fc3e97bd77..f64f3cffff 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,7 @@ Changelog **Added** -- #1462 Allow to extend Add Sample behavior with adapters +- #1462 Allow to extend the behavior of fields from AddSample view with adapters - #1455 Added support for adapters in guard handler - #1436 Setting in setup for auto-reception of samples upon creation - #1433 Added Submitter column in Sample's analyses listing From 4758ee103b60177130dc638c250d76f884720434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 20:37:31 +0200 Subject: [PATCH 19/25] Added cache for get_object_info --- bika/lims/browser/analysisrequest/add2.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 7ab78c00f8..0ef62ec60e 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -1458,6 +1458,14 @@ def get_objects_info(self, record, key): objects = map(lambda obj: self.get_object_info(obj, key), objects) return filter(None, objects) + def object_info_cache_key(method, self, obj, key): + if obj is None: + raise DontCache + field_name = key.replace("_uid", "") + obj_key = api.get_cache_key(obj) + return "-".join([field_name, obj_key]) + + @cache(object_info_cache_key) def get_object_info(self, obj, key): """Returns the object info metadata for the passed in object and key :param obj: the object from which extract the info from From 95ddef4248291e045375d2ecf4d644ad86b26168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 20:40:51 +0200 Subject: [PATCH 20/25] Better cache key --- bika/lims/browser/analysisrequest/add2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 0ef62ec60e..47824a472e 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -1459,9 +1459,9 @@ def get_objects_info(self, record, key): return filter(None, objects) def object_info_cache_key(method, self, obj, key): - if obj is None: + if obj is None or not key: raise DontCache - field_name = key.replace("_uid", "") + field_name = key.replace("_uid", "").lower() obj_key = api.get_cache_key(obj) return "-".join([field_name, obj_key]) From 759ed42f1449602c6841faeba094fb4004e7a824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 20:51:42 +0200 Subject: [PATCH 21/25] Template metadata is handled differently in js --- bika/lims/browser/js/bika.lims.analysisrequest.add.js | 5 ++++- .../browser/js/coffee/bika.lims.analysisrequest.add.coffee | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bika/lims/browser/js/bika.lims.analysisrequest.add.js b/bika/lims/browser/js/bika.lims.analysisrequest.add.js index fdf30b4cb5..6599ef0f01 100644 --- a/bika/lims/browser/js/bika.lims.analysisrequest.add.js +++ b/bika/lims/browser/js/bika.lims.analysisrequest.add.js @@ -256,7 +256,7 @@ $(".service-lockbtn").hide(); return $.each(records, function(arnum, record) { var discard; - discard = ["service_metadata", "specification_metadata"]; + discard = ["service_metadata", "specification_metadata", "template_metadata"]; $.each(record, function(name, metadata) { if (indexOf.call(discard, name) >= 0 || !name.endsWith("_metadata")) { return; @@ -273,6 +273,9 @@ } return me.set_service(arnum, uid, true); }); + $.each(record.template_metadata, function(uid, template) { + return me.set_template(arnum, template); + }); $.each(record.specification_metadata, function(uid, spec) { return $.each(spec.specifications, function(uid, service_spec) { return me.set_service_spec(arnum, uid, service_spec); diff --git a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee index 9ee60b2a0f..48f10c1e55 100644 --- a/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee +++ b/bika/lims/browser/js/coffee/bika.lims.analysisrequest.add.coffee @@ -245,7 +245,7 @@ class window.AnalysisRequestAdd $.each records, (arnum, record) -> # Apply the values generically, but those to be handled differently - discard = ["service_metadata", "specification_metadata"] + discard = ["service_metadata", "specification_metadata", "template_metadata"] $.each record, (name, metadata) -> # Discard those fields that will be handled differently and those that # do not contain explicit object metadata (e.g service_to_specification) @@ -268,6 +268,10 @@ class window.AnalysisRequestAdd # select the service me.set_service arnum, uid, yes + # set template + $.each record.template_metadata, (uid, template) -> + me.set_template arnum, template + # set specification $.each record.specification_metadata, (uid, spec) -> $.each spec.specifications, (uid, service_spec) -> From 6ecfd07dc932500db7b178608de9adfa8386c566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 21:41:55 +0200 Subject: [PATCH 22/25] If there is no specific func for get_*_info, call get_base_info --- bika/lims/browser/analysisrequest/add2.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 47824a472e..1decb045b1 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -1145,22 +1145,6 @@ def get_service_by_keyword(keyword): info["service_uids"] = specifications.keys() return info - @cache(cache_key) - def get_container_info(self, obj): - """Returns the info for a Container - """ - info = self.get_base_info(obj) - info.update({}) - return info - - @cache(cache_key) - def get_preservation_info(self, obj): - """Returns the info for a Preservation - """ - info = self.get_base_info(obj) - info.update({}) - return info - def ajax_get_global_settings(self): """Returns the global Bika settings """ @@ -1478,9 +1462,7 @@ def get_object_info(self, obj, key): func = getattr(self, func_name, None) # Get the info for each object - info = {} - if callable(func): - info = func(obj) + info = callable(func) and func(obj) or self.get_base_info(obj) # Check if there is any adapter to handle objects for this field for name, adapter in getAdapters((obj, ), IAddSampleObjectInfo): From 3d6e66742aedbde0c09d3f0dcf7a6c529adda361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 21:52:16 +0200 Subject: [PATCH 23/25] Autofill Client Contact in Sample Add form when current user is a client --- CHANGES.rst | 1 + bika/lims/browser/analysisrequest/add2.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index f64f3cffff..44f3d7b5fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -36,6 +36,7 @@ Changelog **Fixed** +- #1462 Autofill Client Contact in Sample Add form when current user is a client - #1449 sort_limit was not considered in ReferenceWidget searches - #1449 Fix Clients were unable to add batches - #1453 Fix initial IDs not starting with 1 diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index 1decb045b1..dd5c7a360b 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -441,6 +441,11 @@ def get_default_contact(self, client=None): contacts = catalog(query) if len(contacts) == 1: return api.get_object(contacts[0]) + elif client == api.get_current_client(): + # Current user is a Client contact. Use current contact + current_user = api.get_current_user() + return api.get_user_contact(current_user, contact_types=["Contact"]) + return None def getMemberDiscountApplies(self): From 3772789a4f3076adc31379bf82bd5dd5b5fe1216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= <jp@naralabs.com> Date: Sun, 20 Oct 2019 23:08:09 +0200 Subject: [PATCH 24/25] viewcache for self.get_object_by_uid --- bika/lims/browser/analysisrequest/add2.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index dd5c7a360b..d091284296 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -29,6 +29,7 @@ from Products.Five.browser import BrowserView from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile from plone import protect +from plone.memoize import view as viewcache from plone.memoize.volatile import DontCache from plone.memoize.volatile import cache from zope.annotation.interfaces import IAnnotations @@ -110,6 +111,8 @@ def get_view_url(self): return url return "{}?{}".format(url, qs) + # XXX HEADS-UP! + @viewcache.memoize def get_object_by_uid(self, uid): """Get the object by UID """ @@ -1332,7 +1335,7 @@ def get_template_additional_info(self, metadata): template = metadata.get("template_metadata", {}) # We don't expect more than one template, but who knows about future? for uid, obj_info in template.items(): - obj = api.get_object_by_uid(uid) + obj = self.get_object_by_uid(uid) # profile from the template profile = obj.getAnalysisProfile() # add the profile to the other profiles @@ -1377,7 +1380,7 @@ def get_profiles_additional_info(self, metadata): service_metadata = metadata.get("service_metadata", {}) profiles = metadata.get("profiles_metadata", {}) for uid, obj_info in profiles.items(): - obj = api.get_object_by_uid(uid) + obj = self.get_object_by_uid(uid) # get all services of this profile services = obj.getService() # get all UIDs of the profile services @@ -1409,7 +1412,7 @@ def get_unmet_dependencies_info(self, metadata): unmet_dependencies = {} services = metadata.get("service_metadata", {}).copy() for uid, obj_info in services.items(): - obj = api.get_object_by_uid(uid) + obj = self.get_object_by_uid(uid) # get the dependencies of this service deps = get_service_dependencies_for(obj) From e82cf1e6aa775af23216310b1b566db5e73811b4 Mon Sep 17 00:00:00 2001 From: Ramon Bartl <rb@ridingbytes.com> Date: Sun, 20 Oct 2019 23:17:33 +0200 Subject: [PATCH 25/25] Improved comment --- bika/lims/browser/analysisrequest/add2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bika/lims/browser/analysisrequest/add2.py b/bika/lims/browser/analysisrequest/add2.py index d091284296..5d308d82a4 100644 --- a/bika/lims/browser/analysisrequest/add2.py +++ b/bika/lims/browser/analysisrequest/add2.py @@ -111,7 +111,9 @@ def get_view_url(self): return url return "{}?{}".format(url, qs) - # XXX HEADS-UP! + # N.B.: We are caching here persistent objects! + # It should be safe to do this but only on the view object, + # because it get recreated per request (transaction border). @viewcache.memoize def get_object_by_uid(self, uid): """Get the object by UID