fix: conflicts

This commit is contained in:
Rucha Mahabal 2020-04-23 00:52:47 +05:30
commit d7304519e2
156 changed files with 4634 additions and 2015 deletions

View File

@ -69,6 +69,7 @@ class TestAccount(unittest.TestCase):
acc.account_name = "Accumulated Depreciation"
acc.parent_account = "Fixed Assets - _TC"
acc.company = "_Test Company"
acc.account_type = "Accumulated Depreciation"
acc.insert()
doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
@ -149,7 +150,7 @@ def _make_test_records(verbose):
# fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, None, None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],

View File

@ -152,10 +152,9 @@ def build_forest(data):
return [parent_account]
elif account_name == child:
parent_account_list = return_parent(data, parent_account)
if not parent_account_list:
if not parent_account_list and parent_account:
frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
frappe.bold(parent_account)))
return [child] + parent_account_list
charts_map, paths = {}, []

View File

@ -330,9 +330,9 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
if pr_doc.mixed_conditions:
amt = args.get('qty') * args.get("price_list_rate")
if args.get("item_code") != row.get("item_code"):
amt = row.get('qty') * (row.get("price_list_rate") or args.get("rate"))
amt = flt(row.get('qty')) * flt(row.get("price_list_rate") or args.get("rate"))
sum_qty += row.get("stock_qty") or args.get("stock_qty") or args.get("qty")
sum_qty += flt(row.get("stock_qty")) or flt(args.get("stock_qty")) or flt(args.get("qty"))
sum_amt += amt
if pr_doc.is_cumulative:

View File

@ -174,7 +174,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
read_only: 0,
fieldtype:'Date',
label: __('Release Date'),
default: me.frm.doc.release_date
default: me.frm.doc.release_date,
reqd: 1
},
{
fieldname: 'hold_comment',

View File

@ -13,23 +13,18 @@
"supplier_name",
"tax_id",
"due_date",
"is_paid",
"is_return",
"apply_tds",
"column_break1",
"company",
"posting_date",
"posting_time",
"set_posting_time",
"is_paid",
"is_return",
"apply_tds",
"amended_from",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"sb_14",
"on_hold",
"release_date",
"cb_17",
"hold_comment",
"supplier_invoice_details",
"bill_no",
"column_break_15",
@ -73,9 +68,9 @@
"base_total",
"base_net_total",
"column_break_28",
"total_net_weight",
"total",
"net_total",
"total_net_weight",
"taxes_section",
"tax_category",
"column_break_49",
@ -137,10 +132,15 @@
"terms",
"printing_settings",
"letter_head",
"group_same_items",
"column_break_112",
"select_print_heading",
"column_break_112",
"group_same_items",
"language",
"sb_14",
"on_hold",
"release_date",
"cb_17",
"hold_comment",
"more_info",
"credit_to",
"party_account_currency",
@ -190,6 +190,7 @@
"oldfieldtype": "Link",
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
"search_index": 1
},
{
@ -1232,6 +1233,7 @@
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription Section",
@ -1298,7 +1300,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2019-12-30 19:13:49.610538",
"modified": "2020-04-17 13:05:25.199832",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -12,15 +12,11 @@
"item_name",
"description_section",
"description",
"item_group",
"brand",
"image_section",
"col_break7",
"item_group",
"image",
"image_view",
"manufacture_details",
"manufacturer",
"column_break_13",
"manufacturer_part_no",
"quantity_and_rate",
"received_qty",
"qty",
@ -55,20 +51,19 @@
"item_tax_amount",
"landed_cost_voucher_amount",
"rm_supp_cost",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_38",
"weight_uom",
"warehouse_section",
"warehouse",
"rejected_warehouse",
"from_warehouse",
"quality_inspection",
"batch_no",
"col_br_wh",
"serial_no",
"col_br_wh",
"rejected_warehouse",
"batch_no",
"rejected_serial_no",
"manufacture_details",
"manufacturer",
"column_break_13",
"manufacturer_part_no",
"accounting",
"expense_account",
"col_break5",
@ -92,6 +87,11 @@
"po_detail",
"purchase_receipt",
"pr_detail",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_38",
"weight_uom",
"accounting_dimensions_section",
"project",
"dimension_col_break",
@ -550,23 +550,21 @@
},
{
"fieldname": "brand",
"fieldtype": "Data",
"hidden": 1,
"label": "Brand",
"oldfieldname": "brand",
"oldfieldtype": "Data",
"print_hide": 1
},
{
"fieldname": "item_group",
"fieldtype": "Link",
"hidden": 1,
"label": "Item Group",
"oldfieldname": "item_group",
"oldfieldtype": "Link",
"options": "Item Group",
"label": "Brand",
"print_hide": 1,
"read_only": 1
"options": "Brand"
},
{
"fetch_from": "item_code.item_group",
"fetch_if_empty": 1,
"fieldname": "item_group",
"fieldtype": "Link",
"label": "Item Group",
"print_hide": 1,
"read_only": 1,
"options": "Item Group"
},
{
"description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges",
@ -720,12 +718,6 @@
"fieldname": "section_break_82",
"fieldtype": "Section Break"
},
{
"collapsible": 1,
"fieldname": "image_section",
"fieldtype": "Section Break",
"label": "Image"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
@ -737,6 +729,7 @@
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "manufacture_details",
"fieldtype": "Section Break",
"label": "Manufacture"
@ -771,12 +764,17 @@
"ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"options": "Warehouse"
},
{
"collapsible": 1,
"fieldname": "col_break7",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-04-07 18:34:35.104178",
"modified": "2020-04-22 10:37:35.103176",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@ -784,4 +782,4 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
@ -74,9 +75,9 @@
"base_total",
"base_net_total",
"column_break_32",
"total_net_weight",
"total",
"net_total",
"total_net_weight",
"taxes_section",
"taxes_and_charges",
"column_break_38",
@ -1577,7 +1578,8 @@
"icon": "fa fa-file-text",
"idx": 181,
"is_submittable": 1,
"modified": "2020-02-10 04:57:11.221180",
"links": [],
"modified": "2020-04-17 12:38:41.435728",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -1926,16 +1926,6 @@ class TestSalesInvoice(unittest.TestCase):
item.taxes = []
item.save()
def test_customer_provided_parts_si(self):
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
si = create_sales_invoice(item_code='CUST-0987', rate=0)
self.assertEqual(si.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(si.get("items")[0].amount, 0)
# test if Sales Invoice with rate is allowed
si2 = create_sales_invoice(item_code='CUST-0987', do_not_save=True)
self.assertRaises(frappe.ValidationError, si2.save)
def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args)

View File

@ -2,7 +2,7 @@
<h4 class="text-center">
{% if (filters.party_name) { %}
{%= filters.party_name %}
{% } else if (filters.party) { %}
{% } else if (filters.party && filters.party.length) { %}
{%= filters.party %}
{% } else if (filters.account) { %}
{%= filters.account %}

View File

@ -365,6 +365,7 @@ def get_columns(filters):
columns = [
{
"label": _("GL Entry"),
"fieldname": "gl_entry",
"fieldtype": "Link",
"options": "GL Entry",

View File

@ -11,12 +11,34 @@ from frappe.model.document import Document
class AssetCategory(Document):
def validate(self):
self.validate_finance_books()
self.validate_accounts()
def validate_finance_books(self):
for d in self.finance_books:
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
def validate_accounts(self):
account_type_map = {
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },
'depreciation_expense_account': { 'root_type': 'Expense' },
'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' }
}
for d in self.accounts:
for fieldname in account_type_map.keys():
if d.get(fieldname):
selected_account = d.get(fieldname)
key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
expected_key_type = account_type_map[fieldname][key_to_match]
if selected_key_type != expected_key_type:
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
title=_("Invalid Account"))
@frappe.whitelist()
def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):

View File

@ -24,26 +24,6 @@ frappe.ui.form.on('Asset Maintenance', {
return indicator;
}
);
frm.set_query('select_serial_no', function(doc){
return {
asset: frm.doc.asset_name
}
})
},
select_serial_no: (frm) => {
let serial_nos = frm.doc.serial_no || frm.doc.select_serial_no;
if (serial_nos) {
serial_nos = serial_nos.split('\n');
serial_nos.push(frm.doc.select_serial_no);
const unique_sn = serial_nos.filter(function(elem, index, self) {
return index === self.indexOf(elem);
});
frm.set_value("serial_no", unique_sn.join('\n'));
}
},
refresh: (frm) => {
@ -93,25 +73,6 @@ frappe.ui.form.on('Asset Maintenance Task', {
},
end_date: (frm, cdt, cdn) => {
get_next_due_date(frm, cdt, cdn);
},
assign_to: (frm, cdt, cdn) => {
var d = locals[cdt][cdn];
if (frm.doc.__islocal) {
frappe.model.set_value(cdt, cdn, "assign_to", "");
frappe.model.set_value(cdt, cdn, "assign_to_name", "");
frappe.throw(__("Please save before assigning task."));
}
if (d.assign_to) {
return frappe.call({
method: 'erpnext.assets.doctype.asset_maintenance.asset_maintenance.assign_tasks',
args: {
asset_maintenance_name: frm.doc.name,
assign_to_member: d.assign_to,
maintenance_task: d.maintenance_task,
next_due_date: d.next_due_date
}
});
}
}
});

View File

@ -16,12 +16,11 @@ class AssetMaintenance(Document):
throw(_("Start date should be less than end date for task {0}").format(task.maintenance_task))
if getdate(task.next_due_date) < getdate(nowdate()):
task.maintenance_status = "Overdue"
if not task.assign_to and self.docstatus == 0:
throw(_("Row #{}: Please asign task to a member.").format(task.idx))
def on_update(self):
for task in self.get('asset_maintenance_tasks'):
if not task.assign_to:
task.db_set("assign_to", self.maintenance_manager)
task.db_set("assign_to_name", self.maintenance_manager_name)
assign_tasks(self.name, task.assign_to, task.maintenance_task, task.next_due_date)
self.sync_maintenance_tasks()
@ -108,7 +107,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task):
@frappe.whitelist()
def get_team_members(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.get_values('Maintenance Team Member', {'parent':filters.get("maintenance_team")})
return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") })
@frappe.whitelist()
def get_maintenance_log(asset_name):

View File

@ -125,13 +125,15 @@ def get_maintenance_tasks():
"start_date": nowdate(),
"periodicity": "Monthly",
"maintenance_type": "Preventive Maintenance",
"maintenance_status": "Planned"
"maintenance_status": "Planned",
"assign_to": "marcus@abc.com"
},
{"maintenance_task": "Check Gears",
"start_date": nowdate(),
"periodicity": "Yearly",
"maintenance_type": "Calibration",
"maintenance_status": "Planned"
"maintenance_status": "Planned",
"assign_to": "thalia@abc.com"
}
]

View File

