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.account_name = "Accumulated Depreciation"
acc.parent_account = "Fixed Assets - _TC" acc.parent_account = "Fixed Assets - _TC"
acc.company = "_Test Company" acc.company = "_Test Company"
acc.account_type = "Accumulated Depreciation"
acc.insert() acc.insert()
doc = frappe.get_doc("Account", "Securities and Deposits - _TC") doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
@ -149,7 +150,7 @@ def _make_test_records(verbose):
# fixed asset depreciation # fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_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 Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "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] return [parent_account]
elif account_name == child: elif account_name == child:
parent_account_list = return_parent(data, parent_account) 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.throw(_("The parent account {0} does not exists in the uploaded template").format(
frappe.bold(parent_account))) frappe.bold(parent_account)))
return [child] + parent_account_list return [child] + parent_account_list
charts_map, paths = {}, [] 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: if pr_doc.mixed_conditions:
amt = args.get('qty') * args.get("price_list_rate") amt = args.get('qty') * args.get("price_list_rate")
if args.get("item_code") != row.get("item_code"): 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 sum_amt += amt
if pr_doc.is_cumulative: if pr_doc.is_cumulative:

View File

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

View File

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

View File

@ -12,15 +12,11 @@
"item_name", "item_name",
"description_section", "description_section",
"description", "description",
"item_group",
"brand", "brand",
"image_section", "col_break7",
"item_group",
"image", "image",
"image_view", "image_view",
"manufacture_details",
"manufacturer",
"column_break_13",
"manufacturer_part_no",
"quantity_and_rate", "quantity_and_rate",
"received_qty", "received_qty",
"qty", "qty",
@ -55,20 +51,19 @@
"item_tax_amount", "item_tax_amount",
"landed_cost_voucher_amount", "landed_cost_voucher_amount",
"rm_supp_cost", "rm_supp_cost",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_38",
"weight_uom",
"warehouse_section", "warehouse_section",
"warehouse", "warehouse",
"rejected_warehouse",
"from_warehouse", "from_warehouse",
"quality_inspection", "quality_inspection",
"batch_no",
"col_br_wh",
"serial_no", "serial_no",
"col_br_wh",
"rejected_warehouse",
"batch_no",
"rejected_serial_no", "rejected_serial_no",
"manufacture_details",
"manufacturer",
"column_break_13",
"manufacturer_part_no",
"accounting", "accounting",
"expense_account", "expense_account",
"col_break5", "col_break5",
@ -92,6 +87,11 @@
"po_detail", "po_detail",
"purchase_receipt", "purchase_receipt",
"pr_detail", "pr_detail",
"item_weight_details",
"weight_per_unit",
"total_weight",
"column_break_38",
"weight_uom",
"accounting_dimensions_section", "accounting_dimensions_section",
"project", "project",
"dimension_col_break", "dimension_col_break",
@ -550,23 +550,21 @@
}, },
{ {
"fieldname": "brand", "fieldname": "brand",
"fieldtype": "Data",
"hidden": 1,
"label": "Brand",
"oldfieldname": "brand",
"oldfieldtype": "Data",
"print_hide": 1
},
{
"fieldname": "item_group",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"label": "Item Group", "label": "Brand",
"oldfieldname": "item_group",
"oldfieldtype": "Link",
"options": "Item Group",
"print_hide": 1, "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", "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", "fieldname": "section_break_82",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{
"collapsible": 1,
"fieldname": "image_section",
"fieldtype": "Section Break",
"label": "Image"
},
{ {
"collapsible": 1, "collapsible": 1,
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
@ -737,6 +729,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"collapsible": 1,
"fieldname": "manufacture_details", "fieldname": "manufacture_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Manufacture" "label": "Manufacture"
@ -771,12 +764,17 @@
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"label": "Supplier Warehouse", "label": "Supplier Warehouse",
"options": "Warehouse" "options": "Warehouse"
},
{
"collapsible": 1,
"fieldname": "col_break7",
"fieldtype": "Column Break"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-04-07 18:34:35.104178", "modified": "2020-04-22 10:37:35.103176",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",
@ -784,4 +782,4 @@
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC"
} }

View File

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

View File

@ -1926,16 +1926,6 @@ class TestSalesInvoice(unittest.TestCase):
item.taxes = [] item.taxes = []
item.save() 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): def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")
args = frappe._dict(args) args = frappe._dict(args)

View File

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

View File

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

View File

@ -11,12 +11,34 @@ from frappe.model.document import Document
class AssetCategory(Document): class AssetCategory(Document):
def validate(self): def validate(self):
self.validate_finance_books() self.validate_finance_books()
self.validate_accounts()
def validate_finance_books(self): def validate_finance_books(self):
for d in self.finance_books: for d in self.finance_books:
for field in ("Total Number of Depreciations", "Frequency of Depreciation"): for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
if cint(d.get(frappe.scrub(field)))<1: if cint(d.get(frappe.scrub(field)))<1:
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) 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() @frappe.whitelist()
def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None): 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; 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) => { refresh: (frm) => {
@ -93,25 +73,6 @@ frappe.ui.form.on('Asset Maintenance Task', {
}, },
end_date: (frm, cdt, cdn) => { end_date: (frm, cdt, cdn) => {
get_next_due_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)) throw(_("Start date should be less than end date for task {0}").format(task.maintenance_task))
if getdate(task.next_due_date) < getdate(nowdate()): if getdate(task.next_due_date) < getdate(nowdate()):
task.maintenance_status = "Overdue" 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): def on_update(self):
for task in self.get('asset_maintenance_tasks'): 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) assign_tasks(self.name, task.assign_to, task.maintenance_task, task.next_due_date)
self.sync_maintenance_tasks() self.sync_maintenance_tasks()
@ -108,7 +107,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task):
@frappe.whitelist() @frappe.whitelist()
def get_team_members(doctype, txt, searchfield, start, page_len, filters): 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() @frappe.whitelist()
def get_maintenance_log(asset_name): def get_maintenance_log(asset_name):

