feat: Department Wise Appointment Type charges (#24572)
* feat: Appointment Type Service Items Co-Authored-By: muhammad <muhammadmp@users.noreply.github.com> * fix: set practitioner service item charges mandatory on item selection Co-Authored-By: muhammad <muhammadmp@users.noreply.github.com> * feat: use charges from appointment type during billing * feat: handle appointment charges priority for invoice automation * test: patient appointment auto invoicing scenarios * fix: sider * fix: minor fixes Co-authored-by: muhammad <muhammadmp@users.noreply.github.com> Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
parent
6485eeb302
commit
73c2d23a9a
@ -2,4 +2,82 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Appointment Type', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('price_list', function() {
|
||||
return {
|
||||
filters: {'selling': 1}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('medical_department', 'items', function(doc) {
|
||||
let item_list = doc.items.map(({medical_department}) => medical_department);
|
||||
return {
|
||||
filters: [
|
||||
['Medical Department', 'name', 'not in', item_list]
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('op_consulting_charge_item', 'items', function() {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('inpatient_visit_charge_item', 'items', function() {
|
||||
return {
|
||||
filters: {
|
||||
is_stock_item: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Appointment Type Service Item', {
|
||||
op_consulting_charge_item: function(frm, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
if (frm.doc.price_list && d.op_consulting_charge_item) {
|
||||
frappe.call({
|
||||
'method': 'frappe.client.get_value',
|
||||
args: {
|
||||
'doctype': 'Item Price',
|
||||
'filters': {
|
||||
'item_code': d.op_consulting_charge_item,
|
||||
'price_list': frm.doc.price_list
|
||||
},
|
||||
'fieldname': ['price_list_rate']
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message.price_list_rate) {
|
||||
frappe.model.set_value(cdt, cdn, 'op_consulting_charge', data.message.price_list_rate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
inpatient_visit_charge_item: function(frm, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
if (frm.doc.price_list && d.inpatient_visit_charge_item) {
|
||||
frappe.call({
|
||||
'method': 'frappe.client.get_value',
|
||||
args: {
|
||||
'doctype': 'Item Price',
|
||||
'filters': {
|
||||
'item_code': d.inpatient_visit_charge_item,
|
||||
'price_list': frm.doc.price_list
|
||||
},
|
||||
'fieldname': ['price_list_rate']
|
||||
},
|
||||
callback: function (data) {
|
||||
if (data.message.price_list_rate) {
|
||||
frappe.model.set_value(cdt, cdn, 'inpatient_visit_charge', data.message.price_list_rate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -12,7 +12,10 @@
|
||||
"appointment_type",
|
||||
"ip",
|
||||
"default_duration",
|
||||
"color"
|
||||
"color",
|
||||
"billing_section",
|
||||
"price_list",
|
||||
"items"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@ -52,10 +55,27 @@
|
||||
"label": "Color",
|
||||
"no_copy": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Billing"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List",
|
||||
"options": "Price List"
|
||||
},
|
||||
{
|
||||
"fieldname": "items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Appointment Type Service Items",
|
||||
"options": "Appointment Type Service Item"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-02-03 21:06:05.833050",
|
||||
"modified": "2021-01-22 09:41:05.010524",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Appointment Type",
|
||||
|
@ -4,6 +4,53 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
import frappe
|
||||
|
||||
class AppointmentType(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
if self.items and self.price_list:
|
||||
for item in self.items:
|
||||
existing_op_item_price = frappe.db.exists('Item Price', {
|
||||
'item_code': item.op_consulting_charge_item,
|
||||
'price_list': self.price_list
|
||||
})
|
||||
|
||||
if not existing_op_item_price and item.op_consulting_charge_item and item.op_consulting_charge:
|
||||
make_item_price(self.price_list, item.op_consulting_charge_item, item.op_consulting_charge)
|
||||
|
||||
existing_ip_item_price = frappe.db.exists('Item Price', {
|
||||
'item_code': item.inpatient_visit_charge_item,
|
||||
'price_list': self.price_list
|
||||
})
|
||||
|
||||
if not existing_ip_item_price and item.inpatient_visit_charge_item and item.inpatient_visit_charge:
|
||||
make_item_price(self.price_list, item.inpatient_visit_charge_item, item.inpatient_visit_charge)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_service_item_based_on_department(appointment_type, department):
|
||||
item_list = frappe.db.get_value('Appointment Type Service Item',
|
||||
filters = {'medical_department': department, 'parent': appointment_type},
|
||||
fieldname = ['op_consulting_charge_item',
|
||||
'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'],
|
||||
as_dict = 1
|
||||
)
|
||||
|
||||
# if department wise items are not set up
|
||||
# use the generic items
|
||||
if not item_list:
|
||||
item_list = frappe.db.get_value('Appointment Type Service Item',
|
||||
filters = {'parent': appointment_type},
|
||||
fieldname = ['op_consulting_charge_item',
|
||||
'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'],
|
||||
as_dict = 1
|
||||
)
|
||||
|
||||
return item_list
|
||||
|
||||
def make_item_price(price_list, item, item_price):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Item Price',
|
||||
'price_list': price_list,
|
||||
'item_code': item,
|
||||
'price_list_rate': item_price
|
||||
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
|
@ -0,0 +1,67 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-01-22 09:34:53.373105",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"medical_department",
|
||||
"op_consulting_charge_item",
|
||||
"op_consulting_charge",
|
||||
"column_break_4",
|
||||
"inpatient_visit_charge_item",
|
||||
"inpatient_visit_charge"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "medical_department",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Medical Department",
|
||||
"options": "Medical Department"
|
||||
},
|
||||
{
|
||||
"fieldname": "op_consulting_charge_item",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Out Patient Consulting Charge Item",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "op_consulting_charge",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Out Patient Consulting Charge"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "inpatient_visit_charge_item",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Inpatient Visit Charge Item",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "inpatient_visit_charge",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Inpatient Visit Charge Item"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-22 09:35:26.503443",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Appointment Type Service Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, 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 AppointmentTypeServiceItem(Document):
|
||||
pass
|
@ -159,6 +159,7 @@
|
||||
"fieldname": "op_consulting_charge",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Out Patient Consulting Charge",
|
||||
"mandatory_depends_on": "op_consulting_charge_item",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
@ -174,7 +175,8 @@
|
||||
{
|
||||
"fieldname": "inpatient_visit_charge",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Inpatient Visit Charge"
|
||||
"label": "Inpatient Visit Charge",
|
||||
"mandatory_depends_on": "inpatient_visit_charge_item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: !doc.__islocal",
|
||||
@ -280,7 +282,7 @@
|
||||
],
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-04-06 13:44:24.759623",
|
||||
"modified": "2021-01-22 10:14:43.187675",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Healthcare Practitioner",
|
||||
|
@ -24,11 +24,13 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
});
|
||||
|
||||
frm.set_query('practitioner', function() {
|
||||
return {
|
||||
filters: {
|
||||
'department': frm.doc.department
|
||||
}
|
||||
};
|
||||
if (frm.doc.department) {
|
||||
return {
|
||||
filters: {
|
||||
'department': frm.doc.department
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query('service_unit', function() {
|
||||
@ -140,6 +142,20 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
patient: function(frm) {
|
||||
if (frm.doc.patient) {
|
||||
frm.trigger('toggle_payment_fields');
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {
|
||||
doctype: 'Patient',
|
||||
name: frm.doc.patient
|
||||
},
|
||||
callback: function (data) {
|
||||
let age = null;
|
||||
if (data.message.dob) {
|
||||
age = calculate_age(data.message.dob);
|
||||
}
|
||||
frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frm.set_value('patient_name', '');
|
||||
frm.set_value('patient_sex', '');
|
||||
@ -148,6 +164,37 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
}
|
||||
},
|
||||
|
||||
practitioner: function(frm) {
|
||||
if (frm.doc.practitioner ) {
|
||||
frm.events.set_payment_details(frm);
|
||||
}
|
||||
},
|
||||
|
||||
appointment_type: function(frm) {
|
||||
if (frm.doc.appointment_type) {
|
||||
frm.events.set_payment_details(frm);
|
||||
}
|
||||
},
|
||||
|
||||
set_payment_details: function(frm) {
|
||||
frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing').then(val => {
|
||||
if (val) {
|
||||
frappe.call({
|
||||
method: 'erpnext.healthcare.utils.get_service_item_and_practitioner_charge',
|
||||
args: {
|
||||
doc: frm.doc
|
||||
},
|
||||
callback: function(data) {
|
||||
if (data.message) {
|
||||
frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.practitioner_charge);
|
||||
frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.service_item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
therapy_plan: function(frm) {
|
||||
frm.trigger('set_therapy_type_filter');
|
||||
},
|
||||
@ -190,14 +237,18 @@ frappe.ui.form.on('Patient Appointment', {
|
||||
// show payment fields as non-mandatory
|
||||
frm.toggle_display('mode_of_payment', 0);
|
||||
frm.toggle_display('paid_amount', 0);
|
||||
frm.toggle_display('billing_item', 0);
|
||||
frm.toggle_reqd('mode_of_payment', 0);
|
||||
frm.toggle_reqd('paid_amount', 0);
|
||||
frm.toggle_reqd('billing_item', 0);
|
||||
} else {
|
||||
// if automated appointment invoicing is disabled, hide fields
|
||||
frm.toggle_display('mode_of_payment', data.message ? 1 : 0);
|
||||
frm.toggle_display('paid_amount', data.message ? 1 : 0);
|
||||
frm.toggle_display('billing_item', data.message ? 1 : 0);
|
||||
frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0);
|
||||
frm.toggle_reqd('paid_amount', data.message ? 1 :0);
|
||||
frm.toggle_reqd('billing_item', data.message ? 1 : 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -540,57 +591,6 @@ let update_status = function(frm, status){
|
||||
);
|
||||
};
|
||||
|
||||
frappe.ui.form.on('Patient Appointment', 'practitioner', function(frm) {
|
||||
if (frm.doc.practitioner) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {
|
||||
doctype: 'Healthcare Practitioner',
|
||||
name: frm.doc.practitioner
|
||||
},
|
||||
callback: function (data) {
|
||||
frappe.model.set_value(frm.doctype, frm.docname, 'department', data.message.department);
|
||||
frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.op_consulting_charge);
|
||||
frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.op_consulting_charge_item);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Patient Appointment', 'patient', function(frm) {
|
||||
if (frm.doc.patient) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {
|
||||
doctype: 'Patient',
|
||||
name: frm.doc.patient
|
||||
},
|
||||
callback: function (data) {
|
||||
let age = null;
|
||||
if (data.message.dob) {
|
||||
age = calculate_age(data.message.dob);
|
||||
}
|
||||
frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Patient Appointment', 'appointment_type', function(frm) {
|
||||
if (frm.doc.appointment_type) {
|
||||
frappe.call({
|
||||
method: 'frappe.client.get',
|
||||
args: {
|
||||
doctype: 'Appointment Type',
|
||||
name: frm.doc.appointment_type
|
||||
},
|
||||
callback: function(data) {
|
||||
frappe.model.set_value(frm.doctype,frm.docname, 'duration',data.message.default_duration);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let calculate_age = function(birth) {
|
||||
let ageMS = Date.parse(Date()) - Date.parse(birth);
|
||||
let age = new Date();
|
||||
|
@ -19,19 +19,19 @@
|
||||
"inpatient_record",
|
||||
"column_break_1",
|
||||
"company",
|
||||
"practitioner",
|
||||
"practitioner_name",
|
||||
"department",
|
||||
"service_unit",
|
||||
"section_break_12",
|
||||
"appointment_type",
|
||||
"duration",
|
||||
"procedure_template",
|
||||
"get_procedure_from_encounter",
|
||||
"procedure_prescription",
|
||||
"therapy_plan",
|
||||
"therapy_type",
|
||||
"get_prescribed_therapies",
|
||||
"practitioner",
|
||||
"practitioner_name",
|
||||
"department",
|
||||
"section_break_12",
|
||||
"appointment_type",
|
||||
"duration",
|
||||
"column_break_17",
|
||||
"appointment_date",
|
||||
"appointment_time",
|
||||
@ -79,6 +79,7 @@
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "appointment_type.default_duration",
|
||||
"fieldname": "duration",
|
||||
"fieldtype": "Int",
|
||||
"in_filter": 1,
|
||||
@ -144,7 +145,6 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Healthcare Practitioner",
|
||||
"options": "Healthcare Practitioner",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 1
|
||||
@ -158,7 +158,6 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Department",
|
||||
"options": "Medical Department",
|
||||
"read_only": 1,
|
||||
"search_index": 1,
|
||||
"set_only_once": 1
|
||||
},
|
||||
@ -227,12 +226,14 @@
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
"options": "Mode of Payment",
|
||||
"read_only_depends_on": "invoiced"
|
||||
},
|
||||
{
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount"
|
||||
"label": "Paid Amount",
|
||||
"read_only_depends_on": "invoiced"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
@ -302,7 +303,8 @@
|
||||
"fieldname": "therapy_plan",
|
||||
"fieldtype": "Link",
|
||||
"label": "Therapy Plan",
|
||||
"options": "Therapy Plan"
|
||||
"options": "Therapy Plan",
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "ref_sales_invoice",
|
||||
@ -347,7 +349,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-12-16 13:16:58.578503",
|
||||
"modified": "2021-02-08 13:13:15.116833",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Healthcare",
|
||||
"name": "Patient Appointment",
|
||||
|
@ -26,6 +26,7 @@ class PatientAppointment(Document):
|
||||
|
||||
def after_insert(self):
|
||||
self.update_prescription_details()
|
||||
self.set_payment_details()
|
||||
invoice_appointment(self)
|
||||
self.update_fee_validity()
|
||||
send_confirmation_msg(self)
|
||||
@ -85,6 +86,13 @@ class PatientAppointment(Document):
|
||||
def set_appointment_datetime(self):
|
||||
self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00")
|
||||
|
||||
def set_payment_details(self):
|
||||
if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'):
|
||||
details = get_service_item_and_practitioner_charge(self)
|
||||
self.db_set('billing_item', details.get('service_item'))
|
||||
if not self.paid_amount:
|
||||
self.db_set('paid_amount', details.get('practitioner_charge'))
|
||||
|
||||
def validate_customer_created(self):
|
||||
if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'):
|
||||
if not frappe.db.get_value('Patient', self.patient, 'customer'):
|
||||
@ -148,31 +156,37 @@ def invoice_appointment(appointment_doc):
|
||||
fee_validity = None
|
||||
|
||||
if automate_invoicing and not appointment_invoiced and not fee_validity:
|
||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||
sales_invoice.patient = appointment_doc.patient
|
||||
sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
|
||||
sales_invoice.appointment = appointment_doc.name
|
||||
sales_invoice.due_date = getdate()
|
||||
sales_invoice.company = appointment_doc.company
|
||||
sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
|
||||
create_sales_invoice(appointment_doc)
|
||||
|
||||
item = sales_invoice.append('items', {})
|
||||
item = get_appointment_item(appointment_doc, item)
|
||||
|
||||
# Add payments if payment details are supplied else proceed to create invoice as Unpaid
|
||||
if appointment_doc.mode_of_payment and appointment_doc.paid_amount:
|
||||
sales_invoice.is_pos = 1
|
||||
payment = sales_invoice.append('payments', {})
|
||||
payment.mode_of_payment = appointment_doc.mode_of_payment
|
||||
payment.amount = appointment_doc.paid_amount
|
||||
def create_sales_invoice(appointment_doc):
|
||||
sales_invoice = frappe.new_doc('Sales Invoice')
|
||||
sales_invoice.patient = appointment_doc.patient
|
||||
sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer')
|
||||
sales_invoice.appointment = appointment_doc.name
|
||||
sales_invoice.due_date = getdate()
|
||||
sales_invoice.company = appointment_doc.company
|
||||
sales_invoice.debit_to = get_receivable_account(appointment_doc.company)
|
||||
|
||||
sales_invoice.set_missing_values(for_validate=True)
|
||||
sales_invoice.flags.ignore_mandatory = True
|
||||
sales_invoice.save(ignore_permissions=True)
|
||||
sales_invoice.submit()
|
||||
frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
|
||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'invoiced', 1)
|
||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, 'ref_sales_invoice', sales_invoice.name)
|
||||
item = sales_invoice.append('items', {})
|
||||
item = get_appointment_item(appointment_doc, item)
|
||||
|
||||
# Add payments if payment details are supplied else proceed to create invoice as Unpaid
|
||||
if appointment_doc.mode_of_payment and appointment_doc.paid_amount:
|
||||
sales_invoice.is_pos = 1
|
||||
payment = sales_invoice.append('payments', {})
|
||||
payment.mode_of_payment = appointment_doc.mode_of_payment
|
||||
payment.amount = appointment_doc.paid_amount
|
||||
|
||||
sales_invoice.set_missing_values(for_validate=True)
|
||||
sales_invoice.flags.ignore_mandatory = True
|
||||
sales_invoice.save(ignore_permissions=True)
|
||||
sales_invoice.submit()
|
||||
frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True)
|
||||
frappe.db.set_value('Patient Appointment', appointment_doc.name, {
|
||||
'invoiced': 1,
|
||||
'ref_sales_invoice': sales_invoice.name
|
||||
})
|
||||
|
||||
|
||||
def check_is_new_patient(patient, name=None):
|
||||
@ -187,13 +201,14 @@ def check_is_new_patient(patient, name=None):
|
||||
|
||||
|
||||
def get_appointment_item(appointment_doc, item):
|
||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment_doc)
|
||||
item.item_code = service_item
|
||||
details = get_service_item_and_practitioner_charge(appointment_doc)
|
||||
charge = appointment_doc.paid_amount or details.get('practitioner_charge')
|
||||
item.item_code = details.get('service_item')
|
||||
item.description = _('Consulting Charges: {0}').format(appointment_doc.practitioner)
|
||||
item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company)
|
||||
item.cost_center = frappe.get_cached_value('Company', appointment_doc.company, 'cost_center')
|
||||
item.rate = practitioner_charge
|
||||
item.amount = practitioner_charge
|
||||
item.rate = charge
|
||||
item.amount = charge
|
||||
item.qty = 1
|
||||
item.reference_dt = 'Patient Appointment'
|
||||
item.reference_dn = appointment_doc.name
|
||||
|
@ -32,7 +32,8 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
|
||||
self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1)
|
||||
appointment.reload()
|
||||
self.assertEqual(appointment.invoiced, 1)
|
||||
encounter = make_encounter(appointment.name)
|
||||
self.assertTrue(encounter)
|
||||
self.assertEqual(encounter.company, appointment.company)
|
||||
@ -41,7 +42,7 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
# invoiced flag mapped from appointment
|
||||
self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
|
||||
|
||||
def test_invoicing(self):
|
||||
def test_auto_invoicing(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
|
||||
@ -57,6 +58,50 @@ class TestPatientAppointment(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient)
|
||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
|
||||
|
||||
def test_auto_invoicing_based_on_department(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||
appointment_type = create_appointment_type()
|
||||
|
||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
|
||||
invoice=1, appointment_type=appointment_type.name, department='_Test Medical Department')
|
||||
appointment.reload()
|
||||
|
||||
self.assertEqual(appointment.invoiced, 1)
|
||||
self.assertEqual(appointment.billing_item, 'HLC-SI-001')
|
||||
self.assertEqual(appointment.paid_amount, 200)
|
||||
|
||||
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||
self.assertTrue(sales_invoice_name)
|
||||
self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
|
||||
|
||||
def test_auto_invoicing_according_to_appointment_type_charge(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
|
||||
frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
|
||||
|
||||
item = create_healthcare_service_items()
|
||||
items = [{
|
||||
'op_consulting_charge_item': item,
|
||||
'op_consulting_charge': 300
|
||||
}]
|
||||
appointment_type = create_appointment_type(args={
|
||||
'name': 'Generic Appointment Type charge',
|
||||
'items': items
|
||||
})
|
||||
|
||||
appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
|
||||
invoice=1, appointment_type=appointment_type.name)
|
||||
appointment.reload()
|
||||
|
||||
self.assertEqual(appointment.invoiced, 1)
|
||||
self.assertEqual(appointment.billing_item, item)
|
||||
self.assertEqual(appointment.paid_amount, 300)
|
||||
|
||||
sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
|
||||
self.assertTrue(sales_invoice_name)
|
||||
|
||||
def test_appointment_cancel(self):
|
||||
patient, medical_department, practitioner = create_healthcare_docs()
|
||||
frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
|
||||
@ -178,14 +223,15 @@ def create_encounter(appointment):
|
||||
encounter.submit()
|
||||
return encounter
|
||||
|
||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, service_unit=None, save=1):
|
||||
def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0,
|
||||
service_unit=None, appointment_type=None, save=1, department=None):
|
||||
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)
|
||||
appointment = frappe.new_doc('Patient Appointment')
|
||||
appointment.patient = patient
|
||||
appointment.practitioner = practitioner
|
||||
appointment.department = '_Test Medical Department'
|
||||
appointment.department = department or '_Test Medical Department'
|
||||
appointment.appointment_date = appointment_date
|
||||
appointment.company = '_Test Company'
|
||||
appointment.duration = 15
|
||||
@ -193,7 +239,8 @@ def create_appointment(patient, practitioner, appointment_date, invoice=0, proce
|
||||
appointment.service_unit = service_unit
|
||||
if invoice:
|
||||
appointment.mode_of_payment = 'Cash'
|
||||
appointment.paid_amount = 500
|
||||
if appointment_type:
|
||||
appointment.appointment_type = appointment_type
|
||||
if procedure_template:
|
||||
appointment.procedure_template = create_clinical_procedure_template().get('name')
|
||||
if save:
|
||||
@ -223,4 +270,29 @@ def create_clinical_procedure_template():
|
||||
template.description = 'Knee Surgery and Rehab'
|
||||
template.rate = 50000
|
||||
template.save()
|
||||
return template
|
||||
return template
|
||||
|
||||
def create_appointment_type(args=None):
|
||||
if not args:
|
||||
args = frappe.local.form_dict
|
||||
|
||||
name = args.get('name') or 'Test Appointment Type wise Charge'
|
||||
|
||||
if frappe.db.exists('Appointment Type', name):
|
||||
return frappe.get_doc('Appointment Type', name)
|
||||
|
||||
else:
|
||||
item = create_healthcare_service_items()
|
||||
items = [{
|
||||
'medical_department': '_Test Medical Department',
|
||||
'op_consulting_charge_item': item,
|
||||
'op_consulting_charge': 200
|
||||
}]
|
||||
return frappe.get_doc({
|
||||
'doctype': 'Appointment Type',
|
||||
'appointment_type': args.get('name') or 'Test Appointment Type wise Charge',
|
||||
'default_duration': args.get('default_duration') or 20,
|
||||
'color': args.get('color') or '#7575ff',
|
||||
'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}),
|
||||
'items': args.get('items') or items
|
||||
}).insert()
|
@ -5,9 +5,11 @@
|
||||
from __future__ import unicode_literals
|
||||
import math
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.utils.formatters import format_value
|
||||
from frappe.utils import time_diff_in_hours, rounded
|
||||
from six import string_types
|
||||
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
|
||||
from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
|
||||
from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
|
||||
@ -64,7 +66,9 @@ def get_appointments_to_invoice(patient, company):
|
||||
income_account = None
|
||||
service_item = None
|
||||
if appointment.practitioner:
|
||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(appointment)
|
||||
details = get_service_item_and_practitioner_charge(appointment)
|
||||
service_item = details.get('service_item')
|
||||
practitioner_charge = details.get('practitioner_charge')
|
||||
income_account = get_income_account(appointment.practitioner, appointment.company)
|
||||
appointments_to_invoice.append({
|
||||
'reference_type': 'Patient Appointment',
|
||||
@ -97,7 +101,9 @@ def get_encounters_to_invoice(patient, company):
|
||||
frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'):
|
||||
continue
|
||||
|
||||
service_item, practitioner_charge = get_service_item_and_practitioner_charge(encounter)
|
||||
details = get_service_item_and_practitioner_charge(encounter)
|
||||
service_item = details.get('service_item')
|
||||
practitioner_charge = details.get('practitioner_charge')
|
||||
income_account = get_income_account(encounter.practitioner, encounter.company)
|
||||
|
||||
encounters_to_invoice.append({
|
||||
@ -173,7 +179,7 @@ def get_clinical_procedures_to_invoice(patient, company):
|
||||
if procedure.invoice_separately_as_consumables and procedure.consume_stock \
|
||||
and procedure.status == 'Completed' and not procedure.consumption_invoiced:
|
||||
|
||||
service_item = get_healthcare_service_item('clinical_procedure_consumable_item')
|
||||
service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item')
|
||||
if not service_item:
|
||||
msg = _('Please Configure Clinical Procedure Consumable Item in ')
|
||||
msg += '''<b><a href='#Form/Healthcare Settings'>Healthcare Settings</a></b>'''
|
||||
@ -304,24 +310,50 @@ def get_therapy_sessions_to_invoice(patient, company):
|
||||
|
||||
return therapy_sessions_to_invoice
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_service_item_and_practitioner_charge(doc):
|
||||
if isinstance(doc, string_types):
|
||||
doc = json.loads(doc)
|
||||
doc = frappe.get_doc(doc)
|
||||
|
||||
service_item = None
|
||||
practitioner_charge = None
|
||||
department = doc.medical_department if doc.doctype == 'Patient Encounter' else doc.department
|
||||
|
||||
is_inpatient = doc.inpatient_record
|
||||
if is_inpatient:
|
||||
service_item = get_practitioner_service_item(doc.practitioner, 'inpatient_visit_charge_item')
|
||||
|
||||
if doc.get('appointment_type'):
|
||||
service_item, practitioner_charge = get_appointment_type_service_item(doc.appointment_type, department, is_inpatient)
|
||||
|
||||
if not service_item and not practitioner_charge:
|
||||
service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient)
|
||||
if not service_item:
|
||||
service_item = get_healthcare_service_item('inpatient_visit_charge_item')
|
||||
else:
|
||||
service_item = get_practitioner_service_item(doc.practitioner, 'op_consulting_charge_item')
|
||||
if not service_item:
|
||||
service_item = get_healthcare_service_item('op_consulting_charge_item')
|
||||
service_item = get_healthcare_service_item(is_inpatient)
|
||||
|
||||
if not service_item:
|
||||
throw_config_service_item(is_inpatient)
|
||||
|
||||
practitioner_charge = get_practitioner_charge(doc.practitioner, is_inpatient)
|
||||
if not practitioner_charge:
|
||||
throw_config_practitioner_charge(is_inpatient, doc.practitioner)
|
||||
|
||||
return {'service_item': service_item, 'practitioner_charge': practitioner_charge}
|
||||
|
||||
|
||||
def get_appointment_type_service_item(appointment_type, department, is_inpatient):
|
||||
from erpnext.healthcare.doctype.appointment_type.appointment_type import get_service_item_based_on_department
|
||||
|
||||
item_list = get_service_item_based_on_department(appointment_type, department)
|
||||
service_item = None
|
||||
practitioner_charge = None
|
||||
|
||||
if item_list:
|
||||
if is_inpatient:
|
||||
service_item = item_list.get('inpatient_visit_charge_item')
|
||||
practitioner_charge = item_list.get('inpatient_visit_charge')
|
||||
else:
|
||||
service_item = item_list.get('op_consulting_charge_item')
|
||||
practitioner_charge = item_list.get('op_consulting_charge')
|
||||
|
||||
return service_item, practitioner_charge
|
||||
|
||||
|
||||
@ -345,12 +377,27 @@ def throw_config_practitioner_charge(is_inpatient, practitioner):
|
||||
frappe.throw(msg, title=_('Missing Configuration'))
|
||||
|
||||
|
||||
def get_practitioner_service_item(practitioner, service_item_field):
|
||||
return frappe.db.get_value('Healthcare Practitioner', practitioner, service_item_field)
|
||||
def get_practitioner_service_item(practitioner, is_inpatient):
|
||||
service_item = None
|
||||
practitioner_charge = None
|
||||
|
||||
if is_inpatient:
|
||||
service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['inpatient_visit_charge_item', 'inpatient_visit_charge'])
|
||||
else:
|
||||
service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['op_consulting_charge_item', 'op_consulting_charge'])
|
||||
|
||||
return service_item, practitioner_charge
|
||||
|
||||
|
||||
def get_healthcare_service_item(service_item_field):
|
||||
return frappe.db.get_single_value('Healthcare Settings', service_item_field)
|
||||
def get_healthcare_service_item(is_inpatient):
|
||||
service_item = None
|
||||
|
||||
if is_inpatient:
|
||||
service_item = frappe.db.get_single_value('Healthcare Settings', 'inpatient_visit_charge_item')
|
||||
else:
|
||||
service_item = frappe.db.get_single_value('Healthcare Settings', 'op_consulting_charge_item')
|
||||
|
||||
return service_item
|
||||
|
||||
|
||||
def get_practitioner_charge(practitioner, is_inpatient):
|
||||
@ -381,7 +428,8 @@ def set_invoiced(item, method, ref_invoice=None):
|
||||
invoiced = True
|
||||
|
||||
if item.reference_dt == 'Clinical Procedure':
|
||||
if get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code:
|
||||
service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item')
|
||||
if service_item == item.item_code:
|
||||
frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced)
|
||||
else:
|
||||
frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced)
|
||||
@ -403,7 +451,8 @@ def set_invoiced(item, method, ref_invoice=None):
|
||||
|
||||
|
||||
def validate_invoiced_on_submit(item):
|
||||
if item.reference_dt == 'Clinical Procedure' and get_healthcare_service_item('clinical_procedure_consumable_item') == item.item_code:
|
||||
if item.reference_dt == 'Clinical Procedure' and \
|
||||
frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') == item.item_code:
|
||||
is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced')
|
||||
else:
|
||||
is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced')
|
||||
|
Loading…
Reference in New Issue
Block a user