Automate - Patient Apointment Invoicing
diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
index cbe49c1..24c3cd9 100644
--- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
+++ b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json
@@ -248,6 +248,40 @@
    "bold": 0, 
    "collapsible": 0, 
    "columns": 0, 
+   "default": "0", 
+   "description": "Manage Appointment Invoice submit and cancel automatically for Patient Encounter", 
+   "fieldname": "manage_appointment_invoice_automatically", 
+   "fieldtype": "Check", 
+   "hidden": 0, 
+   "ignore_user_permissions": 0, 
+   "ignore_xss_filter": 0, 
+   "in_filter": 0, 
+   "in_global_search": 0, 
+   "in_list_view": 0, 
+   "in_standard_filter": 0, 
+   "label": "Manage Appointment Invoice Automatically", 
+   "length": 0, 
+   "no_copy": 0, 
+   "permlevel": 0, 
+   "precision": "", 
+   "print_hide": 0, 
+   "print_hide_if_no_value": 0, 
+   "read_only": 0, 
+   "remember_last_selected_value": 0, 
+   "report_hide": 0, 
+   "reqd": 0, 
+   "search_index": 0, 
+   "set_only_once": 0, 
+   "translatable": 0, 
+   "unique": 0
+  }, 
+  {
+   "allow_bulk_edit": 0, 
+   "allow_in_quick_entry": 0, 
+   "allow_on_submit": 0, 
+   "bold": 0, 
+   "collapsible": 0, 
+   "columns": 0, 
    "fieldname": "max_visit", 
    "fieldtype": "Int", 
    "hidden": 0, 
@@ -1293,7 +1327,7 @@
  "issingle": 1, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-08-01 13:30:04.448397", 
+ "modified": "2018-08-03 15:18:36.631441", 
  "modified_by": "Administrator", 
  "module": "Healthcare", 
  "name": "Healthcare Settings", 
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 7d0dade..a0ffeb1 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -6,11 +6,13 @@
 import frappe
 from frappe.model.document import Document
 import json
-from frappe.utils import getdate
+from frappe.utils import getdate, add_days
 from frappe import _
 import datetime
 from frappe.core.doctype.sms_settings.sms_settings import send_sms
 from erpnext.hr.doctype.employee.employee import is_holiday
+from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account
+from erpnext.healthcare.utils import validity_exists, service_item_and_practitioner_charge
 
 class PatientAppointment(Document):
 	def on_update(self):
@@ -41,23 +43,108 @@
 				frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till))
 		confirm_sms(self)
 
+		if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1' and \
+			frappe.db.get_value("Patient Appointment", self.name, "invoiced") != 1:
+			invoice_appointment(self)
+
+@frappe.whitelist()
+def invoice_appointment(appointment_doc):
+	if not appointment_doc.name:
+		return False
+	sales_invoice = frappe.new_doc("Sales Invoice")
+	sales_invoice.customer = frappe.get_value("Patient", appointment_doc.patient, "customer")
+	sales_invoice.appointment = appointment_doc.name
+	sales_invoice.due_date = getdate()
+	sales_invoice.is_pos = True
+	sales_invoice.company = appointment_doc.company
+	sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
+
+	item_line = sales_invoice.append("items")
+	service_item, practitioner_charge = service_item_and_practitioner_charge(appointment_doc)
+	item_line.item_code = service_item
+	item_line.description = "Consulting Charges:  " + appointment_doc.practitioner
+	item_line.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company)
+	item_line.rate = practitioner_charge
+	item_line.amount = practitioner_charge
+	item_line.qty = 1
+	item_line.reference_dt = "Patient Appointment"
+	item_line.reference_dn = appointment_doc.name
+
+	payments_line = sales_invoice.append("payments")
+	payments_line.mode_of_payment = "Cash"
+	payments_line.amount = practitioner_charge
+
+	sales_invoice.set_missing_values(for_validate = True)
+
+	sales_invoice.save(ignore_permissions=True)
+	sales_invoice.submit()
+	frappe.msgprint(_("Sales Invoice {0} created as paid".format(sales_invoice.name)), alert=True)
+
 def appointment_cancel(appointment_id):
 	appointment = frappe.get_doc("Patient Appointment", appointment_id)
