From 2c114053ad3087ad12a8d8a084cabb817b126066 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 27 Nov 2020 15:05:28 +0530 Subject: [PATCH 01/37] feat: Patient History Settings --- .../__init__.py | 0 .../patient_history_custom_document_type.json | 46 ++++++++++ .../patient_history_custom_document_type.py | 10 +++ .../patient_history_settings/__init__.py | 0 .../patient_history_settings.js | 85 +++++++++++++++++++ .../patient_history_settings.json | 55 ++++++++++++ .../patient_history_settings.py | 10 +++ .../test_patient_history_settings.py | 10 +++ .../__init__.py | 0 ...atient_history_standard_document_type.json | 47 ++++++++++ .../patient_history_standard_document_type.py | 10 +++ 11 files changed, 273 insertions(+) create mode 100644 erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json create mode 100644 erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py create mode 100644 erpnext/healthcare/doctype/patient_history_settings/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js create mode 100644 erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json create mode 100644 erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py create mode 100644 erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py create mode 100644 erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json create mode 100644 erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py b/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json new file mode 100644 index 0000000000..a158075e7b --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json @@ -0,0 +1,46 @@ +{ + "actions": [], + "creation": "2020-11-25 13:40:23.054469", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "select_fields", + "selected_fields" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "select_fields", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Select Fields" + }, + { + "fieldname": "selected_fields", + "fieldtype": "Code", + "label": "selected_fields" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-11-25 14:19:33.637543", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient History Custom Document Type", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py new file mode 100644 index 0000000000..f0a1f929f4 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientHistoryCustomDocumentType(Document): + pass diff --git a/erpnext/healthcare/doctype/patient_history_settings/__init__.py b/erpnext/healthcare/doctype/patient_history_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js new file mode 100644 index 0000000000..155476e2b1 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -0,0 +1,85 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient History Settings', { + refresh: function(frm) { + frm.set_query('document_type', 'custom_doctypes', () => { + return { + filters: { + custom: 1, + module: 'Healthcare' + } + }; + }); + }, + + field_selector: function(frm, doc) { + let document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); + let d = new frappe.ui.Dialog({ + title: __('{0} Fields', [__(doc.document_type)]), + fields: [ + { + label: __('Select Fields'), + fieldtype: 'MultiCheck', + fieldname: 'fields', + options: frm.events.get_doctype_fields(frm, doc.document_type, document_fields), + columns: 2 + } + ] + }); + + d.set_primary_action(__('Save'), () => { + let values = d.get_values().fields; + + let selected_fields = []; + + for (let idx in values) { + let value = values[idx]; + + let field = frappe.meta.get_docfield(doc.document_type, value); + if (field) { + selected_fields.push({ + label: field.label, + fieldname: field.fieldname + }); + } + } + + frappe.model.set_value('Patient History Custom Document Type', doc.name, 'selected_fields', JSON.stringify(selected_fields)); + d.hide(); + }); + + d.show(); + }, + + get_doctype_fields(frm, document_type, fields) { + let multiselect_fields = []; + + frappe.model.with_doctype(document_type, () => { + // get doctype fields + frappe.get_doc('DocType', document_type).fields.forEach(field => { + if (!in_list(frappe.model.no_value_type, field.fieldtype) && !field.hidden) { + multiselect_fields.push({ + label: field.label, + value: field.fieldname, + checked: in_list(fields, field.fieldname) + }); + } + }); + }); + + return multiselect_fields; + } +}); + +frappe.ui.form.on('Patient History Custom Document Type', { + select_fields: function(frm) { + let doc = frm.selected_doc; + + if (!doc.document_type) + frappe.throw(__('Select the Document Type first.')) + + frm.events.field_selector(frm, doc); + } + +}); diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json new file mode 100644 index 0000000000..143e2c91eb --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json @@ -0,0 +1,55 @@ +{ + "actions": [], + "creation": "2020-11-25 13:41:37.675518", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "standard_doctypes", + "section_break_2", + "custom_doctypes" + ], + "fields": [ + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "custom_doctypes", + "fieldtype": "Table", + "label": "Custom Document Types", + "options": "Patient History Custom Document Type" + }, + { + "fieldname": "standard_doctypes", + "fieldtype": "Table", + "label": "Standard Document Types", + "options": "Patient History Standard Document Type", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2020-11-25 13:43:38.511771", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient History Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py new file mode 100644 index 0000000000..27cbf2fc60 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientHistorySettings(Document): + pass diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py new file mode 100644 index 0000000000..548c423670 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPatientHistorySettings(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py b/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json new file mode 100644 index 0000000000..ec40d893eb --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json @@ -0,0 +1,47 @@ +{ + "actions": [], + "creation": "2020-11-25 13:39:36.014814", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "select_fields", + "selected_fields" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "select_fields", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Select Fields" + }, + { + "fieldname": "selected_fields", + "fieldtype": "Code", + "label": "Selected Fields" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-11-25 14:19:53.708991", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient History Standard Document Type", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py new file mode 100644 index 0000000000..2d94911855 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientHistoryStandardDocumentType(Document): + pass From f2932d720882433e18c690be130a7d919a0570d6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 28 Nov 2020 19:19:58 +0530 Subject: [PATCH 02/37] feat: set date field in Settings for Patient Medical Record --- .../patient_history_custom_document_type.json | 24 ++++++++++++------- .../patient_history_settings.js | 13 ++++------ .../patient_history_settings.py | 17 +++++++++++-- ...atient_history_standard_document_type.json | 16 ++++++------- 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json index a158075e7b..7986e48ced 100644 --- a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json +++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json @@ -6,7 +6,8 @@ "engine": "InnoDB", "field_order": [ "document_type", - "select_fields", + "date_fieldname", + "add_edit_fields", "selected_fields" ], "fields": [ @@ -18,22 +19,29 @@ "options": "DocType", "reqd": 1 }, - { - "fieldname": "select_fields", - "fieldtype": "Button", - "in_list_view": 1, - "label": "Select Fields" - }, { "fieldname": "selected_fields", "fieldtype": "Code", "label": "selected_fields" + }, + { + "fieldname": "add_edit_fields", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Add / Edit Fields" + }, + { + "fieldname": "date_fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Date Fieldname", + "reqd": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-25 14:19:33.637543", + "modified": "2020-11-28 19:04:48.323164", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient History Custom Document Type", diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index 155476e2b1..ca2707f6a6 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -73,13 +73,10 @@ frappe.ui.form.on('Patient History Settings', { }); frappe.ui.form.on('Patient History Custom Document Type', { - select_fields: function(frm) { - let doc = frm.selected_doc; - - if (!doc.document_type) - frappe.throw(__('Select the Document Type first.')) - - frm.events.field_selector(frm, doc); + add_edit_fields: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.document_type) { + frm.events.field_selector(frm, row); + } } - }); diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 27cbf2fc60..9e876e8c95 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -3,8 +3,21 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe +from frappe import _ from frappe.model.document import Document class PatientHistorySettings(Document): - pass + def validate(self): + self.validate_date_fieldnames() + + def validate_date_fieldnames(self): + for entry in self.custom_doctypes: + field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname) + if not field: + frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format( + entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) + + if field.fieldtype not in ['Date', 'Datetime']: + frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format( + entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json index ec40d893eb..ef4fc2bfe1 100644 --- a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json +++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json @@ -6,7 +6,7 @@ "engine": "InnoDB", "field_order": [ "document_type", - "select_fields", + "add_edit_fields", "selected_fields" ], "fields": [ @@ -19,22 +19,22 @@ "read_only": 1, "reqd": 1 }, - { - "fieldname": "select_fields", - "fieldtype": "Button", - "in_list_view": 1, - "label": "Select Fields" - }, { "fieldname": "selected_fields", "fieldtype": "Code", "label": "Selected Fields" + }, + { + "fieldname": "add_edit_fields", + "fieldtype": "Button", + "in_list_view": 1, + "label": "Add / Edit Fields" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-25 14:19:53.708991", + "modified": "2020-11-28 18:57:30.446348", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient History Standard Document Type", From c91e03c8911286cb50731dd7064501b9c80474a9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 28 Nov 2020 20:24:06 +0530 Subject: [PATCH 03/37] feat: Create Patient Medical Record on configured doctype submission --- .../patient_history_settings.js | 6 ++- .../patient_history_settings.py | 53 ++++++++++++++++++- erpnext/hooks.py | 4 ++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index ca2707f6a6..60926eeb11 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -40,7 +40,8 @@ frappe.ui.form.on('Patient History Settings', { if (field) { selected_fields.push({ label: field.label, - fieldname: field.fieldname + fieldname: field.fieldname, + fieldtype: field.fieldtype }); } } @@ -58,7 +59,8 @@ frappe.ui.form.on('Patient History Settings', { frappe.model.with_doctype(document_type, () => { // get doctype fields frappe.get_doc('DocType', document_type).fields.forEach(field => { - if (!in_list(frappe.model.no_value_type, field.fieldtype) && !field.hidden) { + if (!in_list(frappe.model.no_value_type, field.fieldtype) || + in_list(frappe.model.table_fields, field.fieldtype) && !field.hidden) { multiselect_fields.push({ label: field.label, value: field.fieldname, diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 9e876e8c95..af8c6f4557 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import cstr from frappe.model.document import Document class PatientHistorySettings(Document): @@ -20,4 +21,54 @@ class PatientHistorySettings(Document): if field.fieldtype not in ['Date', 'Datetime']: frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format( - entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) \ No newline at end of file + entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) + + +def create_medical_record(doc, method=None): + if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \ + frappe.db.get_value('Doctype', doc.doctype, 'module') != 'Healthcare': + return + + subject = set_subject_field(doc) + date_field = get_date_field(doc.doctype) + medical_record = frappe.new_doc('Patient Medical Record') + medical_record.patient = doc.patient + medical_record.subject = subject + medical_record.status = 'Open' + medical_record.communication_date = doc.get(date_field) + medical_record.reference_doctype = doc.doctype + medical_record.reference_name = doc.name + medical_record.reference_owner = doc.owner + medical_record.save(ignore_permissions=True) + + +def set_subject_field(doc): + from frappe.utils.formatters import format_value + + meta = frappe.get_meta(doc.doctype) + subject = '' + patient_history_fields = get_patient_history_fields(doc) + + for entry in patient_history_fields: + fieldname = entry.get('fieldname') + if doc.get(fieldname): + formated_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) + subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formated_value) + subject += '
' + + return subject + + +def get_date_field(doctype): + return frappe.db.get_value('Patient History Custom Document Type', + { 'document_type': doctype }, 'date_fieldname') + + +def get_patient_history_fields(doc): + import json + patient_history_fields = frappe.db.get_value('Patient History Custom Document Type', + { 'document_type': doc.doctype }, 'selected_fields') + + if patient_history_fields: + return json.loads(patient_history_fields) + diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 741176f33f..4ee42c7559 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -221,6 +221,10 @@ standard_queries = { } doc_events = { + "*": { + "on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", + "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" + }, "Stock Entry": { "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", "on_cancel": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty" From a75f79762f224f8b38d2877b7a596cc93d9647fd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 28 Nov 2020 23:09:07 +0530 Subject: [PATCH 04/37] feat: Link for individual documents in Patient History --- .../page/patient_history/patient_history.html | 1 - .../page/patient_history/patient_history.js | 19 ++++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html index 7a9446dffd..60f4501fed 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ b/erpnext/healthcare/page/patient_history/patient_history.html @@ -1,6 +1,5 @@
-

{%= __("Select Patient") %}

diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index fe5b7bc488..3e6d790ca7 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -16,6 +16,8 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { fieldtype: "Link", options: "Patient", fieldname: "patient", + placeholder: __('Select Patient'), + only_select: true, change: function(){ if(pid != patient.get_value() && patient.get_value()){ me.start = 0; @@ -27,7 +29,6 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { pid = patient.get_value(); } }, - only_input: true, }); patient.refresh(); @@ -120,7 +121,11 @@ var add_to_records = function(me, data){ data[i].imgsrc = false; } var time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``; - time_line_heading += data[i].reference_doctype + " - "+ data[i].reference_name; + time_line_heading += data[i].reference_doctype + " - " + + ` + ${data[i].reference_name} + ` + details += `
  • `; @@ -135,7 +140,7 @@ var add_to_records = function(me, data){ } details += `
    - `+time_line_heading+` on + `+time_line_heading+` ${data[i].date_sep} @@ -172,11 +177,11 @@ var add_date_separator = function(data) { var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date)); if(diff < 1) { - var pdate = 'Today'; + var pdate = __('Today'); } else if(diff < 2) { - pdate = 'Yesterday'; + pdate = __('Yesterday'); } else { - pdate = frappe.datetime.global_date_format(date); + pdate = __('on ') + frappe.datetime.global_date_format(date); } data.date_sep = pdate; return data; @@ -227,7 +232,7 @@ var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { }, callback: function(r) { if (r.message){ - var show_chart_btns_html = "
    "); - me.page.main.find("."+docname).parent().find('.document-html').show(); - me.page.main.find("."+docname).parent().find('.document-html').attr('data-fetched', "1"); + if (r.message) { + me.page.main.find('.' + docname).hide(); + + me.page.main.find('.' + docname).parent().find('.document-html').html( + `${r.message.html} +
    + + +
    + `); + + me.page.main.find('.' + docname).parent().find('.document-html').show(); + me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1'); } - }, - freeze: true + } }); } } }); - this.page.main.on("click", ".btn-less", function() { - var docname = $(this).attr("data-docname"); - me.page.main.find("."+docname).parent().find('.document-id').show(); - me.page.main.find("."+docname).parent().find('.document-html').hide(); + this.page.main.on('click', '.btn-less', function() { + let docname = $(this).attr('data-docname'); + me.page.main.find('.' + docname).parent().find('.document-id').show(); + me.page.main.find('.' + docname).parent().find('.document-html').hide(); }); me.start = 0; - me.page.main.on("click", ".btn-get-records", function(){ + me.page.main.on('click', '.btn-get-records', function(){ get_documents(patient.get_value(), me); }); }; -var get_documents = function(patient, me){ +let get_documents = function(patient, me) { frappe.call({ - "method": "erpnext.healthcare.page.patient_history.patient_history.get_feed", + 'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed', args: { name: patient, start: me.start, page_length: 20 }, - callback: function (r) { - var data = r.message; - if(data.length){ + callback: function(r) { + let data = r.message; + if (data.length) { add_to_records(me, data); - }else{ - me.page.main.find(".patient_documents_list").append("


    No more records..

    "); - me.page.main.find(".btn-get-records").hide(); + } else { + me.page.main.find('.patient_documents_list').append(` +
    +

    ${__('No more records..')}

    +
    `); + me.page.main.find('.btn-get-records').hide(); } } }); }; -var add_to_records = function(me, data){ - var details = ""; - me.page.main.find(".patient_documents_list").append(details); + + details += ''; + me.page.main.find('.patient_documents_list').append(details); me.start += data.length; - if(data.length===20){ + + if (data.length === 20) { me.page.main.find(".btn-get-records").show(); - }else{ + } else { me.page.main.find(".btn-get-records").hide(); - me.page.main.find(".patient_documents_list").append("


    No more records..

    "); + me.page.main.find(".patient_documents_list").append(` +
    +

    ${__('No more records..')}

    +
    `); } }; -var add_date_separator = function(data) { - var date = frappe.datetime.str_to_obj(data.creation); +let add_date_separator = function(data) { + let date = frappe.datetime.str_to_obj(data.creation); + let pdate = ''; + let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date)); - var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date)); - if(diff < 1) { - var pdate = __('Today'); - } else if(diff < 2) { + if (diff < 1) { + pdate = __('Today'); + } else if (diff < 2) { pdate = __('Yesterday'); } else { pdate = __('on ') + frappe.datetime.global_date_format(date); @@ -187,107 +212,118 @@ var add_date_separator = function(data) { return data; }; -var show_patient_info = function(patient, me){ +let show_patient_info = function(patient, me) { frappe.call({ - "method": "erpnext.healthcare.doctype.patient.patient.get_patient_detail", + 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', args: { patient: patient }, - callback: function (r) { - var data = r.message; - var details = ""; - if(data.image){ - details += "
    "; + callback: function(r) { + let data = r.message; + let details = ''; + if (data.image){ + details += `
    `; } - details += "" + data.patient_name +"
    " + data.sex; - if(data.email) details += "
    " + data.email; - if(data.mobile) details += "
    " + data.mobile; - if(data.occupation) details += "

    Occupation : " + data.occupation; - if(data.blood_group) details += "
    Blood group : " + data.blood_group; - if(data.allergies) details += "

    Allergies : "+ data.allergies.replace("\n", "
    "); - if(data.medication) details += "
    Medication : "+ data.medication.replace("\n", "
    "); - if(data.alcohol_current_use) details += "

    Alcohol use : "+ data.alcohol_current_use; - if(data.alcohol_past_use) details += "
    Alcohol past use : "+ data.alcohol_past_use; - if(data.tobacco_current_use) details += "
    Tobacco use : "+ data.tobacco_current_use; - if(data.tobacco_past_use) details += "
    Tobacco past use : "+ data.tobacco_past_use; - if(data.medical_history) details += "

    Medical history : "+ data.medical_history.replace("\n", "
    "); - if(data.surgical_history) details += "
    Surgical history : "+ data.surgical_history.replace("\n", "
    "); - if(data.surrounding_factors) details += "

    Occupational hazards : "+ data.surrounding_factors.replace("\n", "
    "); - if(data.other_risk_factors) details += "
    Other risk factors : " + data.other_risk_factors.replace("\n", "
    "); - if(data.patient_details) details += "

    More info : " + data.patient_details.replace("\n", "
    "); - if(details){ - details = "
    " + details + "
    "; + details += ` ${data.patient_name}
    ${data.sex}`; + if (data.email) details += `
    ${data.email}` + if (data.mobile) details += `
    ${data.mobile}`; + if (data.occupation) details += `

    ${__('Occupation')} : ${data.occupation}`; + if (data.blood_group) details += `
    ${__('Blood Group')} : ${data.blood_group}`; + if (data.allergies) details += `

    ${__('Allerigies')} : ${data.allergies.replace("\n", ", ")}`; + if (data.medication) details += `
    ${__('Medication')} : ${data.medication.replace("\n", ", ")}`; + if (data.alcohol_current_use) details += `

    ${__('Alcohol use')} : ${data.alcohol_current_use}`; + if (data.alcohol_past_use) details += `
    ${__('Alcohol past use')} : ${data.alcohol_past_use}`; + if (data.tobacco_current_use) details += `
    ${__('Tobacco use')} : ${data.tobacco_current_use}`; + if (data.tobacco_past_use) details += `
    ${__('Tobacco past use')} : ${data.tobacco_past_use}`; + if (data.medical_history) details += `

    ${__('Medical history')} : ${data.medical_history.replace("\n", ", ")}`; + if (data.surgical_history) details += `
    ${__('Surgical history')} : ${data.surgical_history.replace("\n", ", ")}`; + if (data.surrounding_factors) details += `

    ${__('Occupational hazards')} : ${data.surrounding_factors.replace("\n", ", ")}`; + if (data.other_risk_factors) details += `
    ${__('Other risk factors')} : ${data.other_risk_factors.replace("\n", ", ")}`; + if (data.patient_details) details += `

    ${__('More info')} : ${data.patient_details.replace("\n", ", ")}`; + + if (details){ + details = `
    ` + details + `
    `; } - me.page.main.find(".patient_details").html(details); + me.page.main.find('.patient_details').html(details); } }); }; -var show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { +let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { frappe.call({ - method: "erpnext.healthcare.utils.get_patient_vitals", + method: 'erpnext.healthcare.utils.get_patient_vitals', args:{ patient: patient }, callback: function(r) { - if (r.message){ - var show_chart_btns_html = ""; - me.page.main.find(".show_chart_btns").html(show_chart_btns_html); - var data = r.message; + if (r.message) { + let show_chart_btns_html = ` + `; + + me.page.main.find('.show_chart_btns').html(show_chart_btns_html); + let data = r.message; let labels = [], datasets = []; let bp_systolic = [], bp_diastolic = [], temperature = []; let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; - for(var i=0; i d + ' ' + pts, } }); - }else{ - me.page.main.find(".patient_vital_charts").html(""); - me.page.main.find(".show_chart_btns").html(""); + } else { + me.page.main.find('.patient_vital_charts').html(''); + me.page.main.find('.show_chart_btns').html(''); } } }); From fc1e352adf3f7efbbf243a029714d3c8eef0720f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 29 Nov 2020 22:38:14 +0530 Subject: [PATCH 06/37] feat: Doctype filter for Patient History --- .../page/patient_history/patient_history.css | 5 ++ .../page/patient_history/patient_history.html | 8 +++ .../page/patient_history/patient_history.js | 58 ++++++++++++++---- .../page/patient_history/patient_history.py | 59 ++++++++++++------- 4 files changed, 98 insertions(+), 32 deletions(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.css b/erpnext/healthcare/page/patient_history/patient_history.css index 865d6abee0..1bb589164e 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.css +++ b/erpnext/healthcare/page/patient_history/patient_history.css @@ -109,6 +109,11 @@ padding-right: 0px; } +.patient-history-filter { + margin-left: 35px; + width: 25%; +} + #page-medical_record .plot-wrapper { padding: 20px 15px; border-bottom: 1px solid #d1d8dd; diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html index 60f4501fed..00b38e7258 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ b/erpnext/healthcare/page/patient_history/patient_history.html @@ -10,6 +10,14 @@
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 5f1851fb0f..40b86fdff4 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -20,14 +20,16 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { placeholder: __('Select Patient'), only_select: true, change: function(){ - if (pid != patient.get_value() && patient.get_value()) { + let patient_id = patient.get_value(); + if (pid != patient_id && patient_id) { me.start = 0; me.page.main.find('.patient_documents_list').html(''); - get_documents(patient.get_value(), me); - show_patient_info(patient.get_value(), me); - show_patient_vital_charts(patient.get_value(), me, 'bp', 'mmHg', 'Blood Pressure'); + setup_filters(patient_id, me) + get_documents(patient_id, me); + show_patient_info(patient_id, me); + show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure'); } - pid = patient.get_value(); + pid = patient_id; } }, }); @@ -93,14 +95,48 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { }); }; -let get_documents = function(patient, me) { +let setup_filters = function(patient, me) { + frappe.xcall( + 'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes' + ).then(document_types => { + let doctype_filter = frappe.ui.form.make_control({ + parent: $('.doctype-filter'), + df: { + fieldtype: 'MultiSelectList', + fieldname: 'document_type', + placeholder: __('Select Document Type'), + input_class: 'input-xs', + change: () => { + me.start = 0; + me.page.main.find('.patient_documents_list').html(''); + get_documents(patient, me, doctype_filter.get_value()); + }, + get_data: () => { + return document_types.map(document_type => { + return { + description: document_type, + value: document_type + }; + }); + }, + } + }); + doctype_filter.refresh(); + }) +} + +let get_documents = function(patient, me, document_types="") { + let filters = { + name: patient, + start: me.start, + page_length: 20 + }; + if (document_types) + filters['document_types'] = document_types; + frappe.call({ 'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed', - args: { - name: patient, - start: me.start, - page_length: 20 - }, + args: filters, callback: function(r) { let data = r.message; if (data.length) { diff --git a/erpnext/healthcare/page/patient_history/patient_history.py b/erpnext/healthcare/page/patient_history/patient_history.py index 772aa4ef5e..c04b376197 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.py +++ b/erpnext/healthcare/page/patient_history/patient_history.py @@ -4,36 +4,53 @@ from __future__ import unicode_literals import frappe +import json from frappe.utils import cint from erpnext.healthcare.utils import render_docs_as_html @frappe.whitelist() -def get_feed(name, start=0, page_length=20): +def get_feed(name, document_types=None, start=0, page_length=20): """get feed""" - result = frappe.db.sql("""select name, owner, creation, - reference_doctype, reference_name, subject - from `tabPatient Medical Record` - where patient=%(patient)s - order by creation desc - limit %(start)s, %(page_length)s""", - { - "patient": name, - "start": cint(start), - "page_length": cint(page_length) - }, as_dict=True) + filters = {'patient': name} + if document_types: + document_types = json.loads(document_types) + filters['reference_doctype'] = ['IN', document_types] + + result = frappe.db.get_all('Patient Medical Record', + fields=['name', 'owner', 'creation', + 'reference_doctype', 'reference_name', 'subject'], + filters=filters, + order_by='creation DESC', + limit=cint(page_length), + start=cint(start) + ) + return result @frappe.whitelist() def get_feed_for_dt(doctype, docname): """get feed""" - result = frappe.db.sql("""select name, owner, modified, creation, - reference_doctype, reference_name, subject - from `tabPatient Medical Record` - where reference_name=%(docname)s and reference_doctype=%(doctype)s - order by creation desc""", - { - "docname": docname, - "doctype": doctype - }, as_dict=True) + result = frappe.db.get_all('Patient Medical Record', + fields=['name', 'owner', 'creation', + 'reference_doctype', 'reference_name', 'subject'], + filters={ + 'reference_doctype': doctype, + 'reference_name': docname + }, + order_by='creation DESC' + ) return result + +@frappe.whitelist() +def get_patient_history_doctypes(): + document_types = [] + settings = frappe.get_single("Patient History Settings") + + for entry in settings.standard_doctypes: + document_types.append(entry.document_type) + + for entry in settings.custom_doctypes: + document_types.append(entry.document_type) + + return document_types \ No newline at end of file From 4af3d4e4bb745f35549ce3b638981206c1f84a3e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 29 Nov 2020 22:43:56 +0530 Subject: [PATCH 07/37] fix: feed not visible when filter is reset --- erpnext/healthcare/page/patient_history/patient_history.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.py b/erpnext/healthcare/page/patient_history/patient_history.py index c04b376197..b8494c9e58 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.py +++ b/erpnext/healthcare/page/patient_history/patient_history.py @@ -14,7 +14,8 @@ def get_feed(name, document_types=None, start=0, page_length=20): filters = {'patient': name} if document_types: document_types = json.loads(document_types) - filters['reference_doctype'] = ['IN', document_types] + if len(document_types): + filters['reference_doctype'] = ['IN', document_types] result = frappe.db.get_all('Patient Medical Record', fields=['name', 'owner', 'creation', From 4d6d115a4d43aa3b7c6ebf2604998a2a4728050c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sun, 29 Nov 2020 23:16:12 +0530 Subject: [PATCH 08/37] feat: date range filter for Patient History --- .../page/patient_history/patient_history.html | 3 +- .../page/patient_history/patient_history.js | 27 +++++++++++++++-- .../page/patient_history/patient_history.py | 30 ++++++++++++++----- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html index 00b38e7258..deaaa97868 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ b/erpnext/healthcare/page/patient_history/patient_history.html @@ -14,8 +14,7 @@
    -
    -
    +
    diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 40b86fdff4..5a6295b707 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -109,7 +109,7 @@ let setup_filters = function(patient, me) { change: () => { me.start = 0; me.page.main.find('.patient_documents_list').html(''); - get_documents(patient, me, doctype_filter.get_value()); + get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value()); }, get_data: () => { return document_types.map(document_type => { @@ -122,10 +122,29 @@ let setup_filters = function(patient, me) { } }); doctype_filter.refresh(); - }) + + let date_range_field = frappe.ui.form.make_control({ + df: { + fieldtype: 'DateRange', + fieldname: 'date_range', + placeholder: __('Date Range'), + input_class: 'input-xs', + change: () => { + let selected_date_range = date_range_field.get_value(); + if (selected_date_range && selected_date_range.length === 2) { + me.start = 0; + me.page.main.find('.patient_documents_list').html(''); + get_documents(patient, me, doctype_filter.get_value(), selected_date_range); + } + } + }, + parent: $('.date-filter') + }); + date_range_field.refresh(); + }); } -let get_documents = function(patient, me, document_types="") { +let get_documents = function(patient, me, document_types="", selected_date_range="") { let filters = { name: patient, start: me.start, @@ -133,6 +152,8 @@ let get_documents = function(patient, me, document_types="") { }; if (document_types) filters['document_types'] = document_types; + if (selected_date_range) + filters['date_range'] = selected_date_range; frappe.call({ 'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed', diff --git a/erpnext/healthcare/page/patient_history/patient_history.py b/erpnext/healthcare/page/patient_history/patient_history.py index b8494c9e58..de8a5771d2 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.py +++ b/erpnext/healthcare/page/patient_history/patient_history.py @@ -9,13 +9,9 @@ from frappe.utils import cint from erpnext.healthcare.utils import render_docs_as_html @frappe.whitelist() -def get_feed(name, document_types=None, start=0, page_length=20): +def get_feed(name, document_types=None, date_range=None, start=0, page_length=20): """get feed""" - filters = {'patient': name} - if document_types: - document_types = json.loads(document_types) - if len(document_types): - filters['reference_doctype'] = ['IN', document_types] + filters = get_filters(name, document_types, date_range) result = frappe.db.get_all('Patient Medical Record', fields=['name', 'owner', 'creation', @@ -28,6 +24,25 @@ def get_feed(name, document_types=None, start=0, page_length=20): return result + +def get_filters(name, document_types=None, date_range=None): + filters = {'patient': name} + if document_types: + document_types = json.loads(document_types) + if len(document_types): + filters['reference_doctype'] = ['IN', document_types] + + if date_range: + try: + date_range = json.loads(date_range) + if date_range: + filters['creation'] = ['between', [date_range[0], date_range[1]]] + except json.decoder.JSONDecodeError: + pass + + return filters + + @frappe.whitelist() def get_feed_for_dt(doctype, docname): """get feed""" @@ -43,6 +58,7 @@ def get_feed_for_dt(doctype, docname): return result + @frappe.whitelist() def get_patient_history_doctypes(): document_types = [] @@ -54,4 +70,4 @@ def get_patient_history_doctypes(): for entry in settings.custom_doctypes: document_types.append(entry.document_type) - return document_types \ No newline at end of file + return document_types From 06e7cc2c35bf526f5a7bfba81d645ed4028a276b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 12:01:48 +0530 Subject: [PATCH 09/37] fix: Handle table field rendering in Patient Medical record --- .../patient_history_settings.py | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index af8c6f4557..759fcadef2 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -51,10 +51,16 @@ def set_subject_field(doc): for entry in patient_history_fields: fieldname = entry.get('fieldname') - if doc.get(fieldname): - formated_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) - subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formated_value) - subject += '
    ' + if entry.get('fieldtype') == 'Table' and doc.get(fieldname): + formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname)) + subject += frappe.bold(_(entry.get('label')) + ': ') + '
    ' + cstr(formatted_value) + + else: + if doc.get(fieldname): + formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) + subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + + subject += '
    ' return subject @@ -72,3 +78,27 @@ def get_patient_history_fields(doc): if patient_history_fields: return json.loads(patient_history_fields) + +def get_formatted_value_for_table_field(items, df): + child_meta = frappe.get_meta(df.options) + + table_head = '' + table_row = '' + html = '' + create_head = True + for item in items: + table_row += '' + for cdf in child_meta.fields: + if cdf.in_list_view: + if create_head: + table_head += '' + cdf.label + '' + if item.get(cdf.fieldname): + table_row += '' + str(item.get(cdf.fieldname)) + '' + else: + table_row += '' + create_head = False + table_row += '' + + html += "" + table_head + table_row + '
    ' + + return html From f3df5c9f3cec4db7092c301d03897eca3acc67e0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 12:16:28 +0530 Subject: [PATCH 10/37] feat: patch to setup standard doctype config in Patient History Settings --- .../healthcare/doctype/lab_test/lab_test.json | 6 +- .../patient_encounter/patient_encounter.json | 4 +- ...atient_history_standard_document_type.json | 11 ++- erpnext/healthcare/setup.py | 80 +++++++++++++++++++ erpnext/patches.txt | 1 + ..._history_settings_for_standard_doctypes.py | 9 +++ 6 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json index edf1d911aa..ac61fea3ad 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ b/erpnext/healthcare/doctype/lab_test/lab_test.json @@ -359,6 +359,7 @@ { "fieldname": "normal_test_items", "fieldtype": "Table", + "label": "Normal Test Result", "options": "Normal Test Result", "print_hide": 1 }, @@ -380,6 +381,7 @@ { "fieldname": "sensitivity_test_items", "fieldtype": "Table", + "label": "Sensitivity Test Result", "options": "Sensitivity Test Result", "print_hide": 1, "report_hide": 1 @@ -529,6 +531,7 @@ { "fieldname": "descriptive_test_items", "fieldtype": "Table", + "label": "Descriptive Test Result", "options": "Descriptive Test Result", "print_hide": 1, "report_hide": 1 @@ -549,13 +552,14 @@ { "fieldname": "organism_test_items", "fieldtype": "Table", + "label": "Organism Test Result", "options": "Organism Test Result", "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-07-30 18:18:38.516215", + "modified": "2020-11-30 11:04:17.195848", "modified_by": "Administrator", "module": "Healthcare", "name": "Lab Test", diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index 15675f4673..b646ff9ebe 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -210,7 +210,7 @@ { "fieldname": "drug_prescription", "fieldtype": "Table", - "label": "Items", + "label": "Drug Prescription", "options": "Drug Prescription" }, { @@ -328,7 +328,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-05-16 21:00:08.644531", + "modified": "2020-11-30 10:39:00.783119", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json index ef4fc2bfe1..9c9d0cb4cd 100644 --- a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json +++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json @@ -6,6 +6,7 @@ "engine": "InnoDB", "field_order": [ "document_type", + "date_fieldname", "add_edit_fields", "selected_fields" ], @@ -29,12 +30,20 @@ "fieldtype": "Button", "in_list_view": 1, "label": "Add / Edit Fields" + }, + { + "fieldname": "date_fieldname", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Date Fieldname", + "read_only": 1, + "reqd": 1 } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-28 18:57:30.446348", + "modified": "2020-11-30 12:15:14.940935", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient History Standard Document Type", diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py index 06840801d3..bf4df7e4c8 100644 --- a/erpnext/healthcare/setup.py +++ b/erpnext/healthcare/setup.py @@ -16,6 +16,7 @@ def setup_healthcare(): create_healthcare_item_groups() create_sensitivity() add_healthcare_service_unit_tree_root() + setup_patient_history_settings() def create_medical_departments(): departments = [ @@ -213,3 +214,82 @@ def get_company(): if company: return company[0].name return None + +def setup_patient_history_settings(): + import json + + settings = frappe.get_single('Patient History Settings') + configuration = get_patient_history_config() + for dt, config in configuration.items(): + settings.append("standard_doctypes", { + "document_type": dt, + "date_fieldname": config[0], + "selected_fields": json.dumps(config[1]) + }) + settings.save() + +def get_patient_history_config(): + return { + "Patient Encounter": ("encounter_date", [ + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"}, + {"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"}, + {"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"}, + {"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"}, + {"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"}, + {"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"}, + {"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"} + ]), + "Clinical Procedure": ("start_date", [ + {"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"}, + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"}, + {"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"}, + {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, + {"label": "Sample", "fieldname": "sample", "fieldtype": "Link"} + ]), + "Lab Test": ("result_date", [ + {"label": "Test Template", "fieldname": "template", "fieldtype": "Link"}, + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"}, + {"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"}, + {"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"}, + {"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"}, + {"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"}, + {"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"}, + {"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"}, + {"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"} + ]), + "Therapy Session": ("start_date", [ + {"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"}, + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"}, + {"label": "Duration", "fieldname": "duration", "fieldtype": "Int"}, + {"label": "Location", "fieldname": "location", "fieldtype": "Link"}, + {"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"}, + {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, + {"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"}, + {"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"}, + {"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"} + ]), + "Vital Signs": ("signs_date", [ + {"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"}, + {"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"}, + {"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"}, + {"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"}, + {"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"}, + {"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"}, + {"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"}, + {"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"}, + {"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"}, + {"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"}, + {"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"} + ]), + "Inpatient Medication Order": ("start_date", [ + {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, + {"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"}, + {"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"}, + {"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"}, + {"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"} + ]) + } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 25be884117..fcb63ed6e4 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -735,3 +735,4 @@ erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail erpnext.patches.v13_0.update_reason_for_resignation_in_employee erpnext.patches.v13_0.update_custom_fields_for_shopify execute:frappe.delete_doc("Report", "Quoted Item Comparison") +erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py new file mode 100644 index 0000000000..3332be0561 --- /dev/null +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +import frappe +from erpnext.healthcare.setup import setup_patient_history_settings + +def execute(): + if 'Healthcare' not in frappe.get_active_domains(): + return + + setup_patient_history_settings() \ No newline at end of file From 4097e89f8b80140389eec6b29aea337d3a7835f0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 12:48:39 +0530 Subject: [PATCH 11/37] feat: Standard doctype config for Patient History --- .../patient_history_settings.js | 17 ++++++++++++-- .../patient_history_settings.py | 22 ++++++++++++++----- erpnext/hooks.py | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index 60926eeb11..c3d0dce675 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Patient History Settings', { }); }, - field_selector: function(frm, doc) { + field_selector: function(frm, doc, standard=1) { let document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); let d = new frappe.ui.Dialog({ title: __('{0} Fields', [__(doc.document_type)]), @@ -45,8 +45,12 @@ frappe.ui.form.on('Patient History Settings', { }); } } + let doctype = 'Patient History Custom Document Type'; + if (standard) + doctype = 'Patient History Standard Document Type'; - frappe.model.set_value('Patient History Custom Document Type', doc.name, 'selected_fields', JSON.stringify(selected_fields)); + + frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); d.hide(); }); @@ -75,6 +79,15 @@ frappe.ui.form.on('Patient History Settings', { }); frappe.ui.form.on('Patient History Custom Document Type', { + add_edit_fields: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.document_type) { + frm.events.field_selector(frm, row, standard=0); + } + } +}); + +frappe.ui.form.on('Patient History Standard Document Type', { add_edit_fields: function(frm, cdt, cdn) { let row = locals[cdt][cdn]; if (row.document_type) { diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 759fcadef2..20b062e909 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.utils import cstr from frappe.model.document import Document +from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes class PatientHistorySettings(Document): def validate(self): @@ -29,6 +30,9 @@ def create_medical_record(doc, method=None): frappe.db.get_value('Doctype', doc.doctype, 'module') != 'Healthcare': return + if doc.doctype not in get_patient_history_doctypes(): + return + subject = set_subject_field(doc) date_field = get_date_field(doc.doctype) medical_record = frappe.new_doc('Patient Medical Record') @@ -66,14 +70,15 @@ def set_subject_field(doc): def get_date_field(doctype): - return frappe.db.get_value('Patient History Custom Document Type', - { 'document_type': doctype }, 'date_fieldname') + dt = get_patient_history_config_dt(doctype) + + return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname') def get_patient_history_fields(doc): import json - patient_history_fields = frappe.db.get_value('Patient History Custom Document Type', - { 'document_type': doc.doctype }, 'selected_fields') + dt = get_patient_history_config_dt(doc.doctype) + patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields') if patient_history_fields: return json.loads(patient_history_fields) @@ -99,6 +104,13 @@ def get_formatted_value_for_table_field(items, df): create_head = False table_row += '' - html += "" + table_head + table_row + '
    ' + html += "" + table_head + table_row + "
    " return html + + +def get_patient_history_config_dt(doctype): + if frappe.db.get_value('DocType', doctype, 'custom'): + return 'Patient History Custom Document Type' + else: + return 'Patient History Standard Document Type' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4ee42c7559..aa5291aa0e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -223,6 +223,7 @@ standard_queries = { doc_events = { "*": { "on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", + "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" }, "Stock Entry": { From c0fcc807d338ad614aa76f7ea03d2b804760da77 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 13:22:01 +0530 Subject: [PATCH 12/37] feat: hooks for updating and deleting medical records --- .../patient_history_settings.py | 41 +++++++++++++++++-- erpnext/hooks.py | 2 +- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 20b062e909..367c34f1e8 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -26,11 +26,11 @@ class PatientHistorySettings(Document): def create_medical_record(doc, method=None): - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \ - frappe.db.get_value('Doctype', doc.doctype, 'module') != 'Healthcare': + medical_record_required = validate_medical_record_required(doc) + if not medical_record_required: return - if doc.doctype not in get_patient_history_doctypes(): + if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }): return subject = set_subject_field(doc) @@ -46,6 +46,30 @@ def create_medical_record(doc, method=None): medical_record.save(ignore_permissions=True) +def update_medical_record(doc, method=None): + medical_record_required = validate_medical_record_required(doc) + if not medical_record_required: + return + + medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }) + + if medical_record_id: + subject = set_subject_field(doc) + frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject) + else: + create_medical_record(doc) + + +def delete_medical_record(doc, method=None): + medical_record_required = validate_medical_record_required(doc) + if not medical_record_required: + return + + record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }) + if record: + frappe.delete_doc('Patient Medical Record', record, force=1) + + def set_subject_field(doc): from frappe.utils.formatters import format_value @@ -114,3 +138,14 @@ def get_patient_history_config_dt(doctype): return 'Patient History Custom Document Type' else: return 'Patient History Standard Document Type' + + +def validate_medical_record_required(doc): + if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \ + frappe.db.get_value('Doctype', doc.doctype, 'module') != 'Healthcare': + return False + + if doc.doctype not in get_patient_history_doctypes(): + return False + + return True \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index aa5291aa0e..51c169f400 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -223,7 +223,7 @@ standard_queries = { doc_events = { "*": { "on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", - "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", + "on_update": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" }, "Stock Entry": { From c538a4a31d2c9f829a308e03a01d9eac5d537d0e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 13:22:34 +0530 Subject: [PATCH 13/37] refactor: remove medical record creation methods from individual doctypes --- .../clinical_procedure/clinical_procedure.py | 19 ------ .../healthcare/doctype/lab_test/lab_test.py | 56 ----------------- .../patient_encounter/patient_encounter.py | 62 +------------------ .../therapy_session/therapy_session.py | 21 ------- .../doctype/vital_signs/vital_signs.py | 40 ------------ 5 files changed, 1 insertion(+), 197 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py index e55a1433a5..c324228467 100644 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py @@ -100,7 +100,6 @@ class ClinicalProcedure(Document): allow_start = self.set_actual_qty() if allow_start: self.db_set('status', 'In Progress') - insert_clinical_procedure_to_medical_record(self) return 'success' return 'insufficient stock' @@ -247,21 +246,3 @@ def make_procedure(source_name, target_doc=None): }, target_doc, set_missing_values) return doc - - -def insert_clinical_procedure_to_medical_record(doc): - subject = frappe.bold(_("Clinical Procedure conducted: ")) + cstr(doc.procedure_template) + "
    " - if doc.practitioner: - subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner - if subject and doc.notes: - subject += '
    ' + doc.notes - - medical_record = frappe.new_doc('Patient Medical Record') - medical_record.patient = doc.patient - medical_record.subject = subject - medical_record.status = 'Open' - medical_record.communication_date = doc.start_date - medical_record.reference_doctype = 'Clinical Procedure' - medical_record.reference_name = doc.name - medical_record.reference_owner = doc.owner - medical_record.save(ignore_permissions=True) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py index 2db7743865..4b57cd073d 100644 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ b/erpnext/healthcare/doctype/lab_test/lab_test.py @@ -17,11 +17,9 @@ class LabTest(Document): self.validate_result_values() self.db_set('submitted_date', getdate()) self.db_set('status', 'Completed') - insert_lab_test_to_medical_record(self) def on_cancel(self): self.db_set('status', 'Cancelled') - delete_lab_test_from_medical_record(self) self.reload() def on_update(self): @@ -330,60 +328,6 @@ def get_employee_by_user_id(user_id): return frappe.get_doc('Employee', emp_id) return None -def insert_lab_test_to_medical_record(doc): - table_row = False - subject = cstr(doc.lab_test_name) - if doc.practitioner: - subject += frappe.bold(_('Healthcare Practitioner: '))+ doc.practitioner + '
    ' - if doc.normal_test_items: - item = doc.normal_test_items[0] - comment = '' - if item.lab_test_comment: - comment = str(item.lab_test_comment) - table_row = frappe.bold(_('Lab Test Conducted: ')) + item.lab_test_name - - if item.lab_test_event: - table_row += frappe.bold(_('Lab Test Event: ')) + item.lab_test_event - - if item.result_value: - table_row += ' ' + frappe.bold(_('Lab Test Result: ')) + item.result_value - - if item.normal_range: - table_row += ' ' + _('Normal Range: ') + item.normal_range - table_row += ' ' + comment - - elif doc.descriptive_test_items: - item = doc.descriptive_test_items[0] - - if item.lab_test_particulars and item.result_value: - table_row = item.lab_test_particulars + ' ' + item.result_value - - elif doc.sensitivity_test_items: - item = doc.sensitivity_test_items[0] - - if item.antibiotic and item.antibiotic_sensitivity: - table_row = item.antibiotic + ' ' + item.antibiotic_sensitivity - - if table_row: - subject += '
    ' + table_row - if doc.lab_test_comment: - subject += '
    ' + cstr(doc.lab_test_comment) - - medical_record = frappe.new_doc('Patient Medical Record') - medical_record.patient = doc.patient - medical_record.subject = subject - medical_record.status = 'Open' - medical_record.communication_date = doc.result_date - medical_record.reference_doctype = 'Lab Test' - medical_record.reference_name = doc.name - medical_record.reference_owner = doc.owner - medical_record.save(ignore_permissions = True) - -def delete_lab_test_from_medical_record(self): - medical_record_id = frappe.db.sql('select name from `tabPatient Medical Record` where reference_name=%s', (self.name)) - - if medical_record_id and medical_record_id[0][0]: - frappe.delete_doc('Patient Medical Record', medical_record_id[0][0]) @frappe.whitelist() def get_lab_test_prescribed(patient): diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py index 87f42491fc..cc2141790f 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py @@ -17,10 +17,6 @@ class PatientEncounter(Document): def on_update(self): if self.appointment: frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') - update_encounter_medical_record(self) - - def after_insert(self): - insert_encounter_to_medical_record(self) def on_submit(self): if self.therapies: @@ -33,8 +29,6 @@ class PatientEncounter(Document): if self.inpatient_record and self.drug_prescription: delete_ip_medication_order(self) - delete_medical_record(self) - def set_title(self): self.title = _('{0} with {1}').format(self.patient_name or self.patient, self.practitioner_name or self.practitioner)[:100] @@ -102,61 +96,7 @@ def create_therapy_plan(encounter): frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True) -def insert_encounter_to_medical_record(doc): - subject = set_subject_field(doc) - medical_record = frappe.new_doc('Patient Medical Record') - medical_record.patient = doc.patient - medical_record.subject = subject - medical_record.status = 'Open' - medical_record.communication_date = doc.encounter_date - medical_record.reference_doctype = 'Patient Encounter' - medical_record.reference_name = doc.name - medical_record.reference_owner = doc.owner - medical_record.save(ignore_permissions=True) - - -def update_encounter_medical_record(encounter): - medical_record_id = frappe.db.exists('Patient Medical Record', {'reference_name': encounter.name}) - - if medical_record_id and medical_record_id[0][0]: - subject = set_subject_field(encounter) - frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject) - else: - insert_encounter_to_medical_record(encounter) - - -def delete_medical_record(encounter): - record = frappe.db.exists('Patient Medical Record', {'reference_name', encounter.name}) - if record: - frappe.delete_doc('Patient Medical Record', record, force=1) - def delete_ip_medication_order(encounter): record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name}) if record: - frappe.delete_doc('Inpatient Medication Order', record, force=1) - - -def set_subject_field(encounter): - subject = frappe.bold(_('Healthcare Practitioner: ')) + encounter.practitioner + '
    ' - if encounter.symptoms: - subject += frappe.bold(_('Symptoms: ')) + '
    ' - for entry in encounter.symptoms: - subject += cstr(entry.complaint) + '
    ' - else: - subject += frappe.bold(_('No Symptoms')) + '
    ' - - if encounter.diagnosis: - subject += frappe.bold(_('Diagnosis: ')) + '
    ' - for entry in encounter.diagnosis: - subject += cstr(entry.diagnosis) + '
    ' - else: - subject += frappe.bold(_('No Diagnosis')) + '
    ' - - if encounter.drug_prescription: - subject += '
    ' + _('Drug(s) Prescribed.') - if encounter.lab_test_prescription: - subject += '
    ' + _('Test(s) Prescribed.') - if encounter.procedure_prescription: - subject += '
    ' + _('Procedure(s) Prescribed.') - - return subject + frappe.delete_doc('Inpatient Medication Order', record, force=1) \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py index 85d0970177..f8a8e0c8a1 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py @@ -41,7 +41,6 @@ class TherapySession(Document): def on_submit(self): self.update_sessions_count_in_therapy_plan() - insert_session_medical_record(self) def on_cancel(self): self.update_sessions_count_in_therapy_plan(on_cancel=True) @@ -135,23 +134,3 @@ def get_therapy_item(therapy, item): item.reference_dt = 'Therapy Session' item.reference_dn = therapy.name return item - - -def insert_session_medical_record(doc): - subject = frappe.bold(_('Therapy: ')) + cstr(doc.therapy_type) + '
    ' - if doc.therapy_plan: - subject += frappe.bold(_('Therapy Plan: ')) + cstr(doc.therapy_plan) + '
    ' - if doc.practitioner: - subject += frappe.bold(_('Healthcare Practitioner: ')) + doc.practitioner - subject += frappe.bold(_('Total Counts Targeted: ')) + cstr(doc.total_counts_targeted) + '
    ' - subject += frappe.bold(_('Total Counts Completed: ')) + cstr(doc.total_counts_completed) + '
    ' - - medical_record = frappe.new_doc('Patient Medical Record') - medical_record.patient = doc.patient - medical_record.subject = subject - medical_record.status = 'Open' - medical_record.communication_date = doc.start_date - medical_record.reference_doctype = 'Therapy Session' - medical_record.reference_name = doc.name - medical_record.reference_owner = doc.owner - medical_record.save(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py index 69d81ff4b0..35c823d739 100644 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py +++ b/erpnext/healthcare/doctype/vital_signs/vital_signs.py @@ -12,47 +12,7 @@ class VitalSigns(Document): def validate(self): self.set_title() - def on_submit(self): - insert_vital_signs_to_medical_record(self) - - def on_cancel(self): - delete_vital_signs_from_medical_record(self) - def set_title(self): self.title = _('{0} on {1}').format(self.patient_name or self.patient, frappe.utils.format_date(self.signs_date))[:100] -def insert_vital_signs_to_medical_record(doc): - subject = set_subject_field(doc) - medical_record = frappe.new_doc('Patient Medical Record') - medical_record.patient = doc.patient - medical_record.subject = subject - medical_record.status = 'Open' - medical_record.communication_date = doc.signs_date - medical_record.reference_doctype = 'Vital Signs' - medical_record.reference_name = doc.name - medical_record.reference_owner = doc.owner - medical_record.flags.ignore_mandatory = True - medical_record.save(ignore_permissions=True) - -def delete_vital_signs_from_medical_record(doc): - medical_record = frappe.db.get_value('Patient Medical Record', {'reference_name': doc.name}) - if medical_record: - frappe.delete_doc('Patient Medical Record', medical_record) - -def set_subject_field(doc): - subject = '' - if doc.temperature: - subject += frappe.bold(_('Temperature: ')) + cstr(doc.temperature) + '
    ' - if doc.pulse: - subject += frappe.bold(_('Pulse: ')) + cstr(doc.pulse) + '
    ' - if doc.respiratory_rate: - subject += frappe.bold(_('Respiratory Rate: ')) + cstr(doc.respiratory_rate) + '
    ' - if doc.bp: - subject += frappe.bold(_('BP: ')) + cstr(doc.bp) + '
    ' - if doc.bmi: - subject += frappe.bold(_('BMI: ')) + cstr(doc.bmi) + '
    ' - if doc.nutrition_note: - subject += frappe.bold(_('Note: ')) + cstr(doc.nutrition_note) + '
    ' - - return subject From 5e3c51bf7d157a34baed3e791f8bb40052587654 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 13:35:00 +0530 Subject: [PATCH 14/37] refactor: format value using standard formatters --- erpnext/healthcare/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 96282f50a9..6b495a4eac 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import math import frappe from frappe import _ +from frappe.utils.formatters import format_value from frappe.utils import time_diff_in_hours, rounded from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity @@ -642,11 +643,15 @@ def render_doc_as_html(doctype, docname, exclude_fields = []): html += "" \ + table_head + table_row + "
    " continue + #on other field types add label and value to html if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields: - html += '
    {0} : {1}'.format(df.label or df.fieldname, \ - doc.get(df.fieldname)) + if doc.get(df.fieldname): + formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc) + html += '
    {0} : {1}'.format(df.label or df.fieldname, formatted_value) + if not has_data : has_data = True + if sec_on and col_on and has_data: doc_html += section_html + html + '
    ' elif sec_on and not col_on and has_data: From 4ee293d2f45c257fa4b103adc1af088e68d4dd5f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 14:12:48 +0530 Subject: [PATCH 15/37] fix: handle non-configured fields --- .../patient_history_custom_document_type.json | 5 +++-- .../patient_history_settings.js | 12 ++++++++---- .../patient_history_standard_document_type.json | 5 +++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json index 7986e48ced..3025c7b06d 100644 --- a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json +++ b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json @@ -22,7 +22,8 @@ { "fieldname": "selected_fields", "fieldtype": "Code", - "label": "selected_fields" + "label": "Selected Fields", + "read_only": 1 }, { "fieldname": "add_edit_fields", @@ -41,7 +42,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-28 19:04:48.323164", + "modified": "2020-11-30 13:54:37.474671", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient History Custom Document Type", diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index c3d0dce675..ee363462ef 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -14,7 +14,11 @@ frappe.ui.form.on('Patient History Settings', { }, field_selector: function(frm, doc, standard=1) { - let document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); + let document_fields = []; + if (doc.selected_fields) + document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); + + let doctype_fields = frm.events.get_doctype_fields(frm, doc.document_type, document_fields); let d = new frappe.ui.Dialog({ title: __('{0} Fields', [__(doc.document_type)]), fields: [ @@ -22,7 +26,7 @@ frappe.ui.form.on('Patient History Settings', { label: __('Select Fields'), fieldtype: 'MultiCheck', fieldname: 'fields', - options: frm.events.get_doctype_fields(frm, doc.document_type, document_fields), + options: doctype_fields, columns: 2 } ] @@ -49,7 +53,7 @@ frappe.ui.form.on('Patient History Settings', { if (standard) doctype = 'Patient History Standard Document Type'; - + d.refresh(); frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); d.hide(); }); @@ -82,7 +86,7 @@ frappe.ui.form.on('Patient History Custom Document Type', { add_edit_fields: function(frm, cdt, cdn) { let row = locals[cdt][cdn]; if (row.document_type) { - frm.events.field_selector(frm, row, standard=0); + frm.events.field_selector(frm, row, 0); } } }); diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json index 9c9d0cb4cd..b43099c4ea 100644 --- a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json +++ b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json @@ -23,7 +23,8 @@ { "fieldname": "selected_fields", "fieldtype": "Code", - "label": "Selected Fields" + "label": "Selected Fields", + "read_only": 1 }, { "fieldname": "add_edit_fields", @@ -43,7 +44,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-11-30 12:15:14.940935", + "modified": "2020-11-30 13:54:56.773325", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient History Standard Document Type", From ed3fc20731ae7e2279fabf6274a7660ca520a2e7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 14:56:24 +0530 Subject: [PATCH 16/37] fix: sider issues --- .../page/patient_history/patient_history.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 5a6295b707..d509ea22a2 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -24,7 +24,7 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { if (pid != patient_id && patient_id) { me.start = 0; me.page.main.find('.patient_documents_list').html(''); - setup_filters(patient_id, me) + setup_filters(patient_id, me); get_documents(patient_id, me); show_patient_info(patient_id, me); show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure'); @@ -90,7 +90,7 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { me.page.main.find('.' + docname).parent().find('.document-html').hide(); }); me.start = 0; - me.page.main.on('click', '.btn-get-records', function(){ + me.page.main.on('click', '.btn-get-records', function() { get_documents(patient.get_value(), me); }); }; @@ -142,7 +142,7 @@ let setup_filters = function(patient, me) { }); date_range_field.refresh(); }); -} +}; let get_documents = function(patient, me, document_types="", selected_date_range="") { let filters = { @@ -176,7 +176,7 @@ let get_documents = function(patient, me, document_types="", selected_date_range let add_to_records = function(me, data) { let details = "
    `; } details += ` ${data.patient_name}
    ${data.sex}`; - if (data.email) details += `
    ${data.email}` + if (data.email) details += `
    ${data.email}`; if (data.mobile) details += `
    ${data.mobile}`; if (data.occupation) details += `

    ${__('Occupation')} : ${data.occupation}`; if (data.blood_group) details += `
    ${__('Blood Group')} : ${data.blood_group}`; @@ -299,7 +299,7 @@ let show_patient_info = function(patient, me) { if (data.other_risk_factors) details += `
    ${__('Other risk factors')} : ${data.other_risk_factors.replace("\n", ", ")}`; if (data.patient_details) details += `

    ${__('More info')} : ${data.patient_details.replace("\n", ", ")}`; - if (details){ + if (details) { details = `
    ` + details + `
    `; } me.page.main.find('.patient_details').html(details); @@ -337,7 +337,7 @@ let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { let bp_systolic = [], bp_diastolic = [], temperature = []; let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; - for(let i=0; i Date: Mon, 30 Nov 2020 15:00:42 +0530 Subject: [PATCH 17/37] refactor: show Patient History feed as per configured date instead of creation --- .../healthcare/page/patient_history/patient_history.js | 2 +- .../healthcare/page/patient_history/patient_history.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index d509ea22a2..9c44d63b96 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -254,7 +254,7 @@ let add_to_records = function(me, data) { }; let add_date_separator = function(data) { - let date = frappe.datetime.str_to_obj(data.creation); + let date = frappe.datetime.str_to_obj(data.communication_date); let pdate = ''; let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date)); diff --git a/erpnext/healthcare/page/patient_history/patient_history.py b/erpnext/healthcare/page/patient_history/patient_history.py index de8a5771d2..4cdfd64a69 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.py +++ b/erpnext/healthcare/page/patient_history/patient_history.py @@ -14,10 +14,10 @@ def get_feed(name, document_types=None, date_range=None, start=0, page_length=20 filters = get_filters(name, document_types, date_range) result = frappe.db.get_all('Patient Medical Record', - fields=['name', 'owner', 'creation', + fields=['name', 'owner', 'communication_date', 'reference_doctype', 'reference_name', 'subject'], filters=filters, - order_by='creation DESC', + order_by='communication_date DESC', limit=cint(page_length), start=cint(start) ) @@ -36,7 +36,7 @@ def get_filters(name, document_types=None, date_range=None): try: date_range = json.loads(date_range) if date_range: - filters['creation'] = ['between', [date_range[0], date_range[1]]] + filters['communication_date'] = ['between', [date_range[0], date_range[1]]] except json.decoder.JSONDecodeError: pass @@ -47,13 +47,13 @@ def get_filters(name, document_types=None, date_range=None): def get_feed_for_dt(doctype, docname): """get feed""" result = frappe.db.get_all('Patient Medical Record', - fields=['name', 'owner', 'creation', + fields=['name', 'owner', 'communication_date', 'reference_doctype', 'reference_name', 'subject'], filters={ 'reference_doctype': doctype, 'reference_name': docname }, - order_by='creation DESC' + order_by='communication_date DESC' ) return result From b5b8c5474a14c394fae3ddb4074d7205bc3dfbbb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 16:09:15 +0530 Subject: [PATCH 18/37] refactor: move call to fetch doctype fields to server-side --- .../patient_history_settings.js | 47 +++++++++---------- .../patient_history_settings.py | 17 ++++++- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index ee363462ef..bf3c5b954e 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -18,7 +18,27 @@ frappe.ui.form.on('Patient History Settings', { if (doc.selected_fields) document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); - let doctype_fields = frm.events.get_doctype_fields(frm, doc.document_type, document_fields); + frm.call({ + method: 'get_doctype_fields', + doc: frm.doc, + args: { + document_type: doc.document_type, + fields: document_fields + }, + freeze: true, + callback: function(r) { + if (r.message) { + let doctype = 'Patient History Custom Document Type'; + if (standard) + doctype = 'Patient History Standard Document Type'; + + frm.events.show_field_selector_dialog(frm, doc, doctype, r.message); + } + } + }); + }, + + show_field_selector_dialog: function(frm, doc, doctype, doc_fields) { let d = new frappe.ui.Dialog({ title: __('{0} Fields', [__(doc.document_type)]), fields: [ @@ -26,7 +46,7 @@ frappe.ui.form.on('Patient History Settings', { label: __('Select Fields'), fieldtype: 'MultiCheck', fieldname: 'fields', - options: doctype_fields, + options: doc_fields, columns: 2 } ] @@ -49,9 +69,6 @@ frappe.ui.form.on('Patient History Settings', { }); } } - let doctype = 'Patient History Custom Document Type'; - if (standard) - doctype = 'Patient History Standard Document Type'; d.refresh(); frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); @@ -59,26 +76,6 @@ frappe.ui.form.on('Patient History Settings', { }); d.show(); - }, - - get_doctype_fields(frm, document_type, fields) { - let multiselect_fields = []; - - frappe.model.with_doctype(document_type, () => { - // get doctype fields - frappe.get_doc('DocType', document_type).fields.forEach(field => { - if (!in_list(frappe.model.no_value_type, field.fieldtype) || - in_list(frappe.model.table_fields, field.fieldtype) && !field.hidden) { - multiselect_fields.push({ - label: field.label, - value: field.fieldname, - checked: in_list(fields, field.fieldname) - }); - } - }); - }); - - return multiselect_fields; } }); diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 367c34f1e8..9f18c6bbf5 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +import json from frappe import _ from frappe.utils import cstr from frappe.model.document import Document @@ -24,6 +25,21 @@ class PatientHistorySettings(Document): frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format( entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) + def get_doctype_fields(self, document_type, fields): + multicheck_fields = [] + doc_fields = frappe.get_meta(document_type).fields + + for field in doc_fields: + if field.fieldtype not in frappe.model.no_value_fields or \ + field.fieldtype in frappe.model.table_fields and not field.hidden: + multicheck_fields.append({ + 'label': field.label, + 'value': field.fieldname, + 'checked': 1 if field.fieldname in fields else 0 + }) + + return multicheck_fields + def create_medical_record(doc, method=None): medical_record_required = validate_medical_record_required(doc) @@ -100,7 +116,6 @@ def get_date_field(doctype): def get_patient_history_fields(doc): - import json dt = get_patient_history_config_dt(doc.doctype) patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields') From f6756838ba63075e16ca5c4b3601d2eebe143d33 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 30 Nov 2020 17:51:56 +0530 Subject: [PATCH 19/37] fix: patch --- .../doctype/patient_appointment/test_patient_appointment.py | 1 + .../setup_patient_history_settings_for_standard_doctypes.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index eeed157291..f8b7f7f2f0 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -148,6 +148,7 @@ def create_healthcare_service_items(): item.item_name = 'Consulting Charges' item.item_group = 'Services' item.is_stock_item = 0 + item.stock_uom = 'Nos' item.save() return item.name diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py index 3332be0561..de08aa26b3 100644 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py @@ -3,7 +3,11 @@ import frappe from erpnext.healthcare.setup import setup_patient_history_settings def execute(): - if 'Healthcare' not in frappe.get_active_domains(): + if "Healthcare" not in frappe.get_active_domains(): return + frappe.reload_doc("healthcare", "doctype", "Patient History Settings") + frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") + frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") + setup_patient_history_settings() \ No newline at end of file From 060d6472488f314665e17ca0c736a5238c0907d5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 15 Jan 2021 00:19:14 +0530 Subject: [PATCH 20/37] fix: travis --- .../patient_history_settings/patient_history_settings.py | 4 ++-- .../patient_medical_record/test_patient_medical_record.py | 1 + erpnext/healthcare/page/patient_history/patient_history.js | 2 +- erpnext/hooks.py | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 9f18c6bbf5..9374870ac8 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -156,8 +156,8 @@ def get_patient_history_config_dt(doctype): def validate_medical_record_required(doc): - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \ - frappe.db.get_value('Doctype', doc.doctype, 'module') != 'Healthcare': + if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \ + or doc.meta.module != 'Healthcare': return False if doc.doctype not in get_patient_history_doctypes(): diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py index 419d956425..c1d9872a01 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -18,6 +18,7 @@ class TestPatientMedicalRecord(unittest.TestCase): patient, medical_department, practitioner = create_healthcare_docs() appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) encounter = create_encounter(appointment) + # check for encounter medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name}) self.assertTrue(medical_rec) diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 9c44d63b96..05c5190f80 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -19,7 +19,7 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { fieldname: 'patient', placeholder: __('Select Patient'), only_select: true, - change: function(){ + change: function() { let patient_id = patient.get_value(); if (pid != patient_id && patient_id) { me.start = 0; diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 9a0e06db7a..57b0b07504 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -223,7 +223,7 @@ standard_queries = { doc_events = { "*": { "on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", - "on_update": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", + "on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" }, "Stock Entry": { From b15a19c6e038cd05febde2c9390ce864b386a36b Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 15 Jan 2021 01:27:30 +0530 Subject: [PATCH 21/37] test: Medical Record creation for custom doctypes --- .../patient_history_settings.py | 6 +- .../test_patient_history_settings.py | 98 ++++++++++++++++++- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 9374870ac8..7c7b6c8f4b 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -97,14 +97,12 @@ def set_subject_field(doc): fieldname = entry.get('fieldname') if entry.get('fieldtype') == 'Table' and doc.get(fieldname): formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname)) - subject += frappe.bold(_(entry.get('label')) + ': ') + '
    ' + cstr(formatted_value) + subject += frappe.bold(_(entry.get('label')) + ': ') + '
    ' + cstr(formatted_value) + '
    ' else: if doc.get(fieldname): formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) - subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) - - subject += '
    ' + subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '
    ' return subject diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py index 548c423670..3190d84477 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py @@ -3,8 +3,102 @@ # See license.txt from __future__ import unicode_literals -# import frappe +import frappe import unittest +import json +from frappe.utils import getdate +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient class TestPatientHistorySettings(unittest.TestCase): - pass + def setUp(self): + dt = create_custom_doctype() + settings = frappe.get_single('Patient History Settings') + settings.append("custom_doctypes", { + "document_type": dt.name, + "date_fieldname": "date", + "selected_fields": json.dumps([{ + "label": "Date", + "fieldname": "date", + "fieldtype": "Date" + }, + { + "label": "Rating", + "fieldname": "rating", + "fieldtype": "Rating" + }, + { + "label": "Feedback", + "fieldname": "feedback", + "fieldtype": "Small Text" + }]) + }) + settings.save() + + def test_custom_doctype_medical_record(self): + # tests for medical record creation of standard doctypes in test_patient_medical_record.py + patient = create_patient() + doc = create_doc(patient) + + # check for medical record + medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': doc.name}) + self.assertTrue(medical_rec) + + medical_rec = frappe.get_doc('Patient Medical Record', medical_rec) + expected_subject = "Date: {0}
    Rating: 3
    Feedback: Test Patient History Settings
    ".format( + getdate().strftime("%d-%m-%Y")) + self.assertEqual(medical_rec.subject, expected_subject) + self.assertEqual(medical_rec.patient, patient) + self.assertEqual(medical_rec.communication_date, getdate()) + + +def create_custom_doctype(): + if not frappe.db.exists("DocType", "Test Patient Feedback"): + doc = frappe.get_doc({ + "doctype": "DocType", + "module": "Healthcare", + "custom": 1, + "is_submittable": 1, + "fields": [{ + "label": "Date", + "fieldname": "date", + "fieldtype": "Date" + }, + { + "label": "Patient", + "fieldname": "patient", + "fieldtype": "Link", + "options": "Patient" + }, + { + "label": "Rating", + "fieldname": "rating", + "fieldtype": "Rating" + }, + { + "label": "Feedback", + "fieldname": "feedback", + "fieldtype": "Small Text" + }], + "permissions": [{ + "role": "System Manager", + "read": 1 + }], + "name": "Test Patient Feedback", + }) + doc.insert() + return doc + else: + return frappe.get_doc("DocType", "Test Patient Feedback") + + +def create_doc(patient): + doc = frappe.get_doc({ + "doctype": "Test Patient Feedback", + "patient": patient, + "date": getdate(), + "rating": 3, + "feedback": "Test Patient History Settings" + }).insert() + doc.submit() + + return doc \ No newline at end of file From 1873c389e5eaadc11365b1adcdab664c75478662 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 15 Jan 2021 02:12:57 +0530 Subject: [PATCH 22/37] feat: Only allow submittable doctypes for Patient Medical Record --- .../patient_history_settings.js | 3 ++- .../patient_history_settings.py | 22 ++++++++++++++++--- .../test_patient_history_settings.py | 8 +++---- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index bf3c5b954e..92922b2888 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -7,7 +7,8 @@ frappe.ui.form.on('Patient History Settings', { return { filters: { custom: 1, - module: 'Healthcare' + is_submittable: 1, + module: 'Healthcare', } }; }); diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 7c7b6c8f4b..9ef97214c5 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -6,14 +6,23 @@ from __future__ import unicode_literals import frappe import json from frappe import _ -from frappe.utils import cstr +from frappe.utils import cstr, cint from frappe.model.document import Document from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes class PatientHistorySettings(Document): def validate(self): + self.validate_submittable_doctypes() self.validate_date_fieldnames() + def validate_submittable_doctypes(self): + for entry in self.custom_doctypes: + if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')): + msg = _('Row #{0}: Document Type {1} is not submittable. ').format( + entry.idx, frappe.bold(entry.document_type)) + msg += _('Patient Medical Record can only be created for submittable document types.') + frappe.throw(msg) + def validate_date_fieldnames(self): for entry in self.custom_doctypes: field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname) @@ -155,10 +164,17 @@ def get_patient_history_config_dt(doctype): def validate_medical_record_required(doc): if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \ - or doc.meta.module != 'Healthcare': + or get_module(doc) != 'Healthcare': return False if doc.doctype not in get_patient_history_doctypes(): return False - return True \ No newline at end of file + return True + +def get_module(doc): + module = doc.meta.module + if not module: + module = frappe.db.get_value('DocType', doc.doctype, 'module') + + return module \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py index 3190d84477..c93b788aed 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py @@ -12,7 +12,7 @@ from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment imp class TestPatientHistorySettings(unittest.TestCase): def setUp(self): dt = create_custom_doctype() - settings = frappe.get_single('Patient History Settings') + settings = frappe.get_single("Patient History Settings") settings.append("custom_doctypes", { "document_type": dt.name, "date_fieldname": "date", @@ -40,12 +40,12 @@ class TestPatientHistorySettings(unittest.TestCase): doc = create_doc(patient) # check for medical record - medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': doc.name}) + medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name}) self.assertTrue(medical_rec) - medical_rec = frappe.get_doc('Patient Medical Record', medical_rec) + medical_rec = frappe.get_doc("Patient Medical Record", medical_rec) expected_subject = "Date: {0}
    Rating: 3
    Feedback: Test Patient History Settings
    ".format( - getdate().strftime("%d-%m-%Y")) + frappe.utils.format_date(getdate())) self.assertEqual(medical_rec.subject, expected_subject) self.assertEqual(medical_rec.patient, patient) self.assertEqual(medical_rec.communication_date, getdate()) From 20e5315480fc48c6c04ad14131ae6a7862743a1c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Jan 2021 14:56:55 +0530 Subject: [PATCH 23/37] feat: Allow selecting admission service unit in Patient Appointment for inpatients --- erpnext/controllers/queries.py | 28 +++++++++++++++++++ .../inpatient_medication_entry.py | 2 +- .../patient_appointment.js | 10 +++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index e3aac9aba8..81f0ad3fed 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -655,6 +655,34 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters): + query = """ + select name + from `tabHealthcare Service Unit` + where + is_group = 0 + and company = {company} + and name like {txt}""".format( + company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt))) + + if filters and filters.get('inpatient_record'): + from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit + service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record')) + + # if the patient is admitted, then appointments should be allowed against the admission service unit, + # inspite of it being an Inpatient Occupancy service unit + if service_unit: + query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit)) + else: + query += " and allow_appointments = 1" + else: + query += " and allow_appointments = 1" + + return frappe.db.sql(query, filters) + + @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_tax_template(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py index bba521313d..e7319085e4 100644 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py +++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py @@ -264,7 +264,7 @@ def get_filters(entry): def get_current_healthcare_service_unit(inpatient_record): ip_record = frappe.get_doc('Inpatient Record', inpatient_record) - if ip_record.inpatient_occupancies: + if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies: return ip_record.inpatient_occupancies[-1].service_unit return diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 79e1775b9d..3d9f8788de 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -31,14 +31,14 @@ frappe.ui.form.on('Patient Appointment', { }; }); - frm.set_query('service_unit', function(){ + frm.set_query('service_unit', function() { return { + query: 'erpnext.controllers.queries.get_healthcare_service_units', filters: { - 'is_group': false, - 'allow_appointments': true, - 'company': frm.doc.company + company: frm.doc.company, + inpatient_record: frm.doc.inpatient_record } - }; + } }); frm.set_query('therapy_plan', function() { From fa2b0d43bd2b1035709285e0207d7c7806066a1b Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 18 Jan 2021 15:27:40 +0530 Subject: [PATCH 24/37] fix: handled invoices with no item_code --- .../item_wise_purchase_register.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index a36e7f8581..8a7ea72c16 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -53,8 +53,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, - 'item_name': item_record.item_name, - 'item_group': item_record.item_group, + 'item_name': item_record.item_name if item_record else d.item_name, + 'item_group': item_record.item_group if item_record else "", 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -315,7 +315,7 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, + `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, From 6bec696396307d496fff7e70b38caf378d85c4f7 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Mon, 18 Jan 2021 16:21:56 +0530 Subject: [PATCH 25/37] fix: item_group fallback --- .../item_wise_purchase_register.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 8a7ea72c16..eeb5140bbe 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -54,7 +54,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row = { 'item_code': d.item_code, 'item_name': item_record.item_name if item_record else d.item_name, - 'item_group': item_record.item_group if item_record else "", + 'item_group': item_record.item_group if item_record else d.item_group, 'description': d.description, 'invoice': d.parent, 'posting_date': d.posting_date, @@ -315,7 +315,8 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, - `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_name`, + `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description, + `tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, From f39cbd3a1d170a087cd7f35db4474684f6169798 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Jan 2021 13:59:03 +0530 Subject: [PATCH 26/37] test: appointment booking for admission service unit --- .../inpatient_record/test_inpatient_record.py | 10 ++- .../patient_appointment.js | 2 +- .../patient_appointment.py | 14 ++++ .../test_patient_appointment.py | 64 ++++++++++++++++++- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py index 10990d412d..8a918b0275 100644 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py @@ -142,11 +142,15 @@ def create_inpatient(patient): return inpatient_record -def get_healthcare_service_unit(): - service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) +def get_healthcare_service_unit(unit_name=None): + if not unit_name: + service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1}) + else: + service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name}) + if not service_unit: service_unit = frappe.new_doc("Healthcare Service Unit") - service_unit.healthcare_service_unit_name = "Test Service Unit Ip Occupancy" + service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy" service_unit.company = "_Test Company" service_unit.service_unit_type = get_service_unit_type() service_unit.inpatient_occupancy = 1 diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index 3d9f8788de..3d5073b13e 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -38,7 +38,7 @@ frappe.ui.form.on('Patient Appointment', { company: frm.doc.company, inpatient_record: frm.doc.inpatient_record } - } + }; }); frm.set_query('therapy_plan', function() { diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index dc820cb464..b05c673d84 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -18,6 +18,7 @@ from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_pr class PatientAppointment(Document): def validate(self): self.validate_overlaps() + self.validate_service_unit() self.set_appointment_datetime() self.validate_customer_created() self.set_status() @@ -68,6 +69,19 @@ class PatientAppointment(Document): overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4]) frappe.throw(overlapping_details, title=_('Appointments Overlapping')) + def validate_service_unit(self): + if self.inpatient_record and self.service_unit: + from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import get_current_healthcare_service_unit + + is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit, + 'inpatient_occupancy') + service_unit = get_current_healthcare_service_unit(self.inpatient_record) + if is_inpatient_occupancy_unit and service_unit != self.service_unit: + msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '
    ' + msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.') + frappe.throw(msg, title=_('Invalid Healthcare Service Unit')) + + def set_appointment_datetime(self): self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00") diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index b681ed1a22..7870813980 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import unittest import frappe from erpnext.healthcare.doctype.patient_appointment.patient_appointment import update_status, make_encounter -from frappe.utils import nowdate, add_days +from frappe.utils import nowdate, add_days, now_datetime from frappe.utils.make_random import get_random from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile @@ -78,6 +78,61 @@ class TestPatientAppointment(unittest.TestCase): sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled') + def test_appointment_booking_for_admission_service_unit(self): + from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge + from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter + from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \ + create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy + + frappe.db.sql("""delete from `tabInpatient Record`""") + patient, medical_department, practitioner = create_healthcare_docs() + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions = True) + + # Admit + service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + admit_patient(ip_record, service_unit, now_datetime()) + + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) + self.assertEqual(appointment.service_unit, service_unit) + + # Discharge + schedule_discharge(frappe.as_json({'patient': patient})) + ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) + mark_invoiced_inpatient_occupancy(ip_record1) + discharge_patient(ip_record1) + + def test_invalid_healthcare_service_unit_validation(self): + from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge + from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter + from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \ + create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy + + frappe.db.sql("""delete from `tabInpatient Record`""") + patient, medical_department, practitioner = create_healthcare_docs() + patient = create_patient() + # Schedule Admission + ip_record = create_inpatient(patient) + ip_record.expected_length_of_stay = 0 + ip_record.save(ignore_permissions = True) + + # Admit + service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy') + admit_patient(ip_record, service_unit, now_datetime()) + + appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment') + appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0) + self.assertRaises(frappe.exceptions.ValidationError, appointment.save) + + # Discharge + schedule_discharge(frappe.as_json({'patient': patient})) + ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) + mark_invoiced_inpatient_occupancy(ip_record1) + discharge_patient(ip_record1) + def create_healthcare_docs(): patient = create_patient() @@ -125,7 +180,7 @@ def create_encounter(appointment): encounter.submit() return encounter -def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0): +def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1): item = create_healthcare_service_items() frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item) frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item) @@ -136,12 +191,15 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce appointment.appointment_date = appointment_date appointment.company = '_Test Company' appointment.duration = 15 + if service_unit: + appointment.service_unit = service_unit if invoice: appointment.mode_of_payment = 'Cash' appointment.paid_amount = 500 if procedure_template: appointment.procedure_template = create_clinical_procedure_template().get('name') - appointment.save(ignore_permissions=True) + if save: + appointment.save(ignore_permissions=True) return appointment def create_healthcare_service_items(): From a28579a1302e1c301c41f623c35b21902d002458 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 19 Jan 2021 14:16:44 +0530 Subject: [PATCH 27/37] fix: sider issues --- .../doctype/patient_appointment/test_patient_appointment.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py index 7870813980..6886f31843 100644 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py @@ -80,7 +80,6 @@ class TestPatientAppointment(unittest.TestCase): def test_appointment_booking_for_admission_service_unit(self): from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge - from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \ create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy @@ -107,7 +106,6 @@ class TestPatientAppointment(unittest.TestCase): def test_invalid_healthcare_service_unit_validation(self): from erpnext.healthcare.doctype.inpatient_record.inpatient_record import admit_patient, discharge_patient, schedule_discharge - from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import \ create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy From ded08245cc7cb1b0f0ec6d7801de800e51aff3ab Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 22 Jan 2021 08:52:17 +0530 Subject: [PATCH 28/37] fix: duplicate filters added on patient change --- erpnext/healthcare/page/patient_history/patient_history.html | 2 +- erpnext/healthcare/page/patient_history/patient_history.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html index deaaa97868..be486c62d1 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ b/erpnext/healthcare/page/patient_history/patient_history.html @@ -10,7 +10,7 @@
    -
    +
    diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js index 05c5190f80..54343aae44 100644 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ b/erpnext/healthcare/page/patient_history/patient_history.js @@ -10,6 +10,7 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { frappe.breadcrumbs.add('Healthcare'); let pid = ''; page.main.html(frappe.render_template('patient_history', {})); + page.main.find('.header-separator').hide(); let patient = frappe.ui.form.make_control({ parent: page.main.find('.patient'), @@ -96,6 +97,7 @@ frappe.pages['patient_history'].on_page_load = function(wrapper) { }; let setup_filters = function(patient, me) { + $('.doctype-filter').empty(); frappe.xcall( 'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes' ).then(document_types => { @@ -123,6 +125,7 @@ let setup_filters = function(patient, me) { }); doctype_filter.refresh(); + $('.date-filter').empty(); let date_range_field = frappe.ui.form.make_control({ df: { fieldtype: 'DateRange', @@ -389,9 +392,11 @@ let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) { formatTooltipY: d => d + ' ' + pts, } }); + me.page.main.find('.header-separator').show(); } else { me.page.main.find('.patient_vital_charts').html(''); me.page.main.find('.show_chart_btns').html(''); + me.page.main.find('.header-separator').hide(); } } }); From b9a21c4824b0743415d4cf3049b807dec183b9b1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 22 Jan 2021 09:00:40 +0530 Subject: [PATCH 29/37] feat: added search to the Select fields dialog in Patient History Settings --- .../patient_history_settings/patient_history_settings.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index 92922b2888..5ea17af459 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -53,6 +53,14 @@ frappe.ui.form.on('Patient History Settings', { ] }); + d.$body.prepend(` + ` + ); + + frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area'); + d.set_primary_action(__('Save'), () => { let values = d.get_values().fields; From cf7209f3d4879179dc7c912f00ff82ff6d6dd87e Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 22 Jan 2021 11:47:13 +0530 Subject: [PATCH 30/37] fix: Project Template patch --- .../v13_0/update_project_template_tasks.py | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 5fa062306c..1b0441fe68 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -5,40 +5,41 @@ from __future__ import unicode_literals import frappe def execute(): - frappe.reload_doc("projects", "doctype", "project_template") - frappe.reload_doc("projects", "doctype", "project_template_task") - frappe.reload_doc("projects", "doctype", "project_template") - frappe.reload_doc("projects", "doctype", "task") + frappe.reload_doc("projects", "doctype", "project_template") + frappe.reload_doc("projects", "doctype", "project_template_task") + frappe.reload_doc("projects", "doctype", "task") - for template_name in frappe.db.sql(""" - select - name - from - `tabProject Template` """, - as_dict=1): - - template = frappe.get_doc("Project Template", template_name.name) - replace_tasks = False - new_tasks = [] - for task in template.tasks: - if task.subject: - replace_tasks = True - new_task = frappe.get_doc(dict( - doctype = "Task", - subject = task.subject, - start = task.start, - duration = task.duration, - task_weight = task.task_weight, - description = task.description, - is_template = 1 - )).insert() - new_tasks.append(new_task) + # Update property setter status if any + property_setter_doc = frappe.get_doc('Property Setter', {'doc_type': 'Task', + 'field_name': 'status', 'property': 'options'}) - if replace_tasks: - template.tasks = [] - for tsk in new_tasks: - template.append("tasks", { - "task": tsk.name, - "subject": tsk.subject - }) - template.save() \ No newline at end of file + if property_setter_doc: + property_setter_doc.value += "\nTemplate" + property_setter_doc.save() + + for template_name in frappe.get_all('Project Template'): + template = frappe.get_doc("Project Template", template_name.name) + replace_tasks = False + new_tasks = [] + for task in template.tasks: + if task.subject: + replace_tasks = True + new_task = frappe.get_doc(dict( + doctype = "Task", + subject = task.subject, + start = task.start, + duration = task.duration, + task_weight = task.task_weight, + description = task.description, + is_template = 1 + )).insert() + new_tasks.append(new_task) + + if replace_tasks: + template.tasks = [] + for tsk in new_tasks: + template.append("tasks", { + "task": tsk.name, + "subject": tsk.subject + }) + template.save() \ No newline at end of file From 75a93d90573ea97391b6ca03d8a404d681588a69 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 22 Jan 2021 15:51:25 +0530 Subject: [PATCH 31/37] feat: fetch date field for custom doctypes --- .../patient_history_settings.js | 23 +++++++++++++++++++ .../patient_history_settings.py | 8 +++++++ 2 files changed, 31 insertions(+) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index 5ea17af459..17324495e6 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -85,10 +85,33 @@ frappe.ui.form.on('Patient History Settings', { }); d.show(); + }, + + get_date_field_for_dt: function(frm, row) { + frm.call({ + method: 'get_date_field_for_dt', + doc: frm.doc, + args: { + document_type: row.document_type + }, + callback: function(data) { + if (data.message) { + frappe.model.set_value('Patient History Custom Document Type', + row.name, 'date_fieldname', data.message); + } + } + }); } }); frappe.ui.form.on('Patient History Custom Document Type', { + document_type: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.document_type) { + frm.events.get_date_field_for_dt(frm, row); + } + }, + add_edit_fields: function(frm, cdt, cdn) { let row = locals[cdt][cdn]; if (row.document_type) { diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py index 9ef97214c5..2e8c994c3d 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py @@ -49,6 +49,14 @@ class PatientHistorySettings(Document): return multicheck_fields + def get_date_field_for_dt(self, document_type): + meta = frappe.get_meta(document_type) + date_fields = meta.get('fields', { + 'fieldtype': ['in', ['Date', 'Datetime']] + }) + + if date_fields: + return date_fields[0].get('fieldname') def create_medical_record(doc, method=None): medical_record_required = validate_medical_record_required(doc) From 5b4ece50547bb4acc7984c266798d2dde3428aa9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 22 Jan 2021 17:12:35 +0530 Subject: [PATCH 32/37] fix: Update patch --- erpnext/patches/v13_0/update_project_template_tasks.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/update_project_template_tasks.py b/erpnext/patches/v13_0/update_project_template_tasks.py index 1b0441fe68..8cc27d217f 100644 --- a/erpnext/patches/v13_0/update_project_template_tasks.py +++ b/erpnext/patches/v13_0/update_project_template_tasks.py @@ -10,10 +10,12 @@ def execute(): frappe.reload_doc("projects", "doctype", "task") # Update property setter status if any - property_setter_doc = frappe.get_doc('Property Setter', {'doc_type': 'Task', + property_setter = frappe.db.get_value('Property Setter', {'doc_type': 'Task', 'field_name': 'status', 'property': 'options'}) - if property_setter_doc: + if property_setter: + property_setter_doc = frappe.get_doc('Property Setter', {'doc_type': 'Task', + 'field_name': 'status', 'property': 'options'}) property_setter_doc.value += "\nTemplate" property_setter_doc.save() From cb1da4d07cb7128e38dfa8047b0a5294a7e77ab0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 22 Jan 2021 19:26:56 +0530 Subject: [PATCH 33/37] fix: doctype meta not loading while setting up fields --- .../patient_history_settings.js | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index 17324495e6..cf01fcc35b 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -66,21 +66,24 @@ frappe.ui.form.on('Patient History Settings', { let selected_fields = []; - for (let idx in values) { - let value = values[idx]; + frappe.model.with_doctype(doc.document_type, function() { + for (let idx in values) { + let value = values[idx]; - let field = frappe.meta.get_docfield(doc.document_type, value); - if (field) { - selected_fields.push({ - label: field.label, - fieldname: field.fieldname, - fieldtype: field.fieldtype - }); + let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0]; + if (field) { + selected_fields.push({ + label: field.label, + fieldname: field.fieldname, + fieldtype: field.fieldtype + }); + } } - } - d.refresh(); - frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); + d.refresh(); + frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); + }) + d.hide(); }); From 6f4ad3b73d74c11426743024a04b786ab3666644 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 Jan 2021 12:52:41 +0530 Subject: [PATCH 34/37] fix: sider --- .../patient_history_settings/patient_history_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js index cf01fcc35b..453da6a12b 100644 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js @@ -82,7 +82,7 @@ frappe.ui.form.on('Patient History Settings', { d.refresh(); frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); - }) + }); d.hide(); }); From ee283280067b42ba673eebd8e74043b46c300305 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 Jan 2021 13:26:24 +0530 Subject: [PATCH 35/37] fix(travis): Issue Analytics Report Test (#24453) --- erpnext/support/doctype/issue/issue.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 62b39cced5..1ac295919b 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -214,7 +214,10 @@ class Issue(Document): def before_insert(self): if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"): - self.set_response_and_resolution_time() + if frappe.flags.in_test: + self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement) + else: + self.set_response_and_resolution_time() def set_response_and_resolution_time(self, priority=None, service_level_agreement=None): service_level_agreement = get_active_service_level_agreement_for(priority=priority, From c680547be37c98d1a3638547dedb64cf8f83c6c3 Mon Sep 17 00:00:00 2001 From: Kaviya Periyasamy <36359901+KaviyaPeriyasamy@users.noreply.github.com> Date: Sun, 24 Jan 2021 10:59:37 +0530 Subject: [PATCH 36/37] fix(einvoice): QRCode generation (#24412) --- erpnext/regional/india/e_invoice/einvoice.js | 3 ++ erpnext/regional/india/e_invoice/utils.py | 36 +++++++++++--------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js index 9c86cc89f5..9fa94c401f 100644 --- a/erpnext/regional/india/e_invoice/einvoice.js +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -18,6 +18,9 @@ erpnext.setup_einvoice_actions = (doctype) => { if (!irn && !__unsaved) { const action = () => { + if (frm.doc.__unsaved) { + frappe.throw(__('Please save the document to generate IRN.')); + } frappe.call({ method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', args: { doctype, docname: name }, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index eb210be16a..2366fcb9ed 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -11,6 +11,7 @@ import json import base64 import frappe import traceback +import io from frappe import _, bold from pyqrcode import create as qrcreate from frappe.integrations.utils import make_post_request, make_get_request @@ -436,7 +437,7 @@ class GSPConnector(): self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn' self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice' self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' - self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi' + self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB' self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' def get_credentials(self): @@ -527,7 +528,7 @@ class GSPConnector(): except Exception: self.log_error() self.raise_error(True) - + @staticmethod def get_gstin_details(gstin): '''fetch and cache GSTIN details''' @@ -622,7 +623,7 @@ class GSPConnector(): except Exception: self.log_error(data) self.raise_error(True) - + def generate_eway_bill(self, **kwargs): args = frappe._dict(kwargs) @@ -671,7 +672,8 @@ class GSPConnector(): 'cancelRsnCode': reason, 'cancelRmrk': remark }, indent=4) - + headers["username"] = headers["user_name"] + del headers["user_name"] try: res = self.make_request('post', self.cancel_ewaybill_url, headers, data) if res.get('success'): @@ -769,21 +771,21 @@ class GSPConnector(): qrcode = self.invoice.signed_qr_code doctype = self.invoice.doctype docname = self.invoice.name + filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__") - _file = frappe.new_doc('File') - _file.update({ - 'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')), - 'attached_to_doctype': doctype, - 'attached_to_name': docname, - 'content': str(base64.b64encode(os.urandom(64))), - 'is_private': 1 - }) - _file.insert() - frappe.db.commit() + qr_image = io.BytesIO() url = qrcreate(qrcode, error='L') - abs_file_path = os.path.abspath(_file.get_full_path()) - url.png(abs_file_path, scale=2, quiet_zone=1) - + url.png(qr_image, scale=2, quiet_zone=1) + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "attached_to_doctype": doctype, + "attached_to_name": docname, + "attached_to_field": "qrcode_image", + "is_private": 1, + "content": qr_image.getvalue()}) + _file.save() + frappe.db.commit() self.invoice.qrcode_image = _file.file_url def update_invoice(self): From 91eb9bb5c22510b3203471b2cd79484073a9449b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 24 Jan 2021 19:15:57 +0530 Subject: [PATCH 37/37] fix: Add loan to value field in Loan Interest Report --- .../applicant_wise_loan_security_exposure.py | 26 ++++++-- .../loan_interest_report.py | 61 ++++++++++++++++++- .../loan_security_exposure.py | 10 ++- 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py index 6d7c3b730d..f280402ce9 100644 --- a/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py +++ b/erpnext/loan_management/report/applicant_wise_loan_security_exposure/applicant_wise_loan_security_exposure.py @@ -26,6 +26,7 @@ def get_columns(filters): {"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80}, {"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100}, {"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100}, + {"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100}, {"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100}, {"label": _("% Of Applicant Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100}, {"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100}, @@ -43,13 +44,16 @@ def get_data(filters): for key, qty in iteritems(pledge_values): row = {} - current_value = flt(qty * loan_security_details.get(key[1])['latest_price']) + current_value = flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0)) + valid_upto = loan_security_details.get(key[1], {}).get('valid_upto') + row.update(loan_security_details.get(key[1])) row.update({ 'applicant_type': applicant_type_map.get(key[0]), 'applicant_name': key[0], 'total_qty': qty, 'current_value': current_value, + 'price_valid_upto': valid_upto, 'portfolio_percent': flt(current_value * 100 / total_value_map.get(key[0]), 2), 'currency': currency }) @@ -60,20 +64,30 @@ def get_data(filters): def get_loan_security_details(filters): security_detail_map = {} + loan_security_price_map = {} + lsp_validity_map = {} - loan_security_price_map = frappe._dict(frappe.db.sql(""" - SELECT loan_security, loan_security_price + loan_security_prices = frappe.db.sql(""" + SELECT loan_security, loan_security_price, valid_upto FROM `tabLoan Security Price` t1 WHERE valid_from >= (SELECT MAX(valid_from) FROM `tabLoan Security Price` t2 WHERE t1.loan_security = t2.loan_security) - """, as_list=1)) + """, as_dict=1) + + for security in loan_security_prices: + loan_security_price_map.setdefault(security.loan_security, security.loan_security_price) + lsp_validity_map.setdefault(security.loan_security, security.valid_upto) loan_security_details = frappe.get_all('Loan Security', fields=['name as loan_security', 'loan_security_code', 'loan_security_name', 'haircut', 'loan_security_type', 'disabled']) for security in loan_security_details: - security.update({'latest_price': flt(loan_security_price_map.get(security.loan_security))}) + security.update({ + 'latest_price': flt(loan_security_price_map.get(security.loan_security)), + 'valid_upto': lsp_validity_map.get(security.loan_security) + }) + security_detail_map.setdefault(security.loan_security, security) return security_detail_map @@ -118,6 +132,6 @@ def get_applicant_wise_total_loan_security_qty(filters, loan_security_details): applicant_wise_unpledges.get((security.applicant, security.loan_security), 0.0) total_value_map[security.applicant] += current_pledges.get((security.applicant, security.loan_security)) \ - * loan_security_details.get(security.loan_security)['latest_price'] + * loan_security_details.get(security.loan_security, {}).get('latest_price', 0) return current_pledges, total_value_map, applicant_type_map \ No newline at end of file diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py index aa0325ef35..2bfe6d3c33 100644 --- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py +++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py @@ -6,6 +6,8 @@ import frappe import erpnext from frappe import _ from frappe.utils import flt, getdate, add_days +from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applicant_wise_loan_security_exposure \ + import get_loan_security_details def execute(filters=None): @@ -31,6 +33,7 @@ def get_columns(filters): {"label": _("Undue Booked Interest"), "fieldname": "undue_interest", "fieldtype": "Currency", "options": "currency", "width": 120}, {"label": _("Interest %"), "fieldname": "rate_of_interest", "fieldtype": "Percent", "width": 100}, {"label": _("Penalty Interest %"), "fieldname": "penalty_interest", "fieldtype": "Percent", "width": 100}, + {"label": _("Loan To Value Ratio"), "fieldname": "loan_to_value", "fieldtype": "Percent", "width": 100}, {"label": _("Currency"), "fieldname": "currency", "fieldtype": "Currency", "options": "Currency", "hidden": 1, "width": 100}, ] @@ -50,6 +53,9 @@ def get_active_loan_details(filters): loan_list = [d.loan for d in loan_details] + current_pledges = get_loan_wise_pledges(filters) + loan_wise_security_value = get_loan_wise_security_value(filters, current_pledges) + sanctioned_amount_map = get_sanctioned_amount_map() penal_interest_rate_map = get_penal_interest_rate_map() payments = get_payments(loan_list) @@ -67,12 +73,16 @@ def get_active_loan_details(filters): "penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")), "penalty_interest": penal_interest_rate_map.get(loan.loan_type), "undue_interest": flt(accrual_map.get(loan.loan, {}).get("undue_interest")), + "loan_to_value": 0.0, "currency": currency }) loan['total_outstanding'] = loan['principal_outstanding'] + loan['interest_outstanding'] \ + loan['penalty'] + if loan_wise_security_value.get(loan.loan): + loan['loan_to_value'] = (loan['principal_outstanding'] * 100) / loan_wise_security_value.get(loan.loan) + return loan_details def get_sanctioned_amount_map(): @@ -121,4 +131,53 @@ def get_interest_accruals(loans): return accrual_map def get_penal_interest_rate_map(): - return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1)) \ No newline at end of file + return frappe._dict(frappe.get_all("Loan Type", fields=["name", "penalty_interest_rate"], as_list=1)) + +def get_loan_wise_pledges(filters): + loan_wise_unpledges = {} + current_pledges = {} + + conditions = "" + + if filters.get('company'): + conditions = "AND company = %(company)s" + + unpledges = frappe.db.sql(""" + SELECT up.loan, u.loan_security, sum(u.qty) as qty + FROM `tabLoan Security Unpledge` up, `tabUnpledge` u + WHERE u.parent = up.name + AND up.status = 'Approved' + {conditions} + GROUP BY up.loan + """.format(conditions=conditions), filters, as_dict=1) + + for unpledge in unpledges: + loan_wise_unpledges.setdefault((unpledge.loan, unpledge.loan_security), unpledge.qty) + + pledges = frappe.db.sql(""" + SELECT lp.loan, p.loan_security, sum(p.qty) as qty + FROM `tabLoan Security Pledge` lp, `tabPledge`p + WHERE p.parent = lp.name + AND lp.status = 'Pledged' + {conditions} + GROUP BY lp.loan + """.format(conditions=conditions), filters, as_dict=1) + + for security in pledges: + current_pledges.setdefault((security.loan, security.loan_security), security.qty) + current_pledges[(security.loan, security.loan_security)] -= \ + loan_wise_unpledges.get((security.loan, security.loan_security), 0.0) + + return current_pledges + +def get_loan_wise_security_value(filters, current_pledges): + loan_security_details = get_loan_security_details(filters) + loan_wise_security_value = {} + + for key in current_pledges: + qty = current_pledges.get(key) + loan_wise_security_value.setdefault(key[0], 0.0) + loan_wise_security_value[key[0]] += \ + flt(qty * loan_security_details.get(key[1], {}).get('latest_price', 0)) + + return loan_wise_security_value \ No newline at end of file diff --git a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py index 3ef10c0f41..ff88052df5 100644 --- a/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py +++ b/erpnext/loan_management/report/loan_security_exposure/loan_security_exposure.py @@ -24,6 +24,7 @@ def get_columns(filters): {"label": _("Disabled"), "fieldname": "disabled", "fieldtype": "Check", "width": 80}, {"label": _("Total Qty"), "fieldname": "total_qty", "fieldtype": "Float", "width": 100}, {"label": _("Latest Price"), "fieldname": "latest_price", "fieldtype": "Currency", "options": "currency", "width": 100}, + {"label": _("Price Valid Upto"), "fieldname": "price_valid_upto", "fieldtype": "Datetime", "width": 100}, {"label": _("Current Value"), "fieldname": "current_value", "fieldtype": "Currency", "options": "currency", "width": 100}, {"label": _("% Of Total Portfolio"), "fieldname": "portfolio_percent", "fieldtype": "Percentage", "width": 100}, {"label": _("Pledged Applicant Count"), "fieldname": "pledged_applicant_count", "fieldtype": "Percentage", "width": 100}, @@ -40,13 +41,16 @@ def get_data(filters): for security, value in iteritems(current_pledges): row = {} - current_value = flt(value['qty'] * loan_security_details.get(security)['latest_price']) + current_value = flt(value.get('qty', 0) * loan_security_details.get(security, {}).get('latest_price', 0)) + valid_upto = loan_security_details.get(security, {}).get('valid_upto') + row.update(loan_security_details.get(security)) row.update({ - 'total_qty': value['qty'], + 'total_qty': value.get('qty'), 'current_value': current_value, + 'price_valid_upto': valid_upto, 'portfolio_percent': flt(current_value * 100 / total_portfolio_value, 2), - 'pledged_applicant_count': value['applicant_count'], + 'pledged_applicant_count': value.get('applicant_count'), 'currency': currency })