feat: Therapy Plan Template (#23558)

* feat: Therapy Plan Template

* feat: Handle billing Therapy Plans created via Templates

* feat: add dashboard to Therapy Plan Template

* fix: codacy issues

* fix: sider

* fix: validate Therapy Session overlap

* feat: Create Sales Invoice from Therapy Plan

* fix: sider

* chore: added tests for Therapy Plan Template

* fix: test

* fix: test

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index 526bb95..a061c66 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -5,9 +5,9 @@
 
 import frappe
 import unittest
-from frappe.utils import getdate
+from frappe.utils import getdate, flt
 from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
-from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session
+from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
 from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
 
 class TestTherapyPlan(unittest.TestCase):
@@ -20,25 +20,45 @@
 		plan = create_therapy_plan()
 		self.assertEquals(plan.status, 'Not Started')
 
-		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab')
+		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
 		frappe.get_doc(session).submit()
 		self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
 
-		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab')
+		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company')
 		frappe.get_doc(session).submit()
 		self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
 
+	def test_therapy_plan_from_template(self):
+		patient = create_patient()
+		template = create_therapy_plan_template()
+		# check linked item
+		self.assertTrue(frappe.db.exists('Therapy Plan Template', {'linked_item': 'Complete Rehab'}))
 
-def create_therapy_plan():
+		plan = create_therapy_plan(template)
+		# invoice
+		si = make_sales_invoice(plan.name, patient, '_Test Company', template)
+		si.save()
+
+		therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount')
+		self.assertEquals(si.items[0].amount, therapy_plan_template_amt)
+
+
+def create_therapy_plan(template=None):
 	patient = create_patient()
 	therapy_type = create_therapy_type()
 	plan = frappe.new_doc('Therapy Plan')
 	plan.patient = patient
 	plan.start_date = getdate()
-	plan.append('therapy_plan_details', {
-		'therapy_type': therapy_type.name,
-		'no_of_sessions': 2
-	})
+
+	if template:
+		plan.therapy_plan_template = template
+		plan = plan.set_therapy_details_from_template()
+	else:
+		plan.append('therapy_plan_details', {
+			'therapy_type': therapy_type.name,
+			'no_of_sessions': 2
+		})
+
 	plan.save()
 	return plan
 
@@ -55,3 +75,22 @@
 	encounter.save()
 	encounter.submit()
 	return encounter
+
+def create_therapy_plan_template():
+	template_name = frappe.db.exists('Therapy Plan Template', 'Complete Rehab')
+	if not template_name:
+		therapy_type = create_therapy_type()
+		template = frappe.new_doc('Therapy Plan Template')
+		template.plan_name = template.item_code = template.item_name = 'Complete Rehab'
+		template.item_group = 'Services'
+		rate = frappe.db.get_value('Therapy Type', therapy_type.name, 'rate')
+		template.append('therapy_types', {
+			'therapy_type': therapy_type.name,
+			'no_of_sessions': 2,
+			'rate': rate,
+			'amount': 2 * flt(rate)
+		})
+		template.save()
+		template_name = template.name
+
+	return template_name
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
index dea0cfe..490d458 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
@@ -37,7 +37,8 @@
 						args: {
 							therapy_plan: frm.doc.name,
 							patient: frm.doc.patient,
-							therapy_type: data.therapy_type
+							therapy_type: data.therapy_type,
+							company: frm.doc.company
 						},
 						freeze: true,
 						callback: function(r) {
@@ -49,13 +50,53 @@
 					});
 				}, __('Select Therapy Type'), __('Create'));
 			}, __('Create'));