@ -1,4 +1,5 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
@ -63,9 +64,9 @@
"base_total",
"base_net_total",
"column_break_26",
"total_net_weight",
"total",
"net_total",
"total_net_weight",
"taxes_section",
"tax_category",
"column_break_50",
@ -170,8 +171,8 @@
"search_index": 1
},
{
"description": "Fetch items based on Default Supplier.",
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
"description": "Fetch items based on Default Supplier.",
"fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button",
"label": "Get Items from Open Material Requests"
@ -1054,7 +1055,8 @@
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"modified": "2020-01-14 18:54:39.694448",
"links": [],
"modified": "2020-04-17 13:04:28.185197",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@ -1,24 +1,31 @@
{
"actions": [],
"creation": "2013-02-22 01:27:42",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"main_item_code",
"rm_item_code",
"description",
"batch_no",
"serial_no",
"bom_detail_no",
"col_break1",
"required_qty",
"consumed_qty",
"rm_item_code",
"stock_uom",
"rate",
"amount",
"conversion_factor",
"current_stock",
"reference_name",
"bom_detail_no"
"secbreak_1",
"rate",
"col_break2",
"amount",
"secbreak_2",
"required_qty",
"col_break3",
"consumed_qty",
"current_stock",
"secbreak_3",
"batch_no",
"col_break4",
"serial_no"
],
"fields": [
{
@ -152,11 +159,36 @@
"oldfieldname": "bom_detail_no",
"oldfieldtype": "Data",
"read_only": 1
},
{
"fieldname": "secbreak_1",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break2",
"fieldtype": "Column Break"
},
{
"fieldname": "secbreak_2",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break3",
"fieldtype": "Column Break"
},
{
"fieldname": "secbreak_3",
"fieldtype": "Section Break"
},
{
"fieldname": "col_break4",
"fieldtype": "Column Break"
}
],
"idx": 1,
"istable": 1,
"modified": "2019-11-21 16:25:29.909112",
"links": [],
"modified": "2020-04-10 18:09:33.997618",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",

View File

@ -214,5 +214,41 @@ def get_data():
"label": _("Lab Test Report")
}
]
},
{
"label": _("Rehabilitation"),
"icon": "icon-cog",
"items": [
{
"type": "doctype",
"name": "Exercise Type",
"label": _("Exercise Type")
},
{
"type": "doctype",
"name": "Exercise Difficulty Level",
"label": _("Exercise Difficulty Level")
},
{
"type": "doctype",
"name": "Therapy Type",
"label": _("Therapy Type")
},
{
"type": "doctype",
"name": "Therapy Plan",
"label": _("Therapy Plan")
},
{
"type": "doctype",
"name": "Therapy Session",
"label": _("Therapy Session")
},
{
"type": "doctype",
"name": "Motor Assessment Scale",
"label": _("Motor Assessment Scale")
}
]
}
]

View File

@ -383,9 +383,6 @@ class StockController(AccountsController):
# Customer Provided parts will have zero valuation rate
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1
if d.parenttype in ["Delivery Note", "Sales Invoice"] and d.rate:
frappe.throw(_("Row #{0}: {1} cannot have {2} as it is a Customer Provided Item")
.format(d.idx, frappe.bold(d.item_code), frappe.bold("Rate")))
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=None):

View File

@ -13,7 +13,7 @@ class TestMapper(unittest.TestCase):
'''Test mapping of multiple source docs on a single target doc'''
make_test_records("Item")
items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0})
items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0, 'disabled': 0})
customers = frappe.get_all("Customer")
if items and customers:
# Make source docs (quotations) and a target doc (sales order)

View File

