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(