+
+			if (frm.doc.therapy_plan_template && !frm.doc.invoiced) {
+				frm.add_custom_button(__('Sales Invoice'), function() {
+					frm.trigger('make_sales_invoice');
+				}, __('Create'));
+			}
+		}
+
+		if (frm.doc.therapy_plan_template) {
+			frappe.meta.get_docfield('Therapy Plan Detail', 'therapy_type', frm.doc.name).read_only = 1;
+			frappe.meta.get_docfield('Therapy Plan Detail', 'no_of_sessions', frm.doc.name).read_only = 1;
+		}
+	},
+
+	make_sales_invoice: function(frm) {
+		frappe.call({
+			args: {
+				'reference_name': frm.doc.name,
+				'patient': frm.doc.patient,
+				'company': frm.doc.company,
+				'therapy_plan_template': frm.doc.therapy_plan_template
+			},
+			method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_sales_invoice',
+			callback: function(r) {
+				var doclist = frappe.model.sync(r.message);
+				frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
+			}
+		});
+	},
+
+	therapy_plan_template: function(frm) {
+		if (frm.doc.therapy_plan_template) {
+			frappe.call({
+				method: 'set_therapy_details_from_template',
+				doc: frm.doc,
+				freeze: true,
+				freeze_message: __('Fetching Template Details'),
+				callback: function() {
+					refresh_field('therapy_plan_details');
+				}
+			});
 		}
 	},
 
 	show_progress_for_therapies: function(frm) {
 		let bars = [];
 		let message = '';
-		let added_min = false;
 
 		// completed sessions
 		let title = __('{0} sessions completed', [frm.doc.total_sessions_completed]);
@@ -71,7 +112,6 @@
 		});
 		if (bars[0].width == '0%') {
 			bars[0].width = '0.5%';
-			added_min = 0.5;
 		}
 		message = title;
 		frm.dashboard.add_progress(__('Status'), bars, message);
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json
index 9edfeb2..ccb316e 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json
@@ -9,11 +9,13 @@
   "naming_series",
   "patient",
   "patient_name",
+  "invoiced",
   "column_break_4",
   "company",
   "status",
   "start_date",
   "section_break_3",
+  "therapy_plan_template",
   "therapy_plan_details",
   "title",
   "section_break_9",
@@ -46,6 +48,7 @@
    "fieldtype": "Table",
    "label": "Therapy Plan Details",
    "options": "Therapy Plan Detail",
+   "read_only_depends_on": "therapy_plan_template",
    "reqd": 1
   },
   {
@@ -105,11 +108,27 @@
    "fieldtype": "Link",
    "in_standard_filter": 1,
    "label": "Company",
-   "options": "Company"
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "fieldname": "therapy_plan_template",
+   "fieldtype": "Link",
+   "label": "Therapy Plan Template",
+   "options": "Therapy Plan Template"
+  },
+  {
+   "default": "0",
+   "fieldname": "invoiced",
+   "fieldtype": "Check",
+   "label": "Invoiced",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "links": [],
- "modified": "2020-05-25 14:38:53.649315",
+ "modified": "2020-10-23 01:27:42.128855",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Therapy Plan",
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index e0f015f..bc0ff1a 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
-from frappe.utils import today
+from frappe.utils import flt, today
 
 class TherapyPlan(Document):
 	def validate(self):
@@ -33,13 +33,26 @@
 		self.db_set('total_sessions', total_sessions)
 		self.db_set('total_sessions_completed', total_sessions_completed)
 
+	def set_therapy_details_from_template(self):
+		# Add therapy types in the child table
+		self.set('therapy_plan_details', [])
+		therapy_plan_template = frappe.get_doc('Therapy Plan Template', self.therapy_plan_template)
+
+		for data in therapy_plan_template.therapy_types:
+			self.append('therapy_plan_details', {
+				'therapy_type': data.therapy_type,
+				'no_of_sessions': data.no_of_sessions
+			})
+		return self
+
 
 @frappe.whitelist()
-def make_therapy_session(therapy_plan, patient, therapy_type):
+def make_therapy_session(therapy_plan, patient, therapy_type, company):
 	therapy_type = frappe.get_doc('Therapy Type', therapy_type)
 
 	therapy_session = frappe.new_doc('Therapy Session')
 	therapy_session.therapy_plan = therapy_plan
+	therapy_session.company = company
 	therapy_session.patient = patient
 	therapy_session.therapy_type = therapy_type.name
 	therapy_session.duration = therapy_type.default_duration
@@ -48,4 +61,39 @@
 
 	if frappe.flags.in_test:
 		therapy_session.start_date = today()
-	return therapy_session.as_dict()
\ No newline at end of file
+	return therapy_session.as_dict()
+
+
+@frappe.whitelist()
+def make_sales_invoice(reference_name, patient, company, therapy_plan_template):
+	from erpnext.stock.get_item_details import get_item_details
+	si = frappe.new_doc('Sales Invoice')
+	si.company = company
+	si.patient = patient
+	si.customer = frappe.db.get_value('Patient', patient, 'customer')
+
+	item = frappe.db.get_value('Therapy Plan Template', therapy_plan_template, 'linked_item')
+	price_list, price_list_currency = frappe.db.get_values('Price List', {'selling': 1}, ['name', 'currency'])[0]
+	args = {
+		'doctype': 'Sales Invoice',
+		'item_code': item,
+		'company': company,
+		'customer': si.customer,
+		'selling_price_list': price_list,
+		'price_list_currency': price_list_currency,
+		'plc_conversion_rate': 1.0,
+		'conversion_rate': 1.0
+	}
+
+	item_line = si.append('items', {})
+	item_details = get_item_details(args)
+	item_line.item_code = item
+	item_line.qty = 1
+	item_line.rate = item_details.price_list_rate
+	item_line.amount = flt(item_line.rate) * flt(item_line.qty)
+	item_line.reference_dt = 'Therapy Plan'
+	item_line.reference_dn = reference_name
+	item_line.description = item_details.description
+
+	si.set_missing_values(for_validate = True)
+	return si
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py
index df64782..6526acd 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py
@@ -4,10 +4,18 @@
 def get_data():
 	return {
 		'fieldname': 'therapy_plan',
+		'non_standard_fieldnames': {
+			'Sales Invoice': 'reference_dn'
+		},
 		'transactions': [
 			{
 				'label': _('Therapy Sessions'),
 				'items': ['Therapy Session']
+			},
+			{
+				'label': _('Billing'),
+				'items': ['Sales Invoice']
 			}
-		]
+		],
+		'disable_create_buttons': ['Sales Invoice']
 	}