View File

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

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-21 16:16:39", "creation": "2013-05-21 16:16:39",
@ -63,9 +64,9 @@
"base_total", "base_total",
"base_net_total", "base_net_total",
"column_break_26", "column_break_26",
"total_net_weight",
"total", "total",
"net_total", "net_total",
"total_net_weight",
"taxes_section", "taxes_section",
"tax_category", "tax_category",
"column_break_50", "column_break_50",
@ -170,8 +171,8 @@
"search_index": 1 "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))", "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", "fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button", "fieldtype": "Button",
"label": "Get Items from Open Material Requests" "label": "Get Items from Open Material Requests"
@ -1054,7 +1055,8 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"modified": "2020-01-14 18:54:39.694448", "links": [],
"modified": "2020-04-17 13:04:28.185197",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@ -1,24 +1,31 @@
{ {
"actions": [],
"creation": "2013-02-22 01:27:42", "creation": "2013-02-22 01:27:42",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"main_item_code", "main_item_code",
"rm_item_code",
"description", "description",
"batch_no", "bom_detail_no",
"serial_no",
"col_break1", "col_break1",
"required_qty", "rm_item_code",
"consumed_qty",
"stock_uom", "stock_uom",
"rate",
"amount",
"conversion_factor", "conversion_factor",
"current_stock",
"reference_name", "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": [ "fields": [
{ {
@ -152,11 +159,36 @@
"oldfieldname": "bom_detail_no", "oldfieldname": "bom_detail_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"read_only": 1 "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, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-11-21 16:25:29.909112", "links": [],
"modified": "2020-04-10 18:09:33.997618",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Receipt Item Supplied", "name": "Purchase Receipt Item Supplied",

View File

@ -214,5 +214,41 @@ def get_data():
"label": _("Lab Test Report") "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 # Customer Provided parts will have zero valuation rate
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
d.allow_zero_valuation_rate = 1 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, def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, company=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''' '''Test mapping of multiple source docs on a single target doc'''
make_test_records("Item") 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") customers = frappe.get_all("Customer")
if items and customers: if items and customers:
# Make source docs (quotations) and a target doc (sales order) # Make source docs (quotations) and a target doc (sales order)

View File

@ -2,15 +2,40 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Tally Migration', { 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) { 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); frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
if (data.count == data.total) { let error_occurred = data.count === -1;
window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); 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.master_data && !frm.doc.is_master_data_imported) {
if (frm.doc.is_master_data_processed) { if (frm.doc.is_master_data_processed) {
if (frm.doc.status != "Importing Master Data") { 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( frm.add_custom_button(
label, label,
() => frm.call({ () => {
doc: frm.doc, frm.call({
method: method, doc: frm.doc,
freeze: true, method: method,
callback: () => { freeze: true
frm.remove_custom_button(label); });
} frm.reload_doc();
}) }
); );
} }
}); });

View File

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

View File

@ -4,20 +4,23 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from decimal import Decimal
import json import json
import re import re
import traceback import traceback
import zipfile import zipfile
from decimal import Decimal
from bs4 import BeautifulSoup as bs
import frappe 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 import _
from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.naming import getseries, revert_series_if_last from frappe.model.naming import getseries, revert_series_if_last
from frappe.utils.data import format_datetime 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" PRIMARY_ACCOUNT = "Primary"
VOUCHER_CHUNK_SIZE = 500 VOUCHER_CHUNK_SIZE = 500
@ -39,13 +42,15 @@ class TallyMigration(Document):
return string return string
master_file = frappe.get_doc("File", {"file_url": data_file}) 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: if zipfile.is_zipfile(master_file_path):
encoded_content = zf.read(zf.namelist()[0]) with zipfile.ZipFile(master_file_path) as zf:
try: encoded_content = zf.read(zf.namelist()[0])
content = encoded_content.decode("utf-8-sig") try:
except UnicodeDecodeError: content = encoded_content.decode("utf-8-sig")
content = encoded_content.decode("utf-16") except UnicodeDecodeError:
content = encoded_content.decode("utf-16")
master = bs(sanitize(emptify(content)), "xml") master = bs(sanitize(emptify(content)), "xml")
collection = master.BODY.IMPORTDATA.REQUESTDATA collection = master.BODY.IMPORTDATA.REQUESTDATA
@ -58,13 +63,14 @@ class TallyMigration(Document):
"file_name": key + ".json", "file_name": key + ".json",
"attached_to_doctype": self.doctype, "attached_to_doctype": self.doctype,
"attached_to_name": self.name, "attached_to_name": self.name,
"content": json.dumps(value) "content": json.dumps(value),
"is_private": True
}).insert() }).insert()
setattr(self, key, f.file_url) setattr(self, key, f.file_url)
def _process_master_data(self): def _process_master_data(self):
def get_company_name(collection): 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): def get_coa_customers_suppliers(collection):
root_type_map = { root_type_map = {
@ -97,17 +103,17 @@ class TallyMigration(Document):
# If Ledger doesn't have PARENT field then don't create Account # If Ledger doesn't have PARENT field then don't create Account
# For example "Profit & Loss A/c" # For example "Profit & Loss A/c"
if account.PARENT: if account.PARENT:
yield account.PARENT.string, account["NAME"], 0 yield account.PARENT.string.strip(), account["NAME"], 0
def get_parent(account): def get_parent(account):
if account.PARENT: if account.PARENT:
return account.PARENT.string return account.PARENT.string.strip()
return { return {
("Yes", "No"): "Application of Funds (Assets)", ("Yes", "No"): "Application of Funds (Assets)",
("Yes", "Yes"): "Expenses", ("Yes", "Yes"): "Expenses",
("No", "Yes"): "Income", ("No", "Yes"): "Income",
("No", "No"): "Source of Funds (Liabilities)", ("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): def get_children_and_parent_dict(accounts):
children, parents = {}, {} children, parents = {}, {}
@ -145,38 +151,38 @@ class TallyMigration(Document):
parties, addresses = [], [] parties, addresses = [], []
for account in collection.find_all("LEDGER"): for account in collection.find_all("LEDGER"):
party_type = None party_type = None
if account.NAME.string in customers: if account.NAME.string.strip() in customers:
party_type = "Customer" party_type = "Customer"
parties.append({ parties.append({
"doctype": party_type, "doctype": party_type,
"customer_name": account.NAME.string, "customer_name": account.NAME.string.strip(),
"tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, "tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
"customer_group": "All Customer Groups", "customer_group": "All Customer Groups",
"territory": "All Territories", "territory": "All Territories",
"customer_type": "Individual", "customer_type": "Individual",
}) })
elif account.NAME.string in suppliers: elif account.NAME.string.strip() in suppliers:
party_type = "Supplier" party_type = "Supplier"
parties.append({ parties.append({
"doctype": party_type, "doctype": party_type,
"supplier_name": account.NAME.string, "supplier_name": account.NAME.string.strip(),
"pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, "pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
"supplier_group": "All Supplier Groups", "supplier_group": "All Supplier Groups",
"supplier_type": "Individual", "supplier_type": "Individual",
}) })
if party_type: 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({ addresses.append({
"doctype": "Address", "doctype": "Address",
"address_line1": address[:140].strip(), "address_line1": address[:140].strip(),
"address_line2": address[140:].strip(), "address_line2": address[140:].strip(),
"country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, "country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None,
"state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, "state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
"gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, "gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
"pin_code": account.PINCODE.string if account.PINCODE else None, "pin_code": account.PINCODE.string.strip() if account.PINCODE else None,
"mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None, "mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None, "phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
"gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None, "gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
"links": [{"link_doctype": party_type, "link_name": account["NAME"]}], "links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
}) })
return parties, addresses return parties, addresses
@ -184,41 +190,50 @@ class TallyMigration(Document):
def get_stock_items_uoms(collection): def get_stock_items_uoms(collection):
uoms = [] uoms = []
for uom in collection.find_all("UNIT"): 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 = [] items = []
for item in collection.find_all("STOCKITEM"): for item in collection.find_all("STOCKITEM"):
stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom
items.append({ items.append({
"doctype": "Item", "doctype": "Item",
"item_code" : item.NAME.string, "item_code" : item.NAME.string.strip(),
"stock_uom": item.BASEUNITS.string, "stock_uom": stock_uom.strip(),
"is_stock_item": 0, "is_stock_item": 0,
"item_group": "All Item Groups", "item_group": "All Item Groups",
"item_defaults": [{"company": self.erpnext_company}] "item_defaults": [{"company": self.erpnext_company}]
}) })
return items, uoms 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) self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
collection = self.get_collection(self.master_data) chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
company = get_company_name(collection) self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
self.tally_company = company parties, addresses = get_parties_addresses(collection, customers, suppliers)
self.erpnext_company = company
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5) self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection) items, uoms = get_stock_items_uoms(collection)
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5) data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
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.dump_processed_data(data) self.publish("Process Master Data", _("Done"), 5, 5)
self.is_master_data_processed = 1 self.dump_processed_data(data)
self.status = ""
self.save() 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): def publish(self, title, message, count, total):
frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total}) frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total})
@ -256,7 +271,6 @@ class TallyMigration(Document):
except: except:
self.log(address) self.log(address)
def create_items_uoms(items_file_url, uoms_file_url): def create_items_uoms(items_file_url, uoms_file_url):
uoms_file = frappe.get_doc("File", {"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()): for uom in json.loads(uoms_file.get_content()):
@ -273,25 +287,35 @@ class TallyMigration(Document):
except: except:
self.log(item) self.log(item)
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4) try:
create_company_and_coa(self.chart_of_accounts) self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4) create_company_and_coa(self.chart_of_accounts)
create_parties_and_addresses(self.parties, self.addresses)
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4) self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
create_items_uoms(self.items, self.uoms) create_parties_and_addresses(self.parties, self.addresses)
self.publish("Import Master Data", _("Done"), 4, 4)
self.status = "" self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
self.is_master_data_imported = 1 create_items_uoms(self.items, self.uoms)
self.save()
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 _process_day_book_data(self):
def get_vouchers(collection): def get_vouchers(collection):
vouchers = [] vouchers = []
for voucher in collection.find_all("VOUCHER"): for voucher in collection.find_all("VOUCHER"):
if voucher.ISCANCELLED.string == "Yes": if voucher.ISCANCELLED.string.strip() == "Yes":
continue continue
inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST") 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 function = voucher_to_invoice
else: else:
function = voucher_to_journal_entry function = voucher_to_journal_entry
@ -307,15 +331,15 @@ class TallyMigration(Document):
accounts = [] accounts = []
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
for entry in ledger_entries: for entry in ledger_entries:
account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center} account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center}
if entry.ISPARTYLEDGER.string == "Yes": if entry.ISPARTYLEDGER.string.strip() == "Yes":
party_details = get_party(entry.LEDGERNAME.string) party_details = get_party(entry.LEDGERNAME.string.strip())
if party_details: if party_details:
party_type, party_account = party_details party_type, party_account = party_details
account["party_type"] = party_type account["party_type"] = party_type
account["account"] = party_account account["account"] = party_account
account["party"] = entry.LEDGERNAME.string account["party"] = entry.LEDGERNAME.string.strip()
amount = Decimal(entry.AMOUNT.string) amount = Decimal(entry.AMOUNT.string.strip())
if amount > 0: if amount > 0:
account["credit_in_account_currency"] = str(abs(amount)) account["credit_in_account_currency"] = str(abs(amount))
else: else:
@ -324,21 +348,21 @@ class TallyMigration(Document):
journal_entry = { journal_entry = {
"doctype": "Journal Entry", "doctype": "Journal Entry",
"tally_guid": voucher.GUID.string, "tally_guid": voucher.GUID.string.strip(),
"posting_date": voucher.DATE.string, "posting_date": voucher.DATE.string.strip(),
"company": self.erpnext_company, "company": self.erpnext_company,
"accounts": accounts, "accounts": accounts,
} }
return journal_entry return journal_entry
def voucher_to_invoice(voucher): 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" doctype = "Sales Invoice"
party_field = "customer" party_field = "customer"
account_field = "debit_to" account_field = "debit_to"
account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company) account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
price_list_field = "selling_price_list" 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" doctype = "Purchase Invoice"
party_field = "supplier" party_field = "supplier"
account_field = "credit_to" account_field = "credit_to"
@ -351,10 +375,10 @@ class TallyMigration(Document):
invoice = { invoice = {
"doctype": doctype, "doctype": doctype,
party_field: voucher.PARTYNAME.string, party_field: voucher.PARTYNAME.string.strip(),
"tally_guid": voucher.GUID.string, "tally_guid": voucher.GUID.string.strip(),
"posting_date": voucher.DATE.string, "posting_date": voucher.DATE.string.strip(),
"due_date": voucher.DATE.string, "due_date": voucher.DATE.string.strip(),
"items": get_voucher_items(voucher, doctype), "items": get_voucher_items(voucher, doctype),
"taxes": get_voucher_taxes(voucher), "taxes": get_voucher_taxes(voucher),
account_field: account_name, account_field: account_name,
@ -375,15 +399,15 @@ class TallyMigration(Document):
for entry in inventory_entries: for entry in inventory_entries:
qty, uom = entry.ACTUALQTY.string.strip().split() qty, uom = entry.ACTUALQTY.string.strip().split()
items.append({ items.append({
"item_code": entry.STOCKITEMNAME.string, "item_code": entry.STOCKITEMNAME.string.strip(),
"description": entry.STOCKITEMNAME.string, "description": entry.STOCKITEMNAME.string.strip(),
"qty": qty.strip(), "qty": qty.strip(),
"uom": uom.strip(), "uom": uom.strip(),
"conversion_factor": 1, "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, "cost_center": self.default_cost_center,
"warehouse": self.default_warehouse, "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 return items
@ -391,13 +415,13 @@ class TallyMigration(Document):
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
taxes = [] taxes = []
for entry in ledger_entries: for entry in ledger_entries:
if entry.ISPARTYLEDGER.string == "No": if entry.ISPARTYLEDGER.string.strip() == "No":
tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company) tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company)
taxes.append({ taxes.append({
"charge_type": "Actual", "charge_type": "Actual",
"account_head": tax_account, "account_head": tax_account,
"description": tax_account, "description": tax_account,
"tax_amount": entry.AMOUNT.string, "tax_amount": entry.AMOUNT.string.strip(),
"cost_center": self.default_cost_center, "cost_center": self.default_cost_center,
}) })
return taxes return taxes
@ -408,15 +432,24 @@ class TallyMigration(Document):
elif frappe.db.exists({"doctype": "Customer", "customer_name": party}): elif frappe.db.exists({"doctype": "Customer", "customer_name": party}):
return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company) return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3) try:
collection = self.get_collection(self.day_book_data) self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3) collection = self.get_collection(self.day_book_data)
vouchers = get_vouchers(collection)
self.publish("Process Day Book Data", _("Done"), 3, 3) self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
self.dump_processed_data({"vouchers": vouchers}) vouchers = get_vouchers(collection)
self.status = ""
self.is_day_book_data_processed = 1 self.publish("Process Day Book Data", _("Done"), 3, 3)
self.save() 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 _import_day_book_data(self):
def create_fiscal_years(vouchers): def create_fiscal_years(vouchers):
@ -454,23 +487,31 @@ class TallyMigration(Document):
"currency": "INR" "currency": "INR"
}).insert() }).insert()
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable") try:
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable") frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account) 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_file = frappe.get_doc("File", {"file_url": self.vouchers})
vouchers = json.loads(vouchers_file.get_content()) vouchers = json.loads(vouchers_file.get_content())
create_fiscal_years(vouchers) create_fiscal_years(vouchers)
create_price_list() create_price_list()
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
total = len(vouchers) total = len(vouchers)
is_last = False is_last = False
for index in range(0, total, VOUCHER_CHUNK_SIZE):
if index + VOUCHER_CHUNK_SIZE >= total: for index in range(0, total, VOUCHER_CHUNK_SIZE):
is_last = True if index + VOUCHER_CHUNK_SIZE >= total:
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last) 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): def _import_vouchers(self, start, total, is_last=False):
frappe.flags.in_migrate = True frappe.flags.in_migrate = True
@ -494,25 +535,26 @@ class TallyMigration(Document):
frappe.flags.in_migrate = False frappe.flags.in_migrate = False
def process_master_data(self): def process_master_data(self):
self.status = "Processing Master Data" self.set_status("Processing Master Data")
self.save()
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600) frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
def import_master_data(self): def import_master_data(self):
self.status = "Importing Master Data" self.set_status("Importing Master Data")
self.save()
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600) frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
def process_day_book_data(self): def process_day_book_data(self):
self.status = "Processing Day Book Data" self.set_status("Processing Day Book Data")
self.save()
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600) frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
def import_day_book_data(self): def import_day_book_data(self):
self.status = "Importing Day Book Data" self.set_status("Importing Day Book Data")
self.save()
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600) frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
def log(self, data=None): 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) 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": [ "cards": [
{ {
"icon": "", "hidden": 0,
"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]", "label": "Masters",
"title": "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": "", "hidden": 0,
"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]", "label": "Consultation Setup",
"title": "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", "hidden": 0,
"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]", "label": "Consultation",
"title": "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]", "hidden": 0,
"title": "Settings" "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]", "hidden": 0,
"title": "Laboratory Setup" "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]", "hidden": 0,
"title": "Laboratory" "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]", "hidden": 0,
"title": "Records and History" "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]", "hidden": 0,
"title": "Reports" "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", "category": "Domains",
"charts": [ "charts": [],
{
"chart_name": "Patient Appointments",
"label": "Patient Appointments"
}
],
"charts_label": "", "charts_label": "",
"creation": "2020-03-02 17:23:17.919682", "creation": "2020-03-02 17:23:17.919682",
"developer_mode_only": 0, "developer_mode_only": 0,
@ -53,7 +58,7 @@
"idx": 0, "idx": 0,
"is_standard": 1, "is_standard": 1,
"label": "Healthcare", "label": "Healthcare",
"modified": "2020-03-26 16:10:44.629795", "modified": "2020-04-20 11:42:43.889576",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Healthcare", "name": "Healthcare",
@ -64,32 +69,32 @@
"shortcuts": [ "shortcuts": [
{ {
"format": "{} Open", "format": "{} Open",
"is_query_report": 0, "label": "Patient Appointment",
"link_to": "Patient Appointment", "link_to": "Patient Appointment",
"stats_filter": "{\n \"status\": \"Open\"\n}", "stats_filter": "{\n \"status\": \"Open\"\n}",
"type": "DocType" "type": "DocType"
}, },
{ {
"format": "{} Active", "format": "{} Active",
"is_query_report": 0, "label": "Patient",
"link_to": "Patient", "link_to": "Patient",
"stats_filter": "{\n \"status\": \"Active\"\n}", "stats_filter": "{\n \"status\": \"Active\"\n}",
"type": "DocType" "type": "DocType"
}, },
{ {
"format": "{} Vacant", "format": "{} Vacant",
"is_query_report": 0, "label": "Healthcare Service Unit",
"link_to": "Healthcare Service Unit", "link_to": "Healthcare Service Unit",
"stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}", "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}",
"type": "DocType" "type": "DocType"
}, },
{ {
"is_query_report": 0, "label": "Healthcare Practitioner",
"link_to": "Healthcare Practitioner", "link_to": "Healthcare Practitioner",
"type": "DocType" "type": "DocType"
}, },
{ {
"is_query_report": 0, "label": "Patient History",
"link_to": "patient_history", "link_to": "patient_history",
"type": "Page" "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: if doc.is_billable and not doc.disabled:
disabled = 0 disabled = 0
uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
item = frappe.get_doc({ item = frappe.get_doc({
'doctype': 'Item', 'doctype': 'Item',
'item_code': doc.template, 'item_code': doc.template,
@ -92,7 +93,7 @@ def create_item_from_template(doc):
'show_in_website': 0, 'show_in_website': 0,
'is_pro_applicable': 0, 'is_pro_applicable': 0,
'disabled': disabled, 'disabled': disabled,
'stock_uom': 'Unit' 'stock_uom': uom
}).insert(ignore_permissions=True, ignore_mandatory=True) }).insert(ignore_permissions=True, ignore_mandatory=True)
make_item_price(item.name, doc.rate) 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): def create_item_from_template(doc):
if doc.is_billable: disabled = doc.disabled
if doc.is_billable and not doc.disabled:
disabled = 0 disabled = 0
else:
disabled = 1 uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
# insert item # insert item
item = frappe.get_doc({ item = frappe.get_doc({
"doctype": "Item", "doctype": "Item",
"item_code": doc.lab_test_code, "item_code": doc.lab_test_code,
"item_name":doc.lab_test_name, "item_name":doc.lab_test_name,
"item_group": doc.lab_test_group, "item_group": doc.lab_test_group,
"description":doc.lab_test_description, "description":doc.lab_test_description,
"is_sales_item": 1, "is_sales_item": 1,
"is_service_item": 1, "is_service_item": 1,
"is_purchase_item": 0, "is_purchase_item": 0,
"is_stock_item": 0, "is_stock_item": 0,
"show_in_website": 0, "show_in_website": 0,
"is_pro_applicable": 0, "is_pro_applicable": 0,
"disabled": disabled, "disabled": disabled,
"stock_uom": "Unit" "stock_uom": uom
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True, ignore_mandatory=True)
# insert item price # insert item price
# get item price list to 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, frm: frm,
}); });
}, __('Create')); }, __('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 { } else {
frm.add_custom_button(__('Patient Encounter'), function() { frm.add_custom_button(__('Patient Encounter'), function() {
frappe.model.open_mapped_doc({ 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_procedure_from_encounter: function(frm) {
get_prescribed_procedure(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(); 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) { let create_vital_signs = function(frm) {
if (!frm.doc.patient) { if (!frm.doc.patient) {
frappe.throw(__('Please select patient')); frappe.throw(__('Please select patient'));

View File

@ -24,6 +24,10 @@
"column_break_13", "column_break_13",
"procedure_template", "procedure_template",
"procedure_prescription", "procedure_prescription",
"therapy_type",
"get_prescribed_therapies",
"therapy_plan",
"service_unit",
"section_break_12", "section_break_12",
"practitioner", "practitioner",
"department", "department",
@ -271,6 +275,28 @@
"print_hide": 1, "print_hide": 1,
"report_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", "fieldname": "ref_sales_invoice",
"fieldtype": "Link", "fieldtype": "Link",

View File

@ -415,11 +415,36 @@ def get_events(start, end, filters=None):
@frappe.whitelist() @frappe.whitelist()
def get_procedure_prescribed(patient): def get_procedure_prescribed(patient):
return frappe.db.sql("""select pp.name, pp.procedure, pp.parent, ct.practitioner, return frappe.db.sql(
ct.encounter_date, pp.practitioner, pp.date, pp.department """
from `tabPatient Encounter` ct, `tabProcedure Prescription` pp SELECT
where ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0 pp.name, pp.procedure, pp.parent, ct.practitioner,
order by ct.creation desc""", {'patient': patient}) 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(): 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', { frappe.ui.form.on('Patient Encounter', {
setup: function(frm) { 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 = [ frm.get_field('drug_prescription').grid.editable_fields = [
{fieldname: 'drug_code', columns: 2}, {fieldname: 'drug_code', columns: 2},
{fieldname: 'drug_name', columns: 2}, {fieldname: 'drug_name', columns: 2},

View File

@ -42,6 +42,10 @@
"lab_test_prescription", "lab_test_prescription",
"sb_procedures", "sb_procedures",
"procedure_prescription", "procedure_prescription",
"rehabilitation_section",
"therapy_plan",
"therapies",
"section_break_33",
"encounter_comment", "encounter_comment",
"sb_refs", "sb_refs",
"company", "company",
@ -255,6 +259,29 @@
"print_hide": 1, "print_hide": 1,
"read_only": 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", "fieldname": "appointment_type",
"fieldtype": "Link", "fieldtype": "Link",

View File

@ -4,6 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr from frappe.utils import cstr
from frappe import _ from frappe import _
@ -22,6 +23,24 @@ class PatientEncounter(Document):
frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open')
delete_medical_record(self) 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): def insert_encounter_to_medical_record(doc):
subject = set_subject_field(doc) subject = set_subject_field(doc)
medical_record = frappe.new_doc('Patient Medical Record') 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_lab_tests_to_invoice(patient, company)
items_to_invoice += get_clinical_procedures_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_inpatient_services_to_invoice(patient, company)
items_to_invoice += get_therapy_sessions_to_invoice(patient, company)
return items_to_invoice return items_to_invoice
@ -245,6 +247,25 @@ def get_inpatient_services_to_invoice(patient, company):
return services_to_invoice 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): def get_service_item_and_practitioner_charge(doc):
is_inpatient = doc.inpatient_record is_inpatient = doc.inpatient_record
if is_inpatient: if is_inpatient:

View File

@ -776,22 +776,16 @@ class SalarySlip(TransactionBase):
for payment in self.get('loans'): for payment in self.get('loans'):
amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment") 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_interest_amount += payment.interest_amount
self.total_principal_amount += payment.principal_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): 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 = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68)
repayment_entry.save() repayment_entry.save()
repayment_entry.submit()
penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year)) 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)) 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): def test_loan_closure_repayment(self):
pledges = [] pledges = []
@ -189,15 +195,19 @@ class TestLoan(unittest.TestCase):
process_loan_interest_accrual_for_demand_loans(posting_date = last_date) 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), repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
"Loan Closure", 13315.0681) "Loan Closure", flt(loan.loan_amount + accrued_interest_amount))
repayment_entry.save() 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) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0)
repayment_entry.submit()
loan.load_from_db() loan.load_from_db()
self.assertEquals(loan.status, "Loan Closure Requested") 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()) 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), 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.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(amounts[0], 11250.00)
self.assertEquals(repayment_entry.payable_principal_amount, 78303.00) self.assertEquals(amounts[1], 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)
def test_security_shortfall(self): def test_security_shortfall(self):
pledges = [] pledges = []
@ -294,7 +262,7 @@ class TestLoan(unittest.TestCase):
make_loan_disbursement_entry(loan.name, loan.loan_amount) 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'""") where loan_security='Test Security 2'""")
create_process_loan_security_shortfall() create_process_loan_security_shortfall()

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