@ -2,15 +2,40 @@
// For license information, please see license.txt
frappe.ui.form.on('Tally Migration', {
onload: function(frm) {
onload: function (frm) {
let reload_status = true;
frappe.realtime.on("tally_migration_progress_update", function (data) {
if (reload_status) {
frappe.model.with_doc(frm.doc.doctype, frm.doc.name, () => {
frm.refresh_header();
});
reload_status = false;
}
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
if (data.count == data.total) {
window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title);
let error_occurred = data.count === -1;
if (data.count == data.total || error_occurred) {
window.setTimeout((title) => {
frm.dashboard.hide_progress(title);
frm.reload_doc();
if (error_occurred) {
frappe.msgprint({
message: __("An error has occurred during {0}. Check {1} for more details",
[
repl("<a href='#Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
tally_document: frm.docname
}),
"<a href='#List/Error Log' class='variant-click'>Error Log</a>"
]
),
title: __("Tally Migration Error"),
indicator: "red"
});
}
}, 2000, data.title);
}
});
},
refresh: function(frm) {
refresh: function (frm) {
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
if (frm.doc.is_master_data_processed) {
if (frm.doc.status != "Importing Master Data") {
@ -34,17 +59,17 @@ frappe.ui.form.on('Tally Migration', {
}
}
},
add_button: function(frm, label, method) {
add_button: function (frm, label, method) {
frm.add_custom_button(
label,
() => frm.call({
doc: frm.doc,
method: method,
freeze: true,
callback: () => {
frm.remove_custom_button(label);
}
})
() => {
frm.call({
doc: frm.doc,
method: method,
freeze: true
});
frm.reload_doc();
}
);
}
});

View File

@ -1,4 +1,5 @@
{
"actions": [],
"beta": 1,
"creation": "2019-02-01 14:27:09.485238",
"doctype": "DocType",
@ -14,6 +15,7 @@
"tally_debtors_account",
"company_section",
"tally_company",
"default_uom",
"column_break_8",
"erpnext_company",
"processed_files_section",
@ -43,6 +45,7 @@
"label": "Status"
},
{
"description": "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs",
"fieldname": "master_data",
"fieldtype": "Attach",
"in_list_view": 1,
@ -50,6 +53,7 @@
},
{
"default": "Sundry Creditors",
"description": "Creditors Account set in Tally",
"fieldname": "tally_creditors_account",
"fieldtype": "Data",
"label": "Tally Creditors Account",
@ -61,6 +65,7 @@
},
{
"default": "Sundry Debtors",
"description": "Debtors Account set in Tally",
"fieldname": "tally_debtors_account",
"fieldtype": "Data",
"label": "Tally Debtors Account",
@ -72,6 +77,7 @@
"fieldtype": "Section Break"
},
{
"description": "Company Name as per Imported Tally Data",
"fieldname": "tally_company",
"fieldtype": "Data",
"label": "Tally Company",
@ -82,9 +88,11 @@
"fieldtype": "Column Break"
},
{
"description": "Your Company set in ERPNext",
"fieldname": "erpnext_company",
"fieldtype": "Data",
"label": "ERPNext Company"
"label": "ERPNext Company",
"read_only_depends_on": "eval:doc.is_master_data_processed == 1"
},
{
"fieldname": "processed_files_section",
@ -155,24 +163,28 @@
"options": "Cost Center"
},
{
"default": "0",
"fieldname": "is_master_data_processed",
"fieldtype": "Check",
"label": "Is Master Data Processed",
"read_only": 1
},
{
"default": "0",
"fieldname": "is_day_book_data_processed",
"fieldtype": "Check",
"label": "Is Day Book Data Processed",
"read_only": 1
},
{
"default": "0",
"fieldname": "is_day_book_data_imported",
"fieldtype": "Check",
"label": "Is Day Book Data Imported",
"read_only": 1
},
{
"default": "0",
"fieldname": "is_master_data_imported",
"fieldtype": "Check",
"label": "Is Master Data Imported",
@ -188,13 +200,23 @@
"fieldtype": "Column Break"
},
{
"description": "Day Book Data exported from Tally that consists of all historic transactions",
"fieldname": "day_book_data",
"fieldtype": "Attach",
"in_list_view": 1,
"label": "Day Book Data"
},
{
"default": "Unit",
"description": "UOM in case unspecified in imported data",
"fieldname": "default_uom",
"fieldtype": "Link",
"label": "Default UOM",
"options": "UOM"
}
],
"modified": "2019-04-29 05:46:54.394967",
"links": [],
"modified": "2020-04-16 13:03:28.894919",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Tally Migration",

View File

@ -4,20 +4,23 @@
from __future__ import unicode_literals
from decimal import Decimal
import json
import re
import traceback
import zipfile
from decimal import Decimal
from bs4 import BeautifulSoup as bs
import frappe
from erpnext import encode_company_abbr
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
from frappe import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document
from frappe.model.naming import getseries, revert_series_if_last
from frappe.utils.data import format_datetime
from bs4 import BeautifulSoup as bs
from erpnext import encode_company_abbr
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
PRIMARY_ACCOUNT = "Primary"
VOUCHER_CHUNK_SIZE = 500
@ -39,13 +42,15 @@ class TallyMigration(Document):
return string
master_file = frappe.get_doc("File", {"file_url": data_file})
master_file_path = master_file.get_full_path()
with zipfile.ZipFile(master_file.get_full_path()) as zf:
encoded_content = zf.read(zf.namelist()[0])
try:
content = encoded_content.decode("utf-8-sig")
except UnicodeDecodeError:
content = encoded_content.decode("utf-16")
if zipfile.is_zipfile(master_file_path):
with zipfile.ZipFile(master_file_path) as zf:
encoded_content = zf.read(zf.namelist()[0])
try:
content = encoded_content.decode("utf-8-sig")
except UnicodeDecodeError:
content = encoded_content.decode("utf-16")
master = bs(sanitize(emptify(content)), "xml")
collection = master.BODY.IMPORTDATA.REQUESTDATA
@ -58,13 +63,14 @@ class TallyMigration(Document):
"file_name": key + ".json",
"attached_to_doctype": self.doctype,
"attached_to_name": self.name,
"content": json.dumps(value)
"content": json.dumps(value),
"is_private": True
}).insert()
setattr(self, key, f.file_url)
def _process_master_data(self):
def get_company_name(collection):
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
def get_coa_customers_suppliers(collection):
root_type_map = {
@ -97,17 +103,17 @@ class TallyMigration(Document):
# If Ledger doesn't have PARENT field then don't create Account
# For example "Profit & Loss A/c"
if account.PARENT:
yield account.PARENT.string, account["NAME"], 0
yield account.PARENT.string.strip(), account["NAME"], 0
def get_parent(account):
if account.PARENT:
return account.PARENT.string
return account.PARENT.string.strip()
return {
("Yes", "No"): "Application of Funds (Assets)",
("Yes", "Yes"): "Expenses",
("No", "Yes"): "Income",
("No", "No"): "Source of Funds (Liabilities)",
}[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)]
}[(account.ISDEEMEDPOSITIVE.string.strip(), account.ISREVENUE.string.strip())]
def get_children_and_parent_dict(accounts):
children, parents = {}, {}
@ -145,38 +151,38 @@ class TallyMigration(Document):
parties, addresses = [], []
for account in collection.find_all("LEDGER"):
party_type = None
if account.NAME.string in customers:
if account.NAME.string.strip() in customers:
party_type = "Customer"
parties.append({
"doctype": party_type,
"customer_name": account.NAME.string,
"tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
"customer_name": account.NAME.string.strip(),
"tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
"customer_group": "All Customer Groups",
"territory": "All Territories",
"customer_type": "Individual",
})
elif account.NAME.string in suppliers:
elif account.NAME.string.strip() in suppliers:
party_type = "Supplier"
parties.append({
"doctype": party_type,
"supplier_name": account.NAME.string,
"pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
"supplier_name": account.NAME.string.strip(),
"pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
"supplier_group": "All Supplier Groups",
"supplier_type": "Individual",
})
if party_type:
address = "\n".join([a.string for a in account.find_all("ADDRESS")])
address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
addresses.append({
"doctype": "Address",
"address_line1": address[:140].strip(),
"address_line2": address[140:].strip(),
"country": account.COUNTRYNAME.string if account.COUNTRYNAME else None,
"state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
"gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
"pin_code": account.PINCODE.string if account.PINCODE else None,
"mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
"phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
"gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None,
"country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None,
"state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
"gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
"pin_code": account.PINCODE.string.strip() if account.PINCODE else None,
"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
})
return parties, addresses
@ -184,41 +190,50 @@ class TallyMigration(Document):
def get_stock_items_uoms(collection):
uoms = []
for uom in collection.find_all("UNIT"):
uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string})
uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string.strip()})
items = []
for item in collection.find_all("STOCKITEM"):
stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom
items.append({
"doctype": "Item",
"item_code" : item.NAME.string,
"stock_uom": item.BASEUNITS.string,
"item_code" : item.NAME.string.strip(),
"stock_uom": stock_uom.strip(),
"is_stock_item": 0,
"item_group": "All Item Groups",
"item_defaults": [{"company": self.erpnext_company}]
})
return items, uoms
try:
self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
collection = self.get_collection(self.master_data)
company = get_company_name(collection)
self.tally_company = company
self.erpnext_company = company
self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
collection = self.get_collection(self.master_data)
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
company = get_company_name(collection)
self.tally_company = company
self.erpnext_company = company
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
parties, addresses = get_parties_addresses(collection, customers, suppliers)
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
parties, addresses = get_parties_addresses(collection, customers, suppliers)
self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
items, uoms = get_stock_items_uoms(collection)
data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
self.publish("Process Master Data", _("Done"), 5, 5)
self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
items, uoms = get_stock_items_uoms(collection)
data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
self.dump_processed_data(data)
self.is_master_data_processed = 1
self.status = ""
self.save()
self.publish("Process Master Data", _("Done"), 5, 5)
self.dump_processed_data(data)
self.is_master_data_processed = 1
except:
self.publish("Process Master Data", _("Process Failed"), -1, 5)
self.log()
finally:
self.set_status()
def publish(self, title, message, count, total):
frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total})
@ -256,7 +271,6 @@ class TallyMigration(Document):
except:
self.log(address)
def create_items_uoms(items_file_url, uoms_file_url):
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
for uom in json.loads(uoms_file.get_content()):
@ -273,25 +287,35 @@ class TallyMigration(Document):
except:
self.log(item)
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
create_company_and_coa(self.chart_of_accounts)
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
create_parties_and_addresses(self.parties, self.addresses)
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
create_items_uoms(self.items, self.uoms)
self.publish("Import Master Data", _("Done"), 4, 4)
self.status = ""
self.is_master_data_imported = 1
self.save()
try:
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
create_company_and_coa(self.chart_of_accounts)
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
create_parties_and_addresses(self.parties, self.addresses)
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
create_items_uoms(self.items, self.uoms)
self.publish("Import Master Data", _("Done"), 4, 4)
self.is_master_data_imported = 1
except:
self.publish("Import Master Data", _("Process Failed"), -1, 5)
self.log()
finally:
self.set_status()
def _process_day_book_data(self):
def get_vouchers(collection):
vouchers = []
for voucher in collection.find_all("VOUCHER"):
if voucher.ISCANCELLED.string == "Yes":
if voucher.ISCANCELLED.string.strip() == "Yes":
continue
inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST")
if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
if voucher.VOUCHERTYPENAME.string.strip() not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
function = voucher_to_invoice
else:
function = voucher_to_journal_entry
@ -307,15 +331,15 @@ class TallyMigration(Document):
accounts = []
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
for entry in ledger_entries:
account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center}
if entry.ISPARTYLEDGER.string == "Yes":
party_details = get_party(entry.LEDGERNAME.string)
account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center}
if entry.ISPARTYLEDGER.string.strip() == "Yes":
party_details = get_party(entry.LEDGERNAME.string.strip())
if party_details:
party_type, party_account = party_details
account["party_type"] = party_type
account["account"] = party_account
account["party"] = entry.LEDGERNAME.string
amount = Decimal(entry.AMOUNT.string)
account["party"] = entry.LEDGERNAME.string.strip()
amount = Decimal(entry.AMOUNT.string.strip())
if amount > 0:
account["credit_in_account_currency"] = str(abs(amount))
else:
@ -324,21 +348,21 @@ class TallyMigration(Document):
journal_entry = {
"doctype": "Journal Entry",
"tally_guid": voucher.GUID.string,
"posting_date": voucher.DATE.string,
"tally_guid": voucher.GUID.string.strip(),
"posting_date": voucher.DATE.string.strip(),
"company": self.erpnext_company,
"accounts": accounts,
}
return journal_entry
def voucher_to_invoice(voucher):
if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]:
if voucher.VOUCHERTYPENAME.string.strip() in ["Sales", "Credit Note"]:
doctype = "Sales Invoice"
party_field = "customer"
account_field = "debit_to"
account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
price_list_field = "selling_price_list"
elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]:
elif voucher.VOUCHERTYPENAME.string.strip() in ["Purchase", "Debit Note"]:
doctype = "Purchase Invoice"
party_field = "supplier"
account_field = "credit_to"
@ -351,10 +375,10 @@ class TallyMigration(Document):
invoice = {
"doctype": doctype,
party_field: voucher.PARTYNAME.string,
"tally_guid": voucher.GUID.string,
"posting_date": voucher.DATE.string,
"due_date": voucher.DATE.string,
party_field: voucher.PARTYNAME.string.strip(),
"tally_guid": voucher.GUID.string.strip(),
"posting_date": voucher.DATE.string.strip(),
"due_date": voucher.DATE.string.strip(),
"items": get_voucher_items(voucher, doctype),
"taxes": get_voucher_taxes(voucher),
account_field: account_name,
@ -375,15 +399,15 @@ class TallyMigration(Document):
for entry in inventory_entries:
qty, uom = entry.ACTUALQTY.string.strip().split()
items.append({
"item_code": entry.STOCKITEMNAME.string,
"description": entry.STOCKITEMNAME.string,
"item_code": entry.STOCKITEMNAME.string.strip(),
"description": entry.STOCKITEMNAME.string.strip(),
"qty": qty.strip(),
"uom": uom.strip(),
"conversion_factor": 1,
"price_list_rate": entry.RATE.string.split("/")[0],
"price_list_rate": entry.RATE.string.strip().split("/")[0],
"cost_center": self.default_cost_center,
"warehouse": self.default_warehouse,
account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company),
account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string.strip(), self.erpnext_company),
})
return items
@ -391,13 +415,13 @@ class TallyMigration(Document):
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
taxes = []
for entry in ledger_entries:
if entry.ISPARTYLEDGER.string == "No":
tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company)
if entry.ISPARTYLEDGER.string.strip() == "No":
tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company)
taxes.append({
"charge_type": "Actual",
"account_head": tax_account,
"description": tax_account,
"tax_amount": entry.AMOUNT.string,
"tax_amount": entry.AMOUNT.string.strip(),
"cost_center": self.default_cost_center,
})
return taxes
@ -408,15 +432,24 @@ class TallyMigration(Document):
elif frappe.db.exists({"doctype": "Customer", "customer_name": party}):
return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
collection = self.get_collection(self.day_book_data)
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
vouchers = get_vouchers(collection)
self.publish("Process Day Book Data", _("Done"), 3, 3)
self.dump_processed_data({"vouchers": vouchers})
self.status = ""
self.is_day_book_data_processed = 1
self.save()
try:
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
collection = self.get_collection(self.day_book_data)
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
vouchers = get_vouchers(collection)
self.publish("Process Day Book Data", _("Done"), 3, 3)
self.dump_processed_data({"vouchers": vouchers})
self.is_day_book_data_processed = 1
except:
self.publish("Process Day Book Data", _("Process Failed"), -1, 5)
self.log()
finally:
self.set_status()
def _import_day_book_data(self):
def create_fiscal_years(vouchers):
@ -454,23 +487,31 @@ class TallyMigration(Document):
"currency": "INR"
}).insert()
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
try:
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
vouchers = json.loads(vouchers_file.get_content())
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
vouchers = json.loads(vouchers_file.get_content())
create_fiscal_years(vouchers)
create_price_list()
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
create_fiscal_years(vouchers)
create_price_list()
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
total = len(vouchers)
is_last = False
for index in range(0, total, VOUCHER_CHUNK_SIZE):
if index + VOUCHER_CHUNK_SIZE >= total:
is_last = True
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
total = len(vouchers)
is_last = False
for index in range(0, total, VOUCHER_CHUNK_SIZE):
if index + VOUCHER_CHUNK_SIZE >= total:
is_last = True
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
except:
self.log()
finally:
self.set_status()
def _import_vouchers(self, start, total, is_last=False):
frappe.flags.in_migrate = True
@ -494,25 +535,26 @@ class TallyMigration(Document):
frappe.flags.in_migrate = False
def process_master_data(self):
self.status = "Processing Master Data"
self.save()
self.set_status("Processing Master Data")
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
def import_master_data(self):
self.status = "Importing Master Data"
self.save()
self.set_status("Importing Master Data")
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
def process_day_book_data(self):
self.status = "Processing Day Book Data"
self.save()
self.set_status("Processing Day Book Data")
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
def import_day_book_data(self):
self.status = "Importing Day Book Data"
self.save()
self.set_status("Importing Day Book Data")
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
def log(self, data=None):
message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()])
data = data or self.status
message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
return frappe.log_error(title="Tally Migration Error", message=message)
def set_status(self, status=""):
self.status = status
self.save()

View File