diff --git a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json
index 9eb20e2..555587e 100644
--- a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json
+++ b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json
@@ -35,7 +35,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-03-30 22:02:01.740109",
+ "modified": "2020-10-08 01:17:34.778028",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Therapy Plan Detail",
diff --git a/erpnext/healthcare/doctype/therapy_plan_template/__init__.py b/erpnext/healthcare/doctype/therapy_plan_template/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template/__init__.py
diff --git a/erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py b/erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py
new file mode 100644
index 0000000..33ee29d
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.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 TestTherapyPlanTemplate(unittest.TestCase):
+	pass
diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js
new file mode 100644
index 0000000..86de192
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Therapy Plan Template', {
+	refresh: function(frm) {
+		frm.set_query('therapy_type', 'therapy_types', () => {
+			return {
+				filters: {
+					'is_billable': 1
+				}
+			};
+		});
+	},
+
+	set_totals: function(frm) {
+		let total_sessions = 0;
+		let total_amount = 0.0;
+		frm.doc.therapy_types.forEach((d) => {
+			if (d.no_of_sessions) total_sessions += cint(d.no_of_sessions);
+			if (d.amount) total_amount += flt(d.amount);
+		});
+		frm.set_value('total_sessions', total_sessions);
+		frm.set_value('total_amount', total_amount);
+		frm.refresh_fields();
+	}
+});
+
+frappe.ui.form.on('Therapy Plan Template Detail', {
+	therapy_type: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		frappe.call('frappe.client.get', {
+			doctype: 'Therapy Type',
+			name: row.therapy_type
+		}).then((res) => {
+			row.rate = res.message.rate;
+			if (!row.no_of_sessions)
+				row.no_of_sessions = 1;
+			row.amount = flt(row.rate) * cint(row.no_of_sessions);
+			frm.refresh_field('therapy_types');
+			frm.trigger('set_totals');
+		});
+	},
+
+	no_of_sessions: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		row.amount = flt(row.rate) * cint(row.no_of_sessions);
+		frm.refresh_field('therapy_types');
+		frm.trigger('set_totals');
+	},
+
+	rate: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		row.amount = flt(row.rate) * cint(row.no_of_sessions);
+		frm.refresh_field('therapy_types');
+		frm.trigger('set_totals');
+	}
+});
diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json
new file mode 100644
index 0000000..48fc896
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json
@@ -0,0 +1,132 @@
+{
+ "actions": [],
+ "autoname": "field:plan_name",
+ "creation": "2020-09-22 17:51:38.861055",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "plan_name",
+  "linked_item_details_section",
+  "item_code",
+  "item_name",
+  "item_group",
+  "column_break_6",
+  "description",
+  "linked_item",
+  "therapy_types_section",
+  "therapy_types",
+  "section_break_11",
+  "total_sessions",
+  "column_break_13",
+  "total_amount"
+ ],
+ "fields": [
+  {
+   "fieldname": "plan_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Plan Name",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "therapy_types_section",
+   "fieldtype": "Section Break",
+   "label": "Therapy Types"
+  },
+  {
+   "fieldname": "therapy_types",
+   "fieldtype": "Table",
+   "label": "Therapy Types",
+   "options": "Therapy Plan Template Detail",
+   "reqd": 1
+  },
+  {
+   "fieldname": "linked_item",
+   "fieldtype": "Link",
+   "label": "Linked Item",
+   "options": "Item",
+   "read_only": 1
+  },
+  {
+   "fieldname": "linked_item_details_section",
+   "fieldtype": "Section Break",
+   "label": "Linked Item Details"
+  },
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Data",
+   "label": "Item Code",
+   "reqd": 1,
+   "set_only_once": 1
+  },
+  {
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "reqd": 1
+  },
+  {
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "label": "Item Group",
+   "options": "Item Group",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "label": "Item Description"
+  },
+  {
+   "fieldname": "total_amount",
+   "fieldtype": "Currency",
+   "label": "Total Amount",
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_11",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "total_sessions",
+   "fieldtype": "Int",
+   "label": "Total Sessions",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-10-08 00:56:58.062105",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Therapy Plan Template",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 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/therapy_plan_template/therapy_plan_template.py b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py
new file mode 100644
index 0000000..748c12c
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py
@@ -0,0 +1,73 @@
+# -*- 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
+from frappe.utils import cint, flt
+from erpnext.healthcare.doctype.therapy_type.therapy_type import make_item_price
+
+class TherapyPlanTemplate(Document):
+	def after_insert(self):
+		self.create_item_from_template()
+
+	def validate(self):
+		self.set_totals()
+
+	def on_update(self):
+		doc_before_save = self.get_doc_before_save()
+		if not doc_before_save: return
+		if doc_before_save.item_name != self.item_name or doc_before_save.item_group != self.item_group \
+			or doc_before_save.description != self.description:
+			self.update_item()
+
+		if doc_before_save.therapy_types != self.therapy_types:
+			self.update_item_price()
+
+	def set_totals(self):
+		total_sessions = 0
+		total_amount = 0
+
+		for entry in self.therapy_types:
+			total_sessions += cint(entry.no_of_sessions)
+			total_amount += flt(entry.amount)
+
+		self.total_sessions = total_sessions
+		self.total_amount = total_amount
+
+	def create_item_from_template(self):
+		uom = frappe.db.exists('UOM', 'Nos') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
+
+		item = frappe.get_doc({
+			'doctype': 'Item',
+			'item_code': self.item_code,
+			'item_name': self.item_name,
+			'item_group': self.item_group,
+			'description': self.description,
+			'is_sales_item': 1,
+			'is_service_item': 1,
+			'is_purchase_item': 0,
+			'is_stock_item': 0,
+			'show_in_website': 0,
+			'is_pro_applicable': 0,
+			'stock_uom': uom
+		}).insert(ignore_permissions=True, ignore_mandatory=True)
+
+		make_item_price(item.name, self.total_amount)
+		self.db_set('linked_item', item.name)
+
+	def update_item(self):
+		item_doc = frappe.get_doc('Item', {'item_code': self.linked_item})
+		item_doc.item_name = self.item_name
+		item_doc.item_group = self.item_group
+		item_doc.description = self.description
+		item_doc.ignore_mandatory = True
+		item_doc.save(ignore_permissions=True)
+
+	def update_item_price(self):
+		item_price = frappe.get_doc('Item Price', {'item_code': self.linked_item})
+		item_price.item_name = self.item_name
+		item_price.price_list_rate = self.total_amount
+		item_price.ignore_mandatory = True
+		item_price.save(ignore_permissions=True)
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py
new file mode 100644
index 0000000..c748fbf
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py
@@ -0,0 +1,13 @@
+from __future__ import unicode_literals
+from frappe import _
+
+def get_data():
+	return {
+		'fieldname': 'therapy_plan_template',
+		'transactions': [
+			{
+				'label': _('Therapy Plans'),
+				'items': ['Therapy Plan']
+			}
+		]
+	}
diff --git a/erpnext/healthcare/doctype/therapy_plan_template_detail/__init__.py b/erpnext/healthcare/doctype/therapy_plan_template_detail/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template_detail/__init__.py
diff --git a/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json b/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json
new file mode 100644
index 0000000..5553a11
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json
@@ -0,0 +1,54 @@
+{
+ "actions": [],
+ "creation": "2020-10-07 23:04:44.373381",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "therapy_type",
+  "no_of_sessions",
+  "rate",
+  "amount"
+ ],
+ "fields": [
+  {
+   "fieldname": "therapy_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Therapy Type",
+   "options": "Therapy Type",
+   "reqd": 1
+  },
+  {
+   "fieldname": "no_of_sessions",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "No of Sessions"
+  },
+  {
+   "fieldname": "rate",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Rate"
+  },
+  {
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Amount",
+   "read_only": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2020-10-07 23:46:54.296322",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Therapy Plan Template Detail",
+ "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/therapy_plan_template_detail/therapy_plan_template_detail.py b/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py
new file mode 100644
index 0000000..7b979fe
--- /dev/null
+++ b/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.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 TherapyPlanTemplateDetail(Document):
+	pass
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
index e66e667..65d4cc4 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js
@@ -92,7 +92,8 @@
 						'start_date': data.message.appointment_date,
 						'start_time': data.message.appointment_time,
 						'service_unit': data.message.service_unit,
-						'company': data.message.company
+						'company': data.message.company,
+						'duration': data.message.duration
 					};
 					frm.set_value(values);
 				}
@@ -107,6 +108,7 @@
 				'start_date': '',
 				'start_time': '',
 				'service_unit': '',
+				'duration': ''
 			};
 			frm.set_value(values);
 		}
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json
index dc0cafc..1f877cc 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json
@@ -47,7 +47,8 @@
    "fieldname": "appointment",
    "fieldtype": "Link",
    "label": "Appointment",
