Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions healthcare/healthcare/doctype/observation/observation.html
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@
</b>
</div>
{% for comps in data[data.get("observation")] %}
{% set comps_dict = comps.get("observation") %}
{% if comps_dict and isinstance(comps_dict, dict) %}
{% if comps.get("observation").get("preferred_display_name") %}
{% set observation_name = comps.get("observation").get("preferred_display_name") %}
{% else %}
Expand Down Expand Up @@ -345,6 +347,7 @@
</div>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
<div class="note obs-second-line">
{{data.get("description") or ""}}
Expand Down
44 changes: 30 additions & 14 deletions healthcare/healthcare/doctype/observation/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,12 @@ def component_has_result(self):
component_obs = frappe.db.get_all("Observation", {"parent_observation": self.name}, pluck="name")
for obs in component_obs:
obs_doc = frappe.get_doc("Observation", obs)
if not obs_doc.has_result():
return False
if obs_doc.has_component:
if not obs_doc.component_has_result():
return False
else:
if not obs_doc.has_result():
return False

return True

Expand Down Expand Up @@ -220,25 +224,38 @@ def return_child_observation_data_as_dict(child_observations, obs, obs_length=0)
obs_list = []
has_result = False
obs_approved = False
all_children_approved = True

for child in child_observations:
if child.get("permitted_data_type"):
obs_length += 1
if child.get("permitted_data_type") == "Select" and child.get("options"):
child["options_list"] = child.get("options").split("\n")
if child.get("specimen"):
child["received_time"] = frappe.get_value("Specimen", child.get("specimen"), "received_time")
observation_data = {"observation": child}
obs_list.append(observation_data)
if child.get("has_component"):
grand_children = get_child_observations(child)
grand_dict, obs_length = return_child_observation_data_as_dict(
grand_children, child, obs_length
)
obs_list.append(grand_dict)
if not grand_dict.get("obs_approved", False):
all_children_approved = False
else:
if child.get("permitted_data_type"):
obs_length += 1
if child.get("permitted_data_type") == "Select" and child.get("options"):
child["options_list"] = child.get("options").split("\n")
if child.get("specimen"):
child["received_time"] = frappe.get_value("Specimen", child.get("specimen"), "received_time")
observation_data = {"observation": child}
obs_list.append(observation_data)
if child.get("status") != "Approved":
all_children_approved = False

if (
child.get("result_data")
or child.get("result_text")
or child.get("result_select") not in [None, "", "Null"]
):
has_result = True
if child.get("status") == "Approved":
obs_approved = True

if all_children_approved and child_observations:
obs_approved = True