@ -1,48 +1,53 @@
{
"cards": [
{
"icon": "",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]",
"title": "Masters"
"hidden": 0,
"label": "Masters",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]"
},
{
"icon": "",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]",
"title": "Consultation Setup"
"hidden": 0,
"label": "Consultation Setup",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]"
},
{
"icon": "icon-star",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]",
"title": "Consultation"
"hidden": 0,
"label": "Consultation",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]"
},
{
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]",
"title": "Settings"
"hidden": 0,
"label": "Settings",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]"
},
{
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]",
"title": "Laboratory Setup"
"hidden": 0,
"label": "Laboratory Setup",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]"
},
{
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]",
"title": "Laboratory"
"hidden": 0,
"label": "Laboratory",
"links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]"
},
{
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]",
"title": "Records and History"
"hidden": 0,
"label": "Rehabilitation and Physiotherapy",
"links": "[\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exercise Type\",\n\t\t\"label\": \"Exercise Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Type\",\n\t\t\"label\": \"Therapy Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Plan\",\n\t\t\"label\": \"Therapy Plan\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Session\",\n\t\t\"label\": \"Therapy Session\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment Template\",\n\t\t\"label\": \"Patient Assessment Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment\",\n\t\t\"label\": \"Patient Assessment\"\n\t}\n]"
},
{
"links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]",
"title": "Reports"
"hidden": 0,
"label": "Records and History",
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
},
{
"hidden": 0,
"label": "Reports",
"links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]"
}
],
"category": "Domains",
"charts": [
{
"chart_name": "Patient Appointments",
"label": "Patient Appointments"
}
],
"charts": [],
"charts_label": "",
"creation": "2020-03-02 17:23:17.919682",
"developer_mode_only": 0,
@ -53,7 +58,7 @@
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
"modified": "2020-03-26 16:10:44.629795",
"modified": "2020-04-20 11:42:43.889576",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",
@ -64,32 +69,32 @@
"shortcuts": [
{
"format": "{} Open",
"is_query_report": 0,
"label": "Patient Appointment",
"link_to": "Patient Appointment",
"stats_filter": "{\n \"status\": \"Open\"\n}",
"type": "DocType"
},
{
"format": "{} Active",
"is_query_report": 0,
"label": "Patient",
"link_to": "Patient",
"stats_filter": "{\n \"status\": \"Active\"\n}",
"type": "DocType"
},
{
"format": "{} Vacant",
"is_query_report": 0,
"label": "Healthcare Service Unit",
"link_to": "Healthcare Service Unit",
"stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Healthcare Practitioner",
"link_to": "Healthcare Practitioner",
"type": "DocType"
},
{
"is_query_report": 0,
"label": "Patient History",
"link_to": "patient_history",
"type": "Page"
}

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Body Part', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,45 @@
{
"actions": [],
"autoname": "field:body_part",
"creation": "2020-04-10 12:21:55.036402",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"body_part"
],
"fields": [
{
"fieldname": "body_part",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Body Part",
"reqd": 1,
"unique": 1
}
],
"links": [],
"modified": "2020-04-10 12:26:44.087985",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Body Part",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class BodyPart(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestBodyPart(unittest.TestCase):
pass

View File

@ -0,0 +1,32 @@
{
"actions": [],
"creation": "2020-04-10 12:23:15.259816",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"body_part"
],
"fields": [
{
"fieldname": "body_part",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Body Part",
"options": "Body Part",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-04-10 12:25:23.101749",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Body Part Link",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class BodyPartLink(Document):
pass

View File

@ -79,6 +79,7 @@ def create_item_from_template(doc):
if doc.is_billable and not doc.disabled:
disabled = 0
uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
item = frappe.get_doc({
'doctype': 'Item',
'item_code': doc.template,
@ -92,7 +93,7 @@ def create_item_from_template(doc):
'show_in_website': 0,
'is_pro_applicable': 0,
'disabled': disabled,
'stock_uom': 'Unit'
'stock_uom': uom
}).insert(ignore_permissions=True, ignore_mandatory=True)
make_item_price(item.name, doc.rate)

View File

@ -0,0 +1,61 @@
{
"actions": [],
"creation": "2020-03-11 09:25:00.968572",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"exercise_type",
"difficulty_level",
"counts_target",
"counts_completed",
"assistance_level"
],
"fields": [
{
"fieldname": "exercise_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Exercise Type",
"options": "Exercise Type",
"reqd": 1
},
{
"fetch_from": "exercise_type.difficulty_level",
"fieldname": "difficulty_level",
"fieldtype": "Link",
"label": "Difficulty Level",
"options": "Exercise Difficulty Level"
},
{
"fieldname": "counts_target",
"fieldtype": "Int",
"in_list_view": 1,
"label": "Counts Target"
},
{
"depends_on": "eval:doc.parenttype==\"Therapy\";",
"fieldname": "counts_completed",
"fieldtype": "Int",
"label": "Counts Completed"
},
{
"fieldname": "assistance_level",
"fieldtype": "Select",
"label": "Assistance Level",
"options": "\nPassive\nActive Assist\nActive"
}
],
"istable": 1,
"links": [],
"modified": "2020-04-10 13:41:06.662351",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Exercise",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class Exercise(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise Difficulty Level', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,45 @@
{
"actions": [],
"autoname": "field:difficulty_level",
"creation": "2020-03-29 21:12:55.835941",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"difficulty_level"
],
"fields": [
{
"fieldname": "difficulty_level",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Difficulty Level",
"reqd": 1,
"unique": 1
}
],
"links": [],
"modified": "2020-03-31 23:14:33.554066",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Exercise Difficulty Level",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class ExerciseDifficultyLevel(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestExerciseDifficultyLevel(unittest.TestCase):
pass

View File

@ -0,0 +1,180 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Exercise Type', {
refresh: function(frm) {
let wrapper = frm.fields_dict.steps_html.wrapper;
frm.ExerciseEditor = new erpnext.ExerciseEditor(frm, wrapper);
}
});
erpnext.ExerciseEditor = Class.extend({
init: function(frm, wrapper) {
this.wrapper = wrapper;
this.frm = frm;
this.make(frm, wrapper);
},
make: function(frm, wrapper) {
$(this.wrapper).empty();
this.exercise_toolbar = $('<p>\
<button class="btn btn-default btn-add btn-xs" style="margin-left: 10px;"></button>').appendTo(this.wrapper);
this.exercise_cards = $('<div class="exercise-cards"></div>').appendTo(this.wrapper);
let me = this;
this.exercise_toolbar.find(".btn-add")
.html(__('Add'))
.on("click", function() {
me.show_add_card_dialog(frm);
});
if (frm.doc.steps_table.length > 0) {
this.make_cards(frm);
this.make_buttons(frm);
}
},
make_cards: function(frm) {
var me = this;
$(me.exercise_cards).empty();
this.row = $('<div class="exercise-row"></div>').appendTo(me.exercise_cards);
$.each(frm.doc.steps_table, function(i, step) {
$(repl(`
<div class="exercise-col col-sm-4" id="%(col_id)s">
<div class="card h-100 exercise-card" id="%(card_id)s">
<div class="card-body exercise-card-body">
<img src=%(image_src)s class="card-img-top" alt="...">
<h4 class="card-title">%(title)s</h4>
<p class="card-text text-truncate">%(description)s</p>
</div>
<div class="card-footer">
<button class="btn btn-default btn-xs btn-edit" data-id="%(id)s"><i class="fa fa-pencil" aria-hidden="true"></i></button>
<button class="btn btn-default btn-xs btn-del" data-id="%(id)s"><i class="fa fa-trash" aria-hidden="true"></i></button>
</div>
</div>
</div>`, {image_src: step.image, title: step.title, description: step.description, col_id: "col-"+i, card_id: "card-"+i, id: i})).appendTo(me.row);
});
},
make_buttons: function(frm) {
let me = this;
$('.btn-edit').on('click', function() {
let id = $(this).attr('data-id');
me.show_edit_card_dialog(frm, id);
});
$('.btn-del').on('click', function() {
let id = $(this).attr('data-id');
$('#card-'+id).addClass("zoomOutDelete");
setTimeout(() => {
// not using grid_rows[id].remove because
// grid_rows is not defined when the table is hidden
frm.doc.steps_table.pop(id);
frm.refresh_field('steps_table');
$('#col-'+id).remove();
}, 300);
});
},
show_add_card_dialog: function(frm) {
let me = this;
let d = new frappe.ui.Dialog({
title: __('Add Exercise Step'),
fields: [
{
"label": "Title",
"fieldname": "title",
"fieldtype": "Data",
"reqd": 1
},
{
"label": "Attach Image",
"fieldname": "image",
"fieldtype": "Attach Image"
},
{
"label": "Step Description",
"fieldname": "step_description",
"fieldtype": "Long Text"
}
],
primary_action: function() {
let data = d.get_values();
let i = frm.doc.steps_table.length;
$(repl(`
<div class="exercise-col col-sm-4" id="%(col_id)s">
<div class="card h-100 exercise-card" id="%(card_id)s">
<div class="card-body exercise-card-body">
<img src=%(image_src)s class="card-img-top" alt="...">
<h4 class="card-title">%(title)s</h4>
<p class="card-text text-truncate">%(description)s</p>
</div>
<div class="card-footer">
<button class="btn btn-default btn-xs btn-edit" data-id="%(id)s"><i class="fa fa-pencil" aria-hidden="true"></i></button>
<button class="btn btn-default btn-xs btn-del" data-id="%(id)s"><i class="fa fa-trash" aria-hidden="true"></i></button>
</div>
</div>
</div>`, {image_src: data.image, title: data.title, description: data.step_description, col_id: "col-"+i, card_id: "card-"+i, id: i})).appendTo(me.row);
let step = frappe.model.add_child(frm.doc, 'Exercise Type Step', 'steps_table');
step.title = data.title;
step.image = data.image;
step.description = data.step_description;
me.make_buttons(frm);
frm.refresh_field('steps_table');
d.hide();
},
primary_action_label: __('Add')
});
d.show();
},
show_edit_card_dialog: function(frm, id) {
let new_dialog = new frappe.ui.Dialog({
title: __("Edit Exercise Step"),
fields: [
{
"label": "Title",
"fieldname": "title",
"fieldtype": "Data",
"reqd": 1
},
{
"label": "Attach Image",
"fieldname": "image",
"fieldtype": "Attach Image"
},
{
"label": "Step Description",
"fieldname": "step_description",
"fieldtype": "Long Text"
}
],
primary_action: () => {
let data = new_dialog.get_values();
$('#card-'+id).find('.card-title').html(data.title);
$('#card-'+id).find('img').attr('src', data.image);
$('#card-'+id).find('.card-text').html(data.step_description);
frm.doc.steps_table[id].title = data.title;
frm.doc.steps_table[id].image = data.image;
frm.doc.steps_table[id].description = data.step_description;
refresh_field('steps_table');
new_dialog.hide();
},
primary_action_label: __("Save"),
});
new_dialog.set_values({
title: frm.doc.steps_table[id].title,
image: frm.doc.steps_table[id].image,
step_description: frm.doc.steps_table[id].description
});
new_dialog.show();
}
});

View File

@ -0,0 +1,144 @@
{
"actions": [],
"creation": "2020-03-29 21:37:03.366344",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"exercise_name",
"body_parts",
"column_break_3",
"difficulty_level",
"section_break_5",
"description",
"section_break_7",
"exercise_steps",
"column_break_9",
"exercise_video",
"section_break_11",
"steps_html",
"section_break_13",
"steps_table"
],
"fields": [
{
"fieldname": "exercise_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Exercise Name",
"reqd": 1
},
{
"fieldname": "difficulty_level",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Difficulty Level",
"options": "Exercise Difficulty Level"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break"
},
{
"fieldname": "description",
"fieldtype": "Long Text",
"label": "Description"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"fieldname": "exercise_steps",
"fieldtype": "Attach",
"label": "Exercise Instructions"
},
{
"fieldname": "exercise_video",
"fieldtype": "Link",
"label": "Exercise Video",
"options": "Video"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "steps_html",
"fieldtype": "HTML",
"label": "Steps"
},
{
"fieldname": "steps_table",
"fieldtype": "Table",
"hidden": 1,
"label": "Steps Table",
"options": "Exercise Type Step"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break",
"label": "Exercise Steps"
},
{
"fieldname": "section_break_13",
"fieldtype": "Section Break"
},
{
"fieldname": "body_parts",
"fieldtype": "Table MultiSelect",
"label": "Body Parts",
"options": "Body Part Link"
}
],
"links": [],
"modified": "2020-04-21 13:05:36.555060",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Exercise Type",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"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,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class ExerciseType(Document):
def autoname(self):
if self.difficulty_level:
self.name = ' - '.join(filter(None, [self.exercise_name, self.difficulty_level]))
else:
self.name = self.exercise_name

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestExerciseType(unittest.TestCase):
pass

View File

@ -0,0 +1,44 @@
{
"actions": [],
"creation": "2020-03-31 23:01:18.761967",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"title",
"image",
"description"
],
"fields": [
{
"fieldname": "title",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Title",
"reqd": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"label": "Image"
},
{
"fieldname": "description",
"fieldtype": "Long Text",
"in_list_view": 1,
"label": "Description"
}
],
"istable": 1,
"links": [],
"modified": "2020-04-02 20:39:34.258512",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Exercise Type Step",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class ExerciseTypeStep(Document):
pass

View File

@ -74,26 +74,27 @@ class LabTestTemplate(Document):
def create_item_from_template(doc):
if doc.is_billable:
disabled = doc.disabled
if doc.is_billable and not doc.disabled:
disabled = 0
else:
disabled = 1
uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
# insert item
item = frappe.get_doc({
"doctype": "Item",
"item_code": doc.lab_test_code,
"item_name":doc.lab_test_name,
"item_group": doc.lab_test_group,
"description":doc.lab_test_description,
"is_sales_item": 1,
"is_service_item": 1,
"is_purchase_item": 0,
"is_stock_item": 0,
"show_in_website": 0,
"is_pro_applicable": 0,
"disabled": disabled,
"stock_uom": "Unit"
}).insert(ignore_permissions=True)
"doctype": "Item",
"item_code": doc.lab_test_code,
"item_name":doc.lab_test_name,
"item_group": doc.lab_test_group,
"description":doc.lab_test_description,
"is_sales_item": 1,
"is_service_item": 1,
"is_purchase_item": 0,
"is_stock_item": 0,
"show_in_website": 0,
"is_pro_applicable": 0,
"disabled": disabled,
"stock_uom": uom
}).insert(ignore_permissions=True, ignore_mandatory=True)
# insert item price
# get item price list to insert item price