-   "options": "Patient Appointment"
+   "options": "Patient Appointment",
+   "set_only_once": 1
   },
   {
    "fieldname": "patient",
@@ -90,7 +91,8 @@
    "fetch_from": "therapy_template.default_duration",
    "fieldname": "duration",
    "fieldtype": "Int",
-   "label": "Duration"
+   "label": "Duration",
+   "reqd": 1
   },
   {
    "fieldname": "location",
@@ -220,7 +222,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-30 10:56:10.354268",
+ "modified": "2020-10-22 23:10:21.178644",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Therapy Session",
diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py
index 9650183..85d0970 100644
--- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py
+++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py
@@ -4,16 +4,41 @@
 
 from __future__ import unicode_literals
 import frappe
+import datetime
 from frappe.model.document import Document
+from frappe.utils import get_time, flt
 from frappe.model.mapper import get_mapped_doc
 from frappe import _
-from frappe.utils import cstr, getdate
+from frappe.utils import cstr, getdate, get_link_to_form
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
 
 class TherapySession(Document):
 	def validate(self):
+		self.validate_duplicate()
 		self.set_total_counts()
 
+	def validate_duplicate(self):
+		end_time = datetime.datetime.combine(getdate(self.start_date), get_time(self.start_time)) \
+			 + datetime.timedelta(minutes=flt(self.duration))
+
+		overlaps = frappe.db.sql("""
+		select
+			name
+		from
+			`tabTherapy Session`
+		where
+			start_date=%s and name!=%s and docstatus!=2
+			and (practitioner=%s or patient=%s) and
+			((start_time<%s and start_time + INTERVAL duration MINUTE>%s) or
+			(start_time>%s and start_time<%s) or
+			(start_time=%s))
+		""", (self.start_date, self.name, self.practitioner, self.patient,
+		self.start_time, end_time.time(), self.start_time, end_time.time(), self.start_time))
+
+		if overlaps:
+			overlapping_details = _('Therapy Session overlaps with {0}').format(get_link_to_form('Therapy Session', overlaps[0][0]))
+			frappe.throw(overlapping_details, title=_('Therapy Sessions Overlapping'))
+
 	def on_submit(self):
 		self.update_sessions_count_in_therapy_plan()
 		insert_session_medical_record(self)
diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.py b/erpnext/healthcare/doctype/therapy_type/therapy_type.py
index ea3d84e..6c825b8 100644
--- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py
@@ -41,7 +41,7 @@
 			if self.rate:
 				item_price = frappe.get_doc('Item Price', {'item_code': self.item})
 				item_price.item_name = self.item_name
-				item_price.price_list_name = self.rate
+				item_price.price_list_rate = self.rate
 				item_price.ignore_mandatory = True
 				item_price.save()
 
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index dbd3b83..96282f5 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -23,9 +23,9 @@
 		items_to_invoice += get_lab_tests_to_invoice(patient, company)
 		items_to_invoice += get_clinical_procedures_to_invoice(patient, company)
 		items_to_invoice += get_inpatient_services_to_invoice(patient, company)
+		items_to_invoice += get_therapy_plans_to_invoice(patient, company)
 		items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
 
-
 		return items_to_invoice
 
 
@@ -35,6 +35,7 @@
 		msg +=  " <b><a href='#Form/Patient/{0}'>{0}</a></b>".format(patient.name)
 		frappe.throw(msg, title=_('Customer Not Found'))
 
+
 def get_appointments_to_invoice(patient, company):
 	appointments_to_invoice = []
 	patient_appointments = frappe.get_list(
@@ -246,12 +247,44 @@
 	return services_to_invoice
 
 
+def get_therapy_plans_to_invoice(patient, company):
+	therapy_plans_to_invoice = []
+	therapy_plans = frappe.get_list(
+		'Therapy Plan',
+		fields=['therapy_plan_template', 'name'],
+		filters={
+			'patient': patient.name,
+			'invoiced': 0,
+			'company': company,
+			'therapy_plan_template': ('!=', '')
+		}
+	)
+	for plan in therapy_plans:
+		therapy_plans_to_invoice.append({
+			'reference_type': 'Therapy Plan',
+			'reference_name': plan.name,
+			'service': frappe.db.get_value('Therapy Plan Template', plan.therapy_plan_template, 'linked_item')
+		})
+
+	return therapy_plans_to_invoice
+
+
 def get_therapy_sessions_to_invoice(patient, company):
 	therapy_sessions_to_invoice = []
+	therapy_plans = frappe.db.get_all('Therapy Plan', {'therapy_plan_template': ('!=', '')})
+	therapy_plans_created_from_template = []
+	for entry in therapy_plans:
+		therapy_plans_created_from_template.append(entry.name)
+
 	therapy_sessions = frappe.get_list(
 		'Therapy Session',
 		fields='*',
-		filters={'patient': patient.name, 'invoiced': 0, 'company': company}
+		filters={
+			'patient': patient.name,
+			'invoiced': 0,
+			'company': company,
+			'therapy_plan': ('not in', therapy_plans_created_from_template)
+		}
 	)
 	for therapy in therapy_sessions:
 		if not therapy.appointment:
@@ -368,8 +401,8 @@
 	else:
 		is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced')
 	if is_invoiced:
-		frappe.throw(_('The item referenced by {0} - {1} is already invoiced'\
-		).format(item.reference_dt, item.reference_dn))
+		frappe.throw(_('The item referenced by {0} - {1} is already invoiced').format(
+			item.reference_dt, item.reference_dn))
 
 
 def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field):