obs_dict = {
"has_component": True,
Expand Down Expand Up @@ -495,11 +512,10 @@ def set_observation_status(observation, status, reason=None, parent_obs=None):
parent_obs = new_doc.name

if observation_doc.has_component:
docstatus_filter = 0 if status == "Approved" else 1

component_obs = frappe.db.get_all(
"Observation",
filters={"parent_observation": observation, "docstatus": docstatus_filter},
filters={"parent_observation": observation},
pluck="name",
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,6 @@ frappe.ui.form.on("Observation Template", {
},

refresh: function(frm) {
frm.set_query("observation_template", "observation_component", function () {
return {
"filters": {
"has_component": 0,
}
};
});
frm.set_query("method", function () {
return {
"filters": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def on_update(self):
if not self.item and self.is_billable:
create_item_from_template(self)

MAX_NESTING_LEVEL = 3

def validate(self):
if self.has_component and self.sample_collection_required:
self.sample_collection_required = 0
Expand All @@ -43,9 +45,67 @@ def validate(self):

if self.has_component:
self.abbr = ""

# Prevent self-referencing
for row in self.observation_component:
if row.observation_template == self.name:
frappe.throw(
_("Observation Template '{0}' cannot be added as a component of itself.").format(self.name)
)

# Prevent circular / nested self-reference
for row in self.observation_component:
if ObservationTemplate.is_parent_in_child(row.observation_template, self.name):
frappe.throw(
_(
"Circular reference detected: '{0}' is already a component (directly or indirectly) of '{1}'"
).format(self.name, row.observation_template)
)

child_depth = ObservationTemplate.get_nesting_depth(row.observation_template)

if child_depth >= ObservationTemplate.MAX_NESTING_LEVEL:
frappe.throw(
_(
"You cannot add '{0}' because it already contains {1} levels of nested components. The maximum allowed depth is {2}."
).format(
row.observation_template, child_depth, ObservationTemplate.MAX_NESTING_LEVEL
)
)
else:
self.validate_abbr()

@staticmethod
def is_parent_in_child(child_name, parent_name):
"""Check recursively if parent_name exists as a component inside child_name"""
child_doc = frappe.get_doc("Observation Template", child_name)
if not child_doc.has_component:
return False

for comp in child_doc.observation_component:
if comp.observation_template == parent_name:
return True
# recursive check (grandchildren)
if ObservationTemplate.is_parent_in_child(comp.observation_template, parent_name):
return True

return False

@staticmethod
def get_nesting_depth(template_name, current_level=0):
# Recursively count how deep the component hierarchy goes
template = frappe.get_doc("Observation Template", template_name)
if not template.has_component:
return current_level

max_depth = current_level
for comp in template.observation_component:
depth = ObservationTemplate.get_nesting_depth(comp.observation_template, current_level + 1)
if depth > max_depth:
max_depth = depth

return max_depth

def validate_abbr(self):
if not self.abbr:
self.abbr = frappe.utils.get_abbr(self.observation)
Expand Down
99 changes: 76 additions & 23 deletions healthcare/healthcare/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,17 +1208,31 @@ def insert_diagnostic_report(doc, patient, sample_collection=None):
diagnostic_report.save(ignore_permissions=True)


def insert_observation_and_sample_collection(doc, patient, grp, sample_collection, child=None):
def insert_observation_and_sample_collection(
doc, patient, grp, sample_collection, child=None, parent_observation=None
):
diag_report_required = False
if grp.get("has_component"):
diag_report_required = True
# parent observation
parent_observation = add_observation(
current_parent_observation = add_observation(
patient=patient,
template=grp.get("name"),
practitioner=doc.ref_practitioner,
invoice=doc.name,
child=child if child else "",
parent=parent_observation,
)
sample_collection.append(
"observation_sample_collection",
{
"observation_template": grp.get("name"),
"container_closure_color": grp.get("container_closure_color"),
"sample": grp.get("sample"),
"sample_type": grp.get("sample_type"),
"component_observation_parent": current_parent_observation,
"reference_child": child if child else "",
},
)

sample_reqd_component_obs, non_sample_reqd_component_obs = get_observation_template_details(
Expand All @@ -1228,27 +1242,66 @@ def insert_observation_and_sample_collection(doc, patient, grp, sample_collectio

if len(non_sample_reqd_component_obs) > 0:
for comp in non_sample_reqd_component_obs:
add_observation(
patient=patient,
template=comp,
practitioner=doc.ref_practitioner,
parent=parent_observation,
invoice=doc.name,
child=child if child else "",
comp_details = frappe.get_value(
"Observation Template",
comp,
[
"name",
"has_component",
"sample_collection_required",
"sample",
"sample_type",
"container_closure_color",
],
as_dict=True,
)
# create sample_colleciton child row for sample_collection_reqd grouped templates
if comp_details.get("has_component"):
# recurse if component is also a template with components
sample_collection, diag_report_required = insert_observation_and_sample_collection(
doc,
patient,
comp_details,
sample_collection,
child,
parent_observation=current_parent_observation,
)
else:
add_observation(
patient=patient,
template=comp,
practitioner=doc.ref_practitioner,
parent=current_parent_observation,
invoice=doc.name,
child=child if child else "",
)
# create sample_colleciton child row for sample_collection_reqd grouped templates
if len(sample_reqd_component_obs) > 0:
sample_collection.append(
"observation_sample_collection",
{
"observation_template": grp.get("name"),
"container_closure_color": grp.get("color"),
"sample": grp.get("sample"),
"sample_type": grp.get("sample_type"),
"component_observation_parent": parent_observation,
"reference_child": child if child else "",
},
)
for comp in sample_reqd_component_obs:
comp_details = frappe.get_value(
"Observation Template",
comp,
[
"name",
"has_component",
"sample_collection_required",
"sample",
"sample_type",
"container_closure_color",
],
as_dict=True,
)
if comp_details.get("has_component"):
# recurse into nested template
sub_sc, sub_drc = insert_observation_and_sample_collection(
doc,
patient,
comp_details,
sample_collection,
child,
parent_observation=current_parent_observation,
)
sample_collection = sub_sc
diag_report_required = diag_report_required or sub_drc

else:
diag_report_required = True
Expand All @@ -1262,12 +1315,12 @@ def insert_observation_and_sample_collection(doc, patient, grp, sample_collectio
child=child if child else "",
)
else:
# create sample_colleciton child row for sample_collection_reqd individual templates
# create sample_colleciton child row for sample_collection_reqd individual templates
sample_collection.append(
"observation_sample_collection",
{
"observation_template": grp.get("name"),
"container_closure_color": grp.get("color"),
"container_closure_color": grp.get("container_closure_color"),
"sample": grp.get("sample"),
"sample_type": grp.get("sample_type"),
"reference_child": child if child else "",
Expand Down
Loading
Loading