View File

@ -102,6 +102,13 @@ frappe.ui.form.on('Patient Appointment', {
frm: frm,
});
}, __('Create'));
} else if (frm.doc.therapy_type) {
frm.add_custom_button(__('Therapy Session'),function(){
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session',
frm: frm,
})
}, 'Create');
} else {
frm.add_custom_button(__('Patient Encounter'), function() {
frappe.model.open_mapped_doc({
@ -123,6 +130,16 @@ frappe.ui.form.on('Patient Appointment', {
}
},
therapy_type: function(frm) {
if (frm.doc.therapy_type) {
frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => {
if (r.default_duration) {
frm.set_value('duration', r.default_duration)
}
});
}
},
get_procedure_from_encounter: function(frm) {
get_prescribed_procedure(frm);
},
@ -148,6 +165,26 @@ frappe.ui.form.on('Patient Appointment', {
}
}
});
},
get_prescribed_therapies: function(frm) {
if (frm.doc.patient) {
frappe.call({
method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies",
args: {patient: frm.doc.patient},
callback: function(r) {
if (r.message) {
show_therapy_types(frm, r.message);
} else {
frappe.msgprint({
title: __('Not Therapies Prescribed'),
message: __('There are no Therapies prescribed for Patient {0}', [frm.doc.patient.bold()]),
indicator: 'blue'
});
}
}
});
}
}
});
@ -392,6 +429,50 @@ let show_procedure_templates = function(frm, result){
d.show();
};
let show_therapy_types = function(frm, result) {
var d = new frappe.ui.Dialog({
title: __('Prescribed Therapies'),
fields: [
{
fieldtype: 'HTML', fieldname: 'therapy_type'
}
]
});
var html_field = d.fields_dict.therapy_type.$wrapper;
$.each(result, function(x, y){
var row = $(repl('<div class="col-xs-12" style="padding-top:12px; text-align:center;" >\
<div class="col-xs-5"> %(encounter)s <br> %(practitioner)s <br> %(date)s </div>\
<div class="col-xs-5"> %(therapy)s </div>\
<div class="col-xs-2">\
<a data-therapy="%(therapy)s" data-therapy-plan="%(therapy_plan)s" data-name="%(name)s"\
data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
data-date="%(date)s" data-department="%(department)s">\
<button class="btn btn-default btn-xs">Add\
</button></a></div></div><div class="col-xs-12"><hr/><div/>', {therapy:y[0],
name: y[1], encounter:y[2], practitioner:y[3], date:y[4],
department:y[6]? y[6]:'', therapy_plan:y[5]})).appendTo(html_field);
row.find("a").click(function() {
frm.doc.therapy_type = $(this).attr("data-therapy");
frm.doc.practitioner = $(this).attr("data-practitioner");
frm.doc.department = $(this).attr("data-department");
frm.doc.therapy_plan = $(this).attr("data-therapy-plan");
frm.refresh_field("therapy_type");
frm.refresh_field("practitioner");
frm.refresh_field("department");
frm.refresh_field("therapy-plan");
frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => {
if (r.default_duration) {
frm.set_value('duration', r.default_duration)
}
});
d.hide();
return false;
});
});
d.show();
};
let create_vital_signs = function(frm) {
if (!frm.doc.patient) {
frappe.throw(__('Please select patient'));

View File

@ -24,6 +24,10 @@
"column_break_13",
"procedure_template",
"procedure_prescription",
"therapy_type",
"get_prescribed_therapies",
"therapy_plan",
"service_unit",
"section_break_12",
"practitioner",
"department",
@ -271,6 +275,28 @@
"print_hide": 1,
"report_hide": 1
},
{
"depends_on": "eval:doc.patient;",
"fieldname": "therapy_type",
"fieldtype": "Link",
"label": "Therapy",
"options": "Therapy Type",
"set_only_once": 1
},
{
"depends_on": "eval:doc.patient && doc.__islocal;",
"fieldname": "get_prescribed_therapies",
"fieldtype": "Button",
"label": "Get Prescribed Therapies"
},
{
"depends_on": "eval: doc.patient && doc.therapy_type",
"fieldname": "therapy_plan",
"fieldtype": "Link",
"label": "Therapy Plan",
"mandatory_depends_on": "eval: doc.patient && doc.therapy_type",
"options": "Therapy Plan"
},
{
"fieldname": "ref_sales_invoice",
"fieldtype": "Link",

View File

@ -415,11 +415,36 @@ def get_events(start, end, filters=None):
@frappe.whitelist()
def get_procedure_prescribed(patient):
return frappe.db.sql("""select pp.name, pp.procedure, pp.parent, ct.practitioner,
ct.encounter_date, pp.practitioner, pp.date, pp.department
from `tabPatient Encounter` ct, `tabProcedure Prescription` pp
where ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0
order by ct.creation desc""", {'patient': patient})
return frappe.db.sql(
"""
SELECT
pp.name, pp.procedure, pp.parent, ct.practitioner,
ct.encounter_date, pp.practitioner, pp.date, pp.department
FROM
`tabPatient Encounter` ct, `tabProcedure Prescription` pp
WHERE
ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0
ORDER BY
ct.creation desc
""", {'patient': patient}
)
@frappe.whitelist()
def get_prescribed_therapies(patient):
return frappe.db.sql(
"""
SELECT
t.therapy_type, t.name, t.parent, e.practitioner,
e.encounter_date, e.therapy_plan, e.visit_department
FROM
`tabPatient Encounter` e, `tabTherapy Plan Detail` t
WHERE
e.patient=%(patient)s and t.parent=e.name
ORDER BY
e.creation desc
""", {'patient': patient}
)
def update_appointment_status():

View File

@ -0,0 +1,86 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Patient Assessment', {
refresh: function(frm) {
if (frm.doc.assessment_template) {
frm.trigger('set_score_range');
}
if (!frm.doc.__islocal) {
frm.trigger('show_patient_progress');
}
},
assessment_template: function(frm) {
if (frm.doc.assessment_template) {
frappe.call({
'method': 'frappe.client.get',
args: {
doctype: 'Patient Assessment Template',
name: frm.doc.assessment_template
},
callback: function(data) {
frm.doc.assessment_sheet = [];
$.each(data.message.parameters, function(_i, e) {
let entry = frm.add_child('assessment_sheet');
entry.parameter = e.assessment_parameter;
});
frm.set_value('scale_min', data.message.scale_min);
frm.set_value('scale_max', data.message.scale_max);
frm.set_value('assessment_description', data.message.assessment_description);
frm.set_value('total_score', data.message.scale_max * data.message.parameters.length);
frm.trigger('set_score_range');
refresh_field('assessment_sheet');
}
});
}
},
set_score_range: function(frm) {
let options = [];
for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) {
options.push(i);
}
frappe.meta.get_docfield('Patient Assessment Sheet', 'score', frm.doc.name).options = [''].concat(options);
},
calculate_total_score: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
let total_score = 0;
$.each(frm.doc.assessment_sheet || [], function(_i, item) {
if (item.score) {
total_score += parseInt(item.score);
}
});
frm.set_value('total_score_obtained', total_score);
},
show_patient_progress: function(frm) {
let bars = [];
let message = '';
let added_min = false;
let title = __('{0} out of {1}', [frm.doc.total_score_obtained, frm.doc.total_score]);
bars.push({
'title': title,
'width': (frm.doc.total_score_obtained / frm.doc.total_score * 100) + '%',
'progress_class': 'progress-bar-success'
});
if (bars[0].width == '0%') {
bars[0].width = '0.5%';
added_min = 0.5;
}
message = title;
frm.dashboard.add_progress(__('Status'), bars, message);
},
});
frappe.ui.form.on('Patient Assessment Sheet', {
score: function(frm, cdt, cdn) {
frm.events.calculate_total_score(frm, cdt, cdn);
}
});

View File

