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",