-
 	# If invoiced --> fee_validity update with -1 visit
 	if appointment.invoiced:
-		validity = validity_exists(appointment.practitioner, appointment.patient)
-		if validity:
-			fee_validity = frappe.get_doc("Fee Validity", validity[0][0])
-			visited = fee_validity.visited - 1
-			frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
-			if visited <= 0:
-				frappe.msgprint(
-					_("Appointment cancelled, Please review and cancel the invoice {0}".format(fee_validity.ref_invoice))
-				)
+		sales_invoice = exists_sales_invoice(appointment)
+		if sales_invoice and cancel_sales_invoice(sales_invoice):
+			frappe.msgprint(
+				_("Appointment {0} and Sales Invoice {1} cancelled".format(appointment.name, sales_invoice.name))
+			)
+		else:
+			validity = validity_exists(appointment.practitioner, appointment.patient)
+			if validity:
+				fee_validity = frappe.get_doc("Fee Validity", validity[0][0])
+				if appointment_valid_in_fee_validity(appointment, fee_validity.valid_till, True, fee_validity.ref_invoice):
+					visited = fee_validity.visited - 1
+					frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+					frappe.msgprint(
+						_("Appointment cancelled, Please review and cancel the invoice {0}".format(fee_validity.ref_invoice))
+					)
+				else:
+					frappe.msgprint(_("Appointment cancelled"))
 			else:
 				frappe.msgprint(_("Appointment cancelled"))
+	else:
+		frappe.msgprint(_("Appointment cancelled"))
 
+def appointment_valid_in_fee_validity(appointment, valid_end_date, invoiced, ref_invoice):
+	valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
+	max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
+	valid_start_date = add_days(getdate(valid_end_date), -int(valid_days))
+
+	# Appointments which has same fee validity range with the appointment
+	appointments = frappe.get_list("Patient Appointment",{'patient': appointment.patient, 'invoiced': invoiced,
+	'appointment_date':("<=", getdate(valid_end_date)), 'appointment_date':(">=", getdate(valid_start_date)),
+	'practitioner': appointment.practitioner}, order_by="appointment_date desc", limit=int(max_visit))
+
+	if appointments and len(appointments) > 0:
+		appointment_obj = appointments[len(appointments)-1]
+		sales_invoice = exists_sales_invoice(appointment_obj)
+		if sales_invoice.name == ref_invoice:
+			return True
+	return False
+
+def cancel_sales_invoice(sales_invoice):
+	if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1':
+		if len(sales_invoice.items) == 1:
+			sales_invoice.cancel()
+			return True
+	return False
+
+def exists_sales_invoice_item(appointment):
+	return frappe.db.exists(
+		"Sales Invoice Item",
+		{
+			"reference_dt": "Patient Appointment",
+			"reference_dn": appointment.name
+		}
+	)
+
+def exists_sales_invoice(appointment):
+	sales_item_exist = exists_sales_invoice_item(appointment)
+	if sales_item_exist:
+		sales_invoice = frappe.get_doc("Sales Invoice", frappe.db.get_value("Sales Invoice Item", sales_item_exist, "parent"))
+		return sales_invoice
+	return False
 
 @frappe.whitelist()
 def get_availability_data(date, practitioner):
@@ -193,13 +280,6 @@
 		message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg")
 		send_message(doc, message)
 
-
-def validity_exists(practitioner, patient):
-	return frappe.db.exists({
-			"doctype": "Fee Validity",
-			"practitioner": practitioner,
-			"patient": patient})
-
 @frappe.whitelist()
 def create_encounter(appointment):
 	appointment = frappe.get_doc("Patient Appointment", appointment)
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index 3372e1d..3acb000 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -7,9 +7,8 @@
 import datetime
 from frappe import _
 import math
-from frappe.utils import time_diff_in_hours, rounded
+from frappe.utils import time_diff_in_hours, rounded, getdate, add_days
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
-from erpnext.healthcare.doctype.patient_appointment.patient_appointment import validity_exists
 from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity, update_fee_validity
 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
 