@ -0,0 +1,172 @@
{
"actions": [],
"autoname": "naming_series:",
"creation": "2020-04-19 22:45:12.356209",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"therapy_session",
"patient",
"assessment_template",
"column_break_4",
"healthcare_practitioner",
"assessment_datetime",
"assessment_description",
"section_break_7",
"assessment_sheet",
"section_break_9",
"total_score_obtained",
"column_break_11",
"total_score",
"scale_min",
"scale_max",
"amended_from"
],
"fields": [
{
"fetch_from": "therapy_session.patient",
"fieldname": "patient",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Patient",
"options": "Patient",
"reqd": 1
},
{
"fieldname": "assessment_template",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Assessment Template",
"options": "Patient Assessment Template",
"reqd": 1
},
{
"fieldname": "therapy_session",
"fieldtype": "Link",
"label": "Therapy Session",
"options": "Therapy Session"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fetch_from": "therapy_session.practitioner",
"fieldname": "healthcare_practitioner",
"fieldtype": "Link",
"label": "Healthcare Practitioner",
"options": "Healthcare Practitioner"
},
{
"fieldname": "assessment_datetime",
"fieldtype": "Datetime",
"label": "Assessment Datetime"
},
{
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"fieldname": "assessment_sheet",
"fieldtype": "Table",
"label": "Assessment Sheet",
"options": "Patient Assessment Sheet"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"fieldname": "total_score",
"fieldtype": "Int",
"label": "Total Score",
"read_only": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "total_score_obtained",
"fieldtype": "Int",
"label": "Total Score Obtained",
"read_only": 1
},
{
"fieldname": "scale_min",
"fieldtype": "Int",
"hidden": 1,
"label": "Scale Min",
"read_only": 1
},
{
"fieldname": "scale_max",
"fieldtype": "Int",
"hidden": 1,
"label": "Scale Max",
"read_only": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "HLC-PA-.YYYY.-"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Patient Assessment",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "assessment_description",
"fieldtype": "Small Text",
"label": "Assessment Description"
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-21 13:23:09.815007",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment",
"owner": "Administrator",
"permissions": [
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Physician",
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "patient",
"track_changes": 1
}

View File

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
class PatientAssessment(Document):
def validate(self):
self.set_total_score()
def set_total_score(self):
total_score = 0
for entry in self.assessment_sheet:
total_score += int(entry.score)
self.total_score_obtained = total_score
@frappe.whitelist()
def create_patient_assessment(source_name, target_doc=None):
doc = get_mapped_doc('Therapy Session', source_name, {
'Therapy Session': {
'doctype': 'Patient Assessment',
'field_map': [
['therapy_session', 'name'],
['patient', 'patient'],
['practitioner', 'practitioner']
]
}
}, target_doc)
return doc

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestPatientAssessment(unittest.TestCase):
pass

View File

@ -0,0 +1,32 @@
{
"actions": [],
"creation": "2020-04-19 19:33:00.115395",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"assessment_parameter"
],
"fields": [
{
"fieldname": "assessment_parameter",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Assessment Parameter",
"options": "Patient Assessment Parameter",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-04-19 19:33:00.115395",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment Detail",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PatientAssessmentDetail(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Patient Assessment Parameter', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,45 @@
{
"actions": [],
"autoname": "field:assessment_parameter",
"creation": "2020-04-15 14:34:46.551042",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"assessment_parameter"
],
"fields": [
{
"fieldname": "assessment_parameter",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Assessment Parameter",
"reqd": 1,
"unique": 1
}
],
"links": [],
"modified": "2020-04-20 09:22:19.135196",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment Parameter",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PatientAssessmentParameter(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestPatientAssessmentParameter(unittest.TestCase):
pass

View File

@ -0,0 +1,57 @@
{
"actions": [],
"creation": "2020-04-19 23:07:02.220244",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"parameter",
"score",
"time",
"column_break_4",
"comments"
],
"fields": [
{
"fieldname": "parameter",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Parameter",
"options": "Patient Assessment Parameter",
"reqd": 1
},
{
"fieldname": "score",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Score",
"reqd": 1
},
{
"fieldname": "time",
"fieldtype": "Time",
"label": "Time"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "comments",
"fieldtype": "Small Text",
"label": "Comments"
}
],
"istable": 1,
"links": [],
"modified": "2020-04-20 09:56:28.746619",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment Sheet",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PatientAssessmentSheet(Document):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Patient Assessment Template', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,109 @@
{
"actions": [],
"autoname": "field:assessment_name",
"creation": "2020-04-19 19:33:13.204707",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"assessment_name",
"section_break_2",
"parameters",
"assessment_scale_details_section",
"scale_min",
"scale_max",
"column_break_8",
"assessment_description"
],
"fields": [
{
"fieldname": "parameters",
"fieldtype": "Table",
"label": "Parameters",
"options": "Patient Assessment Detail"
},
{
"fieldname": "assessment_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Assessment Name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break",
"label": "Assessment Parameters"
},
{
"fieldname": "assessment_scale_details_section",
"fieldtype": "Section Break",
"label": "Assessment Scale"
},
{
"fieldname": "scale_min",
"fieldtype": "Int",
"label": "Scale Minimum"
},
{
"fieldname": "scale_max",
"fieldtype": "Int",
"label": "Scale Maximum"
},
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"fieldname": "assessment_description",
"fieldtype": "Small Text",
"label": "Assessment Description"
}
],
"links": [],
"modified": "2020-04-21 13:14:19.075167",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment Template",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Healthcare Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Physician",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class PatientAssessmentTemplate(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestPatientAssessmentTemplate(unittest.TestCase):
pass

View File

@ -3,6 +3,10 @@
frappe.ui.form.on('Patient Encounter', {
setup: function(frm) {
frm.get_field('therapies').grid.editable_fields = [
{fieldname: 'therapy_type', columns: 8},
{fieldname: 'no_of_sessions', columns: 2}
];
frm.get_field('drug_prescription').grid.editable_fields = [
{fieldname: 'drug_code', columns: 2},
{fieldname: 'drug_name', columns: 2},

View File

@ -42,6 +42,10 @@
"lab_test_prescription",
"sb_procedures",
"procedure_prescription",
"rehabilitation_section",
"therapy_plan",
"therapies",
"section_break_33",
"encounter_comment",
"sb_refs",
"company",
@ -255,6 +259,29 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "rehabilitation_section",
"fieldtype": "Section Break",
"label": "Rehabilitation"
},
{
"fieldname": "therapies",
"fieldtype": "Table",
"label": "Therapies",
"options": "Therapy Plan Detail"
},
{
"fieldname": "section_break_33",
"fieldtype": "Section Break"
},
{
"fieldname": "therapy_plan",
"fieldtype": "Link",
"hidden": 1,
"label": "Therapy Plan",
"options": "Therapy Plan",
"read_only": 1
},
{
"fieldname": "appointment_type",
"fieldtype": "Link",

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cstr
from frappe import _
@ -22,6 +23,24 @@ class PatientEncounter(Document):
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
delete_medical_record(self)
def on_submit(self):
create_therapy_plan(self)
def create_therapy_plan(encounter):
if len(encounter.therapies):
doc = frappe.new_doc('Therapy Plan')
doc.patient = encounter.patient
doc.start_date = encounter.encounter_date
for entry in encounter.therapies:
doc.append('therapy_plan_details', {
'therapy_type': entry.therapy_type,
'no_of_sessions': entry.no_of_sessions
})
doc.save(ignore_permissions=True)
if doc.get('name'):
encounter.db_set('therapy_plan', doc.name)
frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True)
def insert_encounter_to_medical_record(doc):
subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record')

View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
from frappe.utils import getdate
from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session
from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient
class TestTherapyPlan(unittest.TestCase):
def test_creation_on_encounter_submission(self):
patient, medical_department, practitioner = create_healthcare_docs()
encounter = create_encounter(patient, medical_department, practitioner)
self.assertTrue(frappe.db.exists('Therapy Plan', encounter.therapy_plan))
def test_status(self):
plan = create_therapy_plan()
self.assertEquals(plan.status, 'Not Started')
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab')
frappe.get_doc(session).submit()
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress')
session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab')
frappe.get_doc(session).submit()
self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
def create_therapy_plan():
patient = create_patient()
therapy_type = create_therapy_type()
plan = frappe.new_doc('Therapy Plan')
plan.patient = patient
plan.start_date = getdate()
plan.append('therapy_plan_details', {
'therapy_type': therapy_type.name,
'no_of_sessions': 2
})
plan.save()
return plan
def create_encounter(patient, medical_department, practitioner):
encounter = frappe.new_doc('Patient Encounter')
encounter.patient = patient
encounter.practitioner = practitioner
encounter.medical_department = medical_department
therapy_type = create_therapy_type()
encounter.append('therapies', {
'therapy_type': therapy_type.name,
'no_of_sessions': 2
})
encounter.save()
encounter.submit()
return encounter

View File

@ -0,0 +1,90 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Therapy Plan', {
setup: function(frm) {
frm.get_field('therapy_plan_details').grid.editable_fields = [
{fieldname: 'therapy_type', columns: 6},
{fieldname: 'no_of_sessions', columns: 2},
{fieldname: 'sessions_completed', columns: 2}
];
},
refresh: function(frm) {
if (!frm.doc.__islocal) {
frm.trigger('show_progress_for_therapies');
}
if (!frm.doc.__islocal && frm.doc.status != 'Completed') {
let therapy_types = (frm.doc.therapy_plan_details || []).map(function(d){ return d.therapy_type });
const fields = [{
fieldtype: 'Link',
label: __('Therapy Type'),
fieldname: 'therapy_type',
options: 'Therapy Type',
reqd: 1,
get_query: function() {
return {
filters: { 'therapy_type': ['in', therapy_types]}
}
}
}];
frm.add_custom_button(__('Therapy Session'), function() {
frappe.prompt(fields, data => {
frappe.call({
method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_therapy_session',
args: {
therapy_plan: frm.doc.name,
patient: frm.doc.patient,
therapy_type: data.therapy_type
},
freeze: true,
callback: function(r) {
if (r.message) {
frappe.model.sync(r.message);
frappe.set_route('Form', r.message.doctype, r.message.name);
}
}
});
}, __('Select Therapy Type'), __('Create'));
}, __('Create'));
}
},
show_progress_for_therapies: function(frm) {
let bars = [];
let message = '';
let added_min = false;
// completed sessions
let title = __('{0} sessions completed', [frm.doc.total_sessions_completed]);
if (frm.doc.total_sessions_completed === 1) {
title = __('{0} session completed', [frm.doc.total_sessions_completed]);
}
title += __(' out of {0}', [frm.doc.total_sessions]);
bars.push({
'title': title,
'width': (frm.doc.total_sessions_completed / frm.doc.total_sessions * 100) + '%',
'progress_class': 'progress-bar-success'
});
if (bars[0].width == '0%') {
bars[0].width = '0.5%';
added_min = 0.5;
}
message = title;
frm.dashboard.add_progress(__('Status'), bars, message);
},
});
frappe.ui.form.on('Therapy Plan Detail', {
no_of_sessions: function(frm) {
let total = 0;
$.each(frm.doc.therapy_plan_details, function(_i, e) {
total += e.no_of_sessions;
});
frm.set_value('total_sessions', total);
refresh_field('total_sessions');
}
});

View File

@ -0,0 +1,151 @@
{
"actions": [],
"autoname": "naming_series:",
"creation": "2020-03-29 20:56:49.758602",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"patient",
"patient_name",
"column_break_4",
"status",
"start_date",
"section_break_3",
"therapy_plan_details",
"title",
"section_break_9",
"total_sessions",
"column_break_11",
"total_sessions_completed"
],
"fields": [
{
"fieldname": "patient",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Patient",
"options": "Patient",
"reqd": 1
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Start Date",
"reqd": 1
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break"
},
{
"fieldname": "therapy_plan_details",
"fieldtype": "Table",
"label": "Therapy Plan Details",
"options": "Therapy Plan Detail",
"reqd": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "HLC-THP-.YYYY.-"
},
{
"fetch_from": "patient.patient_name",
"fieldname": "patient_name",
"fieldtype": "Data",
"label": "Patient Name",
"read_only": 1
},
{
"default": "{patient_name}",
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"label": "Title",
"no_copy": 1
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"fieldname": "total_sessions",
"fieldtype": "Int",
"label": "Total Sessions",
"read_only": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "total_sessions_completed",
"fieldtype": "Int",
"label": "Total Sessions Completed",
"read_only": 1
},
{
"fieldname": "status",
"fieldtype": "Select",
"label": "Status",
"options": "Not Started\nIn Progress\nCompleted\nCancelled",
"read_only": 1
}
],
"links": [],
"modified": "2020-04-21 13:13:43.956014",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Plan",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"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,
"write": 1
}
],
"quick_entry": 1,
"search_fields": "patient",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "patient",
"track_changes": 1
}

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
class TherapyPlan(Document):
def validate(self):
self.set_totals()
self.set_status()
def set_status(self):
if not self.total_sessions_completed:
self.status = 'Not Started'
else:
if self.total_sessions_completed < self.total_sessions:
self.status = 'In Progress'
elif self.total_sessions_completed == self.total_sessions:
self.status = 'Completed'
def set_totals(self):
total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')])
total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')])
self.db_set('total_sessions', total_sessions)
self.db_set('total_sessions_completed', total_sessions_completed)
@frappe.whitelist()
def make_therapy_session(therapy_plan, patient, therapy_type):
therapy_type = frappe.get_doc('Therapy Type', therapy_type)
therapy_session = frappe.new_doc('Therapy Session')
therapy_session.therapy_plan = therapy_plan
therapy_session.patient = patient
therapy_session.therapy_type = therapy_type.name
therapy_session.duration = therapy_type.default_duration
therapy_session.rate = therapy_type.rate
therapy_session.exercises = therapy_type.exercises
return therapy_session.as_dict()

View File

@ -0,0 +1,13 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'therapy_plan',
'transactions': [
{
'label': _('Therapy Sessions'),
'items': ['Therapy Session']
}
]
}

View File

@ -0,0 +1,11 @@
frappe.listview_settings['Therapy Plan'] = {
get_indicator: function(doc) {
var colors = {
'Completed': 'green',
'In Progress': 'orange',
'Not Started': 'red',
'Cancelled': 'grey'
};
return [__(doc.status), colors[doc.status], 'status,=,' + doc.status];
}
};

View File

@ -0,0 +1,48 @@
{
"actions": [],
"creation": "2020-03-29 20:52:57.068731",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"therapy_type",
"no_of_sessions",
"sessions_completed"
],
"fields": [
{
"fieldname": "therapy_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Therapy Type",
"options": "Therapy Type",
"reqd": 1
},
{
"fieldname": "no_of_sessions",
"fieldtype": "Int",
"in_list_view": 1,
"label": "No of Sessions"
},
{
"default": "0",
"depends_on": "eval:doc.parenttype=='Therapy Plan';",
"fieldname": "sessions_completed",
"fieldtype": "Int",
"label": "Sessions Completed",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2020-03-30 22:02:01.740109",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Plan Detail",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class TherapyPlanDetail(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestTherapySession(unittest.TestCase):
pass

View File

@ -0,0 +1,60 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Therapy Session', {
setup: function(frm) {
frm.get_field('exercises').grid.editable_fields = [
{fieldname: 'exercise_type', columns: 7},
{fieldname: 'counts_target', columns: 1},
{fieldname: 'counts_completed', columns: 1},
{fieldname: 'assistance_level', columns: 1}
];
},
refresh: function(frm) {
if (!frm.doc.__islocal) {
let target = 0;
let completed = 0;
$.each(frm.doc.exercises, function(_i, e) {
target += e.counts_target;
completed += e.counts_completed;
});
frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue');
frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green');
}
if (frm.doc.docstatus === 1) {
frm.add_custom_button(__('Patient Assessment'),function() {
frappe.model.open_mapped_doc({
method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment',
frm: frm,
})
}, 'Create');
}
},
therapy_type: function(frm) {
if (frm.doc.therapy_type) {
frappe.call({
'method': 'frappe.client.get',
args: {
doctype: 'Therapy Type',
name: frm.doc.therapy_type
},
callback: function(data) {
frm.set_value('duration', data.message.default_duration);
frm.set_value('rate', data.message.rate);
frm.doc.exercises = [];
$.each(data.message.exercises, function(_i, e) {
let exercise = frm.add_child('exercises');
exercise.exercise_type = e.exercise_type;
exercise.difficulty_level = e.difficulty_level;
exercise.counts_target = e.counts_target;
exercise.assistance_level = e.assistance_level;
});
refresh_field('exercises');
}
});
}
}
});

View File

@ -0,0 +1,218 @@
{
"actions": [],
"autoname": "naming_series:",
"creation": "2020-03-11 08:57:40.669857",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"appointment",
"patient",
"patient_age",
"gender",
"column_break_5",
"therapy_plan",
"therapy_type",
"practitioner",
"department",
"details_section",
"duration",
"rate",
"location",
"company",
"column_break_12",
"service_unit",
"start_date",
"start_time",
"invoiced",
"exercises_section",
"exercises",
"amended_from"
],
"fields": [
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "HLC-THP-.YYYY.-"
},
{
"fieldname": "appointment",
"fieldtype": "Link",
"label": "Appointment",
"options": "Patient Appointment"
},
{
"fieldname": "patient",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Patient",
"options": "Patient",
"reqd": 1
},
{
"fetch_from": "patient.sex",
"fieldname": "gender",
"fieldtype": "Link",
"label": "Gender",
"options": "Gender",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "practitioner",
"fieldtype": "Link",
"label": "Healthcare Practitioner",
"options": "Healthcare Practitioner"
},
{
"fieldname": "department",
"fieldtype": "Link",
"label": "Medical Department",
"options": "Medical Department"
},
{
"fieldname": "details_section",
"fieldtype": "Section Break",
"label": "Details"
},
{
"fetch_from": "therapy_template.default_duration",
"fieldname": "duration",
"fieldtype": "Int",
"label": "Duration"
},
{
"fieldname": "location",
"fieldtype": "Select",
"label": "Location",
"options": "\nCenter\nHome\nTele"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fetch_from": "therapy_template.rate",
"fieldname": "rate",
"fieldtype": "Currency",
"label": "Rate"
},
{
"fieldname": "exercises_section",
"fieldtype": "Section Break",
"label": "Exercises"
},
{
"fieldname": "exercises",
"fieldtype": "Table",
"label": "Exercises",
"options": "Exercise"
},
{
"depends_on": "eval: doc.therapy_plan",
"fieldname": "therapy_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Therapy Type",
"options": "Therapy Type",
"reqd": 1
},
{
"fieldname": "therapy_plan",
"fieldtype": "Link",
"label": "Therapy Plan",
"options": "Therapy Plan",
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Therapy Session",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "service_unit",
"fieldtype": "Link",
"label": "Healthcare Service Unit",
"options": "Healthcare Service Unit"
},
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date"
},
{
"fieldname": "start_time",
"fieldtype": "Time",
"label": "Start Time"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"default": "0",
"fieldname": "invoiced",
"fieldtype": "Check",
"label": "Invoiced",
"read_only": 1
},
{
"fieldname": "patient_age",
"fieldtype": "Data",
"label": "Patient Age",
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-21 13:16:46.378798",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Session",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Physician",
"share": 1,
"submit": 1,
"write": 1
}
],
"quick_entry": 1,
"search_fields": "patient,appointment,therapy_plan,therapy_type",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "patient",
"track_changes": 1
}

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
class TherapySession(Document):
def on_submit(self):
self.update_sessions_count_in_therapy_plan()
def on_cancel(self):
self.update_sessions_count_in_therapy_plan(on_cancel=True)
def update_sessions_count_in_therapy_plan(self, on_cancel=False):
therapy_plan = frappe.get_doc('Therapy Plan', self.therapy_plan)
for entry in therapy_plan.therapy_plan_details:
if entry.therapy_type == self.therapy_type:
if on_cancel:
entry.sessions_completed -= 1
else:
entry.sessions_completed += 1
therapy_plan.save()
@frappe.whitelist()
def create_therapy_session(source_name, target_doc=None):
def set_missing_values(source, target):
therapy_type = frappe.get_doc('Therapy Type', source.therapy_type)
target.exercises = therapy_type.exercises
doc = get_mapped_doc('Patient Appointment', source_name, {
'Patient Appointment': {
'doctype': 'Therapy Session',
'field_map': [
['appointment', 'name'],
['patient', 'patient'],
['patient_age', 'patient_age'],
['gender', 'patient_sex'],
['therapy_type', 'therapy_type'],
['therapy_plan', 'therapy_plan'],
['practitioner', 'practitioner'],
['department', 'department'],
['start_date', 'appointment_date'],
['start_time', 'appointment_time'],
['service_unit', 'service_unit'],
['company', 'company'],
['invoiced', 'invoiced']
]
}
}, target_doc, set_missing_values)
return doc

