diff --git a/healthcare/healthcare/custom_doctype/payment_entry.py b/healthcare/healthcare/custom_doctype/payment_entry.py index 6a6fd53d9d..ddb732e8bf 100644 --- a/healthcare/healthcare/custom_doctype/payment_entry.py +++ b/healthcare/healthcare/custom_doctype/payment_entry.py @@ -2,21 +2,25 @@ @frappe.whitelist() -def set_paid_amount_in_treatment_counselling(doc, method): - if doc.treatment_counselling and doc.paid_amount: +def set_paid_amount_in_healthcare_docs(doc, method): + if doc.paid_amount: on_cancel = True if method == "on_cancel" else False - validate_treatment_counselling(doc, on_cancel) + if doc.treatment_counselling: + validate_doc("Treatment Counselling", doc.treatment_counselling, doc.paid_amount, on_cancel) + if doc.package_subscription: + validate_doc("Package Subscription", doc.package_subscription, doc.paid_amount, on_cancel) -def validate_treatment_counselling(doc, on_cancel=False): - treatment_counselling_doc = frappe.get_doc("Treatment Counselling", doc.treatment_counselling) +def validate_doc(doctype, docname, paid_amount, on_cancel=False): + doc = frappe.get_doc(doctype, docname) - paid_amount = treatment_counselling_doc.paid_amount + doc.paid_amount if on_cancel: - paid_amount = treatment_counselling_doc.paid_amount - doc.paid_amount + paid_amount = doc.paid_amount - paid_amount + else: + paid_amount = doc.paid_amount + paid_amount - treatment_counselling_doc.paid_amount = paid_amount - treatment_counselling_doc.outstanding_amount = ( - treatment_counselling_doc.amount - treatment_counselling_doc.paid_amount - ) - treatment_counselling_doc.save() + doc.paid_amount = paid_amount + amount = doc.total_package_amount if doctype == "Package Subscription" else doc.amount + + doc.outstanding_amount = amount - doc.paid_amount + doc.save() diff --git a/healthcare/healthcare/doctype/healthcare_package/__init__.py b/healthcare/healthcare/doctype/healthcare_package/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/healthcare/doctype/healthcare_package/healthcare_package.js b/healthcare/healthcare/doctype/healthcare_package/healthcare_package.js new file mode 100644 index 0000000000..cec21b00d6 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_package/healthcare_package.js @@ -0,0 +1,194 @@ +// Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and contributors +// For license information, please see license.txt + +let package_docs_filter = ["Item", "Clinical Procedure Template", "Observation Template", "Therapy Type"] + +frappe.ui.form.on("Healthcare Package", { + refresh: function (frm) { + frm.set_query("price_list", function() { + return { + filters: { + "selling": 1, + } + }; + }); + + frm.set_query("package_item_type", "package_items", function() { + return { + filters: { + "name": ["in", package_docs_filter], + } + }; + }); + + frm.set_query("income_account", function() { + return { + filters: { + "account_type": "Income Account", + "is_group": 0 + } + }; + }); + }, + + onload: function (frm) { + if (frm.doc.is_billable) { + let read_only = frm.doc.item ? 1 : 0; + frm.set_df_property("item_code", "read_only", read_only); + frm.set_df_property("item_group", "read_only", read_only); + } + }, + + total_amount: function (frm) { + calculate_total_payable(frm); + }, + + apply_discount_on: function (frm) { + if(frm.doc.discount_percentage) { + frm.trigger("discount_percentage"); + } + }, + + discount_percentage: function (frm) { + frm.via_discount_percentage = true; + + if(frm.doc.discount_percentage && frm.doc.discount_amount) { + frm.set_value("discount_amount", 0); + } + + let discount_field = frm.doc.apply_discount_on == "Total" ? "total_amount" : "net_total"; + var total = flt(frm.doc[discount_field]); + + var discount_amount = flt(total * flt(frm.doc.discount_percentage) / 100, + precision("discount_amount")); + + frm.set_value("discount_amount", discount_amount) + .then(() => delete frm.via_discount_percentage); + calculate_total_payable(frm); + }, + + discount_amount: function (frm) { + if (!frm.via_discount_percentage) { + frm.set_value("discount_percentage", 0); + let discount_field = frm.doc.apply_discount_on == "Total" ? "total_amount" : "net_total"; + var total = flt(frm.doc[discount_field]); + var discount_percentage = (flt(frm.doc.discount_amount) / total) * 100; + + frm.set_value("discount_percentage", discount_percentage) + calculate_total_payable(frm); + } + }, + + package_name: function (frm) { + if (frm.doc.package_name && !frm.doc.item_code) { + frm.set_value("item_code", frm.doc.package_name); + } + } +}); + +frappe.ui.form.on("Healthcare Package Item", { + package_item: async function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (frm.doc.price_list && row.package_item_type && row.package_item) { + if (row.package_item_type == "Item") { + frappe.model.set_value(cdt, cdn, "item_code", row.package_item) + set_item_rate(frm, row, row.package_item); + } else { + let item = (await frappe.db.get_value(row.package_item_type, row.package_item, "item")).message.item; + if (item) { + console.log() + frappe.model.set_value(cdt, cdn, "item_code", item) + set_item_rate(frm, row, item); + } + } + } + }, + + rate: function (frm, cdt, cdn) { + set_amount(frm, cdt, cdn); + }, + + no_of_sessions: function (frm, cdt, cdn) { + set_amount(frm, cdt, cdn); + }, + + amount: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + set_child_discounted_amount(row); + }, + + amount_with_discount: function (frm, cdt, cdn) { + set_totals(frm); + }, + + discount_percentage: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + frm.via_child_discount_percentage = true; + + if(row.discount_percentage && row.discount_amount) { + row.discount_amount = 0; + } + + var discount_amount = flt(row.amount * flt(row.discount_percentage) / 100, + precision("discount_amount")); + + frappe.model.set_value(cdt, cdn, "discount_amount", discount_amount) + .then(() => delete frm.via_child_discount_percentage); + set_child_discounted_amount(row); + }, + + discount_amount: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (!frm.via_child_discount_percentage) { + row.discount_percentage = 0; + var discount_percentage = (flt(row.discount_amount) / row.amount) * 100; + + frappe.model.set_value(cdt, cdn, "discount_percentage", discount_percentage) + set_child_discounted_amount(row); + } + } +}); + +var set_totals = function (frm) { + let total = 0; + let net_total = 0; + frm.doc.package_items.forEach((item) => { + total += item.amount_with_discount; + net_total += item.amount; + }); + frm.set_value("total_amount", total); + frm.set_value("net_total", net_total); +}; + +var set_amount = function (frm, cdt, cdn) { + row = locals[cdt][cdn]; + if (row.rate && row.no_of_sessions) { + frappe.model.set_value(cdt, cdn, "amount", (row.rate * row.no_of_sessions) - row.discount_amount); + } +}; + +var calculate_total_payable = function (frm) { + if (frm.doc.discount_amount) { + let discount_field = frm.doc.apply_discount_on == "Total" ? "total_amount" : "net_total"; + var total = flt(frm.doc[discount_field]); + frm.set_value("discount_amount", flt(total * flt(frm.doc.discount_percentage) / 100)); + frm.set_value("total_package_amount", total - flt(total * flt(frm.doc.discount_percentage) / 100)); + } else { + frm.set_value("total_package_amount", frm.doc.total_amount); + } +}; + +var set_child_discounted_amount = function (row) { + frappe.model.set_value(row.doctype, row.name, "amount_with_discount", row.amount - row.discount_amount) +}; + +var set_item_rate = function (frm, row, item) { + frappe.db.get_value("Item Price", { + "item_code": item, + "price_list": frm.doc.price_list + }, "price_list_rate") + .then(r => { + let price_list_rate = r.message.price_list_rate ? r.message.price_list_rate : 0; + frappe.model.set_value(row.doctype, row.name, "rate", price_list_rate); + }); +}; diff --git a/healthcare/healthcare/doctype/healthcare_package/healthcare_package.json b/healthcare/healthcare/doctype/healthcare_package/healthcare_package.json new file mode 100644 index 0000000000..18bf3fc19c --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_package/healthcare_package.json @@ -0,0 +1,273 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:package_name", + "creation": "2024-10-15 16:08:09.686811", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "section_break_mp9z", + "disabled", + "section_break_lgqb", + "package_name", + "currency_and_price_list_section", + "price_list", + "column_break_samq", + "currency", + "section_break_cydn", + "income_account", + "section_break_ascf", + "package_items", + "section_break_zmmd", + "net_total", + "column_break_axok", + "total_amount", + "discount_details_section", + "apply_discount_on", + "discount_percentage", + "column_break_zgdp", + "discount_amount", + "total_section", + "column_break_gdsk", + "column_break_oqhd", + "total_package_amount", + "billing_tab", + "item", + "item_code", + "item_group", + "column_break_gcaf", + "item_wise_invoicing" + ], + "fields": [ + { + "fieldname": "section_break_mp9z", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "section_break_lgqb", + "fieldtype": "Section Break" + }, + { + "fieldname": "package_name", + "fieldtype": "Data", + "in_list_view": 1, + "in_preview": 1, + "in_standard_filter": 1, + "label": "Package Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency" + }, + { + "fieldname": "section_break_ascf", + "fieldtype": "Section Break" + }, + { + "fieldname": "package_items", + "fieldtype": "Table", + "label": "Package Items", + "options": "Healthcare Package Item" + }, + { + "fieldname": "section_break_zmmd", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_package_amount", + "fieldtype": "Currency", + "label": "Total Package Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "column_break_axok", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_amount", + "fieldtype": "Currency", + "label": "Total Amount", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "column_break_zgdp", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.discount_percentage || doc.discount_amount", + "fieldname": "discount_details_section", + "fieldtype": "Section Break", + "label": "Discount Details" + }, + { + "fieldname": "discount_percentage", + "fieldtype": "Percent", + "label": "Discount Percentage" + }, + { + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Discount Amount", + "options": "currency" + }, + { + "fieldname": "currency_and_price_list_section", + "fieldtype": "Section Break", + "label": "Currency and Price List" + }, + { + "fieldname": "column_break_samq", + "fieldtype": "Column Break" + }, + { + "fieldname": "billing_tab", + "fieldtype": "Tab Break", + "label": "Billing" + }, + { + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Data", + "label": "Item Code", + "reqd": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_cydn", + "fieldtype": "Section Break", + "label": "Accounts" + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fieldname": "column_break_gcaf", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "If checked, billing will happen against package items", + "fieldname": "item_wise_invoicing", + "fieldtype": "Check", + "label": "Item wise Invoicing" + }, + { + "depends_on": "eval: doc.discount_percentage || doc.discount_amount", + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "total_section", + "fieldtype": "Section Break", + "label": "Total" + }, + { + "fieldname": "column_break_gdsk", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_oqhd", + "fieldtype": "Column Break" + }, + { + "default": "Total", + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "label": "Apply Discount on", + "options": "Total\nNet Total" + } + ], + "index_web_pages_for_search": 1, + "links": [ + { + "link_doctype": "Package Subscription", + "link_fieldname": "healthcare_package" + } + ], + "modified": "2024-10-28 18:29:22.391899", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Package", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "search_fields": "total_amount", + "sort_field": "creation", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_package/healthcare_package.py b/healthcare/healthcare/doctype/healthcare_package/healthcare_package.py new file mode 100644 index 0000000000..3e59c8030f --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_package/healthcare_package.py @@ -0,0 +1,91 @@ +# Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document +from frappe.utils import flt, today + + +class HealthcarePackage(Document): + def validate(self): + self.calculate_totals() + self.create_item_from_package() + + def calculate_totals(self): + total, net_total = 0, 0 + + for item in self.package_items: + total += item.amount_with_discount + net_total += item.amount + + self.total_amount = total + self.net_total = net_total + + if self.discount_amount: + self.discount_percentage = (flt(self.discount_amount) / self.total_amount) * 100 + self.total_package_amount = self.total_amount - self.discount_amount + elif self.discount_percentage: + self.discount_amount = flt(self.total_amount * flt(self.discount_percentage) / 100) + self.total_package_amount = self.total_amount - self.discount_amount + else: + self.total_package_amount = self.total_amount + + def create_item_from_package(self): + item_name = self.item + if not item_name: + uom = frappe.db.exists("UOM", "Nos") or frappe.db.get_single_value( + "Stock Settings", "stock_uom" + ) + + item_exists = frappe.db.exists("Item", self.item_code) + if not item_exists: + # Insert item + item = frappe.get_doc( + { + "doctype": "Item", + "item_code": self.item_code, + "item_name": self.package_name, + "item_group": self.item_group, + "description": self.package_name, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "include_item_in_manufacturing": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": 0, + "stock_uom": uom, + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + + item_name = item.name + else: + item_name = item_exists + + # Set item in the template + self.item = item_name + + self.make_item_price(item_name, self.total_package_amount) + + def make_item_price(self, item, item_price=0.0): + exists = frappe.db.exists( + "Item Price", + { + "price_list": self.price_list, + "item_code": item, + "valid_from": today(), + }, + ) + if not exists: + frappe.get_doc( + { + "doctype": "Item Price", + "price_list": self.price_list, + "item_code": item, + "price_list_rate": item_price, + "valid_from": today(), + } + ).insert(ignore_permissions=True, ignore_mandatory=True) + else: + frappe.db.set_value("Item Price", exists, "price_list_rate", item_price) diff --git a/healthcare/healthcare/doctype/healthcare_package/test_healthcare_package.py b/healthcare/healthcare/doctype/healthcare_package/test_healthcare_package.py new file mode 100644 index 0000000000..c2e4988641 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_package/test_healthcare_package.py @@ -0,0 +1,57 @@ +# Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase + +from healthcare.healthcare.doctype.observation_template.test_observation_template import ( + create_observation_template, +) +from healthcare.healthcare.doctype.package_subscription.test_package_subscription import ( + create_healthcare_package, + create_subscription, +) +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_patient, +) +from healthcare.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type + + +class TestHealthcarePackage(FrappeTestCase): + def test_healthcare_package(self): + frappe.delete_doc_if_exists("Healthcare Package", "Package - 1") + obs_name = "Total Cholesterol" + obs_template = create_observation_template(obs_name, "_Test") + patient = create_patient() + therapy_type = create_therapy_type() + income_account = frappe.db.exists( + "Account", {"root_type": "Income", "account_type": "Income Account"} + ) + + package = create_healthcare_package(therapy_type, obs_template, income_account) + + self.assertTrue(package.name) + self.assertTrue(frappe.db.exists("Item", package.item_code)) + + self.assertEqual( + package.total_package_amount, + 5200, + ) + + self.assertEqual( + frappe.db.get_value("Item Price", {"item_code": package.item_code}, "price_list_rate"), + package.total_package_amount, + ) + + subscription = create_subscription(patient, package, income_account) + + self.assertTrue(subscription.name) + self.assertEqual( + subscription.outstanding_amount, + subscription.total_package_amount, + ) + + self.assertEqual( + subscription.paid_amount, + 0, + ) diff --git a/healthcare/healthcare/doctype/healthcare_package_item/__init__.py b/healthcare/healthcare/doctype/healthcare_package_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/healthcare/doctype/healthcare_package_item/healthcare_package_item.json b/healthcare/healthcare/doctype/healthcare_package_item/healthcare_package_item.json new file mode 100644 index 0000000000..0259999670 --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_package_item/healthcare_package_item.json @@ -0,0 +1,148 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2024-10-16 11:14:49.649656", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "package_item_type", + "package_item", + "item_code", + "column_break_dyhx", + "no_of_sessions", + "booked_sessions", + "invoiced", + "section_break_bbtg", + "rate", + "column_break_doch", + "amount", + "amount_with_discount", + "discount_details_section", + "discount_percentage", + "column_break_nnxm", + "discount_amount" + ], + "fields": [ + { + "fieldname": "package_item_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Package Item Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "package_item", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Package Item", + "options": "package_item_type", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "read_only": 1, + "reqd": 1 + }, + { + "default": "1", + "fieldname": "no_of_sessions", + "fieldtype": "Float", + "in_list_view": 1, + "label": "No of Sessions", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "discount_percentage", + "fieldtype": "Percent", + "label": "Discount Percentage" + }, + { + "default": "0", + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Discount Amount" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval: doc.discount_percentage || doc.discount_amount", + "depends_on": "eval: doc.parenttype == \"Healthcare Package\"", + "fieldname": "discount_details_section", + "fieldtype": "Section Break", + "label": "Discount Details" + }, + { + "fieldname": "column_break_nnxm", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_dyhx", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_bbtg", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_doch", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "amount_with_discount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount (With Discount)", + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval: doc.parenttype == \"Package Subscription\"", + "fieldname": "booked_sessions", + "fieldtype": "Float", + "label": "Booked Sessions", + "read_only": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item Code", + "options": "Item", + "read_only_depends_on": "eval: doc.package_item_type != \"Medication\"" + }, + { + "default": "0", + "depends_on": "eval: doc.parenttype == \"Package Subscription\"", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-12-01 10:42:54.758370", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Healthcare Package Item", + "owner": "Administrator", + "permissions": [], + "sort_field": "creation", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/healthcare_package_item/healthcare_package_item.py b/healthcare/healthcare/doctype/healthcare_package_item/healthcare_package_item.py new file mode 100644 index 0000000000..2de89a2e1d --- /dev/null +++ b/healthcare/healthcare/doctype/healthcare_package_item/healthcare_package_item.py @@ -0,0 +1,9 @@ +# Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class HealthcarePackageItem(Document): + pass diff --git a/healthcare/healthcare/doctype/package_subscription/__init__.py b/healthcare/healthcare/doctype/package_subscription/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/healthcare/healthcare/doctype/package_subscription/package_subscription.js b/healthcare/healthcare/doctype/package_subscription/package_subscription.js new file mode 100644 index 0000000000..8b7d2c5eac --- /dev/null +++ b/healthcare/healthcare/doctype/package_subscription/package_subscription.js @@ -0,0 +1,51 @@ +// Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Package Subscription", { + setup: function (frm) { + frm.set_query("patient", function () { + return { + filters: { + status: "Active", + }, + }; + }); + frm.set_query("healthcare_package", function () { + return { + filters: { + disabled: 0, + }, + }; + }); + }, + + refresh: function (frm) { + if (frm.doc.outstanding_amount > 0 && frm.doc.docstatus == 1) { + frm.add_custom_button(__("Payment Entry"), function() { + frappe.call({ + method: "healthcare.healthcare.doctype.package_subscription.package_subscription.create_payment_entry", + args: { + package_subscription: frm.doc.name + }, + callback: function (r) { + if (r && r.message) { + frappe.set_route("Form", "Payment Entry", r.message); + } + } + }); + }, "Create") + } + }, + + healthcare_package: function (frm) { + if (frm.doc.healthcare_package) { + frappe.call({ + doc: frm.doc, + method: "get_package_details", + callback: function (r) { + frm.refresh(); + }, + }); + } + } +}); diff --git a/healthcare/healthcare/doctype/package_subscription/package_subscription.json b/healthcare/healthcare/doctype/package_subscription/package_subscription.json new file mode 100644 index 0000000000..87c676a048 --- /dev/null +++ b/healthcare/healthcare/doctype/package_subscription/package_subscription.json @@ -0,0 +1,268 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2024-10-18 14:54:33.862440", + "default_view": "List", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "section_break_w7l6", + "naming_series", + "patient", + "patient_name", + "healthcare_package", + "column_break_yboa", + "company", + "status", + "valid_to", + "amended_from", + "accounts_section", + "income_account", + "section_break_drdy", + "package_details", + "section_break_dapx", + "discount_amount", + "column_break_zpsu", + "total_amount", + "totals_section", + "column_break_yoac", + "column_break_eoaw", + "total_package_amount", + "outstanding_amount", + "paid_amount", + "section_break_bwrj", + "invoiced", + "item_wise_invoicing" + ], + "fields": [ + { + "fieldname": "section_break_w7l6", + "fieldtype": "Section Break" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Package Subscription", + "print_hide": 1, + "read_only": 1, + "search_index": 1 + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "healthcare_package", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Healthcare Package", + "options": "Healthcare Package", + "reqd": 1 + }, + { + "fieldname": "column_break_yboa", + "fieldtype": "Column Break" + }, + { + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "\nPaid\nPartially Paid\nUnpaid\nDiscontinued" + }, + { + "fieldname": "section_break_drdy", + "fieldtype": "Section Break" + }, + { + "fieldname": "package_details", + "fieldtype": "Table", + "label": "Package Details", + "options": "Healthcare Package Item", + "read_only": 1 + }, + { + "fieldname": "section_break_dapx", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Amount", + "read_only": 1 + }, + { + "fieldname": "column_break_zpsu", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "label": "Outstanding Amount", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-PSUB-.YYYY.-" + }, + { + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Discount Amount", + "read_only": 1 + }, + { + "fieldname": "total_package_amount", + "fieldtype": "Currency", + "label": "Total Package Amount", + "read_only": 1 + }, + { + "fetch_from": "patient.patient_name", + "fetch_if_empty": 1, + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "fieldname": "accounts_section", + "fieldtype": "Section Break", + "label": "Accounts" + }, + { + "fieldname": "income_account", + "fieldtype": "Link", + "label": "Income Account", + "options": "Account" + }, + { + "fieldname": "totals_section", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "column_break_yoac", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_eoaw", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1 + }, + { + "fieldname": "section_break_bwrj", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fetch_from": "healthcare_package.item_wise_invoicing", + "fieldname": "item_wise_invoicing", + "fieldtype": "Check", + "hidden": 1, + "label": "Item wise Invoicing", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "valid_to", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Valid To", + "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [ + { + "link_doctype": "Payment Entry", + "link_fieldname": "package_subscription" + } + ], + "modified": "2024-12-01 10:12:36.922295", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Package Subscription", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "search_fields": "patient_name, total_amount", + "sort_field": "creation", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/package_subscription/package_subscription.py b/healthcare/healthcare/doctype/package_subscription/package_subscription.py new file mode 100644 index 0000000000..54c113ae69 --- /dev/null +++ b/healthcare/healthcare/doctype/package_subscription/package_subscription.py @@ -0,0 +1,85 @@ +# Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe.model.document import Document, _ +from frappe.utils import get_link_to_form + + +class PackageSubscription(Document): + def validate(self): + self.get_package_details() + if self.total_package_amount: + self.outstanding_amount = self.total_package_amount - self.paid_amount + + if not self.status == "Discontinued": + if self.paid_amount == 0: + self.status = "Unpaid" + else: + self.status = "Paid" if self.outstanding_amount == 0 else "Partially Paid" + + def on_update_after_submit(self): + if self.total_package_amount: + self.db_set("outstanding_amount", self.total_package_amount - self.paid_amount) + + if not self.status == "Discontinued": + if self.paid_amount == 0: + self.db_set("status", "Unpaid") + else: + self.db_set("status", "Paid" if self.outstanding_amount == 0 else "Partially Paid") + + def before_insert(self): + exists = frappe.db.exists( + "Package Subscription", + { + "patient": self.patient, + "healthcare_package": self.healthcare_package, + "valid_to": [">=", self.valid_to], + "docstatus": ["!=", 2], + }, + ) + + if exists: + frappe.throw( + _("Subscription already exists for patient {0}: {1}").format( + frappe.bold(self.patient_name), get_link_to_form("Package Subscription", exists) + ) + ) + + @frappe.whitelist() + def get_package_details(self): + if not self.healthcare_package: + return + + package_doc = frappe.get_doc("Healthcare Package", self.healthcare_package) + + self.package_details = [] + for item in package_doc.package_items: + self.append("package_details", (frappe.copy_doc(item)).as_dict()) + self.total_package_amount = package_doc.total_package_amount + self.outstanding_amount = package_doc.total_package_amount + self.discount_amount = package_doc.discount_amount + self.total_amount = package_doc.total_amount + + +@frappe.whitelist() +def create_payment_entry(package_subscription): + subscription_doc = frappe.get_doc("Package Subscription", package_subscription) + customer = frappe.db.get_value("Patient", subscription_doc.patient, "customer") + if customer: + payment_entry_doc = frappe.new_doc("Payment Entry") + payment_entry_doc.update( + { + "payment_type": "Receive", + "party_type": "Customer", + "party": customer, + "package_subscription": package_subscription, + "paid_amount": subscription_doc.outstanding_amount, + "received_amount": subscription_doc.outstanding_amount, + "target_exchange_rate": 1, + } + ) + + payment_entry_doc.insert(ignore_mandatory=True) + + return payment_entry_doc.name diff --git a/healthcare/healthcare/doctype/package_subscription/package_subscription_list.js b/healthcare/healthcare/doctype/package_subscription/package_subscription_list.js new file mode 100644 index 0000000000..96ef44097c --- /dev/null +++ b/healthcare/healthcare/doctype/package_subscription/package_subscription_list.js @@ -0,0 +1,15 @@ +frappe.listview_settings["Package Subscription"] = { + get_indicator: function (doc) { + if (doc.valid_to < frappe.datetime.get_today()) { + return [__("Expired"), "red", "valid_to,<," + frappe.datetime.get_today()]; + } else if (doc.status === "Paid") { + return [__("Paid"), "green", "status,=," + doc.status]; + } else if (doc.status === "Partially Paid") { + return [__("Partially Paid"), "orange", "status,=," + doc.status]; + } else if (doc.status === "Unpaid") { + return [__("Unpaid"), "red", "status,=," + doc.status]; + } else if (doc.status === "Discontinued") { + return [__("Discontinued"), "red", "status,=," + doc.status]; + } + }, +} \ No newline at end of file diff --git a/healthcare/healthcare/doctype/package_subscription/test_package_subscription.py b/healthcare/healthcare/doctype/package_subscription/test_package_subscription.py new file mode 100644 index 0000000000..746c1fe8c9 --- /dev/null +++ b/healthcare/healthcare/doctype/package_subscription/test_package_subscription.py @@ -0,0 +1,224 @@ +# Copyright (c) 2024, earthians Health Informatics Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase +from frappe.utils import add_to_date, getdate + +import erpnext + +from healthcare.healthcare.doctype.healthcare_settings.healthcare_settings import ( + get_income_account, + get_receivable_account, +) +from healthcare.healthcare.doctype.observation_template.test_observation_template import ( + create_observation_template, +) +from healthcare.healthcare.doctype.package_subscription.package_subscription import ( + create_payment_entry, +) +from healthcare.healthcare.doctype.patient_appointment.test_patient_appointment import ( + create_patient, +) +from healthcare.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type + + +class TestPackageSubscription(FrappeTestCase): + def test_package_subscription(self): + frappe.delete_doc_if_exists("Healthcare Package", "Package - 1") + obs_name = "Total Cholesterol" + obs_template = create_observation_template(obs_name, "_Test") + patient = create_patient() + therapy_type = create_therapy_type() + income_account = frappe.db.exists( + "Account", {"root_type": "Income", "account_type": "Income Account"} + ) + + package = create_healthcare_package(therapy_type, obs_template, income_account) + + self.assertTrue(package.name) + self.assertTrue(frappe.db.exists("Item", package.item_code)) + + subscription = create_subscription(patient, package, income_account) + + self.assertTrue(subscription.name) + self.assertEqual( + package.total_package_amount, + subscription.total_package_amount, + ) + + self.assertEqual( + subscription.paid_amount, + 0, + ) + + if subscription.docstatus == 0: + subscription.submit() + + payment_entry = create_payment_entry(subscription.name) + self.assertTrue(payment_entry) + + pe_doc = frappe.get_doc("Payment Entry", payment_entry) + mop_account = frappe.get_cached_value( + "Mode of Payment Account", {"company": pe_doc.company, "parent": "Cash"}, "default_account" + ) + if not mop_account: + mop_account = frappe.get_cached_value("Company", pe_doc.company, "default_cash_account") + paid_to_currency = frappe.get_cached_value("Account", mop_account, "account_currency") + if not paid_to_currency: + paid_to_currency = frappe.get_cached_value("Company", pe_doc.company, "default_currency") + + pe_doc.mode_of_payment = "Cash" + pe_doc.paid_to = mop_account + pe_doc.paid_to_account_currency = paid_to_currency + pe_doc.submit() + self.assertEqual(pe_doc.paid_amount, subscription.total_package_amount) + + self.assertEqual( + pe_doc.paid_amount, + frappe.get_cached_value("Package Subscription", subscription.name, "paid_amount"), + ) + + self.assertEqual( + frappe.get_cached_value("Package Subscription", subscription.name, "outstanding_amount"), 0 + ) + + sales_invoice = create_sales_invoice(patient, subscription.name) + + self.assertTrue(sales_invoice.name) + self.assertEqual(sales_invoice.grand_total, 5200) + + if sales_invoice: + self.assertEqual(sales_invoice.items[0].reference_dt, "Package Subscription") + + +def create_subscription(patient=None, package=None, income_account=None): + if not patient and not package.name: + return + + subscription = frappe.get_doc( + { + "doctype": "Package Subscription", + "healthcare_package": package.name, + "patient": patient, + "valid_to": add_to_date(getdate(), months=1), + "income_account": income_account, + "total_package_amount": package.total_package_amount, + } + ) + for item in package.package_items: + subscription.append("package_details", (frappe.copy_doc(item)).as_dict()) + + subscription.insert(ignore_permissions=True, ignore_mandatory=True) + + return subscription + + +def create_sales_invoice(patient, subscription=None): + if not subscription: + return + + subscriptions_to_invoice = [] + subscription_doc = frappe.get_cached_doc("Package Subscription", subscription) + item, item_wise_invoicing = frappe.get_cached_value( + "Healthcare Package", subscription_doc.healthcare_package, ["item", "item_wise_invoicing"] + ) + if not item_wise_invoicing: + subscriptions_to_invoice.append( + { + "reference_type": "Package Subscription", + "reference_name": subscription, + "service": item, + "qty": 1, + "amount": subscription_doc.total_package_amount, + } + ) + else: + for item in subscription_doc.package_details: + if not item.invoiced: + subscriptions_to_invoice.append( + { + "reference_type": item.doctype, + "reference_name": item.name, + "service": item.item_code, + "qty": item.no_of_sessions, + "amount": item.amount_with_discount, + } + ) + + sales_invoice = frappe.new_doc("Sales Invoice") + sales_invoice.patient = patient + sales_invoice.customer = frappe.get_cached_value("Patient", patient, "customer") + sales_invoice.due_date = getdate() + sales_invoice.currency = frappe.get_cached_value( + "Company", subscription_doc.company, "default_currency" + ) + sales_invoice.company = subscription_doc.company + sales_invoice.debit_to = get_receivable_account(subscription_doc.company) + for item in subscriptions_to_invoice: + sales_invoice.append( + "items", + { + "qty": item.get("qty"), + "uom": "Nos", + "conversion_factor": 1, + "income_account": get_income_account(None, subscription_doc.company), + "rate": item.get("amount") / item.get("qty"), + "amount": item.get("amount"), + "reference_dt": item.get("reference_type"), + "reference_dn": item.get("reference_name"), + "cost_center": erpnext.get_default_cost_center(subscription_doc.company), + "item_code": item.get("service"), + "item_name": item.get("service"), + "description": item.get("service"), + }, + ) + + sales_invoice.set_missing_values() + sales_invoice.submit() + + return sales_invoice + + +def create_healthcare_package(therapy_type=None, obs_template=None, income_account=None): + if not therapy_type or not obs_template: + return + + package = frappe.get_doc( + { + "doctype": "Healthcare Package", + "package_name": "Package - 1", + "price_list": "Standard Selling", + "currency": "INR", + "income_account": income_account, + "discount_amount": 100, + "item_code": "Package - 1", + "item_group": "Services", + "item_wise_invoicing": 0, + } + ) + package.append( + "package_items", + { + "package_item_type": "Observation Template", + "package_item": obs_template.name, + "no_of_sessions": 1, + "rate": obs_template.rate, + "amount": obs_template.rate, + "amount_with_discount": obs_template.rate, + }, + ) + package.append( + "package_items", + { + "package_item_type": "Therapy Type", + "package_item": therapy_type.name, + "no_of_sessions": 1, + "rate": therapy_type.rate, + "amount": therapy_type.rate, + "amount_with_discount": therapy_type.rate, + }, + ) + package.insert(ignore_permissions=True, ignore_mandatory=True) + + return package diff --git a/healthcare/healthcare/utils.py b/healthcare/healthcare/utils.py index e7af302a99..8b58d32e6d 100644 --- a/healthcare/healthcare/utils.py +++ b/healthcare/healthcare/utils.py @@ -40,6 +40,7 @@ def get_healthcare_services_to_invoice(patient, customer, company, link_customer items_to_invoice += get_therapy_sessions_to_invoice(patient, company) items_to_invoice += get_service_requests_to_invoice(patient, company) items_to_invoice += get_observations_to_invoice(patient, company) + items_to_invoice += get_package_subscriptions_to_invoice(patient, company) validate_customer_created(patient, customer, link_customer) return items_to_invoice @@ -436,6 +437,37 @@ def get_service_requests_to_invoice(patient, company): return orders_to_invoice +def get_package_subscriptions_to_invoice(patient, company): + subscriptions_to_invoice = [] + subscriptions = frappe.db.get_all( + "Package Subscription", + fields=["name", "healthcare_package"], + filters={ + "patient": patient.name, + "company": company, + "invoiced": False, + "docstatus": 1, + }, + ) + for sub in subscriptions: + subscription_doc = frappe.get_doc("Package Subscription", sub.name) + item, item_wise_invoicing = frappe.get_cached_value( + "Healthcare Package", sub.healthcare_package, ["item", "item_wise_invoicing"] + ) + if not item_wise_invoicing: + subscriptions_to_invoice.append( + {"reference_type": "Package Subscription", "reference_name": sub.name, "service": item} + ) + else: + for item in subscription_doc.package_details: + if not item.invoiced: + subscriptions_to_invoice.append( + {"reference_type": item.doctype, "reference_name": item.name, "service": item.item_code} + ) + + return subscriptions_to_invoice + + @frappe.whitelist() def get_appointment_billing_item_and_rate(doc): if isinstance(doc, str): @@ -663,6 +695,15 @@ def set_invoiced(item, method, ref_invoice=None): "Lab Test Template": "Lab Test" # 'Healthcare Service Unit': 'Inpatient Occupancy' } + elif item.reference_dt == "Healthcare Package Item": + frappe.db.set_value(item.reference_dt, item.reference_dn, "invoiced", invoiced) + + package_subsription = frappe.get_cached_value(item.reference_dt, item.reference_dn, "parent") + subsription_doc = frappe.get_doc("Package Subscription", package_subsription) + if any(item.get("invoiced") == 0 for item in subsription_doc.package_details): + frappe.db.set_value("Package Subscription", package_subsription, "invoiced", 0) + else: + frappe.db.set_value("Package Subscription", package_subsription, "invoiced", 1) def validate_invoiced_on_submit(item): diff --git a/healthcare/healthcare/workspace/healthcare/healthcare.json b/healthcare/healthcare/workspace/healthcare/healthcare.json index d24b7cfa93..116907040e 100644 --- a/healthcare/healthcare/workspace/healthcare/healthcare.json +++ b/healthcare/healthcare/workspace/healthcare/healthcare.json @@ -1,11 +1,12 @@ { + "app": "healthcare", "charts": [ { "chart_name": "Patient Appointments", "label": "Patient Appointments" } ], - "content": "[{\"id\":\"9b831922c0\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Healthcare\",\"col\":12}},{\"id\":\"f1f8e3812a\", \"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Patients\",\"col\":3}},{\"id\":\"326087361e\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Patients Admitted\",\"col\":3}},{\"id\":\"264b39da0e\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Open Appointments\",\"col\":3}},{\"id\":\"e507c90d85\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Appointments to Bill\",\"col\":3}},{\"id\":\"IPK_0OJyFF\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Patient Appointments\",\"col\":12}},{\"id\":\"fKaNGh_f3X\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"LCsKfCbHFS\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"gtytowgpr9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient Appointment\",\"col\":4}},{\"id\":\"cgh4dqonE3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient\",\"col\":4}},{\"id\":\"pw8ECGStt-\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Healthcare Service Unit\",\"col\":4}},{\"id\":\"irCA7TCgAO\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Healthcare Practitioner\",\"col\":4}},{\"id\":\"wdgJqmB4mx\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient History\",\"col\":4}},{\"id\":\"oN5Gi4f4LN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"id\":\"QAyRuJGfxG\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"-K7NMpJiTM\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"M1PT6PedBj\",\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"id\":\"51eVEiCjvf\",\"type\":\"card\",\"data\":{\"card_name\":\"Consultation\",\"col\":4}},{\"id\":\"Mk_jHVQUPc\",\"type\":\"card\",\"data\":{\"card_name\":\"Orders\",\"col\":4}},{\"id\":\"PwTxarr1_m\",\"type\":\"card\",\"data\":{\"card_name\":\"Inpatient\",\"col\":4}},{\"id\":\"W-3YAoPKn-\",\"type\":\"card\",\"data\":{\"card_name\":\"Rehabilitation and Physiotherapy\",\"col\":4}},{\"id\":\"6cc3885df2\",\"type\":\"card\",\"data\":{\"card_name\":\"Diagnostic Module\",\"col\":4}},{\"id\":\"hMMLOCZpxJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Nursing\",\"col\":4}},{\"id\":\"jC32DydzyL\",\"type\":\"card\",\"data\":{\"card_name\":\"Laboratory\",\"col\":4}},{\"id\":\"AYDEjJSeTB\",\"type\":\"card\",\"data\":{\"card_name\":\"Medical Codes and Standards\",\"col\":4}},{\"id\":\"GvdoebZzcE\",\"type\":\"card\",\"data\":{\"card_name\":\"Service Units \",\"col\":4}},{\"id\":\"1f1f1a7b9e\",\"type\":\"card\",\"data\":{\"card_name\":\"Terminology Mapping\",\"col\":4}},{\"id\":\"rstxE68cTQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"YnekHjQCia\",\"type\":\"card\",\"data\":{\"card_name\":\"Consultation Setup\",\"col\":4}},{\"id\":\"5J3nicaW1a\",\"type\":\"card\",\"data\":{\"card_name\":\"Laboratory Setup\",\"col\":4}},{\"id\":\"zcOmhgxb5f\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", + "content": "[{\"id\":\"9b831922c0\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Healthcare\",\"col\":12}},{\"id\":\"f1f8e3812a\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Patients\",\"col\":3}},{\"id\":\"326087361e\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Total Patients Admitted\",\"col\":3}},{\"id\":\"264b39da0e\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Open Appointments\",\"col\":3}},{\"id\":\"e507c90d85\",\"type\":\"number_card\",\"data\":{\"number_card_name\":\"Appointments to Bill\",\"col\":3}},{\"id\":\"IPK_0OJyFF\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Patient Appointments\",\"col\":12}},{\"id\":\"fKaNGh_f3X\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"LCsKfCbHFS\",\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"col\":12}},{\"id\":\"gtytowgpr9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient Appointment\",\"col\":4}},{\"id\":\"cgh4dqonE3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient\",\"col\":4}},{\"id\":\"pw8ECGStt-\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Healthcare Service Unit\",\"col\":4}},{\"id\":\"irCA7TCgAO\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Healthcare Practitioner\",\"col\":4}},{\"id\":\"wdgJqmB4mx\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Patient History\",\"col\":4}},{\"id\":\"oN5Gi4f4LN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"id\":\"QAyRuJGfxG\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"-K7NMpJiTM\",\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"col\":12}},{\"id\":\"M1PT6PedBj\",\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"id\":\"51eVEiCjvf\",\"type\":\"card\",\"data\":{\"card_name\":\"Consultation\",\"col\":4}},{\"id\":\"Mk_jHVQUPc\",\"type\":\"card\",\"data\":{\"card_name\":\"Orders\",\"col\":4}},{\"id\":\"PwTxarr1_m\",\"type\":\"card\",\"data\":{\"card_name\":\"Inpatient\",\"col\":4}},{\"id\":\"W-3YAoPKn-\",\"type\":\"card\",\"data\":{\"card_name\":\"Rehabilitation and Physiotherapy\",\"col\":4}},{\"id\":\"6cc3885df2\",\"type\":\"card\",\"data\":{\"card_name\":\"Diagnostic Module\",\"col\":4}},{\"id\":\"hMMLOCZpxJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Nursing\",\"col\":4}},{\"id\":\"jC32DydzyL\",\"type\":\"card\",\"data\":{\"card_name\":\"Laboratory\",\"col\":4}},{\"id\":\"GvdoebZzcE\",\"type\":\"card\",\"data\":{\"card_name\":\"Service Units \",\"col\":4}},{\"id\":\"1f1f1a7b9e\",\"type\":\"card\",\"data\":{\"card_name\":\"Terminology Mapping\",\"col\":4}},{\"id\":\"rstxE68cTQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"YnekHjQCia\",\"type\":\"card\",\"data\":{\"card_name\":\"Consultation Setup\",\"col\":4}},{\"id\":\"5J3nicaW1a\",\"type\":\"card\",\"data\":{\"card_name\":\"Laboratory Setup\",\"col\":4}},{\"id\":\"zcOmhgxb5f\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]", "creation": "2020-03-02 17:23:17.919682", "custom_blocks": [], "docstatus": 0, @@ -17,68 +18,6 @@ "is_hidden": 0, "label": "Healthcare", "links": [ - { - "hidden": 0, - "is_query_report": 0, - "label": "Masters", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient", - "link_count": 0, - "link_to": "Patient", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Practitioner", - "link_count": 0, - "link_to": "Healthcare Practitioner", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Practitioner Schedule", - "link_count": 0, - "link_to": "Practitioner Schedule", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Department", - "link_count": 0, - "link_to": "Medical Department", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Patient Care Type", - "link_count": 0, - "link_to": "Patient Care Type", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, @@ -326,14 +265,6 @@ "onboard": 0, "type": "Link" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Records and History", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, { "dependencies": "", "hidden": 0, @@ -730,9 +661,82 @@ "link_type": "DocType", "onboard": 0, "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Masters", + "link_count": 6, + "link_type": "DocType", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Patient", + "link_count": 0, + "link_to": "Patient", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Practitioner", + "link_count": 0, + "link_to": "Healthcare Practitioner", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Practitioner Schedule", + "link_count": 0, + "link_to": "Practitioner Schedule", + "link_type": "DocType", + "onboard": 1, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Medical Department", + "link_count": 0, + "link_to": "Medical Department", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Patient Care Type", + "link_count": 0, + "link_to": "Patient Care Type", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Healthcare Package", + "link_count": 0, + "link_to": "Healthcare Package", + "link_type": "DocType", + "onboard": 0, + "type": "Link" } ], - "modified": "2024-05-14 00:39:38.432511", + "modified": "2024-12-01 11:58:00.855408", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", @@ -809,5 +813,6 @@ "type": "Dashboard" } ], - "title": "Healthcare" + "title": "Healthcare", + "type": "Workspace" } \ No newline at end of file diff --git a/healthcare/hooks.py b/healthcare/hooks.py index 88d8282d27..435d580b22 100644 --- a/healthcare/hooks.py +++ b/healthcare/hooks.py @@ -137,8 +137,8 @@ "after_insert": "healthcare.regional.india.abdm.utils.set_consent_attachment_details" }, "Payment Entry": { - "on_submit": "healthcare.healthcare.custom_doctype.payment_entry.set_paid_amount_in_treatment_counselling", - "on_cancel": "healthcare.healthcare.custom_doctype.payment_entry.set_paid_amount_in_treatment_counselling", + "on_submit": "healthcare.healthcare.custom_doctype.payment_entry.set_paid_amount_in_healthcare_docs", + "on_cancel": "healthcare.healthcare.custom_doctype.payment_entry.set_paid_amount_in_healthcare_docs", }, } diff --git a/healthcare/patches.txt b/healthcare/patches.txt index 4b4ce0ec54..8155b323b6 100644 --- a/healthcare/patches.txt +++ b/healthcare/patches.txt @@ -10,6 +10,7 @@ healthcare.patches.v15_0.setup_service_request healthcare.patches.v15_0.create_custom_field_in_payment_entry healthcare.patches.v15_0.add_observation_to_patient_history healthcare.patches.v15_0.create_patient_medical_records_for_observations #25-12-2024 +healthcare.patches.v15_0.create_custom_field_for_package_subscription [post_model_sync] healthcare.patches.v15_0.rename_field_medical_department_in_appoitment_type_service_item diff --git a/healthcare/patches/v15_0/create_custom_field_for_package_subscription.py b/healthcare/patches/v15_0/create_custom_field_for_package_subscription.py new file mode 100644 index 0000000000..82a0f9f536 --- /dev/null +++ b/healthcare/patches/v15_0/create_custom_field_for_package_subscription.py @@ -0,0 +1,18 @@ +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + custom_field = { + "Payment Entry": [ + { + "fieldname": "package_subscription", + "label": "Package Subscription", + "fieldtype": "Link", + "options": "Package Subscription", + "insert_after": "treatment_counselling", + "read_only": True, + }, + ] + } + + create_custom_fields(custom_field) diff --git a/healthcare/setup.py b/healthcare/setup.py index 45cc76db3e..664c9837ae 100644 --- a/healthcare/setup.py +++ b/healthcare/setup.py @@ -136,6 +136,14 @@ "insert_after": "payment_order", "read_only": True, }, + { + "fieldname": "package_subscription", + "label": "Package Subscription", + "fieldtype": "Link", + "options": "Package Subscription", + "insert_after": "treatment_counselling", + "read_only": True, + }, ], }, "on_setup": "healthcare.setup.setup_healthcare",