@@ -272,6 +271,12 @@
 		doc_created = frappe.db.get_value(dt, {'prescription': ref_dn})
 		frappe.db.set_value(dt, doc_created, 'invoiced', invoiced)
 
+def validity_exists(practitioner, patient):
+	return frappe.db.exists({
+			"doctype": "Fee Validity",
+			"practitioner": practitioner,
+			"patient": patient})
+
 def manage_fee_validity(appointment_name, method, ref_invoice=None):
 	appointment_doc = frappe.get_doc("Patient Appointment", appointment_name)
 	validity_exist = validity_exists(appointment_doc.practitioner, appointment_doc.patient)
@@ -282,12 +287,13 @@
 		# Check if the validity is valid
 		if (fee_validity.valid_till >= appointment_doc.appointment_date):
 			if (method == "on_cancel" and appointment_doc.status != "Closed"):
-				visited = fee_validity.visited - 1
-				if visited < 0:
-					visited = 0
-				frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+				if ref_invoice == fee_validity.ref_invoice:
+					visited = fee_validity.visited - 1
+					if visited < 0:
+						visited = 0
+					frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
 				do_not_update = True
-			elif (fee_validity.visited < fee_validity.max_visit):
+			elif (method == "on_submit" and fee_validity.visited < fee_validity.max_visit):
 				visited = fee_validity.visited + 1
 				frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
 				do_not_update = True
@@ -301,29 +307,48 @@
 		fee_validity = create_fee_validity(appointment_doc.practitioner, appointment_doc.patient, appointment_doc.appointment_date, ref_invoice)
 		visited = fee_validity.visited
 
+	print "do_not_update: ", do_not_update
+	print "visited: ", visited
+
 	# Mark All Patient Appointment invoiced = True in the validity range do not cross the max visit
 	if (method == "on_cancel"):
 		invoiced = True
 	else:
 		invoiced = False
-	patient_appointments = frappe.get_list("Patient Appointment",{'patient': fee_validity.patient, 'invoiced': invoiced,
-	'appointment_date':("<=", fee_validity.valid_till), 'practitioner': fee_validity.practitioner}, order_by="appointment_date")
+
+	patient_appointments = appointments_valid_in_fee_validity(appointment_doc, invoiced)
 	if patient_appointments and fee_validity:
 		visit = visited
 		for appointment in patient_appointments:
 			if (method == "on_cancel" and appointment.status != "Closed"):
-				visited = visited - 1
-				if visited < 0:
-					visited = 0
-				frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+				if ref_invoice == fee_validity.ref_invoice:
+					visited = visited - 1
+					if visited < 0:
+						visited = 0
+					frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
 				frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", False)
 				manage_doc_for_appoitnment("Patient Encounter", appointment.name, False)
-			elif int(fee_validity.max_visit) > visit:
-				visited = visited + 1
-				frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
+			elif method == "on_submit" and int(fee_validity.max_visit) > visit:
+				if ref_invoice == fee_validity.ref_invoice:
+					visited = visited + 1
+					frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
 				frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", True)
 				manage_doc_for_appoitnment("Patient Encounter", appointment.name, True)
-			visit = visit + 1
+			if ref_invoice == fee_validity.ref_invoice:
+				visit = visit + 1
+
+	if method == "on_cancel":
+		ref_invoice_in_fee_validity = frappe.db.get_value("Fee Validity", fee_validity.name, 'ref_invoice')
+		if ref_invoice_in_fee_validity == ref_invoice:
+			frappe.delete_doc("Fee Validity", fee_validity.name)
+
+def appointments_valid_in_fee_validity(appointment, invoiced):
+	valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
+	max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
+	valid_days_date = add_days(getdate(appointment.appointment_date), int(valid_days))
+	return frappe.get_list("Patient Appointment",{'patient': appointment.patient, 'invoiced': invoiced,
+	'appointment_date':("<=", valid_days_date), 'appointment_date':(">=", getdate(appointment.appointment_date)),
+	'practitioner': appointment.practitioner}, order_by="appointment_date", limit=int(max_visit)-1)
 
 def manage_doc_for_appoitnment(dt_from_appointment, appointment, invoiced):
 	dn_from_appointment = frappe.db.exists(