View File

@ -0,0 +1,13 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'therapy_session',
'transactions': [
{
'label': _('Assessments'),
'items': ['Patient Assessment']
}
]
}

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestTherapyType(unittest.TestCase):
def test_therapy_type_item(self):
therapy_type = create_therapy_type()
self.assertTrue(frappe.db.exists('Item', therapy_type.item))
therapy_type.disabled = 1
therapy_type.save()
self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1)
def create_therapy_type():
exercise = create_exercise_type()
therapy_type = frappe.db.exists('Therapy Type', 'Basic Rehab')
if not therapy_type:
therapy_type = frappe.new_doc('Therapy Type')
therapy_type.therapy_type = 'Basic Rehab'
therapy_type.default_duration = 30
therapy_type.is_billable = 1
therapy_type.rate = 5000
therapy_type.item_code = 'Basic Rehab'
therapy_type.item_name = 'Basic Rehab'
therapy_type.item_group = 'Services'
therapy_type.append('exercises', {
'exercise_type': exercise.name,
'counts_target': 10,
'assistance_level': 'Passive'
})
therapy_type.save()
else:
therapy_type = frappe.get_doc('Therapy Type', 'Basic Rehab')
return therapy_type
def create_exercise_type():
exercise_type = frappe.db.exists('Exercise Type', 'Sit to Stand')
if not exercise_type:
exercise_type = frappe.new_doc('Exercise Type')
exercise_type.exercise_name = 'Sit to Stand'
exercise_type.append('steps_table', {
'title': 'Step 1',
'description': 'Squat and Rise'
})
exercise_type.save()
return exercise_type

View File

@ -0,0 +1,93 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Therapy Type', {
setup: function(frm) {
frm.get_field('exercises').grid.editable_fields = [
{fieldname: 'exercise_type', columns: 7},
{fieldname: 'difficulty_level', columns: 1},
{fieldname: 'counts_target', columns: 1},
{fieldname: 'assistance_level', columns: 1}
];
},
refresh: function(frm) {
if (!frm.doc.__islocal) {
cur_frm.add_custom_button(__('Change Item Code'), function() {
change_template_code(frm.doc);
});
}
},
therapy_type: function(frm) {
if (!frm.doc.item_code)
frm.set_value('item_code', frm.doc.therapy_type);
if (!frm.doc.description)
frm.set_value('description', frm.doc.therapy_type);
mark_change_in_item(frm);
},
rate: function(frm) {
mark_change_in_item(frm);
},
is_billable: function (frm) {
mark_change_in_item(frm);
},
item_group: function(frm) {
mark_change_in_item(frm);
},
description: function(frm) {
mark_change_in_item(frm);
},
medical_department: function(frm) {
mark_change_in_item(frm);
}
});
let mark_change_in_item = function(frm) {
if (!frm.doc.__islocal) {
frm.doc.change_in_item = 1;
}
};
let change_template_code = function(doc) {
let d = new frappe.ui.Dialog({
title:__('Change Item Code'),
fields:[
{
'fieldtype': 'Data',
'label': 'Item Code',
'fieldname': 'item_code',
reqd: 1
}
],
primary_action: function() {
let values = d.get_values();
if (values) {
frappe.call({
'method': 'erpnext.healthcare.doctype.therapy_type.therapy_type.change_item_code_from_therapy',
'args': {item_code: values.item_code, doc: doc},
callback: function () {
cur_frm.reload_doc();
frappe.show_alert({
message: 'Item Code renamed successfully',
indicator: 'green'
});
}
});
}
d.hide();
},
primary_action_label: __('Change Item Code')
});
d.show();
d.set_values({
'item_code': doc.item_code
});
};

