diff --git a/CHANGES.rst b/CHANGES.rst index cf5a503a12..5ebed3e0b5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -13,6 +13,7 @@ Changelog **Fixed** +- #403 Calculations not triggered in manage results view - #402 Sort Analysis Services correctly based on their Sortkey + Title (Again) - #398 PR-2315 ID Server does not find the next correct sequence after flushing the number generator - #399 PR-2318 AR Add fails silently if e.g. the ID of the AR was already taken diff --git a/bika/lims/browser/fields/historyawarereferencefield.py b/bika/lims/browser/fields/historyawarereferencefield.py index 9c866b4d32..28340e4f07 100644 --- a/bika/lims/browser/fields/historyawarereferencefield.py +++ b/bika/lims/browser/fields/historyawarereferencefield.py @@ -72,6 +72,7 @@ def set(self, instance, value, **kwargs): add = [v for v in uids if v and v not in targetUIDs] newuids = [t for t in list(targetUIDs) + list(uids) if t not in sub] + newuids = list(set(newuids)) for uid in newuids: # update version_id of all existing references that aren't # about to be removed anyway (contents of sub) diff --git a/bika/lims/content/calculation.py b/bika/lims/content/calculation.py index 728a09ade7..d6aca3890d 100644 --- a/bika/lims/content/calculation.py +++ b/bika/lims/content/calculation.py @@ -189,15 +189,11 @@ def setFormula(self, Formula=None): self.setDependentServices(None) self.getField('Formula').set(self, Formula) else: - DependentServices = [] keywords = re.compile(r"\[([^.^\]]+)\]").findall(Formula) - for keyword in keywords: - service = bsc(portal_type="AnalysisService", - getKeyword=keyword) - if service: - DependentServices.append(service[0].getObject()) - - self.getField('DependentServices').set(self, DependentServices) + brains = bsc(portal_type='AnalysisService', + getKeyword=keywords) + services = [brain.getObject() for brain in brains] + self.getField('DependentServices').set(self, services) self.getField('Formula').set(self, Formula) def getMinifiedFormula(self): diff --git a/bika/lims/upgrade/v01_01_006.py b/bika/lims/upgrade/v01_01_006.py index e3afb801c0..dbc5d44105 100644 --- a/bika/lims/upgrade/v01_01_006.py +++ b/bika/lims/upgrade/v01_01_006.py @@ -28,8 +28,17 @@ def upgrade(tool): logger.info("Upgrading {0}: {1} -> {2}".format(product, ver_from, version)) + # Convert ReferenceField's values into UIDReferenceFields. UpgradeReferenceFields() + # Calculations not triggered in manage results view + # https://github.com/senaite/bika.lims/issues/355 + # Since we've already migrated the ReferenceField DependentServices from + # Calculation (with relation name 'CalculationAnalysisService' above, this + # wouldn't be strictly necessary, but who knows... maybe we've lost the + # at_references too, so just do it. + fix_broken_calculations() + # Indexes and colums were changed as per # https://github.com/senaite/bika.lims/pull/353 ut.delIndex(CATALOG_ANALYSIS_LISTING, 'getAnalysisRequestUID') @@ -44,6 +53,92 @@ def upgrade(tool): return True +def fix_broken_calculations(): + """Walks-through calculations associated to undergoing analyses and + resets the value for DependentServices field""" + + logger.info("Fixing broken calculations (re-assignment of dependents)...") + + # Fetch only the subset of analyses that are undergoing. + # Analyses that have been verified or published cannot be updated, so there + # is no sense to check their calculations + review_states = [ + 'attachment_due', + 'not_requested', + 'rejected', + 'retracted', + 'sample_due', + 'sample_prep', + 'sample_received', + 'sample_received', + 'sample_registered', + 'sampled', + 'to_be_preserved', + 'to_be_sampled', + ] + uc = api.get_tool('uid_catalog') + catalog = get_tool(CATALOG_ANALYSIS_LISTING) + brains = catalog(portal_type='Analysis', review_state=review_states) + for brain in brains: + analysis = brain.getObject() + calculation = analysis.getCalculation() + if not calculation: + continue + + dependents = calculation.getDependentServices() + # We don't want eventualities such as [None,] + dependents = filter(None, dependents) + if not dependents: + # Assign the formula again to the calculation. Note the function + # setFormula inferes the dependent services (and stores them) by + # inspecting the keywords set in the formula itself. + # So, instead of doing this job here, we just let setFormula to work + # for us. + formula = calculation.getFormula() + calculation.setFormula(formula) + deps = calculation.getDependentServices() + if not deps: + # Ok, this calculation does not depend on the result of other + # analyses, so we can omit this one, he is already ok + continue + + deps = [dep.getKeyword() for dep in deps] + deps = ', '.join(deps) + arid = analysis.getRequestID() + logger.info("Dependents for {}.{}.{}: {}".format(arid, + analysis.getKeyword(), + calculation.Title(), + deps)) + + # Set the calculation to the analysis again (field Calculation is an + # HistoryAwareReferenceField in Analyses that inherits from + # AbstractRoutineAnalysis + analysis.setCalculation(calculation) + + # Check if all is ok + an_deps = analysis.getCalculation().getDependentServices() + if not an_deps: + # Maybe the version of the calculation is an old one. If so, we + # need to use the last version, cause HistoryAwareReferenceField + # will always point to the version assigned to the calculation + # that was associated to the analysis. + uid = calculation.UID() + target_version = analysis.reference_versions[uid] + last_calc = uc(UID=uid) + if not last_calc: + # This should not happen + logger.warn("No calculation found for %s " % calculation.UID()) + continue + last_calc = last_calc[0].getObject() + if last_calc.version_id != target_version: + # Ok, this is another version. We have no choice here... we + # need to assign the latest version... + analysis.reference_versions[uid]=last_calc.version_id + + # Just in case + analysis.reindexObject() + + refs_to_remove = [] objs_to_reindex = [] @@ -142,7 +237,8 @@ def UpgradeReferenceFields(): ]], ['Calculation', [ - ('DependentServices', 'CalculationDependentServices') + ('DependentServices', 'CalculationDependentServices'), + ('DependentServices', 'CalculationAnalysisService') ]], ['Instrument', [