View File

@ -0,0 +1,211 @@
{
"actions": [],
"autoname": "field:therapy_type",
"creation": "2020-03-29 20:48:31.715063",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"disabled",
"section_break_2",
"therapy_type",
"default_duration",
"medical_department",
"column_break_3",
"is_billable",
"rate",
"healthcare_service_unit",
"item_details_section",
"item",
"item_code",
"item_name",
"item_group",
"column_break_12",
"description",
"section_break_18",
"therapy_for",
"add_exercises",
"section_break_6",
"exercises",
"change_in_item"
],
"fields": [
{
"fieldname": "therapy_type",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Therapy Type",
"reqd": 1,
"unique": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "is_billable",
"fieldtype": "Check",
"label": "Is Billable"
},
{
"depends_on": "eval:doc.is_billable;",
"fieldname": "rate",
"fieldtype": "Currency",
"label": "Rate",
"mandatory_depends_on": "eval:doc.is_billable;"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"label": "Exercises"
},
{
"fieldname": "exercises",
"fieldtype": "Table",
"label": "Exercises",
"options": "Exercise"
},
{
"fieldname": "default_duration",
"fieldtype": "Int",
"label": "Default Duration (In Minutes)"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "item_details_section",
"fieldtype": "Section Break",
"label": "Item Details"
},
{
"fieldname": "item",
"fieldtype": "Link",
"label": "Item",
"options": "Item",
"read_only": 1
},
{
"fieldname": "item_code",
"fieldtype": "Data",
"label": "Item Code",
"reqd": 1,
"set_only_once": 1
},
{
"fieldname": "item_group",
"fieldtype": "Link",
"label": "Item Group",
"options": "Item Group",
"reqd": 1
},
{
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"reqd": 1
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"fieldname": "description",
"fieldtype": "Small Text",
"label": "Description"
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "medical_department",
"fieldtype": "Link",
"label": "Medical Department",
"options": "Medical Department"
},
{
"default": "0",
"fieldname": "change_in_item",
"fieldtype": "Check",
"hidden": 1,
"label": "Change In Item",
"print_hide": 1,
"read_only": 1,
"report_hide": 1
},
{
"fieldname": "therapy_for",
"fieldtype": "Table MultiSelect",
"label": "Therapy For",
"options": "Body Part Link"
},
{
"fieldname": "healthcare_service_unit",
"fieldtype": "Link",
"label": "Healthcare Service Unit",
"options": "Healthcare Service Unit"
},
{
"depends_on": "eval: doc.therapy_for",
"fieldname": "add_exercises",
"fieldtype": "Button",
"label": "Add Exercises",
"options": "add_exercises"
},
{
"fieldname": "section_break_18",
"fieldtype": "Section Break"
}
],
"links": [],
"modified": "2020-04-21 13:09:04.006289",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Type",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"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,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
import json
from frappe import _
from frappe.utils import cint
from frappe.model.document import Document
from frappe.model.rename_doc import rename_doc
class TherapyType(Document):
def validate(self):
self.enable_disable_item()
def after_insert(self):
create_item_from_therapy(self)
def on_update(self):
if self.change_in_item:
self.update_item_and_item_price()
def enable_disable_item(self):
if self.is_billable:
if self.disabled:
frappe.db.set_value('Item', self.item, 'disabled', 1)
else:
frappe.db.set_value('Item', self.item, 'disabled', 0)
def update_item_and_item_price(self):
if self.is_billable and self.item:
item_doc = frappe.get_doc('Item', {'item_code': self.item})
item_doc.item_name = self.item_name
item_doc.item_group = self.item_group
item_doc.description = self.description
item_doc.disabled = 0
item_doc.ignore_mandatory = True
item_doc.save(ignore_permissions=True)
if self.rate:
item_price = frappe.get_doc('Item Price', {'item_code': self.item})
item_price.item_name = self.item_name
item_price.price_list_name = self.rate
item_price.ignore_mandatory = True
item_price.save()
elif not self.is_billable and self.item:
frappe.db.set_value('Item', self.item, 'disabled', 1)
self.db_set('change_in_item', 0)
def add_exercises(self):
exercises = self.get_exercises_for_body_parts()
last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,])
for i, d in enumerate(exercises):
ch = self.append('exercises', {})
ch.exercise_type = d.parent
ch.idx = last_idx + i + 1
def get_exercises_for_body_parts(self):
body_parts = [entry.body_part for entry in self.therapy_for]
exercises = frappe.db.sql(
"""
SELECT DISTINCT
b.parent, e.name, e.difficulty_level
FROM
`tabExercise Type` e, `tabBody Part Link` b
WHERE
b.body_part IN %(body_parts)s AND b.parent=e.name
""", {'body_parts': body_parts}, as_dict=1)
return exercises
def create_item_from_therapy(doc):
disabled = doc.disabled
if doc.is_billable and not doc.disabled:
disabled = 0
uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
item = frappe.get_doc({
'doctype': 'Item',
'item_code': doc.item_code,
'item_name': doc.item_name,
'item_group': doc.item_group,
'description': doc.description,
'is_sales_item': 1,
'is_service_item': 1,
'is_purchase_item': 0,
'is_stock_item': 0,
'show_in_website': 0,
'is_pro_applicable': 0,
'disabled': disabled,
'stock_uom': uom
}).insert(ignore_permissions=True, ignore_mandatory=True)
make_item_price(item.name, doc.rate)
doc.db_set('item', item.name)
def make_item_price(item, item_price):
price_list_name = frappe.db.get_value('Price List', {'selling': 1})
frappe.get_doc({
'doctype': 'Item Price',
'price_list': price_list_name,
'item_code': item,
'price_list_rate': item_price
}).insert(ignore_permissions=True, ignore_mandatory=True)
@frappe.whitelist()
def change_item_code_from_therapy(item_code, doc):
doc = frappe._dict(json.loads(doc))
if frappe.db.exists('Item', {'item_code': item_code}):
frappe.throw(_('Item with Item Code {0} already exists').format(item_code))
else:
rename_doc('Item', doc.item, item_code, ignore_permissions=True)
frappe.db.set_value('Therapy Type', doc.name, 'item_code', item_code)
return

View File

@ -23,6 +23,8 @@ def get_healthcare_services_to_invoice(patient, company):
items_to_invoice += get_lab_tests_to_invoice(patient, company)
items_to_invoice += get_clinical_procedures_to_invoice(patient, company)
items_to_invoice += get_inpatient_services_to_invoice(patient, company)
items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
return items_to_invoice
@ -245,6 +247,25 @@ def get_inpatient_services_to_invoice(patient, company):
return services_to_invoice
def get_therapy_sessions_to_invoice(patient, company):
therapy_sessions_to_invoice = []
therapy_sessions = frappe.get_list(
'Therapy Session',
fields='*',
filters={'patient': patient.name, 'invoiced': 0, 'company': company}
)
for therapy in therapy_sessions:
if not therapy.appointment:
if therapy.therapy_type and frappe.db.get_value('Therapy Type', therapy.therapy_type, 'is_billable'):
therapy_sessions_to_invoice.append({
'reference_type': 'Therapy Session',
'reference_name': therapy.name,
'service': frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item')
})
return therapy_sessions_to_invoice
def get_service_item_and_practitioner_charge(doc):
is_inpatient = doc.inpatient_record
if is_inpatient:

View File

@ -776,22 +776,16 @@ class SalarySlip(TransactionBase):
for payment in self.get('loans'):
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment")
total_amount = amounts['interest_amount'] + amounts['payable_principal_amount']
if payment.total_payment > total_amount:
frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2}
against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment),
frappe.bold(total_amount), frappe.bold(payment.loan)))
if payment.interest_amount > amounts['interest_amount']:
frappe.throw(_("""Row {0}: Paid Interest amount {1} is greater than pending interest amount {2}
against loan {3}""").format(payment.idx, frappe.bold(payment.interest_amount),
frappe.bold(amounts['interest_amount']), frappe.bold(payment.loan)))
if payment.principal_amount > amounts['payable_principal_amount']:
frappe.throw(_("""Row {0}: Paid Principal amount {1} is greater than pending principal amount {2}
against loan {3}""").format(payment.idx, frappe.bold(payment.principal_amount),
frappe.bold(amounts['payable_principal_amount']), frappe.bold(payment.loan)))
payment.total_payment = payment.interest_amount + payment.principal_amount
self.total_interest_amount += payment.interest_amount
self.total_principal_amount += payment.principal_amount
self.total_loan_repayment = self.total_interest_amount + self.total_principal_amount
self.total_loan_repayment += payment.total_payment
def get_loan_details(self):

View File

@ -149,13 +149,19 @@ class TestLoan(unittest.TestCase):
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
repayment_entry.save()
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year))
self.assertEquals(flt(repayment_entry.interest_payable, 2), flt(accrued_interest_amount, 2))
self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2))
repayment_entry.submit()
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
'paid_principal_amount'])
loan.load_from_db()
self.assertEquals(amounts[0], repayment_entry.interest_payable)
self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid -
penalty_amount - amounts[0], 2))
def test_loan_closure_repayment(self):
pledges = []
@ -189,15 +195,19 @@ class TestLoan(unittest.TestCase):
process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
"Loan Closure", 13315.0681)
repayment_entry.save()
"Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
repayment_entry.submit()
repayment_entry.amount_paid = repayment_entry.payable_amount
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
'paid_principal_amount'])
self.assertEquals(flt(repayment_entry.interest_payable, 3), flt(accrued_interest_amount, 3))
unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \
/ (days_in_year(get_datetime(first_date).year) * 100)
self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3),
flt(accrued_interest_amount, 3))
self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
repayment_entry.submit()
loan.load_from_db()
self.assertEquals(loan.status, "Loan Closure Requested")
@ -227,57 +237,15 @@ class TestLoan(unittest.TestCase):
process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5),
"Regular Payment", 89768.7534247)
"Regular Payment", 89768.75)
repayment_entry.save()
repayment_entry.submit()
repayment_entry.load_from_db()
amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount',
'paid_principal_amount'])
self.assertEquals(repayment_entry.interest_payable, 11250.00)
self.assertEquals(repayment_entry.payable_principal_amount, 78303.00)
def test_partial_loan_repayment(self):
pledges = []
pledges.append({
"loan_security": "Test Security 1",
"qty": 4000.00,
"haircut": 50
})
loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges)
loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name,
posting_date=get_first_day(nowdate()))
loan.submit()
self.assertEquals(loan.loan_amount, 1000000)
first_date = '2019-10-01'
last_date = '2019-10-30'
no_of_days = date_diff(last_date, first_date) + 1
accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \
/ (days_in_year(get_datetime().year) * 100)
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 15))
process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 30))
repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 1), "Regular Payment", 6500)
repayment_entry.save()
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year))
lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name')
lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name')
self.assertTrue(lia1)
self.assertTrue(lia2)
self.assertEquals(amounts[0], 11250.00)
self.assertEquals(amounts[1], 78303.00)
def test_security_shortfall(self):
pledges = []
@ -294,7 +262,7 @@ class TestLoan(unittest.TestCase):
make_loan_disbursement_entry(loan.name, loan.loan_amount)
frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 100
frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100
where loan_security='Test Security 2'""")
create_process_loan_security_shortfall()

Some files were not shown because too many files have changed in this diff Show More