From 9cc451fc7dbffd487316a3407d2eef1eee105d5d Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 26 Aug 2021 07:35:59 +0500 Subject: [PATCH 001/416] feat(Regional): KSA VAT Report --- .../workspace/accounting/accounting.json | 23 ++- .../ksa_vat_purchase_account/__init__.py | 0 .../ksa_vat_purchase_account.json | 49 +++++ .../ksa_vat_purchase_account.py | 8 + .../doctype/ksa_vat_sales_account/__init__.py | 0 .../ksa_vat_sales_account.js | 8 + .../ksa_vat_sales_account.json | 49 +++++ .../ksa_vat_sales_account.py | 8 + .../test_ksa_vat_sales_account.py | 8 + .../doctype/ksa_vat_setting/__init__.py | 0 .../ksa_vat_setting/ksa_vat_setting.js | 8 + .../ksa_vat_setting/ksa_vat_setting.json | 49 +++++ .../ksa_vat_setting/ksa_vat_setting.py | 8 + .../ksa_vat_setting/ksa_vat_setting_list.js | 5 + .../ksa_vat_setting/test_ksa_vat_setting.py | 8 + erpnext/regional/report/ksa_vat/__init__.py | 0 erpnext/regional/report/ksa_vat/ksa_vat.js | 60 ++++++ erpnext/regional/report/ksa_vat/ksa_vat.json | 32 ++++ erpnext/regional/report/ksa_vat/ksa_vat.py | 177 ++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++ .../regional/saudi_arabia/wizard/__init__.py | 0 .../saudi_arabia/wizard/data/__init__.py | 0 .../wizard/data/ksa_vat_settings.json | 47 +++++ .../wizard/operations/__init__.py | 0 .../operations/setup_ksa_vat_setting.py | 45 +++++ 25 files changed, 608 insertions(+), 1 deletion(-) create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py create mode 100644 erpnext/regional/report/ksa_vat/__init__.py create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.js create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.json create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.py create mode 100644 erpnext/regional/saudi_arabia/wizard/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index b5bd14d137..1ee5914d77 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -515,6 +515,17 @@ "only_for": "United Arab Emirates", "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "KSA VAT Report", + "link_to": "KSA VAT", + "link_type": "Report", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1135,6 +1146,16 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "KSA VAT Setting", + "link_to": "KSA VAT Setting", + "link_type": "DocType", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1188,7 +1209,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:52.872470", + "modified": "2021-08-26 13:15:52.872470", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json new file mode 100644 index 0000000000..89ba3e977a --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 09:17:09.862163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:38.205597", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Purchase Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py new file mode 100644 index 0000000000..1302e314c3 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATPurchaseAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js new file mode 100644 index 0000000000..72613f4064 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Sales Account', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json new file mode 100644 index 0000000000..df2747891d --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 08:46:33.820968", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:00.081407", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Sales Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py new file mode 100644 index 0000000000..8e437f832b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSalesAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py new file mode 100644 index 0000000000..d7f911343b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSalesAccount(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js new file mode 100644 index 0000000000..0238c7b306 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Setting', { + onload: function(frm) { + frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); + } +}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json new file mode 100644 index 0000000000..33619467ed --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-13 08:49:01.100356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "ksa_vat_sales_accounts", + "ksa_vat_purchase_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ksa_vat_sales_accounts", + "fieldtype": "Table", + "label": "KSA VAT Sales Accounts", + "options": "KSA VAT Sales Account", + "reqd": 1 + }, + { + "fieldname": "ksa_vat_purchase_accounts", + "fieldtype": "Table", + "label": "KSA VAT Purchase Accounts", + "options": "KSA VAT Purchase Account", + "reqd": 1 + } + ], + "links": [], + "modified": "2021-08-26 04:29:06.499378", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Setting", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "company", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py new file mode 100644 index 0000000000..6db6f9cc59 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSetting(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js new file mode 100644 index 0000000000..23d28b9e68 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -0,0 +1,5 @@ +frappe.listview_settings['KSA VAT Setting'] = { + onload(list) { + frappe.breadcrumbs.add('Accounts'); + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py new file mode 100644 index 0000000000..ae2defc61d --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js new file mode 100644 index 0000000000..5dd9c92bf7 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -0,0 +1,60 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["KSA VAT"] = { + onload() { + frappe.breadcrumbs.add('Accounts'); + }, + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') + && data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else if (data.title=='Grand Total'){ + if (data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else{ + value = default_formatter(value, row, column, data); + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + console.log($value) + return value + } + }else{ + value = default_formatter(value, row, column, data); + return value; + } + }, +}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json new file mode 100644 index 0000000000..036e260310 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-13 08:54:38.000949", + "disable_prepared_report": 1, + "disabled": 1, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-26 04:14:37.202594", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "GL Entry", + "report_name": "KSA VAT", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py new file mode 100644 index 0000000000..6828007203 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -0,0 +1,177 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import get_url_to_list +from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data, get_rounded_tax_amount +import json + +def execute(filters=None): + columns = columns = get_columns() + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "fieldname": "title", + "label": _("Title"), + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": _("Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "adjustment_amount", + "label": _("Adjustment (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "vat_amount", + "label": _("VAT Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + } + ] + +def get_data(filters): + data = [] + + # Validate if vat settings exist + company = filters.get('company') + if frappe.db.exists('KSA VAT Setting', company) is None: + url = get_url_to_list('KSA VAT Setting') + frappe.msgprint(f'Create KSA VAT Setting for this company') + return data + + ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) + + # Sales Heading + append_data(data, 'VAT on Sales', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Sales Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + # Blank Line + append_data(data, '', '', '', '') + + # Purchase Heading + append_data(data, 'VAT on Purchases', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Purchase Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + return data + +def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): + ''' + (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n + calculates and returns \n + total_taxable_amount, total_taxable_adjustment_amount, total_tax''' + from_date = filters.get('from_date') + to_date = filters.get('to_date') + + # Initiate variables + total_taxable_amount = 0 + total_taxable_adjustment_amount = 0 + total_tax = 0 + # Fetch All Invoices + invoices = frappe.get_list(doctype, + filters ={ + 'docstatus': 1, + 'posting_date': ['between', [from_date, to_date]] + }, + fields =['name', 'is_return']) + + for invoice in invoices: + invoice_items = frappe.get_list(f'{doctype} Item', + filters ={ + 'docstatus': 1, + 'parent': invoice.name, + 'item_tax_template': vat_setting.item_tax_template + }, + fields =['item_code', 'net_amount']) + + + for item in invoice_items: + # Summing up total taxable amount + if invoice.is_return == 0: + total_taxable_amount += item.net_amount + + if invoice.is_return == 1: + total_taxable_adjustment_amount += item.net_amount + + # Summing up total tax + total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) + + return total_taxable_amount, total_taxable_adjustment_amount, total_tax + + + +def append_data(data, title, amount, adjustment_amount, vat_amount): + """Returns data with appended value.""" + data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + +def get_tax_amount(item_code, account_head, doctype, parent): + if doctype == 'Sales Invoice': + tax_doctype = 'Sales Taxes and Charges' + + elif doctype == 'Purchase Invoice': + tax_doctype = 'Purchase Taxes and Charges' + + item_wise_tax_detail = frappe.get_value(tax_doctype, { + 'docstatus': 1, + 'parent': parent, + 'account_head': account_head + }, 'item_wise_tax_detail') + + tax_amount = 0 + if item_wise_tax_detail and len(item_wise_tax_detail) > 0: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + for key, value in item_wise_tax_detail.items(): + if key == item_code: + tax_amount = value[1] + break + + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 9b3677d2c6..053496729f 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -3,9 +3,26 @@ from __future__ import unicode_literals +import frappe +from frappe.permissions import add_permission, update_permission_property from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting def setup(company=None, patch=True): make_custom_fields() add_print_formats() + add_permissions() + create_ksa_vat_setting(company) + + +def add_permissions(): + """Add Permissions for KSA VAT Setting.""" + add_permission('KSA VAT Setting', 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission('KSA VAT Setting', role, 0) + update_permission_property('KSA VAT Setting', role, 0, 'write', 1) + update_permission_property('KSA VAT Setting', role, 0, 'create', 1) + + """Enable KSA VAT Report""" + frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json new file mode 100644 index 0000000000..709d65be04 --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -0,0 +1,47 @@ +[ + { + "type": "Sales Account", + "accounts": [ + { + "title": "Standard rated Sales", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Zero rated domestic sales", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted sales", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + }, + { + "type": "Purchase Account", + "accounts": [ + { + "title": "Standard rated domestic purchases", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Imports subject to VAT paid at customs", + "item_tax_template": "KSA Excise 50%", + "account": "Excise 50%" + }, + { + "title": "Zero rated purchases", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted purchases", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + } +] \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py new file mode 100644 index 0000000000..4bccdc35bf --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -0,0 +1,45 @@ +import frappe +import os +import json +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + +def create_ksa_vat_setting(company): + """ + On creation of first company. Creates KSA VAT Setting""" + + company = frappe.get_doc('Company', company) + setup_taxes_and_charges(company.name, company.country) + + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') + with open(file_path, 'r') as json_file: + account_data = json.load(json_file) + + + # Creating KSA VAT Setting + ksa_vat_setting = frappe.get_doc({ + 'doctype': 'KSA VAT Setting', + 'company': company.name + }) + + for data in account_data: + if data['type'] == 'Sales Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_sales_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + elif data['type'] == 'Purchase Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_purchase_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + ksa_vat_setting.save() \ No newline at end of file From 5e10e103291f5c70fee3647503ba858c84348f3c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 31 Aug 2021 18:23:28 +0530 Subject: [PATCH 002/416] feat: Validity dates in Tax Withholding Rates --- .../tax_withholding_category.py | 111 ++++---- .../test_tax_withholding_category.py | 17 +- .../tax_withholding_rate.json | 256 +++++------------- erpnext/patches.txt | 3 +- ...pdate_dates_in_tax_withholding_category.py | 22 ++ 5 files changed, 159 insertions(+), 250 deletions(-) create mode 100644 erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 0cb872c4b8..c871af9428 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -10,7 +10,24 @@ from frappe.utils import flt, getdate, cint from erpnext.accounts.utils import get_fiscal_year class TaxWithholdingCategory(Document): - pass + def validate(self): + self.validate_dates() + self.validate_thresholds() + + def validate_dates(self): + last_date = None + for d in self.get('rates'): + if getdate(d.from_date) >= getdate(d.to_date): + frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx)) + + # validate overlapping of dates + if last_date and getdate(r.to_date) < getdate(last_date): + frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx)) + + def validate_thresholds(self): + for d in self.get('rates'): + if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold: + frappe.throw(_("Row #{0}: Cumulative threshold cannot be less than Single Transaction threshold").format(d.idx)) def get_party_details(inv): party_type, party = '', '' @@ -49,8 +66,8 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): if not parties: parties.append(party) - fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company) - tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company) + posting_date = inv.get('posting_date') or inv.get('transaction_date') + tax_details = get_tax_withholding_details(tax_withholding_category, posting_date, inv.company) if not tax_details: frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}') @@ -64,7 +81,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): tax_amount, tax_deducted = get_tax_amount( party_type, parties, inv, tax_details, - fiscal_year, pan_no + posting_date, pan_no ) if party_type == 'Supplier': @@ -74,16 +91,18 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): return tax_row -def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): +def get_tax_withholding_details(tax_withholding_category, posting_date, company): tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) - tax_rate_detail = get_tax_withholding_rates(tax_withholding, fiscal_year) + tax_rate_detail = get_tax_withholding_rates(tax_withholding, posting_date) for account_detail in tax_withholding.accounts: if company == account_detail.company: return frappe._dict({ "account_head": account_detail.account, "rate": tax_rate_detail.tax_withholding_rate, + "from_date": tax_rate_detail.from_date, + "to_date": tax_rate_detail.to_date, "threshold": tax_rate_detail.single_threshold, "cumulative_threshold": tax_rate_detail.cumulative_threshold, "description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category, @@ -92,13 +111,13 @@ def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): "round_off_tax_amount": tax_withholding.round_off_tax_amount }) -def get_tax_withholding_rates(tax_withholding, fiscal_year): +def get_tax_withholding_rates(tax_withholding, posting_date): # returns the row that matches with the fiscal year from posting date for rate in tax_withholding.rates: - if rate.fiscal_year == fiscal_year: + if getdate(rate.from_date) <= getdate(posting_date) <= getdate(rate.to_date): return rate - frappe.throw(_("No Tax Withholding data found for the current Fiscal Year.")) + frappe.throw(_("No Tax Withholding data found for the current posting date.")) def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted): row = { @@ -140,38 +159,38 @@ def get_tax_row_for_tds(tax_details, tax_amount): "account_head": tax_details.account_head } -def get_lower_deduction_certificate(fiscal_year, pan_no): - ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name') +def get_lower_deduction_certificate(tax_details, pan_no): + ldc_name = frappe.db.get_value('Lower Deduction Certificate', + { + 'pan_no': pan_no, + 'valid_from': ('>=', tax_details.from_date), + 'valid_upto': ('<=', tax_details.to_date) + }, 'name') + if ldc_name: return frappe.get_doc('Lower Deduction Certificate', ldc_name) -def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None): - fiscal_year = fiscal_year_details[0] - - - vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type) - advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type) +def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): + vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type) + advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, + to_date=tax_details.to_date, party_type=party_type) taxable_vouchers = vouchers + advance_vouchers tax_deducted = 0 if taxable_vouchers: - tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details) + tax_deducted = get_deducted_tax(taxable_vouchers, tax_details) tax_amount = 0 - posting_date = inv.get('posting_date') or inv.get('transaction_date') if party_type == 'Supplier': - ldc = get_lower_deduction_certificate(fiscal_year, pan_no) + ldc = get_lower_deduction_certificate(tax_details, pan_no) if tax_deducted: net_total = inv.net_total if ldc: - tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total) + tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 else: - tax_amount = get_tds_amount( - ldc, parties, inv, tax_details, - fiscal_year_details, tax_deducted, vouchers - ) + tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) elif party_type == 'Customer': if tax_deducted: @@ -180,14 +199,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p else: # if no TCS has been charged in FY, # then chargeable value is "prev invoices + advances" value which cross the threshold - tax_amount = get_tcs_amount( - parties, inv, tax_details, - fiscal_year_details, vouchers, advance_vouchers - ) + tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers) return tax_amount, tax_deducted -def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): +def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' filters = { @@ -195,14 +211,14 @@ def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'): 'company': company, 'party_type': party_type, 'party': ['in', parties], - 'fiscal_year': fiscal_year, + 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'is_opening': 'No', 'is_cancelled': 0 } return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] -def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'): +def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'): # for advance vouchers, debit and credit is reversed dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit' @@ -215,8 +231,6 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None 'against_voucher': ['is', 'not set'] } - if fiscal_year: - filters['fiscal_year'] = fiscal_year if company: filters['company'] = company if from_date and to_date: @@ -224,20 +238,21 @@ def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] -def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details): +def get_deducted_tax(taxable_vouchers, tax_details): # check if TDS / TCS account is already charged on taxable vouchers filters = { 'is_cancelled': 0, 'credit': ['>', 0], - 'fiscal_year': fiscal_year, + 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'account': tax_details.account_head, 'voucher_no': ['in', taxable_vouchers], } - field = "sum(credit)" + field = "credit" - return frappe.db.get_value('GL Entry', filters, field) or 0.0 + entries = frappe.db.get_all('GL Entry', filters, pluck=field) + return sum(entries) -def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers): +def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): tds_amount = 0 invoice_filters = { 'name': ('in', vouchers), @@ -261,7 +276,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu supp_credit_amt += supp_jv_credit_amt supp_credit_amt += inv.net_total - debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company) + debit_note_amount = get_debit_note_amount(parties, tax_details.from_date, tax_details.to_date, inv.company) supp_credit_amt -= debit_note_amount threshold = tax_details.get('threshold', 0) @@ -289,9 +304,8 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu return tds_amount -def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers): +def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): tcs_amount = 0 - fiscal_year, _, _ = fiscal_year_details # sum of debit entries made from sales invoices invoiced_amt = frappe.db.get_value('GL Entry', { @@ -310,14 +324,14 @@ def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv }, 'sum(credit)') or 0.0 # sum of credit entries made from sales invoice - credit_note_amt = frappe.db.get_value('GL Entry', { + credit_note_amt = sum(frappe.db.get_all('GL Entry', { 'is_cancelled': 0, 'credit': ['>', 0], 'party': ['in', parties], - 'fiscal_year': fiscal_year, + 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'company': inv.company, 'voucher_type': 'Sales Invoice', - }, 'sum(credit)') or 0.0 + }, pluck='credit')) cumulative_threshold = tax_details.get('cumulative_threshold', 0) @@ -336,7 +350,7 @@ def get_invoice_total_without_tcs(inv, tax_details): return inv.grand_total - tcs_tax_row_amount -def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total): +def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total): tds_amount = 0 limit_consumed = frappe.db.get_value('Purchase Invoice', { 'supplier': ('in', parties), @@ -353,14 +367,13 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post return tds_amount -def get_debit_note_amount(suppliers, fiscal_year_details, company=None): - _, year_start_date, year_end_date = fiscal_year_details +def get_debit_note_amount(suppliers, from_date, to_date, company=None): filters = { 'supplier': ['in', suppliers], 'is_return': 1, 'docstatus': 1, - 'posting_date': ['between', (year_start_date, year_end_date)] + 'posting_date': ['between', (from_date, to_date)] } fields = ['abs(sum(net_total)) as net_total'] diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 0f921db678..138aaec6ab 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -312,16 +312,16 @@ def create_records(): }).insert() def create_tax_with_holding_category(): - fiscal_year = get_fiscal_year(today(), company="_Test Company")[0] - - # Cummulative thresold + fiscal_year = get_fiscal_year(today(), company="_Test Company") + # Cumulative threshold if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"): frappe.get_doc({ "doctype": "Tax Withholding Category", "name": "Cumulative Threshold TDS", "category_name": "10% TDS", "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 0, 'cumulative_threshold': 30000.00 @@ -338,7 +338,8 @@ def create_tax_with_holding_category(): "name": "Cumulative Threshold TCS", "category_name": "10% TCS", "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 0, 'cumulative_threshold': 30000.00 @@ -356,7 +357,8 @@ def create_tax_with_holding_category(): "name": "Single Threshold TDS", "category_name": "10% TDS", "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 20000.00, 'cumulative_threshold': 0 @@ -376,7 +378,8 @@ def create_tax_with_holding_category(): "consider_party_ledger_amount": 1, "tax_on_excess_amount": 1, "rates": [{ - 'fiscal_year': fiscal_year, + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 0, 'cumulative_threshold': 30000 diff --git a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json index 1e8194af6e..d2c505c630 100644 --- a/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json +++ b/erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json @@ -1,202 +1,72 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-07-17 16:53:13.716665", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2018-07-17 16:53:13.716665", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "from_date", + "to_date", + "tax_withholding_rate", + "column_break_3", + "single_threshold", + "cumulative_threshold" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "fiscal_year", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Fiscal Year", - "length": 0, - "no_copy": 0, - "options": "Fiscal Year", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 1, + "fieldname": "tax_withholding_rate", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Tax Withholding Rate", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "tax_withholding_rate", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Tax Withholding Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 2, + "fieldname": "single_threshold", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Single Transaction Threshold" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "single_threshold", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Single Transaction Threshold", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "columns": 3, + "fieldname": "cumulative_threshold", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Cumulative Transaction Threshold" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 3, - "fieldname": "cumulative_threshold", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Cumulative Transaction Threshold", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "columns": 2, + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From Date", + "reqd": 1 + }, + { + "columns": 2, + "fieldname": "to_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "To Date", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-07-17 17:13:09.819580", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Withholding Rate", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-31 11:42:12.213977", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Withholding Rate", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 05c385b6ba..ae83d5eb96 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -300,4 +300,5 @@ erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v13_0.set_operation_time_based_on_operating_cost -erpnext.patches.v13_0.validate_options_for_data_field \ No newline at end of file +erpnext.patches.v13_0.validate_options_for_data_field +erpnext.patches.v13_0.update_dates_in_tax_withholding_category \ No newline at end of file diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py new file mode 100644 index 0000000000..2563d8a8c4 --- /dev/null +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -0,0 +1,22 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from erpnext.accounts.utils import get_fiscal_year + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') + tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) + + fiscal_year_map = {} + for rate in tds_category_rates: + if not fiscal_year_map.get(rate.fiscal_year): + fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) + + from_date = fiscal_year_map.get(rate.fiscal_year)[1] + to_date = fiscal_year_map.get(rate.fiscal_year)[2] + + frappe.db.set_value('Tax Withholding Rate', rate.name, { + 'from_date': from_date, + 'to_date': to_date + }) \ No newline at end of file From d06221ad7a0c622d3b2c9e8992c97af28f38f79f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 Sep 2021 10:05:10 +0530 Subject: [PATCH 003/416] fix: Advance TDS test case --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 37ff52c610..88acebcf49 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1226,7 +1226,8 @@ def update_tax_witholding_category(company, account, date): {'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') tds_category.append('rates', { - 'fiscal_year': fiscal_year[0], + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], 'tax_withholding_rate': 10, 'single_threshold': 2500, 'cumulative_threshold': 0 From b6d0b17ed6930b43157c51c0e0fec8457881f7bb Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 Sep 2021 10:11:18 +0530 Subject: [PATCH 004/416] fix: Linting and patch fixes --- .../tax_withholding_category.py | 2 +- ...pdate_dates_in_tax_withholding_category.py | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index c871af9428..06b8df1dfe 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -21,7 +21,7 @@ class TaxWithholdingCategory(Document): frappe.throw(_("Row #{0}: From Date cannot be before To Date").format(d.idx)) # validate overlapping of dates - if last_date and getdate(r.to_date) < getdate(last_date): + if last_date and getdate(d.to_date) < getdate(last_date): frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx)) def validate_thresholds(self): diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py index 2563d8a8c4..33c4942853 100644 --- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -6,17 +6,19 @@ from erpnext.accounts.utils import get_fiscal_year def execute(): frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') - tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) - fiscal_year_map = {} - for rate in tds_category_rates: - if not fiscal_year_map.get(rate.fiscal_year): - fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) + if frappe.db.has_column('Tax Withholding Rate', 'fiscal_year'): + tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) - from_date = fiscal_year_map.get(rate.fiscal_year)[1] - to_date = fiscal_year_map.get(rate.fiscal_year)[2] + fiscal_year_map = {} + for rate in tds_category_rates: + if not fiscal_year_map.get(rate.fiscal_year): + fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) - frappe.db.set_value('Tax Withholding Rate', rate.name, { - 'from_date': from_date, - 'to_date': to_date - }) \ No newline at end of file + from_date = fiscal_year_map.get(rate.fiscal_year)[1] + to_date = fiscal_year_map.get(rate.fiscal_year)[2] + + frappe.db.set_value('Tax Withholding Rate', rate.name, { + 'from_date': from_date, + 'to_date': to_date + }) \ No newline at end of file From d5a736d11e5096f2861c3e5e6267584488df6d7d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 1 Sep 2021 21:15:24 +0530 Subject: [PATCH 005/416] test: Update test case --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 88acebcf49..06f4fdb799 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1223,7 +1223,8 @@ def update_tax_witholding_category(company, account, date): fiscal_year = get_fiscal_year(date=date, company=company) if not frappe.db.get_value('Tax Withholding Rate', - {'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}): + {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), + 'to_date': ('<=', fiscal_year[2])}): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') tds_category.append('rates', { 'from_date': fiscal_year[1], From bcb3f0fedb8583060044fc3d1346874b76fa044a Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Thu, 2 Sep 2021 18:14:34 +0530 Subject: [PATCH 006/416] chore: Removed healthcare module --- erpnext/healthcare/__init__.py | 0 .../clinical_procedures.json | 26 - .../clinical_procedures_status.json | 26 - .../department_wise_patient_appointments.json | 25 - .../dashboard_chart/diagnoses/diagnoses.json | 26 - .../in_patient_status/in_patient_status.json | 26 - .../dashboard_chart/lab_tests/lab_tests.json | 26 - .../patient_appointments.json | 27 - .../dashboard_chart/symptoms/symptoms.json | 26 - .../dashboard_chart_source/__init__.py | 0 .../__init__.py | 0 .../department_wise_patient_appointments.js | 14 - .../department_wise_patient_appointments.json | 13 - .../department_wise_patient_appointments.py | 74 -- .../desk_page/healthcare/healthcare.json | 122 --- erpnext/healthcare/doctype/__init__.py | 0 .../healthcare/doctype/antibiotic/__init__.py | 0 .../doctype/antibiotic/antibiotic.js | 5 - .../doctype/antibiotic/antibiotic.json | 151 ---- .../doctype/antibiotic/antibiotic.py | 11 - .../doctype/antibiotic/test_antibiotic.py | 10 - .../doctype/appointment_type/__init__.py | 0 .../appointment_type/appointment_type.js | 83 -- .../appointment_type/appointment_type.json | 114 --- .../appointment_type/appointment_type.py | 58 -- .../appointment_type_dashboard.py | 15 - .../appointment_type/test_appointment_type.py | 11 - .../appointment_type_service_item/__init__.py | 0 .../appointment_type_service_item.json | 67 -- .../appointment_type_service_item.py | 12 - .../healthcare/doctype/body_part/__init__.py | 0 .../healthcare/doctype/body_part/body_part.js | 8 - .../doctype/body_part/body_part.json | 45 - .../healthcare/doctype/body_part/body_part.py | 12 - .../doctype/body_part/test_body_part.py | 11 - .../doctype/body_part_link/__init__.py | 0 .../body_part_link/body_part_link.json | 32 - .../doctype/body_part_link/body_part_link.py | 12 - .../doctype/clinical_procedure/__init__.py | 0 .../clinical_procedure/clinical_procedure.js | 377 --------- .../clinical_procedure.json | 345 -------- .../clinical_procedure/clinical_procedure.py | 255 ------ .../clinical_procedure_list.js | 11 - .../test_clinical_procedure.py | 71 -- .../clinical_procedure_item/__init__.py | 0 .../clinical_procedure_item.json | 123 --- .../clinical_procedure_item.py | 11 - .../clinical_procedure_template/__init__.py | 0 .../clinical_procedure_template.js | 190 ----- .../clinical_procedure_template.json | 257 ------ .../clinical_procedure_template.py | 124 --- .../clinical_procedure_template_dashboard.py | 15 - .../test_clinical_procedure_template.py | 10 - .../doctype/codification_table/__init__.py | 0 .../codification_table.json | 56 -- .../codification_table/codification_table.py | 11 - .../healthcare/doctype/complaint/__init__.py | 0 .../healthcare/doctype/complaint/complaint.js | 5 - .../doctype/complaint/complaint.json | 116 --- .../healthcare/doctype/complaint/complaint.py | 11 - .../doctype/complaint/test_complaint.py | 10 - .../descriptive_test_result/__init__.py | 0 .../descriptive_test_result.json | 74 -- .../descriptive_test_result.py | 11 - .../descriptive_test_template/__init__.py | 0 .../descriptive_test_template.json | 41 - .../descriptive_test_template.py | 11 - .../healthcare/doctype/diagnosis/__init__.py | 0 .../healthcare/doctype/diagnosis/diagnosis.js | 5 - .../doctype/diagnosis/diagnosis.json | 116 --- .../healthcare/doctype/diagnosis/diagnosis.py | 11 - .../doctype/diagnosis/test_diagnosis.py | 11 - .../doctype/dosage_form/__init__.py | 0 .../doctype/dosage_form/dosage_form.js | 5 - .../doctype/dosage_form/dosage_form.json | 114 --- .../doctype/dosage_form/dosage_form.py | 11 - .../doctype/dosage_form/test_dosage_form.py | 10 - .../doctype/dosage_strength/__init__.py | 0 .../dosage_strength/dosage_strength.json | 102 --- .../dosage_strength/dosage_strength.py | 11 - .../doctype/drug_prescription/__init__.py | 0 .../drug_prescription/drug_prescription.json | 122 --- .../drug_prescription/drug_prescription.py | 36 - .../healthcare/doctype/exercise/__init__.py | 0 .../healthcare/doctype/exercise/exercise.json | 62 -- .../healthcare/doctype/exercise/exercise.py | 12 - .../exercise_difficulty_level/__init__.py | 0 .../exercise_difficulty_level.js | 8 - .../exercise_difficulty_level.json | 45 - .../exercise_difficulty_level.py | 12 - .../test_exercise_difficulty_level.py | 11 - .../doctype/exercise_type/__init__.py | 0 .../doctype/exercise_type/exercise_type.js | 186 ---- .../doctype/exercise_type/exercise_type.json | 144 ---- .../doctype/exercise_type/exercise_type.py | 16 - .../exercise_type/test_exercise_type.py | 11 - .../doctype/exercise_type_step/__init__.py | 0 .../exercise_type_step.json | 44 - .../exercise_type_step/exercise_type_step.py | 12 - .../doctype/fee_validity/__init__.py | 0 .../doctype/fee_validity/fee_validity.js | 5 - .../doctype/fee_validity/fee_validity.json | 134 --- .../doctype/fee_validity/fee_validity.py | 56 -- .../doctype/fee_validity/test_fee_validity.py | 56 -- .../fee_validity_reference/__init__.py | 0 .../fee_validity_reference.json | 32 - .../fee_validity_reference.py | 12 - erpnext/healthcare/doctype/healthcare.py | 6 - .../healthcare_practitioner/__init__.py | 0 .../healthcare_practitioner.js | 144 ---- .../healthcare_practitioner.json | 336 -------- .../healthcare_practitioner.py | 88 -- .../healthcare_practitioner_dashboard.py | 21 - .../test_healthcare_practitioner.py | 10 - .../healthcare_schedule_time_slot/__init__.py | 0 .../healthcare_schedule_time_slot.json | 136 --- .../healthcare_schedule_time_slot.py | 11 - .../healthcare_service_unit/__init__.py | 0 .../healthcare_service_unit.js | 60 -- .../healthcare_service_unit.json | 257 ------ .../healthcare_service_unit.py | 115 --- .../healthcare_service_unit_tree.js | 185 ---- .../test_healthcare_service_unit.py | 10 - .../healthcare_service_unit_type/__init__.py | 0 .../healthcare_service_unit_type.js | 86 -- .../healthcare_service_unit_type.json | 196 ----- .../healthcare_service_unit_type.py | 136 --- .../healthcare_service_unit_type_dashboard.py | 15 - .../test_healthcare_service_unit_type.py | 36 - .../doctype/healthcare_settings/__init__.py | 0 .../healthcare_settings.js | 75 -- .../healthcare_settings.json | 351 -------- .../healthcare_settings.py | 93 -- .../test_healthcare_settings.py | 10 - .../inpatient_medication_entry/__init__.py | 0 .../inpatient_medication_entry.js | 74 -- .../inpatient_medication_entry.json | 204 ----- .../inpatient_medication_entry.py | 324 ------- .../inpatient_medication_entry_dashboard.py | 18 - .../test_inpatient_medication_entry.py | 174 ---- .../__init__.py | 0 .../inpatient_medication_entry_detail.json | 163 ---- .../inpatient_medication_entry_detail.py | 12 - .../inpatient_medication_order/__init__.py | 0 .../inpatient_medication_order.js | 107 --- .../inpatient_medication_order.json | 196 ----- .../inpatient_medication_order.py | 78 -- .../inpatient_medication_order_list.js | 16 - .../test_inpatient_medication_order.py | 154 ---- .../__init__.py | 0 .../inpatient_medication_order_entry.json | 94 --- .../inpatient_medication_order_entry.py | 12 - .../doctype/inpatient_occupancy/__init__.py | 0 .../inpatient_occupancy.json | 64 -- .../inpatient_occupancy.py | 11 - .../doctype/inpatient_record/__init__.py | 0 .../inpatient_record/inpatient_record.js | 214 ----- .../inpatient_record/inpatient_record.json | 507 ----------- .../inpatient_record/inpatient_record.py | 291 ------- .../inpatient_record_dashboard.py | 19 - .../inpatient_record/test_inpatient_record.py | 205 ----- .../doctype/lab_prescription/__init__.py | 0 .../lab_prescription/lab_prescription.json | 78 -- .../lab_prescription/lab_prescription.py | 11 - .../healthcare/doctype/lab_test/__init__.py | 0 .../healthcare/doctype/lab_test/lab_test.js | 262 ------ .../healthcare/doctype/lab_test/lab_test.json | 610 -------------- .../healthcare/doctype/lab_test/lab_test.py | 352 -------- .../doctype/lab_test/lab_test_list.js | 71 -- .../doctype/lab_test/test_lab_test.py | 215 ----- .../lab_test_group_template/__init__.py | 0 .../lab_test_group_template.json | 119 --- .../lab_test_group_template.py | 11 - .../doctype/lab_test_sample/__init__.py | 0 .../lab_test_sample/lab_test_sample.js | 5 - .../lab_test_sample/lab_test_sample.json | 68 -- .../lab_test_sample/lab_test_sample.py | 11 - .../lab_test_sample/test_lab_test_sample.py | 10 - .../doctype/lab_test_template/__init__.py | 0 .../lab_test_template/lab_test_template.js | 97 --- .../lab_test_template/lab_test_template.json | 356 -------- .../lab_test_template/lab_test_template.py | 146 ---- .../lab_test_template_dashboard.py | 15 - .../lab_test_template_list.js | 7 - .../test_lab_test_template.py | 11 - .../doctype/lab_test_uom/__init__.py | 0 .../doctype/lab_test_uom/lab_test_uom.js | 5 - .../doctype/lab_test_uom/lab_test_uom.json | 148 ---- .../doctype/lab_test_uom/lab_test_uom.py | 11 - .../doctype/lab_test_uom/test_lab_test_uom.py | 11 - .../doctype/medical_code/__init__.py | 0 .../doctype/medical_code/medical_code.js | 5 - .../doctype/medical_code/medical_code.json | 69 -- .../doctype/medical_code/medical_code.py | 12 - .../doctype/medical_code/test_medical_code.py | 10 - .../doctype/medical_code_standard/__init__.py | 0 .../medical_code_standard.js | 5 - .../medical_code_standard.json | 94 --- .../medical_code_standard.py | 11 - .../test_medical_code_standard.py | 10 - .../doctype/medical_department/__init__.py | 0 .../medical_department/medical_department.js | 5 - .../medical_department.json | 156 ---- .../medical_department/medical_department.py | 11 - .../test_medical_department.py | 11 - .../doctype/normal_test_result/__init__.py | 0 .../normal_test_result.json | 186 ---- .../normal_test_result/normal_test_result.py | 11 - .../doctype/normal_test_template/__init__.py | 0 .../normal_test_template.json | 84 -- .../normal_test_template.py | 11 - .../healthcare/doctype/organism/__init__.py | 0 .../healthcare/doctype/organism/organism.js | 5 - .../healthcare/doctype/organism/organism.json | 152 ---- .../healthcare/doctype/organism/organism.py | 11 - .../doctype/organism/test_organism.py | 10 - .../doctype/organism_test_item/__init__.py | 0 .../organism_test_item.json | 144 ---- .../organism_test_item/organism_test_item.py | 11 - .../doctype/organism_test_result/__init__.py | 0 .../organism_test_result.json | 144 ---- .../organism_test_result.py | 11 - .../healthcare/doctype/patient/__init__.py | 0 erpnext/healthcare/doctype/patient/patient.js | 146 ---- .../healthcare/doctype/patient/patient.json | 542 ------------ erpnext/healthcare/doctype/patient/patient.py | 282 ------- .../doctype/patient/patient_dashboard.py | 40 - .../doctype/patient/test_patient.py | 37 - .../doctype/patient_appointment/__init__.py | 0 .../patient_appointment.js | 657 --------------- .../patient_appointment.json | 403 --------- .../patient_appointment.py | 559 ------------ .../patient_appointment_calendar.js | 14 - .../patient_appointment_dashboard.py | 18 - .../patient_appointment_list.js | 16 - .../test_patient_appointment.py | 487 ----------- .../doctype/patient_assessment/__init__.py | 0 .../patient_assessment/patient_assessment.js | 88 -- .../patient_assessment.json | 181 ---- .../patient_assessment/patient_assessment.py | 35 - .../test_patient_assessment.py | 11 - .../patient_assessment_detail/__init__.py | 0 .../patient_assessment_detail.json | 32 - .../patient_assessment_detail.py | 12 - .../patient_assessment_parameter/__init__.py | 0 .../patient_assessment_parameter.js | 8 - .../patient_assessment_parameter.json | 45 - .../patient_assessment_parameter.py | 12 - .../test_patient_assessment_parameter.py | 11 - .../patient_assessment_sheet/__init__.py | 0 .../patient_assessment_sheet.json | 57 -- .../patient_assessment_sheet.py | 12 - .../patient_assessment_template/__init__.py | 0 .../patient_assessment_template.js | 8 - .../patient_assessment_template.json | 109 --- .../patient_assessment_template.py | 12 - .../test_patient_assessment_template.py | 11 - .../doctype/patient_encounter/__init__.py | 0 .../patient_encounter/patient_encounter.js | 397 --------- .../patient_encounter/patient_encounter.json | 368 -------- .../patient_encounter/patient_encounter.py | 182 ---- .../patient_encounter_dashboard.py | 24 - .../patient_encounter_list.js | 6 - .../test_patient_encounter.py | 87 -- .../patient_encounter_diagnosis/__init__.py | 0 .../patient_encounter_diagnosis.json | 33 - .../patient_encounter_diagnosis.py | 12 - .../patient_encounter_symptom/__init__.py | 0 .../patient_encounter_symptom.json | 33 - .../patient_encounter_symptom.py | 12 - .../__init__.py | 0 .../patient_history_custom_document_type.json | 55 -- .../patient_history_custom_document_type.py | 12 - .../patient_history_settings/__init__.py | 0 .../patient_history_settings.js | 133 --- .../patient_history_settings.json | 55 -- .../patient_history_settings.py | 194 ----- .../test_patient_history_settings.py | 106 --- .../__init__.py | 0 ...atient_history_standard_document_type.json | 57 -- .../patient_history_standard_document_type.py | 12 - .../patient_medical_record/__init__.py | 0 .../patient_medical_record.js | 5 - .../patient_medical_record.json | 155 ---- .../patient_medical_record.py | 14 - .../test_patient_medical_record.py | 101 --- .../doctype/patient_relation/__init__.py | 0 .../patient_relation/patient_relation.json | 52 -- .../patient_relation/patient_relation.py | 11 - .../doctype/practitioner_schedule/__init__.py | 0 .../practitioner_schedule.js | 117 --- .../practitioner_schedule.json | 71 -- .../practitioner_schedule.py | 12 - .../test_practitioner_schedule.py | 10 - .../__init__.py | 0 .../practitioner_service_unit_schedule.json | 110 --- .../practitioner_service_unit_schedule.py | 11 - .../doctype/prescription_dosage/__init__.py | 0 .../prescription_dosage.js | 5 - .../prescription_dosage.json | 145 ---- .../prescription_dosage.py | 11 - .../test_prescription_dosage.py | 10 - .../doctype/prescription_duration/__init__.py | 0 .../prescription_duration.js | 5 - .../prescription_duration.json | 145 ---- .../prescription_duration.py | 74 -- .../test_prescription_duration.py | 10 - .../procedure_prescription/__init__.py | 0 .../procedure_prescription.json | 99 --- .../procedure_prescription.py | 11 - .../doctype/sample_collection/__init__.py | 0 .../sample_collection/sample_collection.js | 40 - .../sample_collection/sample_collection.json | 256 ------ .../sample_collection/sample_collection.py | 16 - .../test_sample_collection.py | 11 - .../doctype/sensitivity/__init__.py | 0 .../doctype/sensitivity/sensitivity.js | 5 - .../doctype/sensitivity/sensitivity.json | 115 --- .../doctype/sensitivity/sensitivity.py | 11 - .../doctype/sensitivity/test_sensitivity.py | 11 - .../sensitivity_test_result/__init__.py | 0 .../sensitivity_test_result.json | 103 --- .../sensitivity_test_result.py | 11 - .../doctype/therapy_plan/__init__.py | 0 .../doctype/therapy_plan/test_therapy_plan.py | 118 --- .../doctype/therapy_plan/therapy_plan.js | 133 --- .../doctype/therapy_plan/therapy_plan.json | 179 ---- .../doctype/therapy_plan/therapy_plan.py | 103 --- .../therapy_plan/therapy_plan_dashboard.py | 23 - .../doctype/therapy_plan/therapy_plan_list.js | 11 - .../doctype/therapy_plan_detail/__init__.py | 0 .../therapy_plan_detail.json | 49 -- .../therapy_plan_detail.py | 12 - .../doctype/therapy_plan_template/__init__.py | 0 .../test_therapy_plan_template.py | 11 - .../therapy_plan_template.js | 57 -- .../therapy_plan_template.json | 132 --- .../therapy_plan_template.py | 76 -- .../therapy_plan_template_dashboard.py | 15 - .../therapy_plan_template_detail/__init__.py | 0 .../therapy_plan_template_detail.json | 54 -- .../therapy_plan_template_detail.py | 12 - .../doctype/therapy_session/__init__.py | 0 .../therapy_session/test_therapy_session.py | 11 - .../therapy_session/therapy_session.js | 171 ---- .../therapy_session/therapy_session.json | 264 ------ .../therapy_session/therapy_session.py | 149 ---- .../therapy_session_dashboard.py | 15 - .../doctype/therapy_type/__init__.py | 0 .../doctype/therapy_type/test_therapy_type.py | 56 -- .../doctype/therapy_type/therapy_type.js | 103 --- .../doctype/therapy_type/therapy_type.json | 234 ------ .../doctype/therapy_type/therapy_type.py | 126 --- .../treatment_plan_template/__init__.py | 0 .../treatment_plan_template/test_records.json | 7 - .../test_treatment_plan_template.py | 9 - .../treatment_plan_template.js | 14 - .../treatment_plan_template.json | 189 ----- .../treatment_plan_template.py | 20 - .../treatment_plan_template_list.js | 10 - .../treatment_plan_template_item/__init__.py | 0 .../treatment_plan_template_item.json | 55 -- .../treatment_plan_template_item.py | 9 - .../__init__.py | 0 .../treatment_plan_template_practitioner.json | 32 - .../treatment_plan_template_practitioner.py | 9 - .../doctype/vital_signs/__init__.py | 0 .../doctype/vital_signs/test_vital_signs.py | 11 - .../doctype/vital_signs/vital_signs.js | 52 -- .../doctype/vital_signs/vital_signs.json | 305 ------- .../doctype/vital_signs/vital_signs.py | 18 - .../healthcare/healthcare.json | 62 -- .../healthcare/healthcare.json | 41 - .../appointments_to_bill.json | 21 - .../open_appointments/open_appointments.json | 21 - .../total_patients/total_patients.json | 20 - .../total_patients_admitted.json | 20 - .../create_healthcare_practitioner.json | 19 - .../create_patient/create_patient.json | 19 - .../create_practitioner_schedule.json | 19 - .../explore_clinical_procedure_templates.json | 19 - .../explore_healthcare_settings.json | 19 - ...troduction_to_healthcare_practitioner.json | 20 - erpnext/healthcare/page/__init__.py | 1 - .../page/patient_history/__init__.py | 0 .../page/patient_history/patient_history.css | 151 ---- .../page/patient_history/patient_history.html | 18 - .../page/patient_history/patient_history.js | 455 ---------- .../page/patient_history/patient_history.json | 28 - .../page/patient_history/patient_history.py | 75 -- .../patient_history_sidebar.html | 20 - .../page/patient_progress/__init__.py | 0 .../patient_progress/patient_progress.css | 171 ---- .../patient_progress/patient_progress.html | 69 -- .../page/patient_progress/patient_progress.js | 536 ------------ .../patient_progress/patient_progress.json | 33 - .../page/patient_progress/patient_progress.py | 198 ----- .../patient_progress_sidebar.html | 29 - erpnext/healthcare/print_format/__init__.py | 0 .../print_format/encounter_print/__init__.py | 0 .../encounter_print/encounter_print.json | 22 - .../print_format/lab_test_print/__init__.py | 0 .../lab_test_print/lab_test_print.json | 23 - .../print_format/sample_id_print/__init__.py | 0 .../sample_id_print/sample_id_print.json | 22 - erpnext/healthcare/report/__init__.py | 0 .../inpatient_medication_orders/__init__.py | 0 .../inpatient_medication_orders.js | 57 -- .../inpatient_medication_orders.json | 36 - .../inpatient_medication_orders.py | 203 ----- .../test_inpatient_medication_orders.py | 146 ---- .../report/lab_test_report/__init__.py | 0 .../report/lab_test_report/lab_test_report.js | 57 -- .../lab_test_report/lab_test_report.json | 33 - .../report/lab_test_report/lab_test_report.py | 213 ----- .../patient_appointment_analytics/__init__.py | 0 .../patient_appointment_analytics.js | 128 --- .../patient_appointment_analytics.json | 36 - .../patient_appointment_analytics.py | 197 ----- erpnext/healthcare/setup.py | 295 ------- erpnext/healthcare/utils.py | 792 ------------------ erpnext/healthcare/web_form/__init__.py | 0 .../healthcare/web_form/lab_test/__init__.py | 0 .../healthcare/web_form/lab_test/lab_test.js | 34 - .../web_form/lab_test/lab_test.json | 460 ---------- .../healthcare/web_form/lab_test/lab_test.py | 26 - .../web_form/patient_appointments/__init__.py | 0 .../patient_appointments.js | 3 - .../patient_appointments.json | 111 --- .../patient_appointments.py | 26 - .../web_form/patient_registration/__init__.py | 0 .../patient_registration.js | 3 - .../patient_registration.json | 397 --------- .../patient_registration.py | 6 - .../web_form/personal_details/__init__.py | 0 .../personal_details/personal_details.js | 3 - .../personal_details/personal_details.json | 87 -- .../personal_details/personal_details.py | 27 - .../web_form/prescription/__init__.py | 0 .../web_form/prescription/prescription.js | 3 - .../web_form/prescription/prescription.json | 120 --- .../web_form/prescription/prescription.py | 26 - .../workspace/healthcare/healthcare.json | 626 -------------- 443 files changed, 30300 deletions(-) delete mode 100644 erpnext/healthcare/__init__.py delete mode 100644 erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json delete mode 100644 erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json delete mode 100644 erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json delete mode 100644 erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json delete mode 100644 erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json delete mode 100644 erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json delete mode 100644 erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json delete mode 100644 erpnext/healthcare/dashboard_chart/symptoms/symptoms.json delete mode 100644 erpnext/healthcare/dashboard_chart_source/__init__.py delete mode 100644 erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py delete mode 100644 erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js delete mode 100644 erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json delete mode 100644 erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py delete mode 100644 erpnext/healthcare/desk_page/healthcare/healthcare.json delete mode 100644 erpnext/healthcare/doctype/__init__.py delete mode 100644 erpnext/healthcare/doctype/antibiotic/__init__.py delete mode 100644 erpnext/healthcare/doctype/antibiotic/antibiotic.js delete mode 100644 erpnext/healthcare/doctype/antibiotic/antibiotic.json delete mode 100644 erpnext/healthcare/doctype/antibiotic/antibiotic.py delete mode 100644 erpnext/healthcare/doctype/antibiotic/test_antibiotic.py delete mode 100644 erpnext/healthcare/doctype/appointment_type/__init__.py delete mode 100644 erpnext/healthcare/doctype/appointment_type/appointment_type.js delete mode 100644 erpnext/healthcare/doctype/appointment_type/appointment_type.json delete mode 100644 erpnext/healthcare/doctype/appointment_type/appointment_type.py delete mode 100644 erpnext/healthcare/doctype/appointment_type/appointment_type_dashboard.py delete mode 100644 erpnext/healthcare/doctype/appointment_type/test_appointment_type.py delete mode 100644 erpnext/healthcare/doctype/appointment_type_service_item/__init__.py delete mode 100644 erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json delete mode 100644 erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py delete mode 100644 erpnext/healthcare/doctype/body_part/__init__.py delete mode 100644 erpnext/healthcare/doctype/body_part/body_part.js delete mode 100644 erpnext/healthcare/doctype/body_part/body_part.json delete mode 100644 erpnext/healthcare/doctype/body_part/body_part.py delete mode 100644 erpnext/healthcare/doctype/body_part/test_body_part.py delete mode 100644 erpnext/healthcare/doctype/body_part_link/__init__.py delete mode 100644 erpnext/healthcare/doctype/body_part_link/body_part_link.json delete mode 100644 erpnext/healthcare/doctype/body_part_link/body_part_link.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/__init__.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/clinical_procedure_list.js delete mode 100644 erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_item/__init__.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py delete mode 100644 erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py delete mode 100644 erpnext/healthcare/doctype/codification_table/__init__.py delete mode 100644 erpnext/healthcare/doctype/codification_table/codification_table.json delete mode 100644 erpnext/healthcare/doctype/codification_table/codification_table.py delete mode 100644 erpnext/healthcare/doctype/complaint/__init__.py delete mode 100644 erpnext/healthcare/doctype/complaint/complaint.js delete mode 100644 erpnext/healthcare/doctype/complaint/complaint.json delete mode 100644 erpnext/healthcare/doctype/complaint/complaint.py delete mode 100644 erpnext/healthcare/doctype/complaint/test_complaint.py delete mode 100644 erpnext/healthcare/doctype/descriptive_test_result/__init__.py delete mode 100644 erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json delete mode 100644 erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py delete mode 100644 erpnext/healthcare/doctype/descriptive_test_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json delete mode 100644 erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py delete mode 100644 erpnext/healthcare/doctype/diagnosis/__init__.py delete mode 100644 erpnext/healthcare/doctype/diagnosis/diagnosis.js delete mode 100644 erpnext/healthcare/doctype/diagnosis/diagnosis.json delete mode 100644 erpnext/healthcare/doctype/diagnosis/diagnosis.py delete mode 100644 erpnext/healthcare/doctype/diagnosis/test_diagnosis.py delete mode 100644 erpnext/healthcare/doctype/dosage_form/__init__.py delete mode 100644 erpnext/healthcare/doctype/dosage_form/dosage_form.js delete mode 100644 erpnext/healthcare/doctype/dosage_form/dosage_form.json delete mode 100644 erpnext/healthcare/doctype/dosage_form/dosage_form.py delete mode 100644 erpnext/healthcare/doctype/dosage_form/test_dosage_form.py delete mode 100644 erpnext/healthcare/doctype/dosage_strength/__init__.py delete mode 100644 erpnext/healthcare/doctype/dosage_strength/dosage_strength.json delete mode 100644 erpnext/healthcare/doctype/dosage_strength/dosage_strength.py delete mode 100644 erpnext/healthcare/doctype/drug_prescription/__init__.py delete mode 100644 erpnext/healthcare/doctype/drug_prescription/drug_prescription.json delete mode 100755 erpnext/healthcare/doctype/drug_prescription/drug_prescription.py delete mode 100644 erpnext/healthcare/doctype/exercise/__init__.py delete mode 100644 erpnext/healthcare/doctype/exercise/exercise.json delete mode 100644 erpnext/healthcare/doctype/exercise/exercise.py delete mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py delete mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js delete mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json delete mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py delete mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py delete mode 100644 erpnext/healthcare/doctype/exercise_type/__init__.py delete mode 100644 erpnext/healthcare/doctype/exercise_type/exercise_type.js delete mode 100644 erpnext/healthcare/doctype/exercise_type/exercise_type.json delete mode 100644 erpnext/healthcare/doctype/exercise_type/exercise_type.py delete mode 100644 erpnext/healthcare/doctype/exercise_type/test_exercise_type.py delete mode 100644 erpnext/healthcare/doctype/exercise_type_step/__init__.py delete mode 100644 erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json delete mode 100644 erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.py delete mode 100644 erpnext/healthcare/doctype/fee_validity/__init__.py delete mode 100644 erpnext/healthcare/doctype/fee_validity/fee_validity.js delete mode 100644 erpnext/healthcare/doctype/fee_validity/fee_validity.json delete mode 100644 erpnext/healthcare/doctype/fee_validity/fee_validity.py delete mode 100644 erpnext/healthcare/doctype/fee_validity/test_fee_validity.py delete mode 100644 erpnext/healthcare/doctype/fee_validity_reference/__init__.py delete mode 100644 erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json delete mode 100644 erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py delete mode 100644 erpnext/healthcare/doctype/healthcare.py delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/__init__.py delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py delete mode 100644 erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py delete mode 100644 erpnext/healthcare/doctype/healthcare_schedule_time_slot/__init__.py delete mode 100644 erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json delete mode 100644 erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/__init__.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py delete mode 100644 erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py delete mode 100644 erpnext/healthcare/doctype/healthcare_settings/__init__.py delete mode 100644 erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js delete mode 100644 erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json delete mode 100644 erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py delete mode 100644 erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry/__init__.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry_detail/__init__.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order/__init__.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order_entry/__init__.py delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json delete mode 100644 erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py delete mode 100644 erpnext/healthcare/doctype/inpatient_occupancy/__init__.py delete mode 100644 erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json delete mode 100644 erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py delete mode 100644 erpnext/healthcare/doctype/inpatient_record/__init__.py delete mode 100644 erpnext/healthcare/doctype/inpatient_record/inpatient_record.js delete mode 100644 erpnext/healthcare/doctype/inpatient_record/inpatient_record.json delete mode 100644 erpnext/healthcare/doctype/inpatient_record/inpatient_record.py delete mode 100644 erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py delete mode 100644 erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py delete mode 100644 erpnext/healthcare/doctype/lab_prescription/__init__.py delete mode 100644 erpnext/healthcare/doctype/lab_prescription/lab_prescription.json delete mode 100644 erpnext/healthcare/doctype/lab_prescription/lab_prescription.py delete mode 100644 erpnext/healthcare/doctype/lab_test/__init__.py delete mode 100644 erpnext/healthcare/doctype/lab_test/lab_test.js delete mode 100644 erpnext/healthcare/doctype/lab_test/lab_test.json delete mode 100644 erpnext/healthcare/doctype/lab_test/lab_test.py delete mode 100644 erpnext/healthcare/doctype/lab_test/lab_test_list.js delete mode 100644 erpnext/healthcare/doctype/lab_test/test_lab_test.py delete mode 100644 erpnext/healthcare/doctype/lab_test_group_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json delete mode 100644 erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py delete mode 100644 erpnext/healthcare/doctype/lab_test_sample/__init__.py delete mode 100644 erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.js delete mode 100644 erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.json delete mode 100644 erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.py delete mode 100644 erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.py delete mode 100644 erpnext/healthcare/doctype/lab_test_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/lab_test_template/lab_test_template.js delete mode 100644 erpnext/healthcare/doctype/lab_test_template/lab_test_template.json delete mode 100644 erpnext/healthcare/doctype/lab_test_template/lab_test_template.py delete mode 100644 erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py delete mode 100644 erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js delete mode 100644 erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.py delete mode 100644 erpnext/healthcare/doctype/lab_test_uom/__init__.py delete mode 100644 erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.js delete mode 100644 erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.json delete mode 100644 erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.py delete mode 100644 erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.py delete mode 100644 erpnext/healthcare/doctype/medical_code/__init__.py delete mode 100644 erpnext/healthcare/doctype/medical_code/medical_code.js delete mode 100644 erpnext/healthcare/doctype/medical_code/medical_code.json delete mode 100644 erpnext/healthcare/doctype/medical_code/medical_code.py delete mode 100644 erpnext/healthcare/doctype/medical_code/test_medical_code.py delete mode 100644 erpnext/healthcare/doctype/medical_code_standard/__init__.py delete mode 100644 erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.js delete mode 100644 erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.json delete mode 100644 erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.py delete mode 100644 erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.py delete mode 100644 erpnext/healthcare/doctype/medical_department/__init__.py delete mode 100644 erpnext/healthcare/doctype/medical_department/medical_department.js delete mode 100644 erpnext/healthcare/doctype/medical_department/medical_department.json delete mode 100644 erpnext/healthcare/doctype/medical_department/medical_department.py delete mode 100644 erpnext/healthcare/doctype/medical_department/test_medical_department.py delete mode 100644 erpnext/healthcare/doctype/normal_test_result/__init__.py delete mode 100644 erpnext/healthcare/doctype/normal_test_result/normal_test_result.json delete mode 100644 erpnext/healthcare/doctype/normal_test_result/normal_test_result.py delete mode 100644 erpnext/healthcare/doctype/normal_test_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/normal_test_template/normal_test_template.json delete mode 100644 erpnext/healthcare/doctype/normal_test_template/normal_test_template.py delete mode 100644 erpnext/healthcare/doctype/organism/__init__.py delete mode 100644 erpnext/healthcare/doctype/organism/organism.js delete mode 100644 erpnext/healthcare/doctype/organism/organism.json delete mode 100644 erpnext/healthcare/doctype/organism/organism.py delete mode 100644 erpnext/healthcare/doctype/organism/test_organism.py delete mode 100644 erpnext/healthcare/doctype/organism_test_item/__init__.py delete mode 100644 erpnext/healthcare/doctype/organism_test_item/organism_test_item.json delete mode 100644 erpnext/healthcare/doctype/organism_test_item/organism_test_item.py delete mode 100644 erpnext/healthcare/doctype/organism_test_result/__init__.py delete mode 100644 erpnext/healthcare/doctype/organism_test_result/organism_test_result.json delete mode 100644 erpnext/healthcare/doctype/organism_test_result/organism_test_result.py delete mode 100644 erpnext/healthcare/doctype/patient/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient/patient.js delete mode 100644 erpnext/healthcare/doctype/patient/patient.json delete mode 100644 erpnext/healthcare/doctype/patient/patient.py delete mode 100644 erpnext/healthcare/doctype/patient/patient_dashboard.py delete mode 100644 erpnext/healthcare/doctype/patient/test_patient.py delete mode 100644 erpnext/healthcare/doctype/patient_appointment/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_appointment/patient_appointment.js delete mode 100644 erpnext/healthcare/doctype/patient_appointment/patient_appointment.json delete mode 100755 erpnext/healthcare/doctype/patient_appointment/patient_appointment.py delete mode 100644 erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js delete mode 100644 erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py delete mode 100644 erpnext/healthcare/doctype/patient_appointment/patient_appointment_list.js delete mode 100644 erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment/patient_assessment.js delete mode 100644 erpnext/healthcare/doctype/patient_assessment/patient_assessment.json delete mode 100644 erpnext/healthcare/doctype/patient_assessment/patient_assessment.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_detail/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json delete mode 100644 erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js delete mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json delete mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_sheet/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json delete mode 100644 erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js delete mode 100644 erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json delete mode 100644 erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.py delete mode 100644 erpnext/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter/patient_encounter.js delete mode 100644 erpnext/healthcare/doctype/patient_encounter/patient_encounter.json delete mode 100644 erpnext/healthcare/doctype/patient_encounter/patient_encounter.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter/patient_encounter_list.js delete mode 100644 erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter_diagnosis/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json delete mode 100644 erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter_symptom/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json delete mode 100644 erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py delete mode 100644 erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json delete mode 100644 erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py delete mode 100644 erpnext/healthcare/doctype/patient_history_settings/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js delete mode 100644 erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json delete mode 100644 erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py delete mode 100644 erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py delete mode 100644 erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json delete mode 100644 erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py delete mode 100644 erpnext/healthcare/doctype/patient_medical_record/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.js delete mode 100644 erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json delete mode 100644 erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.py delete mode 100644 erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py delete mode 100644 erpnext/healthcare/doctype/patient_relation/__init__.py delete mode 100644 erpnext/healthcare/doctype/patient_relation/patient_relation.json delete mode 100644 erpnext/healthcare/doctype/patient_relation/patient_relation.py delete mode 100644 erpnext/healthcare/doctype/practitioner_schedule/__init__.py delete mode 100644 erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.js delete mode 100644 erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json delete mode 100644 erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.py delete mode 100644 erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py delete mode 100644 erpnext/healthcare/doctype/practitioner_service_unit_schedule/__init__.py delete mode 100644 erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json delete mode 100644 erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py delete mode 100644 erpnext/healthcare/doctype/prescription_dosage/__init__.py delete mode 100644 erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.js delete mode 100644 erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.json delete mode 100644 erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.py delete mode 100644 erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.py delete mode 100644 erpnext/healthcare/doctype/prescription_duration/__init__.py delete mode 100644 erpnext/healthcare/doctype/prescription_duration/prescription_duration.js delete mode 100644 erpnext/healthcare/doctype/prescription_duration/prescription_duration.json delete mode 100644 erpnext/healthcare/doctype/prescription_duration/prescription_duration.py delete mode 100644 erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.py delete mode 100644 erpnext/healthcare/doctype/procedure_prescription/__init__.py delete mode 100644 erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json delete mode 100644 erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.py delete mode 100644 erpnext/healthcare/doctype/sample_collection/__init__.py delete mode 100644 erpnext/healthcare/doctype/sample_collection/sample_collection.js delete mode 100644 erpnext/healthcare/doctype/sample_collection/sample_collection.json delete mode 100644 erpnext/healthcare/doctype/sample_collection/sample_collection.py delete mode 100644 erpnext/healthcare/doctype/sample_collection/test_sample_collection.py delete mode 100644 erpnext/healthcare/doctype/sensitivity/__init__.py delete mode 100644 erpnext/healthcare/doctype/sensitivity/sensitivity.js delete mode 100644 erpnext/healthcare/doctype/sensitivity/sensitivity.json delete mode 100644 erpnext/healthcare/doctype/sensitivity/sensitivity.py delete mode 100644 erpnext/healthcare/doctype/sensitivity/test_sensitivity.py delete mode 100644 erpnext/healthcare/doctype/sensitivity_test_result/__init__.py delete mode 100644 erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json delete mode 100644 erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan/__init__.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan.js delete mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan.json delete mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js delete mode 100644 erpnext/healthcare/doctype/therapy_plan_detail/__init__.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json delete mode 100644 erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template_detail/__init__.py delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json delete mode 100644 erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py delete mode 100644 erpnext/healthcare/doctype/therapy_session/__init__.py delete mode 100644 erpnext/healthcare/doctype/therapy_session/test_therapy_session.py delete mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session.js delete mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session.json delete mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session.py delete mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py delete mode 100644 erpnext/healthcare/doctype/therapy_type/__init__.py delete mode 100644 erpnext/healthcare/doctype/therapy_type/test_therapy_type.py delete mode 100644 erpnext/healthcare/doctype/therapy_type/therapy_type.js delete mode 100644 erpnext/healthcare/doctype/therapy_type/therapy_type.json delete mode 100644 erpnext/healthcare/doctype/therapy_type/therapy_type.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/__init__.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/test_records.json delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json delete mode 100644 erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py delete mode 100644 erpnext/healthcare/doctype/vital_signs/__init__.py delete mode 100644 erpnext/healthcare/doctype/vital_signs/test_vital_signs.py delete mode 100644 erpnext/healthcare/doctype/vital_signs/vital_signs.js delete mode 100644 erpnext/healthcare/doctype/vital_signs/vital_signs.json delete mode 100644 erpnext/healthcare/doctype/vital_signs/vital_signs.py delete mode 100644 erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json delete mode 100644 erpnext/healthcare/module_onboarding/healthcare/healthcare.json delete mode 100644 erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json delete mode 100644 erpnext/healthcare/number_card/open_appointments/open_appointments.json delete mode 100644 erpnext/healthcare/number_card/total_patients/total_patients.json delete mode 100644 erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json delete mode 100644 erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json delete mode 100644 erpnext/healthcare/onboarding_step/create_patient/create_patient.json delete mode 100644 erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json delete mode 100644 erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json delete mode 100644 erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json delete mode 100644 erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json delete mode 100644 erpnext/healthcare/page/__init__.py delete mode 100644 erpnext/healthcare/page/patient_history/__init__.py delete mode 100644 erpnext/healthcare/page/patient_history/patient_history.css delete mode 100644 erpnext/healthcare/page/patient_history/patient_history.html delete mode 100644 erpnext/healthcare/page/patient_history/patient_history.js delete mode 100644 erpnext/healthcare/page/patient_history/patient_history.json delete mode 100644 erpnext/healthcare/page/patient_history/patient_history.py delete mode 100644 erpnext/healthcare/page/patient_history/patient_history_sidebar.html delete mode 100644 erpnext/healthcare/page/patient_progress/__init__.py delete mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.css delete mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.html delete mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.js delete mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.json delete mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.py delete mode 100644 erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html delete mode 100644 erpnext/healthcare/print_format/__init__.py delete mode 100644 erpnext/healthcare/print_format/encounter_print/__init__.py delete mode 100644 erpnext/healthcare/print_format/encounter_print/encounter_print.json delete mode 100644 erpnext/healthcare/print_format/lab_test_print/__init__.py delete mode 100644 erpnext/healthcare/print_format/lab_test_print/lab_test_print.json delete mode 100644 erpnext/healthcare/print_format/sample_id_print/__init__.py delete mode 100644 erpnext/healthcare/print_format/sample_id_print/sample_id_print.json delete mode 100644 erpnext/healthcare/report/__init__.py delete mode 100644 erpnext/healthcare/report/inpatient_medication_orders/__init__.py delete mode 100644 erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js delete mode 100644 erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json delete mode 100644 erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py delete mode 100644 erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py delete mode 100644 erpnext/healthcare/report/lab_test_report/__init__.py delete mode 100644 erpnext/healthcare/report/lab_test_report/lab_test_report.js delete mode 100644 erpnext/healthcare/report/lab_test_report/lab_test_report.json delete mode 100644 erpnext/healthcare/report/lab_test_report/lab_test_report.py delete mode 100644 erpnext/healthcare/report/patient_appointment_analytics/__init__.py delete mode 100644 erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js delete mode 100644 erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json delete mode 100644 erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py delete mode 100644 erpnext/healthcare/setup.py delete mode 100644 erpnext/healthcare/utils.py delete mode 100644 erpnext/healthcare/web_form/__init__.py delete mode 100644 erpnext/healthcare/web_form/lab_test/__init__.py delete mode 100644 erpnext/healthcare/web_form/lab_test/lab_test.js delete mode 100644 erpnext/healthcare/web_form/lab_test/lab_test.json delete mode 100644 erpnext/healthcare/web_form/lab_test/lab_test.py delete mode 100644 erpnext/healthcare/web_form/patient_appointments/__init__.py delete mode 100644 erpnext/healthcare/web_form/patient_appointments/patient_appointments.js delete mode 100644 erpnext/healthcare/web_form/patient_appointments/patient_appointments.json delete mode 100644 erpnext/healthcare/web_form/patient_appointments/patient_appointments.py delete mode 100644 erpnext/healthcare/web_form/patient_registration/__init__.py delete mode 100644 erpnext/healthcare/web_form/patient_registration/patient_registration.js delete mode 100644 erpnext/healthcare/web_form/patient_registration/patient_registration.json delete mode 100644 erpnext/healthcare/web_form/patient_registration/patient_registration.py delete mode 100644 erpnext/healthcare/web_form/personal_details/__init__.py delete mode 100644 erpnext/healthcare/web_form/personal_details/personal_details.js delete mode 100644 erpnext/healthcare/web_form/personal_details/personal_details.json delete mode 100644 erpnext/healthcare/web_form/personal_details/personal_details.py delete mode 100644 erpnext/healthcare/web_form/prescription/__init__.py delete mode 100644 erpnext/healthcare/web_form/prescription/prescription.js delete mode 100644 erpnext/healthcare/web_form/prescription/prescription.json delete mode 100644 erpnext/healthcare/web_form/prescription/prescription.py delete mode 100644 erpnext/healthcare/workspace/healthcare/healthcare.json diff --git a/erpnext/healthcare/__init__.py b/erpnext/healthcare/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json deleted file mode 100644 index 6803528156..0000000000 --- a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "chart_name": "Clinical Procedures", - "chart_type": "Group By", - "creation": "2020-07-14 18:17:54.601236", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Clinical Procedure", - "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]", - "group_by_based_on": "procedure_template", - "group_by_type": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2021-01-30 21:03:30.086891", - "modified": "2021-02-01 13:36:04.469863", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Clinical Procedures", - "number_of_groups": 0, - "owner": "Administrator", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json deleted file mode 100644 index dae9db19b8..0000000000 --- a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "chart_name": "Clinical Procedure Status", - "chart_type": "Group By", - "creation": "2020-07-14 18:17:54.654325", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Clinical Procedure", - "dynamic_filters_json": "[[\"Clinical Procedure\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[[\"Clinical Procedure\",\"docstatus\",\"=\",\"1\",false]]", - "group_by_based_on": "status", - "group_by_type": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2021-02-01 13:36:38.787783", - "modified": "2021-02-01 13:37:18.718275", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Clinical Procedures Status", - "number_of_groups": 0, - "owner": "Administrator", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json deleted file mode 100644 index b24bb345ac..0000000000 --- a/erpnext/healthcare/dashboard_chart/department_wise_patient_appointments/department_wise_patient_appointments.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "chart_name": "Department wise Patient Appointments", - "chart_type": "Custom", - "creation": "2020-07-17 11:25:37.190130", - "custom_options": "{\"colors\": [\"#7CD5FA\", \"#5F62F6\", \"#7544E2\", \"#EE5555\"], \"barOptions\": {\"stacked\": 1}, \"height\": 300}", - "docstatus": 0, - "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\"}", - "filters_json": "{}", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2020-07-22 15:32:05.827566", - "modified": "2020-07-22 15:35:12.798035", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Department wise Patient Appointments", - "number_of_groups": 0, - "owner": "Administrator", - "source": "Department wise Patient Appointments", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json deleted file mode 100644 index 82145d6024..0000000000 --- a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "chart_name": "Diagnoses", - "chart_type": "Group By", - "creation": "2020-07-14 18:17:54.705698", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Patient Encounter Diagnosis", - "dynamic_filters_json": "", - "filters_json": "[]", - "group_by_based_on": "diagnosis", - "group_by_type": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2021-01-30 21:03:33.729487", - "modified": "2021-02-01 13:34:57.385335", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Diagnoses", - "number_of_groups": 0, - "owner": "Administrator", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json b/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json deleted file mode 100644 index 77b47c9e15..0000000000 --- a/erpnext/healthcare/dashboard_chart/in_patient_status/in_patient_status.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "chart_name": "In-Patient Status", - "chart_type": "Group By", - "creation": "2020-07-14 18:17:54.629199", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Inpatient Record", - "dynamic_filters_json": "[[\"Inpatient Record\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[]", - "group_by_based_on": "status", - "group_by_type": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:46.792131", - "modified": "2020-07-22 13:33:16.008150", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "In-Patient Status", - "number_of_groups": 0, - "owner": "Administrator", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json deleted file mode 100644 index 70293b158e..0000000000 --- a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "chart_name": "Lab Tests", - "chart_type": "Group By", - "creation": "2020-07-14 18:17:54.574903", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Lab Test", - "dynamic_filters_json": "[[\"Lab Test\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[[\"Lab Test\",\"docstatus\",\"=\",\"1\",false]]", - "group_by_based_on": "template", - "group_by_type": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2021-01-30 21:03:28.272914", - "modified": "2021-02-01 13:36:08.391433", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Tests", - "number_of_groups": 0, - "owner": "Administrator", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json b/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json deleted file mode 100644 index 19bfb7256f..0000000000 --- a/erpnext/healthcare/dashboard_chart/patient_appointments/patient_appointments.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "based_on": "appointment_datetime", - "chart_name": "Patient Appointments", - "chart_type": "Count", - "creation": "2020-07-14 18:17:54.525082", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Patient Appointment", - "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[[\"Patient Appointment\",\"status\",\"!=\",\"Cancelled\",false]]", - "idx": 0, - "is_public": 0, - "is_standard": 1, - "last_synced_on": "2020-07-22 13:22:46.830491", - "modified": "2020-07-22 13:38:02.254190", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Appointments", - "number_of_groups": 0, - "owner": "Administrator", - "time_interval": "Daily", - "timeseries": 1, - "timespan": "Last Month", - "type": "Line", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json deleted file mode 100644 index 65e5472aa1..0000000000 --- a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "chart_name": "Symptoms", - "chart_type": "Group By", - "creation": "2020-07-14 18:17:54.680852", - "docstatus": 0, - "doctype": "Dashboard Chart", - "document_type": "Patient Encounter Symptom", - "dynamic_filters_json": "", - "filters_json": "[]", - "group_by_based_on": "complaint", - "group_by_type": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "last_synced_on": "2021-01-30 21:03:32.067473", - "modified": "2021-02-01 13:35:30.953718", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Symptoms", - "number_of_groups": 0, - "owner": "Administrator", - "timeseries": 0, - "type": "Bar", - "use_report_chart": 0, - "y_axis": [] -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart_source/__init__.py b/erpnext/healthcare/dashboard_chart_source/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js deleted file mode 100644 index e494489d21..0000000000 --- a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.js +++ /dev/null @@ -1,14 +0,0 @@ -frappe.provide('frappe.dashboards.chart_sources'); - -frappe.dashboards.chart_sources["Department wise Patient Appointments"] = { - method: "erpnext.healthcare.dashboard_chart_source.department_wise_patient_appointments.department_wise_patient_appointments.get", - filters: [ - { - fieldname: "company", - label: __("Company"), - fieldtype: "Link", - options: "Company", - default: frappe.defaults.get_user_default("Company") - } - ] -}; diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json deleted file mode 100644 index 00301ef2c3..0000000000 --- a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "creation": "2020-05-18 19:18:42.571045", - "docstatus": 0, - "doctype": "Dashboard Chart Source", - "idx": 0, - "modified": "2020-05-18 19:18:42.571045", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Department wise Patient Appointments", - "owner": "Administrator", - "source_name": "Department wise Patient Appointments", - "timeseries": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py b/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py deleted file mode 100644 index 9c71ce86ac..0000000000 --- a/erpnext/healthcare/dashboard_chart_source/department_wise_patient_appointments/department_wise_patient_appointments.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -import frappe -from frappe.utils.dashboard import cache_source - - -@frappe.whitelist() -@cache_source -def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, - to_date = None, timespan = None, time_interval = None, heatmap_year = None): - if chart_name: - chart = frappe.get_doc('Dashboard Chart', chart_name) - else: - chart = frappe._dict(frappe.parse_json(chart)) - - filters = frappe.parse_json(filters) - - data = frappe.db.get_list('Medical Department', fields=['name']) - if not filters: - filters = {} - - status = ['Open', 'Scheduled', 'Closed', 'Cancelled'] - for department in data: - filters['department'] = department.name - department['total_appointments'] = frappe.db.count('Patient Appointment', filters=filters) - - for entry in status: - filters['status'] = entry - department[frappe.scrub(entry)] = frappe.db.count('Patient Appointment', filters=filters) - filters.pop('status') - - sorted_department_map = sorted(data, key = lambda i: i['total_appointments'], reverse=True) - - if len(sorted_department_map) > 10: - sorted_department_map = sorted_department_map[:10] - - labels = [] - open_appointments = [] - scheduled = [] - closed = [] - cancelled = [] - - for department in sorted_department_map: - labels.append(department.name) - open_appointments.append(department.open) - scheduled.append(department.scheduled) - closed.append(department.closed) - cancelled.append(department.cancelled) - - return { - 'labels': labels, - 'datasets': [ - { - 'name': 'Open', - 'values': open_appointments - }, - { - 'name': 'Scheduled', - 'values': scheduled - }, - { - 'name': 'Closed', - 'values': closed - }, - { - 'name': 'Cancelled', - 'values': cancelled - } - ], - 'type': 'bar' - } diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json deleted file mode 100644 index af601f3eb2..0000000000 --- a/erpnext/healthcare/desk_page/healthcare/healthcare.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Masters", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Consultation Setup", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Consultation", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]" - }, - { - "hidden": 0, - "label": "Laboratory Setup", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Laboratory", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Inpatient", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Order\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Medication Entry\",\n\t\t\"label\": \"Inpatient Medication Entry\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Rehabilitation and Physiotherapy", - "links": "[\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exercise Type\",\n\t\t\"label\": \"Exercise Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Type\",\n\t\t\"label\": \"Therapy Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Plan\",\n\t\t\"label\": \"Therapy Plan\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Session\",\n\t\t\"label\": \"Therapy Session\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment Template\",\n\t\t\"label\": \"Patient Assessment Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment\",\n\t\t\"label\": \"Patient Assessment\"\n\t}\n]" - }, - { - "hidden": 0, - "label": "Records and History", - "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\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]" - }, - { - "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\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Inpatient Medication Orders\",\n\t\t\"doctype\": \"Inpatient Medication Order\",\n\t\t\"label\": \"Inpatient Medication Orders\"\n\t}\n]" - } - ], - "category": "Domains", - "charts": [ - { - "chart_name": "Patient Appointments", - "label": "Patient Appointments" - } - ], - "charts_label": "", - "creation": "2020-03-02 17:23:17.919682", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Desk Page", - "extends_another_page": 0, - "hide_custom": 0, - "idx": 0, - "is_standard": 1, - "label": "Healthcare", - "modified": "2020-11-26 22:09:09.164584", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare", - "onboarding": "Healthcare", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "restrict_to_domain": "Healthcare", - "shortcuts": [ - { - "color": "#ffe8cd", - "format": "{} Open", - "label": "Patient Appointment", - "link_to": "Patient Appointment", - "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}", - "type": "DocType" - }, - { - "color": "#ffe8cd", - "format": "{} Active", - "label": "Patient", - "link_to": "Patient", - "stats_filter": "{\n \"status\": \"Active\"\n}", - "type": "DocType" - }, - { - "color": "#cef6d1", - "format": "{} Vacant", - "label": "Healthcare Service Unit", - "link_to": "Healthcare Service Unit", - "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}", - "type": "DocType" - }, - { - "label": "Healthcare Practitioner", - "link_to": "Healthcare Practitioner", - "type": "DocType" - }, - { - "label": "Patient History", - "link_to": "patient_history", - "type": "Page" - }, - { - "label": "Dashboard", - "link_to": "Healthcare", - "type": "Dashboard" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/__init__.py b/erpnext/healthcare/doctype/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/antibiotic/__init__.py b/erpnext/healthcare/doctype/antibiotic/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/antibiotic/antibiotic.js b/erpnext/healthcare/doctype/antibiotic/antibiotic.js deleted file mode 100644 index 42e6adb603..0000000000 --- a/erpnext/healthcare/doctype/antibiotic/antibiotic.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Antibiotic', { -}); diff --git a/erpnext/healthcare/doctype/antibiotic/antibiotic.json b/erpnext/healthcare/doctype/antibiotic/antibiotic.json deleted file mode 100644 index 41a3e318f3..0000000000 --- a/erpnext/healthcare/doctype/antibiotic/antibiotic.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "allow_copy": 1, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:antibiotic_name", - "beta": 1, - "creation": "2016-02-23 11:11:30.749731", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "antibiotic_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Antibiotic Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "abbr", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Abbr", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-10-01 17:58:23.136498", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Antibiotic", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "antibiotic_name", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "antibiotic_name", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/antibiotic/antibiotic.py b/erpnext/healthcare/doctype/antibiotic/antibiotic.py deleted file mode 100644 index 6a4b7648b9..0000000000 --- a/erpnext/healthcare/doctype/antibiotic/antibiotic.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class Antibiotic(Document): - pass diff --git a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.py b/erpnext/healthcare/doctype/antibiotic/test_antibiotic.py deleted file mode 100644 index b6ec79f7ff..0000000000 --- a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestAntibiotic(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/appointment_type/__init__.py b/erpnext/healthcare/doctype/appointment_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.js b/erpnext/healthcare/doctype/appointment_type/appointment_type.js deleted file mode 100644 index 99b7cb295a..0000000000 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.js +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Appointment Type', { - refresh: function(frm) { - frm.set_query('price_list', function() { - return { - filters: {'selling': 1} - }; - }); - - frm.set_query('medical_department', 'items', function(doc) { - let item_list = doc.items.map(({medical_department}) => medical_department); - return { - filters: [ - ['Medical Department', 'name', 'not in', item_list] - ] - }; - }); - - frm.set_query('op_consulting_charge_item', 'items', function() { - return { - filters: { - is_stock_item: 0 - } - }; - }); - - frm.set_query('inpatient_visit_charge_item', 'items', function() { - return { - filters: { - is_stock_item: 0 - } - }; - }); - } -}); - -frappe.ui.form.on('Appointment Type Service Item', { - op_consulting_charge_item: function(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - if (frm.doc.price_list && d.op_consulting_charge_item) { - frappe.call({ - 'method': 'frappe.client.get_value', - args: { - 'doctype': 'Item Price', - 'filters': { - 'item_code': d.op_consulting_charge_item, - 'price_list': frm.doc.price_list - }, - 'fieldname': ['price_list_rate'] - }, - callback: function(data) { - if (data.message.price_list_rate) { - frappe.model.set_value(cdt, cdn, 'op_consulting_charge', data.message.price_list_rate); - } - } - }); - } - }, - - inpatient_visit_charge_item: function(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - if (frm.doc.price_list && d.inpatient_visit_charge_item) { - frappe.call({ - 'method': 'frappe.client.get_value', - args: { - 'doctype': 'Item Price', - 'filters': { - 'item_code': d.inpatient_visit_charge_item, - 'price_list': frm.doc.price_list - }, - 'fieldname': ['price_list_rate'] - }, - callback: function (data) { - if (data.message.price_list_rate) { - frappe.model.set_value(cdt, cdn, 'inpatient_visit_charge', data.message.price_list_rate); - } - } - }); - } - } -}); diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.json b/erpnext/healthcare/doctype/appointment_type/appointment_type.json deleted file mode 100644 index 3872318287..0000000000 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:appointment_type", - "beta": 1, - "creation": "2016-07-22 11:52:34.953019", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "appointment_type", - "ip", - "default_duration", - "color", - "billing_section", - "price_list", - "items" - ], - "fields": [ - { - "allow_in_quick_entry": 1, - "fieldname": "appointment_type", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Type", - "reqd": 1, - "translatable": 1, - "unique": 1 - }, - { - "bold": 1, - "default": "0", - "fieldname": "ip", - "fieldtype": "Check", - "label": "Is Inpatient", - "print_hide": 1, - "report_hide": 1 - }, - { - "allow_in_quick_entry": 1, - "bold": 1, - "fieldname": "default_duration", - "fieldtype": "Int", - "in_filter": 1, - "in_list_view": 1, - "label": "Default Duration (In Minutes)" - }, - { - "allow_in_quick_entry": 1, - "fieldname": "color", - "fieldtype": "Color", - "in_list_view": 1, - "label": "Color", - "no_copy": 1, - "report_hide": 1 - }, - { - "fieldname": "billing_section", - "fieldtype": "Section Break", - "label": "Billing" - }, - { - "fieldname": "price_list", - "fieldtype": "Link", - "label": "Price List", - "options": "Price List" - }, - { - "fieldname": "items", - "fieldtype": "Table", - "label": "Appointment Type Service Items", - "options": "Appointment Type Service Item" - } - ], - "links": [], - "modified": "2021-01-22 09:41:05.010524", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Appointment Type", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "search_fields": "appointment_type", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type.py b/erpnext/healthcare/doctype/appointment_type/appointment_type.py deleted file mode 100644 index 94d023f2c3..0000000000 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe.model.document import Document - - -class AppointmentType(Document): - def validate(self): - if self.items and self.price_list: - for item in self.items: - existing_op_item_price = frappe.db.exists('Item Price', { - 'item_code': item.op_consulting_charge_item, - 'price_list': self.price_list - }) - - if not existing_op_item_price and item.op_consulting_charge_item and item.op_consulting_charge: - make_item_price(self.price_list, item.op_consulting_charge_item, item.op_consulting_charge) - - existing_ip_item_price = frappe.db.exists('Item Price', { - 'item_code': item.inpatient_visit_charge_item, - 'price_list': self.price_list - }) - - if not existing_ip_item_price and item.inpatient_visit_charge_item and item.inpatient_visit_charge: - make_item_price(self.price_list, item.inpatient_visit_charge_item, item.inpatient_visit_charge) - -@frappe.whitelist() -def get_service_item_based_on_department(appointment_type, department): - item_list = frappe.db.get_value('Appointment Type Service Item', - filters = {'medical_department': department, 'parent': appointment_type}, - fieldname = ['op_consulting_charge_item', - 'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'], - as_dict = 1 - ) - - # if department wise items are not set up - # use the generic items - if not item_list: - item_list = frappe.db.get_value('Appointment Type Service Item', - filters = {'parent': appointment_type}, - fieldname = ['op_consulting_charge_item', - 'inpatient_visit_charge_item', 'op_consulting_charge', 'inpatient_visit_charge'], - as_dict = 1 - ) - - return item_list - -def make_item_price(price_list, item, item_price): - frappe.get_doc({ - 'doctype': 'Item Price', - 'price_list': price_list, - 'item_code': item, - 'price_list_rate': item_price - }).insert(ignore_permissions=True, ignore_mandatory=True) diff --git a/erpnext/healthcare/doctype/appointment_type/appointment_type_dashboard.py b/erpnext/healthcare/doctype/appointment_type/appointment_type_dashboard.py deleted file mode 100644 index b9c6edbb19..0000000000 --- a/erpnext/healthcare/doctype/appointment_type/appointment_type_dashboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'appointment_type', - 'transactions': [ - { - 'label': _('Patient Appointments'), - 'items': ['Patient Appointment'] - }, - ] - } diff --git a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.py b/erpnext/healthcare/doctype/appointment_type/test_appointment_type.py deleted file mode 100644 index 04452e470e..0000000000 --- a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Appointment Type') - -class TestAppointmentType(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/appointment_type_service_item/__init__.py b/erpnext/healthcare/doctype/appointment_type_service_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json deleted file mode 100644 index ccae129ea0..0000000000 --- a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "actions": [], - "creation": "2021-01-22 09:34:53.373105", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "medical_department", - "op_consulting_charge_item", - "op_consulting_charge", - "column_break_4", - "inpatient_visit_charge_item", - "inpatient_visit_charge" - ], - "fields": [ - { - "fieldname": "medical_department", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Medical Department", - "options": "Medical Department" - }, - { - "fieldname": "op_consulting_charge_item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Out Patient Consulting Charge Item", - "options": "Item" - }, - { - "fieldname": "op_consulting_charge", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Out Patient Consulting Charge" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "inpatient_visit_charge_item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Inpatient Visit Charge Item", - "options": "Item" - }, - { - "fieldname": "inpatient_visit_charge", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Inpatient Visit Charge" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-08-17 06:05:02.240812", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Appointment Type Service Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py b/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py deleted file mode 100644 index 026d5d79fd..0000000000 --- a/erpnext/healthcare/doctype/appointment_type_service_item/appointment_type_service_item.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -# import frappe -from frappe.model.document import Document - - -class AppointmentTypeServiceItem(Document): - pass diff --git a/erpnext/healthcare/doctype/body_part/__init__.py b/erpnext/healthcare/doctype/body_part/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/body_part/body_part.js b/erpnext/healthcare/doctype/body_part/body_part.js deleted file mode 100644 index d2f9d09937..0000000000 --- a/erpnext/healthcare/doctype/body_part/body_part.js +++ /dev/null @@ -1,8 +0,0 @@ -// 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) { - - // } -}); diff --git a/erpnext/healthcare/doctype/body_part/body_part.json b/erpnext/healthcare/doctype/body_part/body_part.json deleted file mode 100644 index 6e3d1d4ce3..0000000000 --- a/erpnext/healthcare/doctype/body_part/body_part.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/body_part/body_part.py b/erpnext/healthcare/doctype/body_part/body_part.py deleted file mode 100644 index 77e8dd90a0..0000000000 --- a/erpnext/healthcare/doctype/body_part/body_part.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/body_part/test_body_part.py b/erpnext/healthcare/doctype/body_part/test_body_part.py deleted file mode 100644 index a81ba179bf..0000000000 --- a/erpnext/healthcare/doctype/body_part/test_body_part.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/body_part_link/__init__.py b/erpnext/healthcare/doctype/body_part_link/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/body_part_link/body_part_link.json b/erpnext/healthcare/doctype/body_part_link/body_part_link.json deleted file mode 100644 index 400b7c6fe8..0000000000 --- a/erpnext/healthcare/doctype/body_part_link/body_part_link.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/body_part_link/body_part_link.py b/erpnext/healthcare/doctype/body_part_link/body_part_link.py deleted file mode 100644 index 07488f0117..0000000000 --- a/erpnext/healthcare/doctype/body_part_link/body_part_link.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/clinical_procedure/__init__.py b/erpnext/healthcare/doctype/clinical_procedure/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js deleted file mode 100644 index b55d5d6f63..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.js +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (c) 2017, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Clinical Procedure', { - setup: function(frm) { - frm.set_query('batch_no', 'items', function(doc, cdt, cdn) { - let item = locals[cdt][cdn]; - if (!item.item_code) { - frappe.throw(__('Please enter Item Code to get Batch Number')); - } else { - let filters = {'item_code': item.item_code}; - - if (frm.doc.status == 'In Progress') { - filters['posting_date'] = frm.doc.start_date || frappe.datetime.nowdate(); - if (frm.doc.warehouse) filters['warehouse'] = frm.doc.warehouse; - } - - return { - query : 'erpnext.controllers.queries.get_batch_no', - filters: filters - }; - } - }); - }, - - refresh: function(frm) { - frm.set_query('patient', function () { - return { - filters: {'status': ['!=', 'Disabled']} - }; - }); - - frm.set_query('appointment', function () { - return { - filters: { - 'procedure_template': ['not in', null], - 'status': ['in', 'Open, Scheduled'] - } - }; - }); - - frm.set_query('service_unit', function() { - return { - filters: { - 'is_group': false, - 'allow_appointments': true, - 'company': frm.doc.company - } - }; - }); - - frm.set_query('practitioner', function() { - return { - filters: { - 'department': frm.doc.medical_department - } - }; - }); - - if (frm.doc.consume_stock) { - frm.set_indicator_formatter('item_code', - function(doc) { return (doc.qty<=doc.actual_qty) ? 'green' : 'orange' ; }); - } - - if (frm.doc.docstatus == 1) { - if (frm.doc.status == 'In Progress') { - let btn_label = ''; - let msg = ''; - if (frm.doc.consume_stock) { - btn_label = __('Complete and Consume'); - msg = __('Complete {0} and Consume Stock?', [frm.doc.name]); - } else { - btn_label = 'Complete'; - msg = __('Complete {0}?', [frm.doc.name]); - } - - frm.add_custom_button(__(btn_label), function () { - frappe.confirm( - msg, - function() { - frappe.call({ - method: 'complete_procedure', - doc: frm.doc, - freeze: true, - callback: function(r) { - if (r.message) { - frappe.show_alert({ - message: __('Stock Entry {0} created', ['' + r.message + '']), - indicator: 'green' - }); - } - frm.reload_doc(); - } - }); - } - ); - }).addClass("btn-primary"); - - } else if (frm.doc.status == 'Pending') { - frm.add_custom_button(__('Start'), function() { - frappe.call({ - doc: frm.doc, - method: 'start_procedure', - callback: function(r) { - if (!r.exc) { - if (r.message == 'insufficient stock') { - let msg = __('Stock quantity to start the Procedure is not available in the Warehouse {0}. Do you want to record a Stock Entry?', [frm.doc.warehouse.bold()]); - frappe.confirm( - msg, - function() { - frappe.call({ - doc: frm.doc, - method: 'make_material_receipt', - freeze: true, - callback: function(r) { - if (!r.exc) { - frm.reload_doc(); - let doclist = frappe.model.sync(r.message); - frappe.set_route('Form', doclist[0].doctype, doclist[0].name); - } - } - }); - } - ); - } else { - frm.reload_doc(); - } - } - } - }); - }).addClass("btn-primary"); - } - } - }, - - onload: function(frm) { - if (frm.is_new()) { - frm.add_fetch('procedure_template', 'medical_department', 'medical_department'); - frm.set_value('start_time', null); - } - }, - - patient: function(frm) { - if (frm.doc.patient) { - frappe.call({ - 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { - patient: frm.doc.patient - }, - callback: function (data) { - let age = ''; - if (data.message.dob) { - age = calculate_age(data.message.dob); - } else if (data.message.age) { - age = data.message.age; - if (data.message.age_as_on) { - age = __('{0} as on {1}', [age, data.message.age_as_on]); - } - } - frm.set_value('patient_name', data.message.patient_name); - frm.set_value('patient_age', age); - frm.set_value('patient_sex', data.message.sex); - } - }); - } else { - frm.set_value('patient_name', ''); - frm.set_value('patient_age', ''); - frm.set_value('patient_sex', ''); - } - }, - - appointment: function(frm) { - if (frm.doc.appointment) { - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'Patient Appointment', - name: frm.doc.appointment - }, - callback: function(data) { - let values = { - 'patient':data.message.patient, - 'procedure_template': data.message.procedure_template, - 'medical_department': data.message.department, - 'practitioner': data.message.practitioner, - 'start_date': data.message.appointment_date, - 'start_time': data.message.appointment_time, - 'notes': data.message.notes, - 'service_unit': data.message.service_unit, - 'company': data.message.company - }; - frm.set_value(values); - } - }); - } else { - let values = { - 'patient': '', - 'patient_name': '', - 'patient_sex': '', - 'patient_age': '', - 'medical_department': '', - 'procedure_template': '', - 'start_date': '', - 'start_time': '', - 'notes': '', - 'service_unit': '', - 'inpatient_record': '' - }; - frm.set_value(values); - } - }, - - procedure_template: function(frm) { - if (frm.doc.procedure_template) { - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'Clinical Procedure Template', - name: frm.doc.procedure_template - }, - callback: function (data) { - frm.set_value('medical_department', data.message.medical_department); - frm.set_value('consume_stock', data.message.consume_stock); - frm.events.set_warehouse(frm); - frm.events.set_procedure_consumables(frm); - } - }); - } - }, - - service_unit: function(frm) { - if (frm.doc.service_unit) { - frappe.call({ - method: 'frappe.client.get_value', - args:{ - fieldname: 'warehouse', - doctype: 'Healthcare Service Unit', - filters:{name: frm.doc.service_unit}, - }, - callback: function(data) { - if (data.message) { - frm.set_value('warehouse', data.message.warehouse); - } - } - }); - } - }, - - practitioner: function(frm) { - if (frm.doc.practitioner) { - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'Healthcare Practitioner', - name: frm.doc.practitioner - }, - callback: function (data) { - frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', data.message.practitioner_name); - } - }); - } else { - frappe.model.set_value(frm.doctype,frm.docname, 'practitioner_name', ''); - } - }, - - set_warehouse: function(frm) { - if (!frm.doc.warehouse) { - frappe.call({ - method: 'frappe.client.get_value', - args: { - doctype: 'Stock Settings', - fieldname: 'default_warehouse' - }, - callback: function (data) { - frm.set_value('warehouse', data.message.default_warehouse); - } - }); - } - }, - - set_procedure_consumables: function(frm) { - frappe.call({ - method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.get_procedure_consumables', - args: { - procedure_template: frm.doc.procedure_template - }, - callback: function(data) { - if (data.message) { - frm.doc.items = []; - $.each(data.message, function(i, v) { - let item = frm.add_child('items'); - item.item_code = v.item_code; - item.item_name = v.item_name; - item.uom = v.uom; - item.stock_uom = v.stock_uom; - item.qty = flt(v.qty); - item.transfer_qty = v.transfer_qty; - item.conversion_factor = v.conversion_factor; - item.invoice_separately_as_consumables = v.invoice_separately_as_consumables; - item.batch_no = v.batch_no; - }); - refresh_field('items'); - } - } - }); - } - -}); - -frappe.ui.form.on('Clinical Procedure Item', { - qty: function(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, 'transfer_qty', d.qty*d.conversion_factor); - }, - - uom: function(doc, cdt, cdn) { - let d = locals[cdt][cdn]; - if (d.uom && d.item_code) { - return frappe.call({ - method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_uom_details', - args: { - item_code: d.item_code, - uom: d.uom, - qty: d.qty - }, - callback: function(r) { - if (r.message) { - frappe.model.set_value(cdt, cdn, r.message); - } - } - }); - } - }, - - item_code: function(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - let args = null; - if (d.item_code) { - args = { - 'doctype' : 'Clinical Procedure', - 'item_code' : d.item_code, - 'company' : frm.doc.company, - 'warehouse': frm.doc.warehouse - }; - return frappe.call({ - method: 'erpnext.healthcare.doctype.clinical_procedure_template.clinical_procedure_template.get_item_details', - args: {args: args}, - callback: function(r) { - if (r.message) { - let d = locals[cdt][cdn]; - $.each(r.message, function(k, v) { - d[k] = v; - }); - refresh_field('items'); - } - } - }); - } - } -}); - -let calculate_age = function(birth) { - let ageMS = Date.parse(Date()) - Date.parse(birth); - let age = new Date(); - age.setTime(ageMS); - let years = age.getFullYear() - 1970; - return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; -}; - -// List Stock items -cur_frm.set_query('item_code', 'items', function() { - return { - filters: { - is_stock_item:1 - } - }; -}); diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json deleted file mode 100644 index b1d62da032..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.json +++ /dev/null @@ -1,345 +0,0 @@ -{ - "actions": [], - "autoname": "naming_series:", - "beta": 1, - "creation": "2017-04-07 12:52:43.542429", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "naming_series", - "title", - "appointment", - "procedure_template", - "medical_code", - "column_break_30", - "company", - "invoiced", - "section_break_6", - "patient", - "patient_name", - "patient_sex", - "patient_age", - "inpatient_record", - "notes", - "column_break_7", - "status", - "practitioner", - "practitioner_name", - "medical_department", - "service_unit", - "start_date", - "start_time", - "sample", - "consumables_section", - "consume_stock", - "warehouse", - "items", - "section_break_24", - "invoice_separately_as_consumables", - "consumption_invoiced", - "consumable_total_amount", - "column_break_27", - "consumption_details", - "sb_refs", - "column_break_34", - "prescription", - "amended_from" - ], - "fields": [ - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-CPR-.YYYY.-" - }, - { - "fieldname": "appointment", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Appointment", - "options": "Patient Appointment", - "set_only_once": 1 - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "label": "Age", - "read_only": 1 - }, - { - "fieldname": "patient_sex", - "fieldtype": "Link", - "label": "Gender", - "options": "Gender", - "read_only": 1, - "set_only_once": 1 - }, - { - "fieldname": "prescription", - "fieldtype": "Link", - "hidden": 1, - "label": "Procedure Prescription", - "options": "Procedure Prescription", - "read_only": 1 - }, - { - "fieldname": "medical_department", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Medical Department", - "options": "Medical Department" - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner" - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fieldname": "procedure_template", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Procedure Template", - "options": "Clinical Procedure Template", - "reqd": 1 - }, - { - "fieldname": "service_unit", - "fieldtype": "Link", - "label": "Service Unit", - "options": "Healthcare Service Unit", - "set_only_once": 1 - }, - { - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "mandatory_depends_on": "eval: doc.consume_stock == 1", - "options": "Warehouse" - }, - { - "default": "Today", - "fieldname": "start_date", - "fieldtype": "Date", - "label": "Start Date" - }, - { - "fieldname": "start_time", - "fieldtype": "Time", - "label": "Start Time" - }, - { - "fieldname": "sample", - "fieldtype": "Link", - "label": "Sample", - "options": "Sample Collection" - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "notes", - "fieldtype": "Small Text", - "label": "Notes", - "set_only_once": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company" - }, - { - "default": "0", - "fieldname": "consume_stock", - "fieldtype": "Check", - "label": "Consume Stock" - }, - { - "fieldname": "items", - "fieldtype": "Table", - "label": "Consumables", - "options": "Clinical Procedure Item" - }, - { - "default": "0", - "fieldname": "invoice_separately_as_consumables", - "fieldtype": "Check", - "hidden": 1, - "label": "Invoice Consumables Separately", - "read_only": 1 - }, - { - "depends_on": "invoice_separately_as_consumables", - "fieldname": "consumable_total_amount", - "fieldtype": "Currency", - "label": "Consumable Total Amount", - "read_only": 1 - }, - { - "depends_on": "invoice_separately_as_consumables", - "fieldname": "consumption_details", - "fieldtype": "Small Text", - "label": "Consumption Details" - }, - { - "default": "0", - "depends_on": "invoice_separately_as_consumables", - "fieldname": "consumption_invoiced", - "fieldtype": "Check", - "hidden": 1, - "label": "Consumption Invoiced", - "read_only": 1 - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "options": "Draft\nSubmitted\nCancelled\nIn Progress\nCompleted\nPending", - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Clinical Procedure", - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "consume_stock", - "fieldname": "consumables_section", - "fieldtype": "Section Break", - "label": "Consumables" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_24", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_30", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_6", - "fieldtype": "Section Break" - }, - { - "collapsible": 1, - "fieldname": "sb_refs", - "fieldtype": "Section Break" - }, - { - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fieldname": "practitioner_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Practitioner Name", - "read_only": 1 - }, - { - "fieldname": "column_break_34", - "fieldtype": "Column Break" - }, - { - "allow_on_submit": 1, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "fetch_from": "procedure_template.medical_code", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code", - "read_only": 1 - } - ], - "is_submittable": 1, - "links": [], - "modified": "2020-06-29 14:28:11.779815", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Clinical Procedure", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py deleted file mode 100644 index df4c2ef905..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py +++ /dev/null @@ -1,255 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.mapper import get_mapped_doc -from frappe.utils import flt, nowdate, nowtime - -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account -from erpnext.healthcare.doctype.lab_test.lab_test import create_sample_doc -from erpnext.stock.get_item_details import get_item_details -from erpnext.stock.stock_ledger import get_previous_sle - - -class ClinicalProcedure(Document): - def validate(self): - self.set_status() - self.set_title() - if self.consume_stock: - self.set_actual_qty() - - if self.items: - self.invoice_separately_as_consumables = False - for item in self.items: - if item.invoice_separately_as_consumables: - self.invoice_separately_as_consumables = True - - def before_insert(self): - if self.consume_stock: - self.set_actual_qty() - - def after_insert(self): - if self.prescription: - frappe.db.set_value('Procedure Prescription', self.prescription, 'procedure_created', 1) - if self.appointment: - frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') - template = frappe.get_doc('Clinical Procedure Template', self.procedure_template) - if template.sample: - patient = frappe.get_doc('Patient', self.patient) - sample_collection = create_sample_doc(template, patient, None, self.company) - frappe.db.set_value('Clinical Procedure', self.name, 'sample', sample_collection.name) - self.reload() - - def set_status(self): - if self.docstatus == 0: - self.status = 'Draft' - elif self.docstatus == 1: - if self.status not in ['In Progress', 'Completed']: - self.status = 'Pending' - elif self.docstatus == 2: - self.status = 'Cancelled' - - def set_title(self): - self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100] - - @frappe.whitelist() - def complete_procedure(self): - if self.consume_stock and self.items: - stock_entry = make_stock_entry(self) - - if self.items: - consumable_total_amount = 0 - consumption_details = False - customer = frappe.db.get_value('Patient', self.patient, 'customer') - if customer: - for item in self.items: - if item.invoice_separately_as_consumables: - price_list, price_list_currency = frappe.db.get_values('Price List', {'selling': 1}, ['name', 'currency'])[0] - args = { - 'doctype': 'Sales Invoice', - 'item_code': item.item_code, - 'company': self.company, - 'warehouse': self.warehouse, - 'customer': customer, - 'selling_price_list': price_list, - 'price_list_currency': price_list_currency, - 'plc_conversion_rate': 1.0, - 'conversion_rate': 1.0 - } - item_details = get_item_details(args) - item_price = item_details.price_list_rate * item.qty - item_consumption_details = item_details.item_name + ' ' + str(item.qty) + ' ' + item.uom + ' ' + str(item_price) - consumable_total_amount += item_price - if not consumption_details: - consumption_details = _('Clinical Procedure ({0}):').format(self.name) - consumption_details += '\n\t' + item_consumption_details - - if consumable_total_amount > 0: - frappe.db.set_value('Clinical Procedure', self.name, 'consumable_total_amount', consumable_total_amount) - frappe.db.set_value('Clinical Procedure', self.name, 'consumption_details', consumption_details) - else: - frappe.throw(_('Please set Customer in Patient {0}').format(frappe.bold(self.patient)), title=_('Customer Not Found')) - - self.db_set('status', 'Completed') - - if self.consume_stock and self.items: - return stock_entry - - @frappe.whitelist() - def start_procedure(self): - allow_start = self.set_actual_qty() - if allow_start: - self.db_set('status', 'In Progress') - return 'success' - return 'insufficient stock' - - def set_actual_qty(self): - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') - - allow_start = True - for d in self.get('items'): - d.actual_qty = get_stock_qty(d.item_code, self.warehouse) - # validate qty - if not allow_negative_stock and d.actual_qty < d.qty: - allow_start = False - break - - return allow_start - - @frappe.whitelist() - def make_material_receipt(self, submit=False): - stock_entry = frappe.new_doc('Stock Entry') - - stock_entry.stock_entry_type = 'Material Receipt' - stock_entry.to_warehouse = self.warehouse - stock_entry.company = self.company - expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company) - for item in self.items: - if item.qty > item.actual_qty: - se_child = stock_entry.append('items') - se_child.item_code = item.item_code - se_child.item_name = item.item_name - se_child.uom = item.uom - se_child.stock_uom = item.stock_uom - se_child.qty = flt(item.qty - item.actual_qty) - se_child.t_warehouse = self.warehouse - # in stock uom - se_child.transfer_qty = flt(item.transfer_qty) - se_child.conversion_factor = flt(item.conversion_factor) - cost_center = frappe.get_cached_value('Company', self.company, 'cost_center') - se_child.cost_center = cost_center - se_child.expense_account = expense_account - if submit: - stock_entry.submit() - return stock_entry - return stock_entry.as_dict() - - -def get_stock_qty(item_code, warehouse): - return get_previous_sle({ - 'item_code': item_code, - 'warehouse': warehouse, - 'posting_date': nowdate(), - 'posting_time': nowtime() - }).get('qty_after_transaction') or 0 - - -@frappe.whitelist() -def get_procedure_consumables(procedure_template): - return get_items('Clinical Procedure Item', procedure_template, 'Clinical Procedure Template') - - -@frappe.whitelist() -def set_stock_items(doc, stock_detail_parent, parenttype): - items = get_items('Clinical Procedure Item', stock_detail_parent, parenttype) - - for item in items: - se_child = doc.append('items') - se_child.item_code = item.item_code - se_child.item_name = item.item_name - se_child.uom = item.uom - se_child.stock_uom = item.stock_uom - se_child.qty = flt(item.qty) - # in stock uom - se_child.transfer_qty = flt(item.transfer_qty) - se_child.conversion_factor = flt(item.conversion_factor) - if item.batch_no: - se_child.batch_no = item.batch_no - if parenttype == 'Clinical Procedure Template': - se_child.invoice_separately_as_consumables = item.invoice_separately_as_consumables - - return doc - - -def get_items(table, parent, parenttype): - items = frappe.db.get_all(table, filters={ - 'parent': parent, - 'parenttype': parenttype - }, fields=['*']) - - return items - - -@frappe.whitelist() -def make_stock_entry(doc): - stock_entry = frappe.new_doc('Stock Entry') - stock_entry = set_stock_items(stock_entry, doc.name, 'Clinical Procedure') - stock_entry.stock_entry_type = 'Material Issue' - stock_entry.from_warehouse = doc.warehouse - stock_entry.company = doc.company - expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company) - - for item_line in stock_entry.items: - cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center') - item_line.cost_center = cost_center - item_line.expense_account = expense_account - - stock_entry.save(ignore_permissions=True) - stock_entry.submit() - return stock_entry.name - - -@frappe.whitelist() -def make_procedure(source_name, target_doc=None): - def set_missing_values(source, target): - consume_stock = frappe.db.get_value('Clinical Procedure Template', source.procedure_template, 'consume_stock') - if consume_stock: - target.consume_stock = 1 - warehouse = None - if source.service_unit: - warehouse = frappe.db.get_value('Healthcare Service Unit', source.service_unit, 'warehouse') - if not warehouse: - warehouse = frappe.db.get_value('Stock Settings', None, 'default_warehouse') - if warehouse: - target.warehouse = warehouse - - set_stock_items(target, source.procedure_template, 'Clinical Procedure Template') - - doc = get_mapped_doc('Patient Appointment', source_name, { - 'Patient Appointment': { - 'doctype': 'Clinical Procedure', - 'field_map': [ - ['appointment', 'name'], - ['patient', 'patient'], - ['patient_age', 'patient_age'], - ['patient_sex', 'patient_sex'], - ['procedure_template', 'procedure_template'], - ['prescription', 'procedure_prescription'], - ['practitioner', 'practitioner'], - ['medical_department', 'department'], - ['start_date', 'appointment_date'], - ['start_time', 'appointment_time'], - ['notes', 'notes'], - ['service_unit', 'service_unit'], - ['company', 'company'], - ['invoiced', 'invoiced'] - ] - } - }, target_doc, set_missing_values) - - return doc diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure_list.js b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure_list.js deleted file mode 100644 index c8601f9677..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure_list.js +++ /dev/null @@ -1,11 +0,0 @@ -frappe.listview_settings['Clinical Procedure'] = { - get_indicator: function(doc) { - var colors = { - 'Completed': 'green', - 'In Progress': 'orange', - 'Pending': 'orange', - 'Cancelled': 'grey' - }; - return [__(doc.status), colors[doc.status], 'status,=,' + doc.status]; - } -}; diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py deleted file mode 100644 index b5c3744204..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py +++ /dev/null @@ -1,71 +0,0 @@ - # -*- coding: utf-8 -*- -# Copyright (c) 2017, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe - -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import ( - create_clinical_procedure_template, - create_healthcare_docs, -) - -test_dependencies = ['Item'] - -class TestClinicalProcedure(unittest.TestCase): - def test_procedure_template_item(self): - patient, practitioner = create_healthcare_docs() - procedure_template = create_clinical_procedure_template() - self.assertTrue(frappe.db.exists('Item', procedure_template.item)) - - procedure_template.disabled = 1 - procedure_template.save() - self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1) - - def test_consumables(self): - patient, practitioner = create_healthcare_docs() - procedure_template = create_clinical_procedure_template() - procedure_template.allow_stock_consumption = 1 - consumable = create_consumable() - procedure_template.append('items', { - 'item_code': consumable.item_code, - 'qty': 1, - 'uom': consumable.stock_uom, - 'stock_uom': consumable.stock_uom - }) - procedure_template.save() - procedure = create_procedure(procedure_template, patient, practitioner) - result = procedure.start_procedure() - if result == 'insufficient stock': - procedure.make_material_receipt(submit=True) - result = procedure.start_procedure() - self.assertEqual(procedure.status, 'In Progress') - result = procedure.complete_procedure() - # check consumption - self.assertTrue(frappe.db.exists('Stock Entry', result)) - - -def create_consumable(): - if frappe.db.exists('Item', 'Syringe'): - return frappe.get_doc('Item', 'Syringe') - consumable = frappe.new_doc('Item') - consumable.item_code = 'Syringe' - consumable.item_group = '_Test Item Group' - consumable.stock_uom = 'Nos' - consumable.valuation_rate = 5.00 - consumable.save() - return consumable - -def create_procedure(procedure_template, patient, practitioner): - procedure = frappe.new_doc('Clinical Procedure') - procedure.procedure_template = procedure_template.name - procedure.patient = patient - procedure.practitioner = practitioner - procedure.consume_stock = procedure_template.allow_stock_consumption - procedure.items = procedure_template.items - procedure.company = "_Test Company" - procedure.warehouse = "_Test Warehouse - _TC" - procedure.submit() - return procedure diff --git a/erpnext/healthcare/doctype/clinical_procedure_item/__init__.py b/erpnext/healthcare/doctype/clinical_procedure_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json b/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json deleted file mode 100644 index a7dde0bcd0..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "actions": [], - "beta": 1, - "creation": "2017-10-05 16:15:10.876952", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "item_name", - "qty", - "barcode", - "uom", - "invoice_separately_as_consumables", - "column_break_5", - "batch_no", - "conversion_factor", - "stock_uom", - "transfer_qty", - "actual_qty" - ], - "fields": [ - { - "bold": 1, - "columns": 3, - "fieldname": "item_code", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_global_search": 1, - "in_list_view": 1, - "label": "Item", - "options": "Item", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "barcode", - "fieldtype": "Data", - "label": "Barcode" - }, - { - "fieldname": "item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Name", - "read_only": 1 - }, - { - "fieldname": "qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Quantity", - "reqd": 1 - }, - { - "fieldname": "uom", - "fieldtype": "Link", - "in_list_view": 1, - "label": "UOM", - "options": "UOM", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "invoice_separately_as_consumables", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Invoice Separately as Consumables" - }, - { - "fieldname": "column_break_5", - "fieldtype": "Column Break" - }, - { - "fieldname": "batch_no", - "fieldtype": "Link", - "label": "Batch", - "options": "Batch" - }, - { - "fieldname": "conversion_factor", - "fieldtype": "Float", - "label": "Conversion Factor", - "read_only": 1 - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "options": "UOM", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "transfer_qty", - "fieldtype": "Float", - "label": "Transfer Qty", - "read_only": 1 - }, - { - "fieldname": "actual_qty", - "fieldtype": "Float", - "label": "Actual Qty (at source/target)", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "search_index": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-01 15:34:54.226722", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Clinical Procedure Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py b/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py deleted file mode 100644 index 16c5369cfe..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_item/clinical_procedure_item.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, earthians and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class ClinicalProcedureItem(Document): - pass diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/__init__.py b/erpnext/healthcare/doctype/clinical_procedure_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js deleted file mode 100644 index ae6b39bb18..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.js +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright (c) 2017, earthians and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Clinical Procedure Template', { - template: function(frm) { - if (!frm.doc.item_code) - frm.set_value('item_code', frm.doc.template); - if (!frm.doc.description) - frm.set_value('description', frm.doc.template); - 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); - }, - - medical_code: function(frm) { - frm.set_query("medical_code", function() { - return { - filters: { - medical_code_standard: frm.doc.medical_code_standard - } - }; - }); - }, - - refresh: function(frm) { - frm.fields_dict['items'].grid.set_column_disp('barcode', false); - frm.fields_dict['items'].grid.set_column_disp('batch_no', false); - - if (!frm.doc.__islocal) { - cur_frm.add_custom_button(__('Change Item Code'), function() { - change_template_code(frm.doc); - }); - } - } -}); - -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.clinical_procedure_template.clinical_procedure_template.change_item_code_from_template', - '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 - }); -}; - -frappe.ui.form.on('Clinical Procedure Item', { - qty: function(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - frappe.model.set_value(cdt, cdn, 'transfer_qty', d.qty * d.conversion_factor); - }, - - uom: function(doc, cdt, cdn){ - let d = locals[cdt][cdn]; - if (d.uom && d.item_code) { - return frappe.call({ - method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_uom_details', - args: { - item_code: d.item_code, - uom: d.uom, - qty: d.qty - }, - callback: function(r) { - if (r.message) { - frappe.model.set_value(cdt, cdn, r.message); - } - } - }); - } - }, - - item_code: function(frm, cdt, cdn) { - let d = locals[cdt][cdn]; - if (d.item_code) { - let args = { - 'item_code' : d.item_code, - 'transfer_qty' : d.transfer_qty, - 'quantity' : d.qty - }; - return frappe.call({ - method: 'erpnext.healthcare.doctype.clinical_procedure_template.clinical_procedure_template.get_item_details', - args: {args: args}, - callback: function(r) { - if (r.message) { - let d = locals[cdt][cdn]; - $.each(r.message, function(k, v) { - d[k] = v; - }); - refresh_field('items'); - } - } - }); - } - } -}); - -// List Stock items -cur_frm.set_query('item_code', 'items', function() { - return { - filters: { - is_stock_item:1 - } - }; -}); - -frappe.tour['Clinical Procedure Template'] = [ - { - fieldname: 'template', - title: __('Template Name'), - description: __('Enter a name for the Clinical Procedure Template') - }, - { - fieldname: 'item_code', - title: __('Item Code'), - description: __('Set the Item Code which will be used for billing the Clinical Procedure.') - }, - { - fieldname: 'item_group', - title: __('Item Group'), - description: __('Select an Item Group for the Clinical Procedure Item.') - }, - { - fieldname: 'is_billable', - title: __('Clinical Procedure Rate'), - description: __('Check this if the Clinical Procedure is billable and also set the rate.') - }, - { - fieldname: 'consume_stock', - title: __('Allow Stock Consumption'), - description: __('Check this if the Clinical Procedure utilises consumables. Click ') + "here" + __(' to know more') - - }, - { - fieldname: 'medical_department', - title: __('Medical Department'), - description: __('You can also set the Medical Department for the template. After saving the document, an Item will automatically be created for billing this Clinical Procedure. You can then use this template while creating Clinical Procedures for Patients. Templates save you from filling up redundant data every single time. You can also create templates for other operations like Lab Tests, Therapy Sessions, etc.') - } -]; diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json deleted file mode 100644 index 17ac7eb1f9..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:template", - "beta": 1, - "creation": "2017-10-05 14:59:55.438359", - "description": "Procedure Template", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "template", - "item", - "item_code", - "item_group", - "description", - "column_break_5", - "disabled", - "is_billable", - "rate", - "medical_department", - "medical_coding_section", - "medical_code_standard", - "medical_code", - "consumables", - "consume_stock", - "items", - "sample_collection", - "sample", - "sample_uom", - "sample_qty", - "column_break_21", - "sample_details", - "change_in_item" - ], - "fields": [ - { - "fieldname": "template", - "fieldtype": "Data", - "in_global_search": 1, - "in_list_view": 1, - "label": "Template Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "item_code", - "fieldtype": "Data", - "label": "Item Code", - "read_only_depends_on": "eval: !doc.__islocal ", - "reqd": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Item Group", - "options": "Item Group", - "reqd": 1 - }, - { - "fieldname": "medical_department", - "fieldtype": "Link", - "label": "Medical Department", - "options": "Medical Department" - }, - { - "fieldname": "column_break_5", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "is_billable", - "fieldtype": "Check", - "label": "Is Billable" - }, - { - "depends_on": "is_billable", - "fieldname": "rate", - "fieldtype": "Float", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Rate", - "mandatory_depends_on": "is_billable" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Description", - "no_copy": 1, - "reqd": 1 - }, - { - "default": "0", - "fieldname": "consume_stock", - "fieldtype": "Check", - "label": "Allow Stock Consumption", - "search_index": 1 - }, - { - "fieldname": "consumables", - "fieldtype": "Section Break", - "label": "Consumables" - }, - { - "depends_on": "eval:doc.consume_stock == 1", - "fieldname": "items", - "fieldtype": "Table", - "ignore_user_permissions": 1, - "label": "Items", - "options": "Clinical Procedure Item" - }, - { - "collapsible": 1, - "fieldname": "sample_collection", - "fieldtype": "Section Break", - "label": "Sample Collection" - }, - { - "fieldname": "sample", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Sample", - "options": "Lab Test Sample" - }, - { - "fetch_from": "sample.sample_uom", - "fieldname": "sample_uom", - "fieldtype": "Data", - "label": "Sample UOM", - "read_only": 1 - }, - { - "fieldname": "sample_qty", - "fieldtype": "Float", - "label": "Quantity" - }, - { - "fieldname": "column_break_21", - "fieldtype": "Column Break" - }, - { - "fieldname": "sample_details", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Collection Details" - }, - { - "default": "0", - "fieldname": "change_in_item", - "fieldtype": "Check", - "hidden": 1, - "label": "Change In Item", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled" - }, - { - "fieldname": "item", - "fieldtype": "Link", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Item", - "no_copy": 1, - "options": "Item", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "medical_coding_section", - "fieldtype": "Section Break", - "label": "Medical Coding" - }, - { - "fieldname": "medical_code_standard", - "fieldtype": "Link", - "label": "Medical Code Standard", - "options": "Medical Code Standard" - }, - { - "depends_on": "medical_code_standard", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code" - } - ], - "links": [], - "modified": "2020-06-29 14:12:27.158130", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Clinical Procedure 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 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "template", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "template", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py deleted file mode 100644 index 16e0969ae4..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, earthians and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.rename_doc import rename_doc - - -class ClinicalProcedureTemplate(Document): - def validate(self): - self.enable_disable_item() - - def after_insert(self): - create_item_from_template(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.template - item_doc.item_group = self.item_group - item_doc.description = self.description - item_doc.disabled = 0 - 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.template - item_price.price_list_rate = self.rate - 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) - - -@frappe.whitelist() -def get_item_details(args=None): - if not isinstance(args, dict): - args = json.loads(args) - - item = frappe.db.get_all('Item', - filters={ - 'disabled': 0, - 'name': args.get('item_code') - }, - fields=['stock_uom', 'item_name'] - ) - - if not item: - frappe.throw(_('Item {0} is not active').format(args.get('item_code'))) - - item = item[0] - ret = { - 'uom': item.stock_uom, - 'stock_uom': item.stock_uom, - 'item_name': item.item_name, - 'qty': 1, - 'transfer_qty': 0, - 'conversion_factor': 1 - } - return ret - -def create_item_from_template(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.template, - 'item_name':doc.template, - '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_template(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_code, item_code, ignore_permissions=True) - frappe.db.set_value('Clinical Procedure Template', doc.name, 'item_code', item_code) - return diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py deleted file mode 100644 index a69899d3ea..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template_dashboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'procedure_template', - 'transactions': [ - { - 'label': _('Consultations'), - 'items': ['Clinical Procedure'] - } - ] - } diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py deleted file mode 100644 index f754c76341..0000000000 --- a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, earthians and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestClinicalProcedureTemplate(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/codification_table/__init__.py b/erpnext/healthcare/doctype/codification_table/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/codification_table/codification_table.json b/erpnext/healthcare/doctype/codification_table/codification_table.json deleted file mode 100644 index 9a917b4fff..0000000000 --- a/erpnext/healthcare/doctype/codification_table/codification_table.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2017-06-22 13:09:23.159579", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "medical_code", - "code", - "description" - ], - "fields": [ - { - "fieldname": "medical_code", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Medical Code", - "options": "Medical Code", - "reqd": 1 - }, - { - "fetch_from": "medical_code.code", - "fieldname": "code", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Code", - "read_only": 1 - }, - { - "fetch_from": "medical_code.description", - "fieldname": "description", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Description", - "read_only": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-02-26 13:17:49.016293", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Codification Table", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/codification_table/codification_table.py b/erpnext/healthcare/doctype/codification_table/codification_table.py deleted file mode 100644 index 232d92ca9d..0000000000 --- a/erpnext/healthcare/doctype/codification_table/codification_table.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class CodificationTable(Document): - pass diff --git a/erpnext/healthcare/doctype/complaint/__init__.py b/erpnext/healthcare/doctype/complaint/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/complaint/complaint.js b/erpnext/healthcare/doctype/complaint/complaint.js deleted file mode 100644 index 5a2d219fe3..0000000000 --- a/erpnext/healthcare/doctype/complaint/complaint.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Complaint', { -}); diff --git a/erpnext/healthcare/doctype/complaint/complaint.json b/erpnext/healthcare/doctype/complaint/complaint.json deleted file mode 100644 index f600838b65..0000000000 --- a/erpnext/healthcare/doctype/complaint/complaint.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:complaints", - "beta": 1, - "creation": "2017-02-15 12:25:28.045267", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "complaints", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Complaints", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-10-05 11:18:42.017864", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Complaint", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "complaints", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "complaints", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/complaint/complaint.py b/erpnext/healthcare/doctype/complaint/complaint.py deleted file mode 100644 index 20e00f6b02..0000000000 --- a/erpnext/healthcare/doctype/complaint/complaint.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class Complaint(Document): - pass diff --git a/erpnext/healthcare/doctype/complaint/test_complaint.py b/erpnext/healthcare/doctype/complaint/test_complaint.py deleted file mode 100644 index d3e10692b0..0000000000 --- a/erpnext/healthcare/doctype/complaint/test_complaint.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestComplaint(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/descriptive_test_result/__init__.py b/erpnext/healthcare/doctype/descriptive_test_result/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json deleted file mode 100644 index fcd3828aa5..0000000000 --- a/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-02-22 15:12:36.202380", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "lab_test_particulars", - "result_value", - "allow_blank", - "template", - "require_result_value" - ], - "fields": [ - { - "fieldname": "lab_test_particulars", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Particulars", - "read_only": 1 - }, - { - "depends_on": "eval:doc.require_result_value == 1", - "fieldname": "result_value", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Value" - }, - { - "fieldname": "template", - "fieldtype": "Link", - "hidden": 1, - "label": "Template", - "options": "Lab Test Template", - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "require_result_value", - "fieldtype": "Check", - "hidden": 1, - "label": "Require Result Value", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "default": "1", - "fieldname": "allow_blank", - "fieldtype": "Check", - "label": "Allow Blank", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-07-23 12:33:47.693065", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Descriptive Test Result", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py b/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py deleted file mode 100644 index c08604694a..0000000000 --- a/erpnext/healthcare/doctype/descriptive_test_result/descriptive_test_result.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class DescriptiveTestResult(Document): - pass diff --git a/erpnext/healthcare/doctype/descriptive_test_template/__init__.py b/erpnext/healthcare/doctype/descriptive_test_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json deleted file mode 100644 index 9ee8f4fc68..0000000000 --- a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-02-22 16:12:12.394200", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "particulars", - "allow_blank" - ], - "fields": [ - { - "fieldname": "particulars", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Result Component" - }, - { - "default": "0", - "fieldname": "allow_blank", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Allow Blank" - } - ], - "istable": 1, - "links": [], - "modified": "2020-06-24 14:03:51.728863", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Descriptive Test Template", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py b/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py deleted file mode 100644 index 84184fbcca..0000000000 --- a/erpnext/healthcare/doctype/descriptive_test_template/descriptive_test_template.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class DescriptiveTestTemplate(Document): - pass diff --git a/erpnext/healthcare/doctype/diagnosis/__init__.py b/erpnext/healthcare/doctype/diagnosis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/diagnosis/diagnosis.js b/erpnext/healthcare/doctype/diagnosis/diagnosis.js deleted file mode 100644 index fb2557face..0000000000 --- a/erpnext/healthcare/doctype/diagnosis/diagnosis.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Diagnosis', { -}); diff --git a/erpnext/healthcare/doctype/diagnosis/diagnosis.json b/erpnext/healthcare/doctype/diagnosis/diagnosis.json deleted file mode 100644 index 936c2c50a9..0000000000 --- a/erpnext/healthcare/doctype/diagnosis/diagnosis.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:diagnosis", - "beta": 1, - "creation": "2017-02-15 12:23:59.341108", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "diagnosis", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Diagnosis", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-10-05 11:25:46.107435", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Diagnosis", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "diagnosis", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "diagnosis", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/diagnosis/diagnosis.py b/erpnext/healthcare/doctype/diagnosis/diagnosis.py deleted file mode 100644 index 05add1b530..0000000000 --- a/erpnext/healthcare/doctype/diagnosis/diagnosis.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class Diagnosis(Document): - pass diff --git a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.py b/erpnext/healthcare/doctype/diagnosis/test_diagnosis.py deleted file mode 100644 index c79164db37..0000000000 --- a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Diagnosis') - -class TestDiagnosis(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/dosage_form/__init__.py b/erpnext/healthcare/doctype/dosage_form/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/dosage_form/dosage_form.js b/erpnext/healthcare/doctype/dosage_form/dosage_form.js deleted file mode 100644 index 60e96969f6..0000000000 --- a/erpnext/healthcare/doctype/dosage_form/dosage_form.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Dosage Form', { -}); diff --git a/erpnext/healthcare/doctype/dosage_form/dosage_form.json b/erpnext/healthcare/doctype/dosage_form/dosage_form.json deleted file mode 100644 index 350aaedcba..0000000000 --- a/erpnext/healthcare/doctype/dosage_form/dosage_form.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:dosage_form", - "beta": 1, - "creation": "2017-04-08 12:04:33.987972", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dosage_form", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Dosage Form", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-10-05 11:24:57.888091", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Dosage Form", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/dosage_form/dosage_form.py b/erpnext/healthcare/doctype/dosage_form/dosage_form.py deleted file mode 100644 index 6b2d88b61b..0000000000 --- a/erpnext/healthcare/doctype/dosage_form/dosage_form.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class DosageForm(Document): - pass diff --git a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.py b/erpnext/healthcare/doctype/dosage_form/test_dosage_form.py deleted file mode 100644 index 0161b82ba3..0000000000 --- a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestDosageForm(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/dosage_strength/__init__.py b/erpnext/healthcare/doctype/dosage_strength/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/dosage_strength/dosage_strength.json b/erpnext/healthcare/doctype/dosage_strength/dosage_strength.json deleted file mode 100644 index da4f1a7964..0000000000 --- a/erpnext/healthcare/doctype/dosage_strength/dosage_strength.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2017-02-14 15:40:14.385707", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "strength", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Strength", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "strength_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-08-31 14:11:59.874645", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Dosage Strength", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/dosage_strength/dosage_strength.py b/erpnext/healthcare/doctype/dosage_strength/dosage_strength.py deleted file mode 100644 index 9e74743a7c..0000000000 --- a/erpnext/healthcare/doctype/dosage_strength/dosage_strength.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class DosageStrength(Document): - pass diff --git a/erpnext/healthcare/doctype/drug_prescription/__init__.py b/erpnext/healthcare/doctype/drug_prescription/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json deleted file mode 100644 index a65c56694e..0000000000 --- a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-09-16 16:41:45.533374", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "drug_code", - "drug_name", - "dosage", - "period", - "dosage_form", - "column_break_7", - "comment", - "usage_interval", - "interval", - "interval_uom", - "update_schedule" - ], - "fields": [ - { - "fieldname": "drug_code", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Drug", - "options": "Item", - "reqd": 1 - }, - { - "fetch_from": "drug_code.item_name", - "fieldname": "drug_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Drug Name / Description" - }, - { - "fieldname": "dosage", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Dosage", - "options": "Prescription Dosage", - "reqd": 1 - }, - { - "fieldname": "period", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Period", - "options": "Prescription Duration", - "reqd": 1 - }, - { - "allow_in_quick_entry": 1, - "fieldname": "dosage_form", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Dosage Form", - "options": "Dosage Form", - "reqd": 1 - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fieldname": "comment", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Comment" - }, - { - "depends_on": "usage_interval", - "fieldname": "interval", - "fieldtype": "Int", - "in_list_view": 1, - "label": "Interval" - }, - { - "default": "1", - "depends_on": "usage_interval", - "fieldname": "update_schedule", - "fieldtype": "Check", - "hidden": 1, - "label": "Update Schedule", - "print_hide": 1, - "report_hide": 1 - }, - { - "depends_on": "use_interval", - "fieldname": "interval_uom", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Interval UOM", - "options": "\nHour\nDay" - }, - { - "default": "0", - "fieldname": "usage_interval", - "fieldtype": "Check", - "hidden": 1, - "label": "Dosage by Time Interval" - } - ], - "istable": 1, - "links": [], - "modified": "2021-06-11 11:53:06.343704", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Drug Prescription", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.py b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.py deleted file mode 100755 index 744bdb03ef..0000000000 --- a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe.model.document import Document - - -class DrugPrescription(Document): - def get_quantity(self): - quantity = 0 - dosage = None - period = None - - if self.dosage: - dosage = frappe.get_doc('Prescription Dosage', self.dosage) - for item in dosage.dosage_strength: - quantity += item.strength - if self.period and self.interval: - period = frappe.get_doc('Prescription Duration', self.period) - if self.interval < period.get_days(): - quantity = quantity * (period.get_days()/self.interval) - - elif self.interval and self.interval_uom and self.period: - period = frappe.get_doc('Prescription Duration', self.period) - interval_in = self.interval_uom - if interval_in == 'Day' and self.interval < period.get_days(): - quantity = period.get_days()/self.interval - elif interval_in == 'Hour' and self.interval < period.get_hours(): - quantity = period.get_hours()/self.interval - if quantity > 0: - return quantity - else: - return 1 diff --git a/erpnext/healthcare/doctype/exercise/__init__.py b/erpnext/healthcare/doctype/exercise/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/exercise/exercise.json b/erpnext/healthcare/doctype/exercise/exercise.json deleted file mode 100644 index 683cc6d3c3..0000000000 --- a/erpnext/healthcare/doctype/exercise/exercise.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "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", - "no_copy": 1 - }, - { - "fieldname": "assistance_level", - "fieldtype": "Select", - "label": "Assistance Level", - "options": "\nPassive\nActive Assist\nActive" - } - ], - "istable": 1, - "links": [], - "modified": "2020-11-04 18:20:25.583491", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Exercise", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/exercise/exercise.py b/erpnext/healthcare/doctype/exercise/exercise.py deleted file mode 100644 index 5d2b1f1a96..0000000000 --- a/erpnext/healthcare/doctype/exercise/exercise.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py b/erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js deleted file mode 100644 index ff51c34f3f..0000000000 --- a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js +++ /dev/null @@ -1,8 +0,0 @@ -// 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) { - - // } -}); diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json deleted file mode 100644 index a6aed75e7a..0000000000 --- a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py deleted file mode 100644 index bbb297275f..0000000000 --- a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py b/erpnext/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py deleted file mode 100644 index dcaea08aba..0000000000 --- a/erpnext/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/exercise_type/__init__.py b/erpnext/healthcare/doctype/exercise_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.js b/erpnext/healthcare/doctype/exercise_type/exercise_type.js deleted file mode 100644 index 06146047eb..0000000000 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.js +++ /dev/null @@ -1,186 +0,0 @@ -// 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 ExerciseEditor { - constructor(frm, wrapper) { - this.wrapper = wrapper; - this.frm = frm; - this.make(frm, wrapper); - } - - make(frm, wrapper) { - $(this.wrapper).empty(); - - this.exercise_toolbar = $('

\ - ').appendTo(this.wrapper); - - this.exercise_cards = $('

').appendTo(this.wrapper); - - this.row = $('
').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 && frm.doc.steps_table.length > 0) { - this.make_cards(frm); - this.make_buttons(frm); - } - } - - make_cards(frm) { - var me = this; - $(me.exercise_cards).empty(); - - $.each(frm.doc.steps_table, function(i, step) { - $(repl(` -
-
-
- ... -

%(title)s

-

%(description)s

-
- -
-
`, {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(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("zoom-out"); - - 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(); - frm.dirty(); - }, 300); - }); - } - - show_add_card_dialog(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 = 0; - if (frm.doc.steps_table) { - i = frm.doc.steps_table.length; - } - $(repl(` -
-
-
- ... -

%(title)s

-

%(description)s

-
- -
-
`, {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(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'); - frm.dirty(); - new_dialog.hide(); - }, - primary_action_label: __("Edit"), - }); - - 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(); - } -}; diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.json b/erpnext/healthcare/doctype/exercise_type/exercise_type.json deleted file mode 100644 index 0db9c6e796..0000000000 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.py b/erpnext/healthcare/doctype/exercise_type/exercise_type.py deleted file mode 100644 index 48eb6ba2d8..0000000000 --- a/erpnext/healthcare/doctype/exercise_type/exercise_type.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/exercise_type/test_exercise_type.py b/erpnext/healthcare/doctype/exercise_type/test_exercise_type.py deleted file mode 100644 index 583aea911a..0000000000 --- a/erpnext/healthcare/doctype/exercise_type/test_exercise_type.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/exercise_type_step/__init__.py b/erpnext/healthcare/doctype/exercise_type_step/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json b/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json deleted file mode 100644 index b37ff007cb..0000000000 --- a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.py b/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.py deleted file mode 100644 index 412ef3225e..0000000000 --- a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/fee_validity/__init__.py b/erpnext/healthcare/doctype/fee_validity/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.js b/erpnext/healthcare/doctype/fee_validity/fee_validity.js deleted file mode 100644 index 7ea2213619..0000000000 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Fee Validity', { -}); diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json deleted file mode 100644 index d76b42e683..0000000000 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "beta": 1, - "creation": "2017-01-05 10:56:29.564806", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "practitioner", - "patient", - "column_break_3", - "status", - "section_break_5", - "section_break_3", - "max_visits", - "visited", - "ref_appointments", - "column_break_6", - "start_date", - "valid_till" - ], - "fields": [ - { - "fieldname": "practitioner", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "read_only": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Patient", - "options": "Patient", - "read_only": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "visited", - "fieldtype": "Int", - "label": "Visits Completed", - "read_only": 1 - }, - { - "fieldname": "valid_till", - "fieldtype": "Date", - "label": "Valid Till", - "read_only": 1 - }, - { - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "label": "Validity", - "read_only": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "max_visits", - "fieldtype": "Int", - "label": "Max number of visit", - "read_only": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "options": "Completed\nPending", - "read_only": 1 - }, - { - "fetch_from": "ref_appointment.appointment_date", - "fieldname": "start_date", - "fieldtype": "Date", - "label": "Start Date", - "read_only": 1 - }, - { - "fieldname": "ref_appointments", - "fieldtype": "Table MultiSelect", - "label": "Reference Appointments", - "options": "Fee Validity Reference", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_5", - "fieldtype": "Section Break" - } - ], - "in_create": 1, - "links": [], - "modified": "2021-08-26 10:51:05.609349", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Fee Validity", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "search_fields": "practitioner, patient", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "practitioner" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py deleted file mode 100644 index aa30becd33..0000000000 --- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import datetime - -import frappe -from frappe.model.document import Document -from frappe.utils import getdate - - -class FeeValidity(Document): - def validate(self): - self.update_status() - - def update_status(self): - if self.visited >= self.max_visits: - self.status = 'Completed' - else: - self.status = 'Pending' - - -def create_fee_validity(appointment): - if not check_is_new_patient(appointment): - return - - fee_validity = frappe.new_doc('Fee Validity') - fee_validity.practitioner = appointment.practitioner - fee_validity.patient = appointment.patient - fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1 - valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1 - fee_validity.visited = 0 - fee_validity.start_date = getdate(appointment.appointment_date) - fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days)) - fee_validity.save(ignore_permissions=True) - return fee_validity - -def check_is_new_patient(appointment): - validity_exists = frappe.db.exists('Fee Validity', { - 'practitioner': appointment.practitioner, - 'patient': appointment.patient - }) - if validity_exists: - return False - - appointment_exists = frappe.db.get_all('Patient Appointment', { - 'name': ('!=', appointment.name), - 'status': ('!=', 'Cancelled'), - 'patient': appointment.patient, - 'practitioner': appointment.practitioner - }) - if len(appointment_exists) and appointment_exists[0]: - return False - return True diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py deleted file mode 100644 index ce1947f5ba..0000000000 --- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import add_days, nowdate - -from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import ( - create_appointment, - create_healthcare_docs, - create_healthcare_service_items, -) - -test_dependencies = ["Company"] - -class TestFeeValidity(unittest.TestCase): - def setUp(self): - frappe.db.sql("""delete from `tabPatient Appointment`""") - frappe.db.sql("""delete from `tabFee Validity`""") - frappe.db.sql("""delete from `tabPatient`""") - make_pos_profile() - - def test_fee_validity(self): - item = create_healthcare_service_items() - healthcare_settings = frappe.get_single("Healthcare Settings") - healthcare_settings.enable_free_follow_ups = 1 - healthcare_settings.max_visits = 1 - healthcare_settings.valid_days = 7 - healthcare_settings.automate_appointment_invoicing = 1 - healthcare_settings.op_consulting_charge_item = item - healthcare_settings.save(ignore_permissions=True) - patient, practitioner = create_healthcare_docs() - - # For first appointment, invoice is generated. First appointment not considered in fee validity - appointment = create_appointment(patient, practitioner, nowdate()) - invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") - self.assertEqual(invoiced, 1) - - # appointment should not be invoiced as it is within fee validity - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4)) - invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") - self.assertEqual(invoiced, 0) - - # appointment should be invoiced as it is within fee validity but the max_visits are exceeded - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 5), invoice=1) - invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") - self.assertEqual(invoiced, 1) - - # appointment should be invoiced as it is not within fee validity and the max_visits are exceeded - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 10), invoice=1) - invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced") - self.assertEqual(invoiced, 1) diff --git a/erpnext/healthcare/doctype/fee_validity_reference/__init__.py b/erpnext/healthcare/doctype/fee_validity_reference/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json deleted file mode 100644 index 40f128e973..0000000000 --- a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-13 16:08:42.859996", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "appointment" - ], - "fields": [ - { - "fieldname": "appointment", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Patient Appointment", - "options": "Patient Appointment", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-15 00:27:02.076470", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Fee Validity Reference", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py b/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py deleted file mode 100644 index c03978aad1..0000000000 --- a/erpnext/healthcare/doctype/fee_validity_reference/fee_validity_reference.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 FeeValidityReference(Document): - pass diff --git a/erpnext/healthcare/doctype/healthcare.py b/erpnext/healthcare/doctype/healthcare.py deleted file mode 100644 index f8e008e7d3..0000000000 --- a/erpnext/healthcare/doctype/healthcare.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals - - -def get_data(): - - return [] diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/__init__.py b/erpnext/healthcare/doctype/healthcare_practitioner/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js deleted file mode 100644 index 44c399856c..0000000000 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.js +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Healthcare Practitioner', { - setup: function(frm) { - frm.set_query('account', 'accounts', function(doc, cdt, cdn) { - let d = locals[cdt][cdn]; - return { - filters: { - 'root_type': 'Income', - 'company': d.company, - 'is_group': 0 - } - }; - }); - }, - refresh: function(frm) { - frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Healthcare Practitioner'}; - - if (!frm.is_new()) { - frappe.contacts.render_address_and_contact(frm); - } else { - frappe.contacts.clear_address_and_contact(frm); - } - - frm.set_query('service_unit', 'practitioner_schedules', function(){ - return { - filters: { - 'is_group': false, - 'allow_appointments': true - } - }; - }); - - set_query_service_item(frm, 'inpatient_visit_charge_item'); - set_query_service_item(frm, 'op_consulting_charge_item'); - } -}); - -let set_query_service_item = function(frm, service_item_field) { - frm.set_query(service_item_field, function() { - return { - filters: { - 'is_sales_item': 1, - 'is_stock_item': 0 - } - }; - }); -}; - -frappe.ui.form.on('Healthcare Practitioner', 'user_id',function(frm) { - if (frm.doc.user_id) { - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'User', - name: frm.doc.user_id - }, - callback: function (data) { - - frappe.model.get_value('Employee', {'user_id': frm.doc.user_id}, 'name', - function(data) { - if (data) { - if (!frm.doc.employee || frm.doc.employee != data.name) - frappe.model.set_value(frm.doctype, frm.docname, 'employee', data.name); - } else { - frappe.model.set_value(frm.doctype, frm.docname, 'employee', ''); - } - } - ); - - if (!frm.doc.first_name || frm.doc.first_name != data.message.first_name) - frappe.model.set_value(frm.doctype,frm.docname, 'first_name', data.message.first_name); - if (!frm.doc.middle_name || frm.doc.middle_name != data.message.middle_name) - frappe.model.set_value(frm.doctype,frm.docname, 'middle_name', data.message.middle_name); - if (!frm.doc.last_name || frm.doc.last_name != data.message.last_name) - frappe.model.set_value(frm.doctype,frm.docname, 'last_name', data.message.last_name); - if (!frm.doc.mobile_phone || frm.doc.mobile_phone != data.message.mobile_no) - frappe.model.set_value(frm.doctype,frm.docname, 'mobile_phone', data.message.mobile_no); - } - }); - } -}); - -frappe.ui.form.on('Healthcare Practitioner', 'employee', function(frm) { - if (frm.doc.employee){ - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'Employee', - name: frm.doc.employee - }, - callback: function (data) { - if (!frm.doc.user_id || frm.doc.user_id != data.message.user_id) - frm.set_value('user_id', data.message.user_id); - if (!frm.doc.designation || frm.doc.designation != data.message.designation) - frappe.model.set_value(frm.doctype,frm.docname, 'designation', data.message.designation); - if (!frm.doc.first_name || !frm.doc.user_id){ - frappe.model.set_value(frm.doctype,frm.docname, 'first_name', data.message.first_name); - frappe.model.set_value(frm.doctype,frm.docname, 'middle_name', ''); - frappe.model.set_value(frm.doctype,frm.docname, 'last_name', data.message.last_name); - } - if (!frm.doc.mobile_phone || !frm.doc.user_id) - frappe.model.set_value(frm.doctype,frm.docname, 'mobile_phone', data.message.cell_number); - if (!frm.doc.address || frm.doc.address != data.message.current_address) - frappe.model.set_value(frm.doctype,frm.docname, 'address', data.message.current_address); - } - }); - } -}); - -frappe.tour['Healthcare Practitioner'] = [ - { - fieldname: 'employee', - title: __('Employee'), - description: __('If you want to track Payroll and other HRMS operations for a Practitoner, create an Employee and link it here.') - }, - { - fieldname: 'practitioner_schedules', - title: __('Practitioner Schedules'), - description: __('Set the Practitioner Schedule you just created. This will be used while booking appointments.') - }, - { - fieldname: 'op_consulting_charge_item', - title: __('Out Patient Consulting Charge Item'), - description: __('Create a service item for Out Patient Consulting.') - }, - { - fieldname: 'inpatient_visit_charge_item', - title: __('Inpatient Visit Charge Item'), - description: __('If this Healthcare Practitioner works for the In-Patient Department, create a service item for Inpatient Visits.') - }, - { - fieldname: 'op_consulting_charge', - title: __('Out Patient Consulting Charge'), - description: __('Set the Out Patient Consulting Charge for this Practitioner.') - - }, - { - fieldname: 'inpatient_visit_charge', - title: __('Inpatient Visit Charge'), - description: __('If this Healthcare Practitioner also works for the In-Patient Department, set the inpatient visit charge for this Practitioner.') - } -]; diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json deleted file mode 100644 index cb455eb501..0000000000 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.json +++ /dev/null @@ -1,336 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2016-02-23 11:20:53.565119", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "basic_details_section", - "naming_series", - "first_name", - "middle_name", - "last_name", - "practitioner_name", - "gender", - "image", - "column_break_7", - "status", - "mobile_phone", - "residence_phone", - "office_phone", - "employee_and_user_details_section", - "employee", - "department", - "designation", - "column_break_17", - "user_id", - "hospital", - "appointments", - "practitioner_schedules", - "charges", - "op_consulting_charge_item", - "op_consulting_charge", - "column_break_18", - "inpatient_visit_charge_item", - "inpatient_visit_charge", - "account_details", - "default_currency", - "accounts", - "address_and_contacts_section", - "address_html", - "column_break_19", - "contact_html" - ], - "fields": [ - { - "fieldname": "first_name", - "fieldtype": "Data", - "label": "First Name", - "no_copy": 1, - "reqd": 1 - }, - { - "fieldname": "middle_name", - "fieldtype": "Data", - "label": "Middle Name (Optional)", - "no_copy": 1 - }, - { - "fieldname": "last_name", - "fieldtype": "Data", - "label": "Last Name", - "no_copy": 1 - }, - { - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Image", - "no_copy": 1, - "print_hide": 1 - }, - { - "fieldname": "employee", - "fieldtype": "Link", - "label": "Employee", - "options": "Employee" - }, - { - "fieldname": "user_id", - "fieldtype": "Link", - "label": "User", - "options": "User", - "search_index": 1 - }, - { - "fetch_from": "employee", - "fieldname": "designation", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Designation", - "options": "Designation", - "read_only": 1 - }, - { - "fieldname": "department", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Medical Department", - "options": "Medical Department" - }, - { - "fieldname": "column_break_7", - "fieldtype": "Column Break" - }, - { - "fieldname": "hospital", - "fieldtype": "Data", - "label": "Hospital" - }, - { - "fieldname": "mobile_phone", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Mobile" - }, - { - "fieldname": "residence_phone", - "fieldtype": "Data", - "label": "Phone (R)" - }, - { - "fieldname": "office_phone", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Phone (Office)" - }, - { - "collapsible": 1, - "fieldname": "appointments", - "fieldtype": "Section Break", - "label": "Appointments" - }, - { - "fieldname": "practitioner_schedules", - "fieldtype": "Table", - "label": "Practitioner Schedules", - "options": "Practitioner Service Unit Schedule" - }, - { - "collapsible": 1, - "fieldname": "charges", - "fieldtype": "Section Break", - "label": "Charges" - }, - { - "fieldname": "op_consulting_charge_item", - "fieldtype": "Link", - "label": "Out Patient Consulting Charge Item", - "options": "Item" - }, - { - "fieldname": "op_consulting_charge", - "fieldtype": "Currency", - "label": "Out Patient Consulting Charge", - "mandatory_depends_on": "op_consulting_charge_item", - "options": "Currency" - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "inpatient_visit_charge_item", - "fieldtype": "Link", - "label": "Inpatient Visit Charge Item", - "options": "Item" - }, - { - "fieldname": "inpatient_visit_charge", - "fieldtype": "Currency", - "label": "Inpatient Visit Charge", - "mandatory_depends_on": "inpatient_visit_charge_item" - }, - { - "depends_on": "eval: !doc.__islocal", - "fieldname": "address_html", - "fieldtype": "HTML", - "label": "Address HTML", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "column_break_19", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval: !doc.__islocal", - "fieldname": "contact_html", - "fieldtype": "HTML", - "label": "Contact HTML", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "account_details", - "fieldtype": "Section Break", - "label": "Account Details" - }, - { - "fieldname": "accounts", - "fieldtype": "Table", - "label": "Income Account", - "options": "Party Account" - }, - { - "fieldname": "default_currency", - "fieldtype": "Link", - "hidden": 1, - "label": "Default Currency", - "no_copy": 1, - "options": "Currency", - "print_hide": 1, - "report_hide": 1 - }, - { - "bold": 1, - "fieldname": "practitioner_name", - "fieldtype": "Data", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Full Name", - "no_copy": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "HLC-PRAC-.YYYY.-", - "report_hide": 1, - "set_only_once": 1 - }, - { - "fieldname": "gender", - "fieldtype": "Link", - "label": "Gender", - "options": "Gender" - }, - { - "fieldname": "employee_and_user_details_section", - "fieldtype": "Section Break", - "label": "Employee and User Details" - }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, - { - "default": "Active", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Status", - "options": "\nActive\nDisabled", - "reqd": 1 - }, - { - "fieldname": "basic_details_section", - "fieldtype": "Section Break", - "label": "Basic Details" - }, - { - "collapsible": 1, - "depends_on": "eval: !doc.__islocal", - "fieldname": "address_and_contacts_section", - "fieldtype": "Section Break", - "label": "Address and Contacts" - } - ], - "image_field": "image", - "links": [], - "modified": "2021-08-24 10:42:08.513054", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare Practitioner", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "select": 1, - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "select": 1, - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "select": 1, - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "practitioner_name, mobile_phone, office_phone", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "practitioner_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py deleted file mode 100644 index 4550e7ab4c..0000000000 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.contacts.address_and_contact import ( - delete_contact_and_address, - load_address_and_contact, -) -from frappe.model.document import Document -from frappe.model.naming import append_number_if_name_exists - -from erpnext.accounts.party import validate_party_accounts - - -class HealthcarePractitioner(Document): - def onload(self): - load_address_and_contact(self) - - def autoname(self): - # concat first and last name - self.name = self.practitioner_name - - if frappe.db.exists('Healthcare Practitioner', self.name): - self.name = append_number_if_name_exists('Contact', self.name) - - def validate(self): - self.set_full_name() - validate_party_accounts(self) - if self.inpatient_visit_charge_item: - validate_service_item(self.inpatient_visit_charge_item, 'Configure a service Item for Inpatient Consulting Charge Item') - if self.op_consulting_charge_item: - validate_service_item(self.op_consulting_charge_item, 'Configure a service Item for Out Patient Consulting Charge Item') - - if self.user_id: - self.validate_user_id() - else: - existing_user_id = frappe.db.get_value('Healthcare Practitioner', self.name, 'user_id') - if existing_user_id: - frappe.permissions.remove_user_permission( - 'Healthcare Practitioner', self.name, existing_user_id) - - def on_update(self): - if self.user_id: - frappe.permissions.add_user_permission('Healthcare Practitioner', self.name, self.user_id) - - def set_full_name(self): - if self.last_name: - self.practitioner_name = ' '.join(filter(None, [self.first_name, self.last_name])) - else: - self.practitioner_name = self.first_name - - def validate_user_id(self): - if not frappe.db.exists('User', self.user_id): - frappe.throw(_('User {0} does not exist').format(self.user_id)) - elif not frappe.db.exists('User', self.user_id, 'enabled'): - frappe.throw(_('User {0} is disabled').format(self.user_id)) - - # check duplicate - practitioner = frappe.db.exists('Healthcare Practitioner', { - 'user_id': self.user_id, - 'name': ('!=', self.name) - }) - if practitioner: - frappe.throw(_('User {0} is already assigned to Healthcare Practitioner {1}').format( - self.user_id, practitioner)) - - def on_trash(self): - delete_contact_and_address('Healthcare Practitioner', self.name) - -def validate_service_item(item, msg): - if frappe.db.get_value('Item', item, 'is_stock_item'): - frappe.throw(_(msg)) - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None): - fields = ['name', 'practitioner_name', 'mobile_phone'] - - filters = { - 'name': ('like', '%%%s%%' % txt) - } - - return frappe.get_all('Healthcare Practitioner', fields = fields, - filters = filters, start=start, page_length=page_len, order_by='name, practitioner_name', as_list=1) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py deleted file mode 100644 index 8e0292e97c..0000000000 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner_dashboard.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'heatmap': True, - 'heatmap_message': _('This is based on transactions against this Healthcare Practitioner.'), - 'fieldname': 'practitioner', - 'transactions': [ - { - 'label': _('Appointments and Patient Encounters'), - 'items': ['Patient Appointment', 'Patient Encounter', 'Fee Validity'] - }, - { - 'label': _('Consultation'), - 'items': ['Clinical Procedure', 'Lab Test'] - } - ] - } diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py deleted file mode 100644 index 214bcd287a..0000000000 --- a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestHealthcarePractitioner(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/__init__.py b/erpnext/healthcare/doctype/healthcare_schedule_time_slot/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json b/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json deleted file mode 100644 index cf54e82199..0000000000 --- a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2017-05-03 17:27:07.466088", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "day", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Day", - "length": 0, - "no_copy": 0, - "options": "Sunday\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "From Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "To Time", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare Schedule Time Slot", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py b/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py deleted file mode 100644 index 721da24adc..0000000000 --- a/erpnext/healthcare/doctype/healthcare_schedule_time_slot/healthcare_schedule_time_slot.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class HealthcareScheduleTimeSlot(Document): - pass diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/__init__.py b/erpnext/healthcare/doctype/healthcare_service_unit/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js deleted file mode 100644 index 2d1caf7efc..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Healthcare Service Unit', { - onload: function(frm) { - frm.list_route = 'Tree/Healthcare Service Unit'; - - // get query select healthcare service unit - frm.fields_dict['parent_healthcare_service_unit'].get_query = function(doc) { - return { - filters: [ - ['Healthcare Service Unit', 'is_group', '=', 1], - ['Healthcare Service Unit', 'name', '!=', doc.healthcare_service_unit_name] - ] - }; - }; - }, - refresh: function(frm) { - frm.trigger('set_root_readonly'); - frm.set_df_property('service_unit_type', 'reqd', 1); - frm.add_custom_button(__('Healthcare Service Unit Tree'), function() { - frappe.set_route('Tree', 'Healthcare Service Unit'); - }); - - frm.set_query('warehouse', function() { - return { - filters: { - 'company': frm.doc.company - } - }; - }); - }, - set_root_readonly: function(frm) { - // read-only for root healthcare service unit - frm.set_intro(''); - if (!frm.doc.parent_healthcare_service_unit) { - frm.set_read_only(); - frm.set_intro(__('This is a root healthcare service unit and cannot be edited.'), true); - } - }, - allow_appointments: function(frm) { - if (!frm.doc.allow_appointments) { - frm.set_value('overlap_appointments', false); - } - }, - is_group: function(frm) { - if (frm.doc.is_group == 1) { - frm.set_value('allow_appointments', false); - frm.set_df_property('service_unit_type', 'reqd', 0); - } - else { - frm.set_df_property('service_unit_type', 'reqd', 1); - } - }, - overlap_appointments: function(frm) { - if (frm.doc.overlap_appointments == 0) { - frm.set_value('service_unit_capacity', ''); - } - } -}); diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json deleted file mode 100644 index 8935ec7d3c..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:healthcare_service_unit_name", - "beta": 1, - "creation": "2016-09-21 13:48:14.731437", - "description": "Healthcare Service Unit", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "healthcare_service_unit_name", - "is_group", - "service_unit_type", - "allow_appointments", - "overlap_appointments", - "service_unit_capacity", - "inpatient_occupancy", - "occupancy_status", - "column_break_9", - "company", - "warehouse", - "tree_details_section", - "parent_healthcare_service_unit", - "lft", - "rgt", - "old_parent" - ], - "fields": [ - { - "fieldname": "healthcare_service_unit_name", - "fieldtype": "Data", - "hide_days": 1, - "hide_seconds": 1, - "in_global_search": 1, - "in_list_view": 1, - "label": "Service Unit", - "reqd": 1, - "unique": 1 - }, - { - "bold": 1, - "fieldname": "parent_healthcare_service_unit", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Parent Service Unit", - "options": "Healthcare Service Unit" - }, - { - "bold": 1, - "default": "0", - "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1", - "fieldname": "is_group", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Is Group" - }, - { - "bold": 1, - "depends_on": "eval:doc.is_group != 1", - "fieldname": "service_unit_type", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Service Unit Type", - "options": "Healthcare Service Unit Type" - }, - { - "default": "0", - "depends_on": "eval:doc.is_group != 1 && doc.inpatient_occupancy != 1", - "fetch_from": "service_unit_type.allow_appointments", - "fieldname": "allow_appointments", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "in_list_view": 1, - "label": "Allow Appointments", - "no_copy": 1, - "read_only": 1 - }, - { - "default": "0", - "depends_on": "eval:doc.is_group != 1 && doc.allow_appointments == 1 && doc.inpatient_occupany != 1", - "fetch_from": "service_unit_type.overlap_appointments", - "fieldname": "overlap_appointments", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Allow Overlap", - "no_copy": 1, - "read_only": 1 - }, - { - "bold": 1, - "default": "0", - "depends_on": "eval:doc.allow_appointments != 1 && doc.is_group != 1", - "fetch_from": "service_unit_type.inpatient_occupancy", - "fieldname": "inpatient_occupancy", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "in_list_view": 1, - "label": "Inpatient Occupancy", - "no_copy": 1, - "read_only": 1, - "search_index": 1 - }, - { - "depends_on": "eval:doc.inpatient_occupancy == 1", - "fieldname": "occupancy_status", - "fieldtype": "Select", - "hide_days": 1, - "hide_seconds": 1, - "label": "Occupancy Status", - "no_copy": 1, - "options": "Vacant\nOccupied", - "read_only": 1 - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, - { - "bold": 1, - "depends_on": "eval:doc.is_group != 1", - "fieldname": "warehouse", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Warehouse", - "no_copy": 1, - "options": "Warehouse" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "lft", - "fieldtype": "Int", - "hidden": 1, - "hide_days": 1, - "hide_seconds": 1, - "label": "lft", - "no_copy": 1, - "print_hide": 1, - "search_index": 1 - }, - { - "fieldname": "rgt", - "fieldtype": "Int", - "hidden": 1, - "hide_days": 1, - "hide_seconds": 1, - "label": "rgt", - "no_copy": 1, - "print_hide": 1, - "search_index": 1 - }, - { - "fieldname": "old_parent", - "fieldtype": "Link", - "hidden": 1, - "hide_days": 1, - "hide_seconds": 1, - "ignore_user_permissions": 1, - "label": "Old Parent", - "no_copy": 1, - "options": "Healthcare Service Unit", - "print_hide": 1, - "report_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "tree_details_section", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Tree Details" - }, - { - "depends_on": "eval:doc.overlap_appointments == 1", - "fieldname": "service_unit_capacity", - "fieldtype": "Int", - "label": "Service Unit Capacity", - "mandatory_depends_on": "eval:doc.overlap_appointments == 1", - "non_negative": 1 - } - ], - "is_tree": 1, - "links": [], - "modified": "2021-08-19 14:09:11.643464", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare Service Unit", - "nsm_parent_field": "parent_healthcare_service_unit", - "owner": "Administrator", - "permissions": [ - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 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 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "search_fields": "healthcare_service_unit_name", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "healthcare_service_unit_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py deleted file mode 100644 index 550b9fbff2..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe import _ -from frappe.utils import cint, cstr -from frappe.utils.nestedset import NestedSet - - -class HealthcareServiceUnit(NestedSet): - nsm_parent_field = 'parent_healthcare_service_unit' - - def validate(self): - self.set_service_unit_properties() - - def autoname(self): - if self.company: - suffix = " - " + frappe.get_cached_value('Company', self.company, 'abbr') - if not self.healthcare_service_unit_name.endswith(suffix): - self.name = self.healthcare_service_unit_name + suffix - else: - self.name = self.healthcare_service_unit_name - - def on_update(self): - super(HealthcareServiceUnit, self).on_update() - self.validate_one_root() - - def set_service_unit_properties(self): - if cint(self.is_group): - self.allow_appointments = False - self.overlap_appointments = False - self.inpatient_occupancy = False - self.service_unit_capacity = 0 - self.occupancy_status = '' - self.service_unit_type = '' - elif self.service_unit_type != '': - service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type) - self.allow_appointments = service_unit_type.allow_appointments - self.inpatient_occupancy = service_unit_type.inpatient_occupancy - - if self.inpatient_occupancy and self.occupancy_status != '': - self.occupancy_status = 'Vacant' - - if service_unit_type.overlap_appointments: - self.overlap_appointments = True - else: - self.overlap_appointments = False - self.service_unit_capacity = 0 - - if self.overlap_appointments: - if not self.service_unit_capacity: - frappe.throw(_('Please set a valid Service Unit Capacity to enable Overlapping Appointments'), - title=_('Mandatory')) - - -@frappe.whitelist() -def add_multiple_service_units(parent, data): - ''' - parent - parent service unit under which the service units are to be created - data (dict) - company, healthcare_service_unit_name, count, service_unit_type, warehouse, service_unit_capacity - ''' - if not parent or not data: - return - - data = json.loads(data) - company = data.get('company') or \ - frappe.defaults.get_defaults().get('company') or \ - frappe.db.get_single_value('Global Defaults', 'default_company') - - if not data.get('healthcare_service_unit_name') or not company: - frappe.throw(_('Service Unit Name and Company are mandatory to create Healthcare Service Units'), - title=_('Missing Required Fields')) - - count = cint(data.get('count') or 0) - if count <= 0: - frappe.throw(_('Number of Service Units to be created should at least be 1'), - title=_('Invalid Number of Service Units')) - - capacity = cint(data.get('service_unit_capacity') or 1) - - service_unit = { - 'doctype': 'Healthcare Service Unit', - 'parent_healthcare_service_unit': parent, - 'service_unit_type': data.get('service_unit_type') or None, - 'service_unit_capacity': capacity if capacity > 0 else 1, - 'warehouse': data.get('warehouse') or None, - 'company': company - } - - service_unit_name = '{}'.format(data.get('healthcare_service_unit_name').strip(' -')) - - last_suffix = frappe.db.sql("""SELECT - IFNULL(MAX(CAST(SUBSTRING(name FROM %(start)s FOR 4) AS UNSIGNED)), 0) - FROM `tabHealthcare Service Unit` - WHERE name like %(prefix)s AND company=%(company)s""", - {'start': len(service_unit_name)+2, 'prefix': '{}-%'.format(service_unit_name), 'company': company}, - as_list=1)[0][0] - start_suffix = cint(last_suffix) + 1 - - failed_list = [] - for i in range(start_suffix, count + start_suffix): - # name to be in the form WARD-#### - service_unit['healthcare_service_unit_name'] = '{}-{}'.format(service_unit_name, cstr('%0*d' % (4, i))) - service_unit_doc = frappe.get_doc(service_unit) - try: - service_unit_doc.insert() - except Exception: - failed_list.append(service_unit['healthcare_service_unit_name']) - - return failed_list diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js deleted file mode 100644 index ea3fea6b7a..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js +++ /dev/null @@ -1,185 +0,0 @@ -frappe.provide("frappe.treeview_settings"); - -frappe.treeview_settings['Healthcare Service Unit'] = { - breadcrumbs: 'Healthcare Service Unit', - title: __('Service Unit Tree'), - get_tree_root: false, - get_tree_nodes: 'erpnext.healthcare.utils.get_children', - filters: [{ - fieldname: 'company', - fieldtype: 'Select', - options: erpnext.utils.get_tree_options('company'), - label: __('Company'), - default: erpnext.utils.get_tree_default('company') - }], - fields: [ - { - fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('New Service Unit Name'), - reqd: true - }, - { - fieldtype: 'Check', fieldname: 'is_group', label: __('Is Group'), - description: __("Child nodes can be only created under 'Group' type nodes") - }, - { - fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), - options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), - depends_on: 'eval:!doc.is_group', default: '', - onchange: () => { - if (cur_dialog) { - if (cur_dialog.fields_dict.service_unit_type.value) { - frappe.db.get_value('Healthcare Service Unit Type', - cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') - .then(r => { - if (r.message.overlap_appointments) { - cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); - cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); - } else { - cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); - cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); - } - }); - } else { - cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); - cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); - } - } - } - }, - { - fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), - description: __('Sets the number of concurrent appointments allowed'), reqd: false, - depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true - }, - { - fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', - description: __('Optional, if you want to manage stock separately for this Service Unit'), - depends_on: 'eval:!doc.is_group' - }, - { - fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, - default: () => { - return cur_page.page.page.fields_dict.company.value; - } - } - ], - ignore_fields: ['parent_healthcare_service_unit'], - onrender: function (node) { - if (node.data.occupied_of_available !== undefined) { - $("" - + ' ' + node.data.occupied_of_available - + '').insertBefore(node.$ul); - } - if (node.data && node.data.inpatient_occupancy !== undefined) { - if (node.data.inpatient_occupancy == 1) { - if (node.data.occupancy_status == 'Occupied') { - $("" - + ' ' + node.data.occupancy_status - + '').insertBefore(node.$ul); - } - if (node.data.occupancy_status == 'Vacant') { - $("" - + ' ' + node.data.occupancy_status - + '').insertBefore(node.$ul); - } - } - } - }, - post_render: function (treeview) { - frappe.treeview_settings['Healthcare Service Unit'].treeview = {}; - $.extend(frappe.treeview_settings['Healthcare Service Unit'].treeview, treeview); - }, - toolbar: [ - { - label: __('Add Multiple'), - condition: function (node) { - return node.expandable; - }, - click: function (node) { - const dialog = new frappe.ui.Dialog({ - title: __('Add Multiple Service Units'), - fields: [ - { - fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('Service Unit Name'), - reqd: true, description: __("Will be serially suffixed to maintain uniquness. Example: 'Ward' will be named as 'Ward-####'"), - }, - { - fieldtype: 'Int', fieldname: 'count', label: __('Number of Service Units'), - reqd: true - }, - { - fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'), - options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'), - depends_on: 'eval:!doc.is_group', default: '', reqd: true, - onchange: () => { - if (cur_dialog) { - if (cur_dialog.fields_dict.service_unit_type.value) { - frappe.db.get_value('Healthcare Service Unit Type', - cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments') - .then(r => { - if (r.message.overlap_appointments) { - cur_dialog.set_df_property('service_unit_capacity', 'hidden', false); - cur_dialog.set_df_property('service_unit_capacity', 'reqd', true); - } else { - cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); - cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); - } - }); - } else { - cur_dialog.set_df_property('service_unit_capacity', 'hidden', true); - cur_dialog.set_df_property('service_unit_capacity', 'reqd', false); - } - } - } - }, - { - fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'), - description: __('Sets the number of concurrent appointments allowed'), reqd: false, - depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true - }, - { - fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse', - description: __('Optional, if you want to manage stock separately for this Service Unit'), - }, - { - fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true, - default: () => { - return cur_page.page.page.fields_dict.company.get_value(); - } - } - ], - primary_action: () => { - dialog.hide(); - let vals = dialog.get_values(); - if (!vals) return; - - return frappe.call({ - method: 'erpnext.healthcare.doctype.healthcare_service_unit.healthcare_service_unit.add_multiple_service_units', - args: { - parent: node.data.value, - data: vals - }, - callback: function (r) { - if (!r.exc && r.message) { - frappe.treeview_settings['Healthcare Service Unit'].treeview.tree.load_children(node, true); - - frappe.show_alert({ - message: __('{0} Service Units created', [vals.count - r.message.length]), - indicator: 'green' - }); - } else { - frappe.msgprint(__('Could not create Service Units')); - } - }, - freeze: true, - freeze_message: __('Creating {0} Service Units', [vals.count]) - }); - }, - primary_action_label: __('Create') - }); - dialog.show(); - } - } - ], - extend_toolbar: true -}; diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py deleted file mode 100644 index 84197e56c5..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestHealthcareServiceUnit(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js deleted file mode 100644 index ecf4aa1a4b..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Healthcare Service Unit Type', { - refresh: function(frm) { - frm.set_df_property('item_code', 'read_only', frm.doc.__islocal ? 0 : 1); - if (!frm.doc.__islocal && frm.doc.is_billable) { - frm.add_custom_button(__('Change Item Code'), function() { - change_item_code(cur_frm, frm.doc); - }); - } - }, - - service_unit_type: function(frm) { - set_item_details(frm); - - if (!frm.doc.__islocal) { - frm.doc.change_in_item = 1; - } - }, - - is_billable: function(frm) { - set_item_details(frm); - }, - - rate: function(frm) { - if (!frm.doc.__islocal) { - frm.doc.change_in_item = 1; - } - }, - item_group: function(frm) { - if (!frm.doc.__islocal) { - frm.doc.change_in_item = 1; - } - }, - description: function(frm) { - if (!frm.doc.__islocal) { - frm.doc.change_in_item = 1; - } - } -}); - -let set_item_details = function(frm) { - if (frm.doc.service_unit_type && frm.doc.is_billable) { - if (!frm.doc.item_code) - frm.set_value('item_code', frm.doc.service_unit_type); - if (!frm.doc.description) - frm.set_value('description', frm.doc.service_unit_type); - if (!frm.doc.item_group) - frm.set_value('item_group', 'Services'); - } -}; - -let change_item_code = function(frm, doc) { - let d = new frappe.ui.Dialog({ - title: __('Change Item Code'), - fields: [ - { - 'fieldtype': 'Data', - 'label': 'Item Code', - 'fieldname': 'item_code', - 'default': doc.item_code, - reqd: 1, - } - ], - primary_action: function() { - let values = d.get_values(); - if (values) { - frappe.call({ - "method": "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code", - "args": { item: doc.item, item_code: values['item_code'], doc_name: doc.name }, - callback: function() { - frm.reload_doc(); - } - }); - } - d.hide(); - }, - primary_action_label: __("Change Template Code") - }); - - d.show(); - d.set_values({ - 'Item Code': frm.doc.item_code - }); -}; diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json deleted file mode 100644 index 9c81c65f6b..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:service_unit_type", - "creation": "2018-07-11 16:47:51.414675", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "disabled", - "service_unit_type", - "allow_appointments", - "overlap_appointments", - "inpatient_occupancy", - "is_billable", - "item_details", - "item", - "item_code", - "item_group", - "uom", - "no_of_hours", - "column_break_11", - "rate", - "description", - "change_in_item" - ], - "fields": [ - { - "fieldname": "service_unit_type", - "fieldtype": "Data", - "hide_days": 1, - "hide_seconds": 1, - "in_list_view": 1, - "label": "Service Unit Type", - "no_copy": 1, - "reqd": 1, - "unique": 1 - }, - { - "bold": 1, - "default": "0", - "depends_on": "eval:doc.inpatient_occupancy != 1", - "fieldname": "allow_appointments", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Allow Appointments" - }, - { - "bold": 1, - "default": "0", - "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1", - "fieldname": "overlap_appointments", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Allow Overlap" - }, - { - "bold": 1, - "default": "0", - "depends_on": "eval:doc.allow_appointments != 1", - "fieldname": "inpatient_occupancy", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Inpatient Occupancy" - }, - { - "bold": 1, - "default": "0", - "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1", - "fieldname": "is_billable", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Is Billable" - }, - { - "depends_on": "is_billable", - "fieldname": "item_details", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Item Details" - }, - { - "fieldname": "item", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Item", - "no_copy": 1, - "options": "Item", - "read_only": 1 - }, - { - "fieldname": "item_code", - "fieldtype": "Data", - "hide_days": 1, - "hide_seconds": 1, - "label": "Item Code", - "mandatory_depends_on": "eval: doc.is_billable == 1", - "no_copy": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Item Group", - "mandatory_depends_on": "eval: doc.is_billable == 1", - "options": "Item Group" - }, - { - "fieldname": "uom", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "UOM", - "mandatory_depends_on": "eval: doc.is_billable == 1", - "options": "UOM" - }, - { - "fieldname": "no_of_hours", - "fieldtype": "Int", - "hide_days": 1, - "hide_seconds": 1, - "label": "UOM Conversion in Hours", - "mandatory_depends_on": "eval: doc.is_billable == 1" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, - { - "fieldname": "rate", - "fieldtype": "Currency", - "hide_days": 1, - "hide_seconds": 1, - "label": "Rate / UOM" - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Disabled", - "no_copy": 1 - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "hide_days": 1, - "hide_seconds": 1, - "label": "Description" - }, - { - "default": "0", - "fieldname": "change_in_item", - "fieldtype": "Check", - "hidden": 1, - "hide_days": 1, - "hide_seconds": 1, - "label": "Change in Item" - } - ], - "links": [], - "modified": "2021-08-19 17:52:30.266667", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare Service Unit Type", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "service_unit_type" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py deleted file mode 100644 index 181cb529fc..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py +++ /dev/null @@ -1,136 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.rename_doc import rename_doc - - -class HealthcareServiceUnitType(Document): - def validate(self): - if self.allow_appointments and self.inpatient_occupancy: - frappe.msgprint( - _('Healthcare Service Unit Type cannot have both {0} and {1}').format( - frappe.bold('Allow Appointments'), frappe.bold('Inpatient Occupancy')), - raise_exception=1, title=_('Validation Error'), indicator='red' - ) - elif not self.allow_appointments and not self.inpatient_occupancy: - frappe.msgprint( - _('Healthcare Service Unit Type must allow atleast one among {0} and {1}').format( - frappe.bold('Allow Appointments'), frappe.bold('Inpatient Occupancy')), - raise_exception=1, title=_('Validation Error'), indicator='red' - ) - - if not self.allow_appointments: - self.overlap_appointments = 0 - - 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 after_insert(self): - if self.inpatient_occupancy and self.is_billable: - create_item(self) - - def on_trash(self): - if self.item: - try: - item = self.item - self.db_set('item', '') - frappe.delete_doc('Item', item) - except Exception: - frappe.throw(_('Not permitted. Please disable the Service Unit Type')) - - def on_update(self): - if self.change_in_item and self.is_billable and self.item: - update_item(self) - - item_price = item_price_exists(self) - - if not item_price: - price_list_name = frappe.db.get_value('Price List', {'selling': 1}) - if self.rate: - make_item_price(self.item_code, price_list_name, self.rate) - else: - make_item_price(self.item_code, price_list_name, 0.0) - else: - frappe.db.set_value('Item Price', item_price, 'price_list_rate', self.rate) - - frappe.db.set_value(self.doctype, self.name, 'change_in_item',0) - elif not self.is_billable and self.item: - frappe.db.set_value('Item', self.item, 'disabled', 1) - self.reload() - - -def item_price_exists(doc): - item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': doc.item_code}) - if len(item_price): - return item_price[0][0] - return False - -def create_item(doc): - # insert item - item = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': doc.item_code, - 'item_name': doc.service_unit_type, - 'item_group': doc.item_group, - 'description': doc.description or doc.item_code, - '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': 0, - 'stock_uom': doc.uom - }).insert(ignore_permissions=True, ignore_mandatory=True) - - # insert item price - # get item price list to insert item price - price_list_name = frappe.db.get_value('Price List', {'selling': 1}) - if doc.rate: - make_item_price(item.name, price_list_name, doc.rate) - item.standard_rate = doc.rate - else: - make_item_price(item.name, price_list_name, 0.0) - item.standard_rate = 0.0 - - item.save(ignore_permissions=True) - - # Set item in the doc - doc.db_set('item', item.name) - -def make_item_price(item, price_list_name, item_price): - 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) - -def update_item(doc): - item = frappe.get_doc("Item", doc.item) - if item: - item.update({ - "item_name": doc.service_unit_type, - "item_group": doc.item_group, - "disabled": 0, - "standard_rate": doc.rate, - "description": doc.description - }) - item.db_update() - -@frappe.whitelist() -def change_item_code(item, item_code, doc_name): - if frappe.db.exists({'doctype': 'Item', 'item_code': item_code}): - frappe.throw(_('Item with Item Code {0} already exists').format(item_code)) - else: - rename_doc('Item', item, item_code, ignore_permissions=True) - frappe.db.set_value('Healthcare Service Unit Type', doc_name, 'item_code', item_code) diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py deleted file mode 100644 index 7421ec3b89..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type_dashboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'service_unit_type', - 'transactions': [ - { - 'label': _('Healthcare Service Units'), - 'items': ['Healthcare Service Unit'] - }, - ] - } diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py deleted file mode 100644 index 839e1d6a4d..0000000000 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe - - -class TestHealthcareServiceUnitType(unittest.TestCase): - def test_item_creation(self): - unit_type = get_unit_type() - self.assertTrue(frappe.db.exists('Item', unit_type.item)) - - # check item disabled - unit_type.disabled = 1 - unit_type.save() - self.assertEqual(frappe.db.get_value('Item', unit_type.item, 'disabled'), 1) - - -def get_unit_type(): - if frappe.db.exists('Healthcare Service Unit Type', 'Inpatient Rooms'): - return frappe.get_doc('Healthcare Service Unit Type', 'Inpatient Rooms') - - unit_type = frappe.new_doc('Healthcare Service Unit Type') - unit_type.service_unit_type = 'Inpatient Rooms' - unit_type.inpatient_occupancy = 1 - unit_type.is_billable = 1 - unit_type.item_code = 'Inpatient Rooms' - unit_type.item_group = 'Services' - unit_type.uom = 'Hour' - unit_type.no_of_hours = 1 - unit_type.rate = 4000 - unit_type.save() - return unit_type diff --git a/erpnext/healthcare/doctype/healthcare_settings/__init__.py b/erpnext/healthcare/doctype/healthcare_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js deleted file mode 100644 index cf2276fc07..0000000000 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.js +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Healthcare Settings', { - setup: function(frm) { - frm.set_query('account', 'receivable_account', function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - 'account_type': 'Receivable', - 'company': d.company, - 'is_group': 0 - } - }; - }); - frm.set_query('account', 'income_account', function(doc, cdt, cdn) { - var d = locals[cdt][cdn]; - return { - filters: { - 'root_type': 'Income', - 'company': d.company, - 'is_group': 0 - } - }; - }); - set_query_service_item(frm, 'inpatient_visit_charge_item'); - set_query_service_item(frm, 'op_consulting_charge_item'); - set_query_service_item(frm, 'clinical_procedure_consumable_item'); - } -}); - -var set_query_service_item = function(frm, service_item_field) { - frm.set_query(service_item_field, function() { - return { - filters: { - 'is_sales_item': 1, - 'is_stock_item': 0 - } - }; - }); -}; - -frappe.tour['Healthcare Settings'] = [ - { - fieldname: 'link_customer_to_patient', - title: __('Link Customer to Patient'), - description: __('If checked, a customer will be created for every Patient. Patient Invoices will be created against this Customer. You can also select existing Customer while creating a Patient. This field is checked by default.') - }, - { - fieldname: 'collect_registration_fee', - title: __('Collect Registration Fee'), - description: __('If your Healthcare facility bills registrations of Patients, you can check this and set the Registration Fee in the field below. Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.') - }, - { - fieldname: 'automate_appointment_invoicing', - title: __('Automate Appointment Invoicing'), - description: __('Checking this will automatically create a Sales Invoice whenever an appointment is booked for a Patient.') - }, - { - fieldname: 'inpatient_visit_charge_item', - title: __('Healthcare Service Items'), - description: __('You can create a service item for Inpatient Visit Charge and set it here. Similarly, you can set up other Healthcare Service Items for billing in this section. Click ') + "here" + __(' to know more') - }, - { - fieldname: 'income_account', - title: __('Set up default Accounts for the Healthcare Facility'), - description: __('If you wish to override default accounts settings and configure the Income and Receivable accounts for Healthcare, you can do so here.') - - }, - { - fieldname: 'send_registration_msg', - title: __('Out Patient SMS alerts'), - description: __('If you want to send SMS alert on Patient Registration, you can enable this option. Similary, you can set up Out Patient SMS alerts for other functionalities in this section. Click ') + "here" + __(' to know more') - } -]; diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json deleted file mode 100644 index ddf1bce492..0000000000 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.json +++ /dev/null @@ -1,351 +0,0 @@ -{ - "actions": [], - "beta": 1, - "creation": "2017-05-09 11:26:22.337760", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "sb_op_settings", - "patient_name_by", - "link_customer_to_patient", - "default_medical_code_standard", - "column_break_9", - "collect_registration_fee", - "registration_fee", - "automate_appointment_invoicing", - "enable_free_follow_ups", - "max_visits", - "valid_days", - "inpatient_settings_section", - "allow_discharge_despite_unbilled_services", - "do_not_bill_inpatient_encounters", - "healthcare_service_items", - "inpatient_visit_charge_item", - "op_consulting_charge_item", - "column_break_13", - "clinical_procedure_consumable_item", - "sb_in_ac", - "income_account", - "receivable_account", - "out_patient_sms_alerts", - "send_registration_msg", - "registration_msg", - "send_appointment_confirmation", - "appointment_confirmation_msg", - "avoid_confirmation", - "column_break_16", - "send_appointment_reminder", - "appointment_reminder_msg", - "remind_before", - "sb_lab_settings", - "create_lab_test_on_si_submit", - "create_sample_collection_for_lab_test", - "column_break_34", - "lab_test_approval_required", - "employee_name_and_designation_in_print", - "custom_signature_in_print", - "laboratory_sms_alerts", - "sms_printed", - "column_break_28", - "sms_emailed" - ], - "fields": [ - { - "fieldname": "sb_op_settings", - "fieldtype": "Section Break", - "label": "Out Patient Settings" - }, - { - "fieldname": "default_medical_code_standard", - "fieldtype": "Link", - "label": "Default Medical Code Standard", - "options": "Medical Code Standard" - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "default": "0", - "description": "Checking this will create new Patients with a Disabled status by default and will only be enabled after invoicing the Registration Fee.", - "fieldname": "collect_registration_fee", - "fieldtype": "Check", - "label": "Collect Fee for Patient Registration" - }, - { - "depends_on": "collect_registration_fee", - "fieldname": "registration_fee", - "fieldtype": "Currency", - "label": "Registration Fee", - "mandatory_depends_on": "eval:doc.collect_registration_fee == 1", - "options": "Currency" - }, - { - "depends_on": "eval:doc.enable_free_follow_ups == 1", - "description": "Time period (Valid number of days) for free consultations", - "fieldname": "valid_days", - "fieldtype": "Int", - "label": "Valid Number of Days", - "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1" - }, - { - "collapsible": 1, - "description": "You can configure default Items for billing consultation charges, procedure consumption items and inpatient visits", - "fieldname": "healthcare_service_items", - "fieldtype": "Section Break", - "label": "Default Healthcare Service Items" - }, - { - "fieldname": "inpatient_visit_charge_item", - "fieldtype": "Link", - "label": "Inpatient Visit Charge Item", - "options": "Item" - }, - { - "fieldname": "op_consulting_charge_item", - "fieldtype": "Link", - "label": "Out Patient Consulting Charge Item", - "options": "Item" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "fieldname": "clinical_procedure_consumable_item", - "fieldtype": "Link", - "label": "Clinical Procedure Consumable Item", - "options": "Item" - }, - { - "collapsible": 1, - "fieldname": "out_patient_sms_alerts", - "fieldtype": "Section Break", - "label": "Out Patient SMS Alerts" - }, - { - "fieldname": "column_break_16", - "fieldtype": "Column Break" - }, - { - "collapsible": 1, - "fieldname": "sb_in_ac", - "fieldtype": "Section Break", - "label": "Default Accounts" - }, - { - "description": "Default income accounts to be used if not set in Healthcare Practitioner to book Appointment charges.", - "fieldname": "income_account", - "fieldtype": "Table", - "label": "Income Account", - "options": "Party Account" - }, - { - "description": "Default receivable accounts to be used to book Appointment charges.", - "fieldname": "receivable_account", - "fieldtype": "Table", - "label": "Receivable Account", - "options": "Party Account" - }, - { - "collapsible": 1, - "fieldname": "sb_lab_settings", - "fieldtype": "Section Break", - "label": "Laboratory Settings" - }, - { - "fieldname": "column_break_34", - "fieldtype": "Column Break" - }, - { - "default": "1", - "description": "Check this if you want the Name and Designation of the Employee associated with the User who submits the document to be printed in the Lab Test Report.", - "fieldname": "employee_name_and_designation_in_print", - "fieldtype": "Check", - "label": "Employee name and designation in print" - }, - { - "depends_on": "eval:doc.employee_name_and_designation_in_print == '0'\n", - "fieldname": "custom_signature_in_print", - "fieldtype": "Small Text", - "label": "Custom Signature in Print" - }, - { - "collapsible": 1, - "fieldname": "laboratory_sms_alerts", - "fieldtype": "Section Break", - "label": "Laboratory SMS Alerts" - }, - { - "default": "Hello {{doc.patient}}, Your {{doc.lab_test_name}} result is ready with {{doc.company }}. \nThank You, Good day!", - "fieldname": "sms_printed", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Result Printed Message" - }, - { - "fieldname": "column_break_28", - "fieldtype": "Column Break" - }, - { - "default": "Hello {{doc.patient}}, Your {{doc.lab_test_name}} result has been emailed to {{doc.email}}. \n{{doc.company }}. \nThank You, Good day!", - "fieldname": "sms_emailed", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Result Emailed Message" - }, - { - "default": "0", - "description": "Checking this will restrict printing and emailing of Lab Test documents unless they have the status as Approved.", - "fieldname": "lab_test_approval_required", - "fieldtype": "Check", - "label": "Do not print or email Lab Tests without Approval" - }, - { - "default": "1", - "description": "If checked, a customer will be created, mapped to Patient.\nPatient Invoices will be created against this Customer. You can also select existing Customer while creating Patient.", - "fieldname": "link_customer_to_patient", - "fieldtype": "Check", - "label": "Link Customer to Patient" - }, - { - "default": "0", - "description": "Checking this will create Lab Test(s) specified in the Sales Invoice on submission.", - "fieldname": "create_lab_test_on_si_submit", - "fieldtype": "Check", - "label": "Create Lab Test(s) on Sales Invoice Submission" - }, - { - "default": "0", - "description": "Checking this will create a Sample Collection document every time you create a Lab Test", - "fieldname": "create_sample_collection_for_lab_test", - "fieldtype": "Check", - "label": "Create Sample Collection document for Lab Test" - }, - { - "fieldname": "patient_name_by", - "fieldtype": "Select", - "label": "Patient Name By", - "options": "Patient Name\nNaming Series" - }, - { - "default": "0", - "description": "Manage Appointment Invoice submit and cancel automatically for Patient Encounter", - "fieldname": "automate_appointment_invoicing", - "fieldtype": "Check", - "label": "Automate Appointment Invoicing" - }, - { - "default": "0", - "fieldname": "send_registration_msg", - "fieldtype": "Check", - "label": "Patient Registration" - }, - { - "default": "Hello {{doc.patient}}, Thank you for registering with {{doc.company}}. Your ID is {{doc.name}} . Please note this ID for future reference. \nThank You!", - "depends_on": "send_registration_msg", - "fieldname": "registration_msg", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Registration Message" - }, - { - "default": "0", - "fieldname": "send_appointment_confirmation", - "fieldtype": "Check", - "label": "Appointment Confirmation" - }, - { - "default": "Hello {{doc.patient}}, You have scheduled an appointment with {{doc.practitioner}} on {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!", - "depends_on": "send_appointment_confirmation", - "fieldname": "appointment_confirmation_msg", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Confirmation Message" - }, - { - "default": "0", - "depends_on": "send_appointment_confirmation", - "description": "Do not confirm if appointment is created for the same day", - "fieldname": "avoid_confirmation", - "fieldtype": "Check", - "label": "Avoid Confirmation" - }, - { - "default": "0", - "fieldname": "send_appointment_reminder", - "fieldtype": "Check", - "label": "Appointment Reminder" - }, - { - "default": "Hello {{doc.patient}}, You have an appointment with {{doc.practitioner}} by {{doc.appointment_datetime}} at {{doc.company}}.\nThank you, Good day!\n", - "depends_on": "send_appointment_reminder", - "fieldname": "appointment_reminder_msg", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Reminder Message" - }, - { - "depends_on": "send_appointment_reminder", - "fieldname": "remind_before", - "fieldtype": "Time", - "label": "Remind Before" - }, - { - "depends_on": "eval:doc.enable_free_follow_ups == 1", - "description": "The number of free follow ups (Patient Encounters in valid days) allowed", - "fieldname": "max_visits", - "fieldtype": "Int", - "label": "Number of Patient Encounters in Valid Days", - "mandatory_depends_on": "eval:doc.enable_free_follow_ups == 1" - }, - { - "default": "0", - "fieldname": "enable_free_follow_ups", - "fieldtype": "Check", - "label": "Enable Free Follow-ups" - }, - { - "fieldname": "inpatient_settings_section", - "fieldtype": "Section Break", - "label": "Inpatient Settings" - }, - { - "default": "0", - "fieldname": "allow_discharge_despite_unbilled_services", - "fieldtype": "Check", - "label": "Allow Discharge Despite Unbilled Healthcare Services" - }, - { - "default": "0", - "fieldname": "do_not_bill_inpatient_encounters", - "fieldtype": "Check", - "label": "Do Not Bill Patient Encounters for Inpatients" - } - ], - "issingle": 1, - "links": [], - "modified": "2021-01-13 09:04:35.877700", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py b/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py deleted file mode 100644 index 9ab8881766..0000000000 --- a/erpnext/healthcare/doctype/healthcare_settings/healthcare_settings.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe import _ -from frappe.core.doctype.sms_settings.sms_settings import send_sms -from frappe.model.document import Document - - -class HealthcareSettings(Document): - def validate(self): - for key in ['collect_registration_fee', 'link_customer_to_patient', 'patient_name_by', - 'lab_test_approval_required', 'create_sample_collection_for_lab_test', 'default_medical_code_standard']: - frappe.db.set_default(key, self.get(key, "")) - - if self.collect_registration_fee: - if self.registration_fee <= 0: - frappe.throw(_('Registration Fee cannot be negative or zero')) - - if self.inpatient_visit_charge_item: - validate_service_item(self.inpatient_visit_charge_item) - if self.op_consulting_charge_item: - validate_service_item(self.op_consulting_charge_item) - if self.clinical_procedure_consumable_item: - validate_service_item(self.clinical_procedure_consumable_item) - - -def validate_service_item(item): - if frappe.db.get_value('Item', item, 'is_stock_item'): - frappe.throw(_('Configure a service Item for {0}').format(item)) - -@frappe.whitelist() -def get_sms_text(doc): - sms_text = {} - doc = frappe.get_doc('Lab Test', doc) - context = {'doc': doc, 'alert': doc, 'comments': None} - - emailed = frappe.db.get_value('Healthcare Settings', None, 'sms_emailed') - sms_text['emailed'] = frappe.render_template(emailed, context) - - printed = frappe.db.get_value('Healthcare Settings', None, 'sms_printed') - sms_text['printed'] = frappe.render_template(printed, context) - - return sms_text - -def send_registration_sms(doc): - if frappe.db.get_single_value('Healthcare Settings', 'send_registration_msg'): - if doc.mobile: - context = {'doc': doc, 'alert': doc, 'comments': None} - if doc.get('_comments'): - context['comments'] = json.loads(doc.get('_comments')) - messages = frappe.db.get_single_value('Healthcare Settings', 'registration_msg') - messages = frappe.render_template(messages, context) - number = [doc.mobile] - send_sms(number,messages) - else: - frappe.msgprint(doc.name + ' has no mobile number to send registration SMS', alert=True) - -def get_receivable_account(company): - receivable_account = get_account(None, 'receivable_account', 'Healthcare Settings', company) - if receivable_account: - return receivable_account - - return frappe.get_cached_value('Company', company, 'default_receivable_account') - -def get_income_account(practitioner, company): - # check income account in Healthcare Practitioner - if practitioner: - income_account = get_account('Healthcare Practitioner', None, practitioner, company) - if income_account: - return income_account - - # else check income account in Healthcare Settings - income_account = get_account(None, 'income_account', 'Healthcare Settings', company) - if income_account: - return income_account - - # else return default income account of company - return frappe.get_cached_value('Company', company, 'default_income_account') - -def get_account(parent_type, parent_field, parent, company): - if parent_type: - return frappe.db.get_value('Party Account', - {'parenttype': parent_type, 'parent': parent, 'company': company}, 'account') - - if parent_field: - return frappe.db.get_value('Party Account', - {'parentfield': parent_field, 'parent': parent, 'company': company}, 'account') diff --git a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.py b/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.py deleted file mode 100644 index af2f2b4c4f..0000000000 --- a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestHealthcareSettings(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/__init__.py b/erpnext/healthcare/doctype/inpatient_medication_entry/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js deleted file mode 100644 index a7b06b1718..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.js +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Inpatient Medication Entry', { - refresh: function(frm) { - // Ignore cancellation of doctype on cancel all - frm.ignore_doctypes_on_cancel_all = ['Stock Entry']; - frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide(); - - frm.set_query('item_code', () => { - return { - filters: { - is_stock_item: 1 - } - }; - }); - - frm.set_query('drug_code', 'medication_orders', () => { - return { - filters: { - is_stock_item: 1 - } - }; - }); - - frm.set_query('warehouse', () => { - return { - filters: { - company: frm.doc.company - } - }; - }); - - if (frm.doc.__islocal || frm.doc.docstatus !== 0 || !frm.doc.update_stock) - return; - - frm.add_custom_button(__('Make Stock Entry'), function() { - frappe.call({ - method: 'erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry.make_difference_stock_entry', - args: { docname: frm.doc.name }, - freeze: true, - callback: function(r) { - if (r.message) { - var doclist = frappe.model.sync(r.message); - frappe.set_route('Form', doclist[0].doctype, doclist[0].name); - } else { - frappe.msgprint({ - title: __('No Drug Shortage'), - message: __('All the drugs are available with sufficient qty to process this Inpatient Medication Entry.'), - indicator: 'green' - }); - } - } - }); - }); - }, - - patient: function(frm) { - if (frm.doc.patient) - frm.set_value('service_unit', ''); - }, - - get_medication_orders: function(frm) { - frappe.call({ - method: 'get_medication_orders', - doc: frm.doc, - freeze: true, - freeze_message: __('Fetching Pending Medication Orders'), - callback: function() { - refresh_field('medication_orders'); - } - }); - } -}); diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json deleted file mode 100644 index b1a6ee4ed1..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "actions": [], - "autoname": "naming_series:", - "creation": "2020-09-25 14:13:20.111906", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "naming_series", - "company", - "column_break_3", - "posting_date", - "status", - "filters_section", - "item_code", - "assigned_to_practitioner", - "patient", - "practitioner", - "service_unit", - "column_break_11", - "from_date", - "to_date", - "from_time", - "to_time", - "select_medication_orders_section", - "get_medication_orders", - "medication_orders", - "section_break_18", - "update_stock", - "warehouse", - "amended_from" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Naming Series", - "options": "HLC-IME-.YYYY.-" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "options": "Company", - "reqd": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "fieldname": "posting_date", - "fieldtype": "Date", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Posting Date", - "reqd": 1 - }, - { - "fieldname": "status", - "fieldtype": "Select", - "label": "Status", - "options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled", - "read_only": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval: doc.__islocal", - "fieldname": "filters_section", - "fieldtype": "Section Break", - "label": "Filters" - }, - { - "fieldname": "item_code", - "fieldtype": "Link", - "label": "Item Code (Drug)", - "options": "Item" - }, - { - "depends_on": "update_stock", - "description": "Warehouse from where medication stock should be consumed", - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Medication Warehouse", - "mandatory_depends_on": "update_stock", - "options": "Warehouse" - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "label": "Patient", - "options": "Patient" - }, - { - "depends_on": "eval:!doc.patient", - "fieldname": "service_unit", - "fieldtype": "Link", - "label": "Healthcare Service Unit", - "options": "Healthcare Service Unit" - }, - { - "fieldname": "column_break_11", - "fieldtype": "Column Break" - }, - { - "fieldname": "from_date", - "fieldtype": "Date", - "label": "From Date" - }, - { - "fieldname": "to_date", - "fieldtype": "Date", - "label": "To Date" - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Inpatient Medication Entry", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner" - }, - { - "fieldname": "select_medication_orders_section", - "fieldtype": "Section Break", - "label": "Medication Orders" - }, - { - "fieldname": "medication_orders", - "fieldtype": "Table", - "label": "Inpatient Medication Orders", - "options": "Inpatient Medication Entry Detail", - "reqd": 1 - }, - { - "depends_on": "eval:doc.docstatus!==1", - "fieldname": "get_medication_orders", - "fieldtype": "Button", - "label": "Get Pending Medication Orders", - "print_hide": 1 - }, - { - "fieldname": "assigned_to_practitioner", - "fieldtype": "Link", - "label": "Assigned To", - "options": "User" - }, - { - "fieldname": "section_break_18", - "fieldtype": "Section Break", - "label": "Stock Details" - }, - { - "default": "1", - "fieldname": "update_stock", - "fieldtype": "Check", - "label": "Update Stock" - }, - { - "fieldname": "from_time", - "fieldtype": "Time", - "label": "From Time" - }, - { - "fieldname": "to_time", - "fieldtype": "Time", - "label": "To Time" - } - ], - "index_web_pages_for_search": 1, - "is_submittable": 1, - "links": [], - "modified": "2021-01-11 12:37:46.749659", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Medication Entry", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py deleted file mode 100644 index b28e37a9c3..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py +++ /dev/null @@ -1,324 +0,0 @@ -# -*- 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 import _ -from frappe.model.document import Document -from frappe.utils import flt, get_link_to_form - -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account -from erpnext.stock.utils import get_latest_stock_qty - - -class InpatientMedicationEntry(Document): - def validate(self): - self.validate_medication_orders() - - @frappe.whitelist() - def get_medication_orders(self): - # pull inpatient medication orders based on selected filters - orders = get_pending_medication_orders(self) - - if orders: - self.add_mo_to_table(orders) - return self - else: - self.set('medication_orders', []) - frappe.msgprint(_('No pending medication orders found for selected criteria')) - - def add_mo_to_table(self, orders): - # Add medication orders in the child table - self.set('medication_orders', []) - - for data in orders: - self.append('medication_orders', { - 'patient': data.patient, - 'patient_name': data.patient_name, - 'inpatient_record': data.inpatient_record, - 'service_unit': data.service_unit, - 'datetime': "%s %s" % (data.date, data.time or "00:00:00"), - 'drug_code': data.drug, - 'drug_name': data.drug_name, - 'dosage': data.dosage, - 'dosage_form': data.dosage_form, - 'against_imo': data.parent, - 'against_imoe': data.name - }) - - def on_submit(self): - self.validate_medication_orders() - success_msg = "" - if self.update_stock: - stock_entry = self.process_stock() - success_msg += _('Stock Entry {0} created and ').format( - frappe.bold(get_link_to_form('Stock Entry', stock_entry))) - - self.update_medication_orders() - success_msg += _('Inpatient Medication Orders updated successfully') - frappe.msgprint(success_msg, title=_('Success'), indicator='green') - - def validate_medication_orders(self): - for entry in self.medication_orders: - docstatus, is_completed = frappe.db.get_value('Inpatient Medication Order Entry', entry.against_imoe, - ['docstatus', 'is_completed']) - - if docstatus == 2: - frappe.throw(_('Row {0}: Cannot create Inpatient Medication Entry against cancelled Inpatient Medication Order {1}').format( - entry.idx, get_link_to_form(entry.against_imo))) - - if is_completed: - frappe.throw(_('Row {0}: This Medication Order is already marked as completed').format( - entry.idx)) - - def on_cancel(self): - self.cancel_stock_entries() - self.update_medication_orders(on_cancel=True) - - def process_stock(self): - allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock') - if not allow_negative_stock: - self.check_stock_qty() - - return self.make_stock_entry() - - def update_medication_orders(self, on_cancel=False): - orders, order_entry_map = self.get_order_entry_map() - # mark completion status - is_completed = 1 - if on_cancel: - is_completed = 0 - - frappe.db.sql(""" - UPDATE `tabInpatient Medication Order Entry` - SET is_completed = %(is_completed)s - WHERE name IN %(orders)s - """, {'orders': orders, 'is_completed': is_completed}) - - # update status and completed orders count - for order, count in order_entry_map.items(): - medication_order = frappe.get_doc('Inpatient Medication Order', order) - completed_orders = flt(count) - current_value = frappe.db.get_value('Inpatient Medication Order', order, 'completed_orders') - - if on_cancel: - completed_orders = flt(current_value) - flt(count) - else: - completed_orders = flt(current_value) + flt(count) - - medication_order.db_set('completed_orders', completed_orders) - medication_order.set_status() - - def get_order_entry_map(self): - # for marking order completion status - orders = [] - # orders mapped - order_entry_map = dict() - - for entry in self.medication_orders: - orders.append(entry.against_imoe) - parent = entry.against_imo - if not order_entry_map.get(parent): - order_entry_map[parent] = 0 - - order_entry_map[parent] += 1 - - return orders, order_entry_map - - def check_stock_qty(self): - drug_shortage = get_drug_shortage_map(self.medication_orders, self.warehouse) - - if drug_shortage: - message = _('Quantity not available for the following items in warehouse {0}. ').format(frappe.bold(self.warehouse)) - message += _('Please enable Allow Negative Stock in Stock Settings or create Stock Entry to proceed.') - - formatted_item_rows = '' - - for drug, shortage_qty in drug_shortage.items(): - item_link = get_link_to_form('Item', drug) - formatted_item_rows += """ - {0} - {1} - """.format(item_link, frappe.bold(shortage_qty)) - - message += """ - - - - - - {2} -
{0}{1}
- """.format(_('Drug Code'), _('Shortage Qty'), formatted_item_rows) - - frappe.throw(message, title=_('Insufficient Stock'), is_minimizable=True, wide=True) - - def make_stock_entry(self): - stock_entry = frappe.new_doc('Stock Entry') - stock_entry.purpose = 'Material Issue' - stock_entry.set_stock_entry_type() - stock_entry.from_warehouse = self.warehouse - stock_entry.company = self.company - stock_entry.inpatient_medication_entry = self.name - cost_center = frappe.get_cached_value('Company', self.company, 'cost_center') - expense_account = get_account(None, 'expense_account', 'Healthcare Settings', self.company) - - for entry in self.medication_orders: - se_child = stock_entry.append('items') - se_child.item_code = entry.drug_code - se_child.item_name = entry.drug_name - se_child.uom = frappe.db.get_value('Item', entry.drug_code, 'stock_uom') - se_child.stock_uom = se_child.uom - se_child.qty = flt(entry.dosage) - # in stock uom - se_child.conversion_factor = 1 - se_child.cost_center = cost_center - se_child.expense_account = expense_account - # references - se_child.patient = entry.patient - se_child.inpatient_medication_entry_child = entry.name - - stock_entry.submit() - return stock_entry.name - - def cancel_stock_entries(self): - stock_entries = frappe.get_all('Stock Entry', {'inpatient_medication_entry': self.name}) - for entry in stock_entries: - doc = frappe.get_doc('Stock Entry', entry.name) - doc.cancel() - - -def get_pending_medication_orders(entry): - filters, values = get_filters(entry) - to_remove = [] - - data = frappe.db.sql(""" - SELECT - ip.inpatient_record, ip.patient, ip.patient_name, - entry.name, entry.parent, entry.drug, entry.drug_name, - entry.dosage, entry.dosage_form, entry.date, entry.time, entry.instructions - FROM - `tabInpatient Medication Order` ip - INNER JOIN - `tabInpatient Medication Order Entry` entry - ON - ip.name = entry.parent - WHERE - ip.docstatus = 1 and - ip.company = %(company)s and - entry.is_completed = 0 - {0} - ORDER BY - entry.date, entry.time - """.format(filters), values, as_dict=1) - - for doc in data: - inpatient_record = doc.inpatient_record - if inpatient_record: - doc['service_unit'] = get_current_healthcare_service_unit(inpatient_record) - - if entry.service_unit and doc.service_unit != entry.service_unit: - to_remove.append(doc) - - for doc in to_remove: - data.remove(doc) - - return data - - -def get_filters(entry): - filters = '' - values = dict(company=entry.company) - if entry.from_date: - filters += ' and entry.date >= %(from_date)s' - values['from_date'] = entry.from_date - - if entry.to_date: - filters += ' and entry.date <= %(to_date)s' - values['to_date'] = entry.to_date - - if entry.from_time: - filters += ' and entry.time >= %(from_time)s' - values['from_time'] = entry.from_time - - if entry.to_time: - filters += ' and entry.time <= %(to_time)s' - values['to_time'] = entry.to_time - - if entry.patient: - filters += ' and ip.patient = %(patient)s' - values['patient'] = entry.patient - - if entry.practitioner: - filters += ' and ip.practitioner = %(practitioner)s' - values['practitioner'] = entry.practitioner - - if entry.item_code: - filters += ' and entry.drug = %(item_code)s' - values['item_code'] = entry.item_code - - if entry.assigned_to_practitioner: - filters += ' and ip._assign LIKE %(assigned_to)s' - values['assigned_to'] = '%' + entry.assigned_to_practitioner + '%' - - return filters, values - - -def get_current_healthcare_service_unit(inpatient_record): - ip_record = frappe.get_doc('Inpatient Record', inpatient_record) - if ip_record.status in ['Admitted', 'Discharge Scheduled'] and ip_record.inpatient_occupancies: - return ip_record.inpatient_occupancies[-1].service_unit - return - - -def get_drug_shortage_map(medication_orders, warehouse): - """ - Returns a dict like { drug_code: shortage_qty } - """ - drug_requirement = dict() - for d in medication_orders: - if not drug_requirement.get(d.drug_code): - drug_requirement[d.drug_code] = 0 - drug_requirement[d.drug_code] += flt(d.dosage) - - drug_shortage = dict() - for drug, required_qty in drug_requirement.items(): - available_qty = get_latest_stock_qty(drug, warehouse) - if flt(required_qty) > flt(available_qty): - drug_shortage[drug] = flt(flt(required_qty) - flt(available_qty)) - - return drug_shortage - - -@frappe.whitelist() -def make_difference_stock_entry(docname): - doc = frappe.get_doc('Inpatient Medication Entry', docname) - drug_shortage = get_drug_shortage_map(doc.medication_orders, doc.warehouse) - - if not drug_shortage: - return None - - stock_entry = frappe.new_doc('Stock Entry') - stock_entry.purpose = 'Material Transfer' - stock_entry.set_stock_entry_type() - stock_entry.to_warehouse = doc.warehouse - stock_entry.company = doc.company - cost_center = frappe.get_cached_value('Company', doc.company, 'cost_center') - expense_account = get_account(None, 'expense_account', 'Healthcare Settings', doc.company) - - for drug, shortage_qty in drug_shortage.items(): - se_child = stock_entry.append('items') - se_child.item_code = drug - se_child.item_name = frappe.db.get_value('Item', drug, 'stock_uom') - se_child.uom = frappe.db.get_value('Item', drug, 'stock_uom') - se_child.stock_uom = se_child.uom - se_child.qty = flt(shortage_qty) - se_child.t_warehouse = doc.warehouse - # in stock uom - se_child.conversion_factor = 1 - se_child.cost_center = cost_center - se_child.expense_account = expense_account - - return stock_entry diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py deleted file mode 100644 index ca9364d66a..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry_dashboard.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'against_imoe', - 'internal_links': { - 'Inpatient Medication Order': ['medication_orders', 'against_imo'] - }, - 'transactions': [ - { - 'label': _('Reference'), - 'items': ['Inpatient Medication Order'] - } - ] - } diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py deleted file mode 100644 index dde7739f7a..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry/test_inpatient_medication_entry.py +++ /dev/null @@ -1,174 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import add_days, getdate, now_datetime - -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_account -from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( - get_drug_shortage_map, - make_difference_stock_entry, -) -from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import ( - create_ipme, - create_ipmo, -) -from erpnext.healthcare.doctype.inpatient_record.inpatient_record import ( - admit_patient, - discharge_patient, - schedule_discharge, -) -from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import ( - create_inpatient, - create_patient, - get_healthcare_service_unit, - mark_invoiced_inpatient_occupancy, -) - - -class TestInpatientMedicationEntry(unittest.TestCase): - def setUp(self): - frappe.db.sql("""delete from `tabInpatient Record`""") - frappe.db.sql("""delete from `tabInpatient Medication Order`""") - frappe.db.sql("""delete from `tabInpatient Medication Entry`""") - self.patient = create_patient() - - # Admit - ip_record = create_inpatient(self.patient) - ip_record.expected_length_of_stay = 0 - ip_record.save() - ip_record.reload() - service_unit = get_healthcare_service_unit() - admit_patient(ip_record, service_unit, now_datetime()) - self.ip_record = ip_record - - def test_filters_for_fetching_pending_mo(self): - ipmo = create_ipmo(self.patient) - ipmo.submit() - ipmo.reload() - - date = add_days(getdate(), -1) - filters = frappe._dict( - from_date=date, - to_date=date, - from_time='', - to_time='', - item_code='Dextromethorphan', - patient=self.patient - ) - - ipme = create_ipme(filters, update_stock=0) - - # 3 dosages per day - self.assertEqual(len(ipme.medication_orders), 3) - self.assertEqual(getdate(ipme.medication_orders[0].datetime), date) - - def test_ipme_with_stock_update(self): - ipmo = create_ipmo(self.patient) - ipmo.submit() - ipmo.reload() - - date = add_days(getdate(), -1) - filters = frappe._dict( - from_date=date, - to_date=date, - from_time='', - to_time='', - item_code='Dextromethorphan', - patient=self.patient - ) - - make_stock_entry() - ipme = create_ipme(filters, update_stock=1) - ipme.submit() - ipme.reload() - - # test order completed - is_order_completed = frappe.db.get_value('Inpatient Medication Order Entry', - ipme.medication_orders[0].against_imoe, 'is_completed') - self.assertEqual(is_order_completed, 1) - - # test stock entry - stock_entry = frappe.db.exists('Stock Entry', {'inpatient_medication_entry': ipme.name}) - self.assertTrue(stock_entry) - - # check references - stock_entry = frappe.get_doc('Stock Entry', stock_entry) - self.assertEqual(stock_entry.items[0].patient, self.patient) - self.assertEqual(stock_entry.items[0].inpatient_medication_entry_child, ipme.medication_orders[0].name) - - def test_drug_shortage_stock_entry(self): - ipmo = create_ipmo(self.patient) - ipmo.submit() - ipmo.reload() - - date = add_days(getdate(), -1) - filters = frappe._dict( - from_date=date, - to_date=date, - from_time='', - to_time='', - item_code='Dextromethorphan', - patient=self.patient - ) - - # check drug shortage - ipme = create_ipme(filters, update_stock=1) - ipme.warehouse = 'Finished Goods - _TC' - ipme.save() - drug_shortage = get_drug_shortage_map(ipme.medication_orders, ipme.warehouse) - self.assertEqual(drug_shortage.get('Dextromethorphan'), 3) - - # check material transfer for drug shortage - make_stock_entry() - stock_entry = make_difference_stock_entry(ipme.name) - self.assertEqual(stock_entry.items[0].item_code, 'Dextromethorphan') - self.assertEqual(stock_entry.items[0].qty, 3) - stock_entry.from_warehouse = 'Stores - _TC' - stock_entry.submit() - - ipme.reload() - ipme.submit() - - def tearDown(self): - # cleanup - Discharge - schedule_discharge(frappe.as_json({'patient': self.patient})) - self.ip_record.reload() - mark_invoiced_inpatient_occupancy(self.ip_record) - - self.ip_record.reload() - discharge_patient(self.ip_record) - - for entry in frappe.get_all('Inpatient Medication Entry'): - doc = frappe.get_doc('Inpatient Medication Entry', entry.name) - doc.cancel() - - for entry in frappe.get_all('Inpatient Medication Order'): - doc = frappe.get_doc('Inpatient Medication Order', entry.name) - doc.cancel() - -def make_stock_entry(warehouse=None): - frappe.db.set_value('Company', '_Test Company', { - 'stock_adjustment_account': 'Stock Adjustment - _TC', - 'default_inventory_account': 'Stock In Hand - _TC' - }) - stock_entry = frappe.new_doc('Stock Entry') - stock_entry.stock_entry_type = 'Material Receipt' - stock_entry.company = '_Test Company' - stock_entry.to_warehouse = warehouse or 'Stores - _TC' - expense_account = get_account(None, 'expense_account', 'Healthcare Settings', '_Test Company') - se_child = stock_entry.append('items') - se_child.item_code = 'Dextromethorphan' - se_child.item_name = 'Dextromethorphan' - se_child.uom = 'Nos' - se_child.stock_uom = 'Nos' - se_child.qty = 6 - se_child.t_warehouse = 'Stores - _TC' - # in stock uom - se_child.conversion_factor = 1.0 - se_child.expense_account = expense_account - stock_entry.submit() diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry_detail/__init__.py b/erpnext/healthcare/doctype/inpatient_medication_entry_detail/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json b/erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json deleted file mode 100644 index e3d7212169..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "actions": [], - "creation": "2020-09-25 14:56:32.636569", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "patient", - "patient_name", - "inpatient_record", - "column_break_4", - "service_unit", - "datetime", - "medication_details_section", - "drug_code", - "drug_name", - "dosage", - "available_qty", - "dosage_form", - "column_break_10", - "instructions", - "references_section", - "against_imo", - "against_imoe" - ], - "fields": [ - { - "columns": 2, - "fieldname": "patient", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1 - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "columns": 2, - "fieldname": "drug_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Drug Code", - "options": "Item", - "reqd": 1 - }, - { - "fetch_from": "drug_code.item_name", - "fieldname": "drug_name", - "fieldtype": "Data", - "label": "Drug Name", - "read_only": 1 - }, - { - "columns": 1, - "fieldname": "dosage", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Dosage", - "reqd": 1 - }, - { - "fieldname": "dosage_form", - "fieldtype": "Link", - "label": "Dosage Form", - "options": "Dosage Form" - }, - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "references_section", - "fieldtype": "Section Break", - "label": "References" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "medication_details_section", - "fieldtype": "Section Break", - "label": "Medication Details" - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "columns": 3, - "fieldname": "datetime", - "fieldtype": "Datetime", - "in_list_view": 1, - "label": "Datetime", - "reqd": 1 - }, - { - "fieldname": "instructions", - "fieldtype": "Small Text", - "label": "Instructions" - }, - { - "columns": 2, - "fieldname": "service_unit", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Service Unit", - "options": "Healthcare Service Unit", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "against_imo", - "fieldtype": "Link", - "label": "Against Inpatient Medication Order", - "no_copy": 1, - "options": "Inpatient Medication Order", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "against_imoe", - "fieldtype": "Data", - "label": "Against Inpatient Medication Order Entry", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "available_qty", - "fieldtype": "Float", - "hidden": 1, - "label": "Available Qty", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2020-09-30 14:48:23.648223", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Medication Entry Detail", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py b/erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py deleted file mode 100644 index 91734312d4..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_entry_detail/inpatient_medication_entry_detail.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 InpatientMedicationEntryDetail(Document): - pass diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/__init__.py b/erpnext/healthcare/doctype/inpatient_medication_order/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js deleted file mode 100644 index 690e2e7a90..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.js +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Inpatient Medication Order', { - refresh: function(frm) { - if (frm.doc.docstatus === 1) { - frm.trigger("show_progress"); - } - - frm.events.show_medication_order_button(frm); - - frm.set_query('patient', () => { - return { - filters: { - 'inpatient_record': ['!=', ''], - 'inpatient_status': 'Admitted' - } - }; - }); - }, - - show_medication_order_button: function(frm) { - frm.fields_dict['medication_orders'].grid.wrapper.find('.grid-add-row').hide(); - frm.fields_dict['medication_orders'].grid.add_custom_button(__('Add Medication Orders'), () => { - let d = new frappe.ui.Dialog({ - title: __('Add Medication Orders'), - fields: [ - { - fieldname: 'drug_code', - label: __('Drug'), - fieldtype: 'Link', - options: 'Item', - reqd: 1, - "get_query": function () { - return { - filters: {'is_stock_item': 1} - }; - } - }, - { - fieldname: 'dosage', - label: __('Dosage'), - fieldtype: 'Link', - options: 'Prescription Dosage', - reqd: 1 - }, - { - fieldname: 'period', - label: __('Period'), - fieldtype: 'Link', - options: 'Prescription Duration', - reqd: 1 - }, - { - fieldname: 'dosage_form', - label: __('Dosage Form'), - fieldtype: 'Link', - options: 'Dosage Form', - reqd: 1 - } - ], - primary_action_label: __('Add'), - primary_action: () => { - let values = d.get_values(); - if (values) { - frm.call({ - doc: frm.doc, - method: 'add_order_entries', - args: { - order: values - }, - freeze: true, - freeze_message: __('Adding Order Entries'), - callback: function() { - frm.refresh_field('medication_orders'); - } - }); - } - }, - }); - d.show(); - }); - }, - - show_progress: function(frm) { - let bars = []; - let message = ''; - - // completed sessions - let title = __('{0} medication orders completed', [frm.doc.completed_orders]); - if (frm.doc.completed_orders === 1) { - title = __('{0} medication order completed', [frm.doc.completed_orders]); - } - title += __(' out of {0}', [frm.doc.total_orders]); - - bars.push({ - 'title': title, - 'width': (frm.doc.completed_orders / frm.doc.total_orders * 100) + '%', - 'progress_class': 'progress-bar-success' - }); - if (bars[0].width == '0%') { - bars[0].width = '0.5%'; - } - message = title; - frm.dashboard.add_progress(__('Status'), bars, message); - } -}); diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json deleted file mode 100644 index e31d2e3e36..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "actions": [], - "autoname": "naming_series:", - "creation": "2020-09-14 18:33:56.715736", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "patient_details_section", - "naming_series", - "patient_encounter", - "patient", - "patient_name", - "patient_age", - "inpatient_record", - "column_break_6", - "company", - "status", - "practitioner", - "start_date", - "end_date", - "medication_orders_section", - "medication_orders", - "section_break_16", - "total_orders", - "column_break_18", - "completed_orders", - "amended_from" - ], - "fields": [ - { - "fieldname": "patient_details_section", - "fieldtype": "Section Break", - "label": "Patient Details" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Naming Series", - "options": "HLC-IMO-.YYYY.-" - }, - { - "fieldname": "patient_encounter", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Patient Encounter", - "options": "Patient Encounter" - }, - { - "fetch_from": "patient_encounter.patient", - "fieldname": "patient", - "fieldtype": "Link", - "label": "Patient", - "options": "Patient", - "read_only_depends_on": "patient_encounter", - "reqd": 1 - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "label": "Patient Age", - "read_only": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1, - "reqd": 1 - }, - { - "fetch_from": "patient_encounter.practitioner", - "fieldname": "practitioner", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "read_only_depends_on": "patient_encounter" - }, - { - "fieldname": "start_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Start Date", - "reqd": 1 - }, - { - "fieldname": "end_date", - "fieldtype": "Date", - "label": "End Date", - "read_only": 1 - }, - { - "depends_on": "eval: doc.patient && doc.start_date", - "fieldname": "medication_orders_section", - "fieldtype": "Section Break", - "label": "Medication Orders" - }, - { - "fieldname": "medication_orders", - "fieldtype": "Table", - "label": "Medication Orders", - "options": "Inpatient Medication Order Entry" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "reqd": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Inpatient Medication Order", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "options": "\nDraft\nSubmitted\nPending\nIn Process\nCompleted\nCancelled", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_16", - "fieldtype": "Section Break", - "label": "Other Details" - }, - { - "fieldname": "total_orders", - "fieldtype": "Float", - "label": "Total Orders", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "fieldname": "completed_orders", - "fieldtype": "Float", - "label": "Completed Orders", - "no_copy": 1, - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "is_submittable": 1, - "links": [], - "modified": "2020-09-30 21:53:27.128591", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Medication Order", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "search_fields": "patient_encounter, patient", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py deleted file mode 100644 index 2e6d73208f..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- 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 import _ -from frappe.model.document import Document -from frappe.utils import cstr - -from erpnext.healthcare.doctype.patient_encounter.patient_encounter import get_prescription_dates - - -class InpatientMedicationOrder(Document): - def validate(self): - self.validate_inpatient() - self.validate_duplicate() - self.set_total_orders() - self.set_status() - - def on_submit(self): - self.validate_inpatient() - self.set_status() - - def on_cancel(self): - self.set_status() - - def validate_inpatient(self): - if not self.inpatient_record: - frappe.throw(_('No Inpatient Record found against patient {0}').format(self.patient)) - - def validate_duplicate(self): - existing_mo = frappe.db.exists('Inpatient Medication Order', { - 'patient_encounter': self.patient_encounter, - 'docstatus': ('!=', 2), - 'name': ('!=', self.name) - }) - if existing_mo: - frappe.throw(_('An Inpatient Medication Order {0} against Patient Encounter {1} already exists.').format( - existing_mo, self.patient_encounter), frappe.DuplicateEntryError) - - def set_total_orders(self): - self.db_set('total_orders', len(self.medication_orders)) - - def set_status(self): - status = { - "0": "Draft", - "1": "Submitted", - "2": "Cancelled" - }[cstr(self.docstatus or 0)] - - if self.docstatus == 1: - if not self.completed_orders: - status = 'Pending' - elif self.completed_orders < self.total_orders: - status = 'In Process' - else: - status = 'Completed' - - self.db_set('status', status) - - @frappe.whitelist() - def add_order_entries(self, order): - if order.get('drug_code'): - dosage = frappe.get_doc('Prescription Dosage', order.get('dosage')) - dates = get_prescription_dates(order.get('period'), self.start_date) - for date in dates: - for dose in dosage.dosage_strength: - entry = self.append('medication_orders') - entry.drug = order.get('drug_code') - entry.drug_name = frappe.db.get_value('Item', order.get('drug_code'), 'item_name') - entry.dosage = dose.strength - entry.dosage_form = order.get('dosage_form') - entry.date = date - entry.time = dose.strength_time - self.end_date = dates[-1] - return diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js deleted file mode 100644 index 1c318768ea..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order_list.js +++ /dev/null @@ -1,16 +0,0 @@ -frappe.listview_settings['Inpatient Medication Order'] = { - add_fields: ["status"], - filters: [["status", "!=", "Cancelled"]], - get_indicator: function(doc) { - if (doc.status === "Pending") { - return [__("Pending"), "orange", "status,=,Pending"]; - - } else if (doc.status === "In Process") { - return [__("In Process"), "blue", "status,=,In Process"]; - - } else if (doc.status === "Completed") { - return [__("Completed"), "green", "status,=,Completed"]; - - } - } -}; diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py deleted file mode 100644 index 477f8c9761..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import add_days, getdate, now_datetime - -from erpnext.healthcare.doctype.inpatient_record.inpatient_record import ( - admit_patient, - discharge_patient, - schedule_discharge, -) -from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import ( - create_inpatient, - create_patient, - get_healthcare_service_unit, - mark_invoiced_inpatient_occupancy, -) - - -class TestInpatientMedicationOrder(unittest.TestCase): - def setUp(self): - frappe.db.sql("""delete from `tabInpatient Record`""") - self.patient = create_patient() - - # Admit - ip_record = create_inpatient(self.patient) - ip_record.expected_length_of_stay = 0 - ip_record.save() - ip_record.reload() - service_unit = get_healthcare_service_unit() - admit_patient(ip_record, service_unit, now_datetime()) - self.ip_record = ip_record - - def test_order_creation(self): - ipmo = create_ipmo(self.patient) - ipmo.submit() - ipmo.reload() - - # 3 dosages per day for 2 days - self.assertEqual(len(ipmo.medication_orders), 6) - self.assertEqual(ipmo.medication_orders[0].date, add_days(getdate(), -1)) - - prescription_dosage = frappe.get_doc('Prescription Dosage', '1-1-1') - for i in range(len(prescription_dosage.dosage_strength)): - self.assertEqual(ipmo.medication_orders[i].time, prescription_dosage.dosage_strength[i].strength_time) - - self.assertEqual(ipmo.medication_orders[3].date, getdate()) - - def test_inpatient_validation(self): - # Discharge - schedule_discharge(frappe.as_json({'patient': self.patient})) - - self.ip_record.reload() - mark_invoiced_inpatient_occupancy(self.ip_record) - - self.ip_record.reload() - discharge_patient(self.ip_record) - - ipmo = create_ipmo(self.patient) - # inpatient validation - self.assertRaises(frappe.ValidationError, ipmo.insert) - - def test_status(self): - ipmo = create_ipmo(self.patient) - ipmo.submit() - ipmo.reload() - - self.assertEqual(ipmo.status, 'Pending') - - filters = frappe._dict(from_date=add_days(getdate(), -1), to_date=add_days(getdate(), -1), from_time='', to_time='') - ipme = create_ipme(filters) - ipme.submit() - ipmo.reload() - self.assertEqual(ipmo.status, 'In Process') - - filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='') - ipme = create_ipme(filters) - ipme.submit() - ipmo.reload() - self.assertEqual(ipmo.status, 'Completed') - - def tearDown(self): - if frappe.db.get_value('Patient', self.patient, 'inpatient_record'): - # cleanup - Discharge - schedule_discharge(frappe.as_json({'patient': self.patient})) - self.ip_record.reload() - mark_invoiced_inpatient_occupancy(self.ip_record) - - self.ip_record.reload() - discharge_patient(self.ip_record) - - for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]: - frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype)) - -def create_dosage_form(): - if not frappe.db.exists('Dosage Form', 'Tablet'): - frappe.get_doc({ - 'doctype': 'Dosage Form', - 'dosage_form': 'Tablet' - }).insert() - -def create_drug(item=None): - if not item: - item = 'Dextromethorphan' - drug = frappe.db.exists('Item', {'item_code': 'Dextromethorphan'}) - if not drug: - drug = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': 'Dextromethorphan', - 'item_name': 'Dextromethorphan', - 'item_group': 'Products', - 'stock_uom': 'Nos', - 'is_stock_item': 1, - 'valuation_rate': 50, - 'opening_stock': 20 - }).insert() - -def get_orders(): - create_dosage_form() - create_drug() - return { - 'drug_code': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': '1-1-1', - 'dosage_form': 'Tablet', - 'period': '2 Day' - } - -def create_ipmo(patient): - orders = get_orders() - ipmo = frappe.new_doc('Inpatient Medication Order') - ipmo.patient = patient - ipmo.company = '_Test Company' - ipmo.start_date = add_days(getdate(), -1) - ipmo.add_order_entries(orders) - - return ipmo - -def create_ipme(filters, update_stock=0): - ipme = frappe.new_doc('Inpatient Medication Entry') - ipme.company = '_Test Company' - ipme.posting_date = getdate() - ipme.update_stock = update_stock - if update_stock: - ipme.warehouse = 'Stores - _TC' - for key, value in filters.items(): - ipme.set(key, value) - ipme = ipme.get_medication_orders() - - return ipme diff --git a/erpnext/healthcare/doctype/inpatient_medication_order_entry/__init__.py b/erpnext/healthcare/doctype/inpatient_medication_order_entry/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json b/erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json deleted file mode 100644 index 72999a908e..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "actions": [], - "creation": "2020-09-14 21:51:30.259164", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "drug", - "drug_name", - "dosage", - "dosage_form", - "instructions", - "column_break_4", - "date", - "time", - "is_completed" - ], - "fields": [ - { - "fieldname": "drug", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Drug", - "options": "Item", - "reqd": 1 - }, - { - "fetch_from": "drug.item_name", - "fieldname": "drug_name", - "fieldtype": "Data", - "label": "Drug Name", - "read_only": 1 - }, - { - "fieldname": "dosage", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Dosage", - "reqd": 1 - }, - { - "fieldname": "dosage_form", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Dosage Form", - "options": "Dosage Form", - "reqd": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Date", - "reqd": 1 - }, - { - "fieldname": "time", - "fieldtype": "Time", - "in_list_view": 1, - "label": "Time", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "is_completed", - "fieldtype": "Check", - "label": "Is Order Completed", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "instructions", - "fieldtype": "Small Text", - "label": "Instructions" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2020-09-30 14:03:26.755925", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Medication Order Entry", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py b/erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py deleted file mode 100644 index 15800ad15f..0000000000 --- a/erpnext/healthcare/doctype/inpatient_medication_order_entry/inpatient_medication_order_entry.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 InpatientMedicationOrderEntry(Document): - pass diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/__init__.py b/erpnext/healthcare/doctype/inpatient_occupancy/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json deleted file mode 100644 index 3fa98b6678..0000000000 --- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "actions": [], - "creation": "2018-07-12 12:07:36.932333", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "service_unit", - "check_in", - "left", - "check_out", - "invoiced" - ], - "fields": [ - { - "fieldname": "service_unit", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Healthcare Service Unit", - "options": "Healthcare Service Unit", - "reqd": 1 - }, - { - "fieldname": "check_in", - "fieldtype": "Datetime", - "in_list_view": 1, - "label": "Check In" - }, - { - "default": "0", - "fieldname": "left", - "fieldtype": "Check", - "label": "Left", - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "check_out", - "fieldtype": "Datetime", - "label": "Check Out" - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-03-18 15:08:54.634132", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Occupancy", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py b/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py deleted file mode 100644 index c8f3296d0d..0000000000 --- a/erpnext/healthcare/doctype/inpatient_occupancy/inpatient_occupancy.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class InpatientOccupancy(Document): - pass diff --git a/erpnext/healthcare/doctype/inpatient_record/__init__.py b/erpnext/healthcare/doctype/inpatient_record/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js deleted file mode 100644 index 60f0f9d56d..0000000000 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.js +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Inpatient Record', { - setup: function(frm) { - frm.get_field('drug_prescription').grid.editable_fields = [ - {fieldname: 'drug_code', columns: 2}, - {fieldname: 'drug_name', columns: 2}, - {fieldname: 'dosage', columns: 2}, - {fieldname: 'period', columns: 2} - ]; - }, - refresh: function(frm) { - if (!frm.doc.__islocal && (frm.doc.status == 'Admission Scheduled' || frm.doc.status == 'Admitted')) { - frm.enable_save(); - } else { - frm.disable_save(); - } - - if (!frm.doc.__islocal && frm.doc.status == 'Admission Scheduled') { - frm.add_custom_button(__('Admit'), function() { - admit_patient_dialog(frm); - } ); - } - - if (!frm.doc.__islocal && frm.doc.status == 'Discharge Scheduled') { - frm.add_custom_button(__('Discharge'), function() { - discharge_patient(frm); - } ); - } - if (!frm.doc.__islocal && frm.doc.status != 'Admitted') { - frm.disable_save(); - frm.set_df_property('btn_transfer', 'hidden', 1); - } else { - frm.set_df_property('btn_transfer', 'hidden', 0); - } - }, - btn_transfer: function(frm) { - transfer_patient_dialog(frm); - } -}); - -let discharge_patient = function(frm) { - frappe.call({ - doc: frm.doc, - method: 'discharge', - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); - } - }, - freeze: true, - freeze_message: __('Processing Inpatient Discharge') - }); -}; - -let admit_patient_dialog = function(frm) { - let dialog = new frappe.ui.Dialog({ - title: 'Admit Patient', - width: 100, - fields: [ - {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', - options: 'Healthcare Service Unit Type', default: frm.doc.admission_service_unit_type - }, - {fieldtype: 'Link', label: 'Service Unit', fieldname: 'service_unit', - options: 'Healthcare Service Unit', reqd: 1 - }, - {fieldtype: 'Datetime', label: 'Admission Datetime', fieldname: 'check_in', - reqd: 1, default: frappe.datetime.now_datetime() - }, - {fieldtype: 'Date', label: 'Expected Discharge', fieldname: 'expected_discharge', - default: frm.doc.expected_length_of_stay ? frappe.datetime.add_days(frappe.datetime.now_datetime(), frm.doc.expected_length_of_stay) : '' - } - ], - primary_action_label: __('Admit'), - primary_action : function(){ - let service_unit = dialog.get_value('service_unit'); - let check_in = dialog.get_value('check_in'); - let expected_discharge = null; - if (dialog.get_value('expected_discharge')) { - expected_discharge = dialog.get_value('expected_discharge'); - } - if (!service_unit && !check_in) { - return; - } - frappe.call({ - doc: frm.doc, - method: 'admit', - args:{ - 'service_unit': service_unit, - 'check_in': check_in, - 'expected_discharge': expected_discharge - }, - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); - } - }, - freeze: true, - freeze_message: __('Processing Patient Admission') - }); - frm.refresh_fields(); - dialog.hide(); - } - }); - - dialog.fields_dict['service_unit_type'].get_query = function() { - return { - filters: { - 'inpatient_occupancy': 1, - 'allow_appointments': 0 - } - }; - }; - dialog.fields_dict['service_unit'].get_query = function() { - return { - filters: { - 'is_group': 0, - 'company': frm.doc.company, - 'service_unit_type': dialog.get_value('service_unit_type'), - 'occupancy_status' : 'Vacant' - } - }; - }; - - dialog.show(); -}; - -let transfer_patient_dialog = function(frm) { - let dialog = new frappe.ui.Dialog({ - title: 'Transfer Patient', - width: 100, - fields: [ - {fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1}, - {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, - {fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1}, - {fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1, default: frappe.datetime.now_datetime()} - ], - primary_action_label: __('Transfer'), - primary_action : function() { - let service_unit = null; - let check_in = dialog.get_value('check_in'); - let leave_from = null; - if(dialog.get_value('leave_from')){ - leave_from = dialog.get_value('leave_from'); - } - if(dialog.get_value('service_unit')){ - service_unit = dialog.get_value('service_unit'); - } - if(check_in > frappe.datetime.now_datetime()){ - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Check-in time cannot be greater than the current time'), - indicator: 'red' - }); - return; - } - frappe.call({ - doc: frm.doc, - method: 'transfer', - args:{ - 'service_unit': service_unit, - 'check_in': check_in, - 'leave_from': leave_from - }, - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); - } - }, - freeze: true, - freeze_message: __('Process Transfer') - }); - frm.refresh_fields(); - dialog.hide(); - } - }); - - dialog.fields_dict['leave_from'].get_query = function(){ - return { - query : 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.get_leave_from', - filters: {docname:frm.doc.name} - }; - }; - dialog.fields_dict['service_unit_type'].get_query = function(){ - return { - filters: { - 'inpatient_occupancy': 1, - 'allow_appointments': 0 - } - }; - }; - dialog.fields_dict['service_unit'].get_query = function(){ - return { - filters: { - 'is_group': 0, - 'service_unit_type': dialog.get_value('service_unit_type'), - 'occupancy_status' : 'Vacant' - } - }; - }; - - dialog.show(); - - let not_left_service_unit = null; - for (let inpatient_occupancy in frm.doc.inpatient_occupancies) { - if (frm.doc.inpatient_occupancies[inpatient_occupancy].left != 1) { - not_left_service_unit = frm.doc.inpatient_occupancies[inpatient_occupancy].service_unit; - } - } - dialog.set_values({ - 'leave_from': not_left_service_unit - }); -}; diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json deleted file mode 100644 index 0e1c2ba766..0000000000 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json +++ /dev/null @@ -1,507 +0,0 @@ -{ - "actions": [], - "autoname": "naming_series:", - "creation": "2018-07-11 17:48:51.404139", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "section_break_1", - "naming_series", - "patient", - "patient_name", - "gender", - "blood_group", - "dob", - "mobile", - "email", - "phone", - "column_break_8", - "company", - "status", - "scheduled_date", - "admitted_datetime", - "expected_discharge", - "references", - "admission_encounter", - "admission_practitioner", - "medical_department", - "admission_ordered_for", - "expected_length_of_stay", - "admission_service_unit_type", - "cb_admission", - "primary_practitioner", - "secondary_practitioner", - "admission_instruction", - "encounter_details_section", - "chief_complaint", - "column_break_29", - "diagnosis", - "medication_section", - "drug_prescription", - "investigations_section", - "lab_test_prescription", - "procedures_section", - "procedure_prescription", - "rehabilitation_section", - "therapy_plan", - "therapies", - "sb_inpatient_occupancy", - "inpatient_occupancies", - "btn_transfer", - "sb_discharge_details", - "discharge_ordered_date", - "discharge_practitioner", - "discharge_encounter", - "discharge_datetime", - "cb_discharge", - "discharge_instructions", - "followup_date", - "sb_discharge_note", - "discharge_note" - ], - "fields": [ - { - "fieldname": "section_break_1", - "fieldtype": "Section Break" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 1, - "label": "Series", - "options": "HLC-INP-.YYYY.-" - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1, - "set_only_once": 1 - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fetch_from": "patient.sex", - "fieldname": "gender", - "fieldtype": "Link", - "label": "Gender", - "options": "Gender", - "read_only": 1 - }, - { - "fetch_from": "patient.blood_group", - "fieldname": "blood_group", - "fieldtype": "Select", - "label": "Blood Group", - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "read_only": 1 - }, - { - "fetch_from": "patient.dob", - "fieldname": "dob", - "fieldtype": "Date", - "label": "Date of birth", - "read_only": 1 - }, - { - "fetch_from": "patient.mobile", - "fieldname": "mobile", - "fieldtype": "Data", - "label": "Mobile", - "read_only": 1 - }, - { - "fetch_from": "patient.email", - "fieldname": "email", - "fieldtype": "Data", - "label": "Email", - "options": "Email", - "read_only": 1 - }, - { - "fetch_from": "patient.phone", - "fieldname": "phone", - "fieldtype": "Data", - "label": "Phone", - "read_only": 1 - }, - { - "fieldname": "medical_department", - "fieldtype": "Link", - "label": "Medical Department", - "options": "Medical Department", - "set_only_once": 1 - }, - { - "fieldname": "primary_practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner (Primary)", - "options": "Healthcare Practitioner" - }, - { - "fieldname": "secondary_practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner (Secondary)", - "options": "Healthcare Practitioner" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "default": "Admission Scheduled", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Status", - "options": "Admission Scheduled\nAdmitted\nDischarge Scheduled\nDischarged", - "read_only": 1 - }, - { - "default": "Today", - "fieldname": "scheduled_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Admission Schedule Date", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "admission_ordered_for", - "fieldtype": "Date", - "label": "Admission Ordered For", - "read_only": 1 - }, - { - "fieldname": "admitted_datetime", - "fieldtype": "Datetime", - "in_list_view": 1, - "label": "Admitted Datetime", - "permlevel": 2 - }, - { - "depends_on": "eval:(doc.expected_length_of_stay > 0)", - "fieldname": "expected_length_of_stay", - "fieldtype": "Int", - "label": "Expected Length of Stay", - "set_only_once": 1 - }, - { - "fieldname": "expected_discharge", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Expected Discharge", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "references", - "fieldtype": "Section Break", - "label": "Admission Order Details" - }, - { - "fieldname": "cb_admission", - "fieldtype": "Column Break" - }, - { - "fieldname": "admission_practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "read_only": 1 - }, - { - "fieldname": "admission_encounter", - "fieldtype": "Link", - "label": "Patient Encounter", - "options": "Patient Encounter", - "read_only": 1 - }, - { - "fieldname": "chief_complaint", - "fieldtype": "Table MultiSelect", - "label": "Chief Complaint", - "options": "Patient Encounter Symptom", - "permlevel": 1 - }, - { - "fieldname": "admission_instruction", - "fieldtype": "Small Text", - "label": "Admission Instruction", - "set_only_once": 1 - }, - { - "fieldname": "cb_discharge", - "fieldtype": "Column Break" - }, - { - "fieldname": "discharge_practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "read_only": 1 - }, - { - "fieldname": "discharge_encounter", - "fieldtype": "Link", - "label": "Patient Encounter", - "options": "Patient Encounter", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "medication_section", - "fieldtype": "Section Break", - "label": "Medications", - "permlevel": 1 - }, - { - "fieldname": "drug_prescription", - "fieldtype": "Table", - "options": "Drug Prescription", - "permlevel": 1 - }, - { - "collapsible": 1, - "fieldname": "investigations_section", - "fieldtype": "Section Break", - "label": "Investigations", - "permlevel": 1 - }, - { - "fieldname": "lab_test_prescription", - "fieldtype": "Table", - "options": "Lab Prescription", - "permlevel": 1 - }, - { - "collapsible": 1, - "fieldname": "procedures_section", - "fieldtype": "Section Break", - "label": "Procedures", - "permlevel": 1 - }, - { - "fieldname": "procedure_prescription", - "fieldtype": "Table", - "options": "Procedure Prescription", - "permlevel": 1 - }, - { - "depends_on": "eval:(doc.status != \"Admission Scheduled\")", - "fieldname": "sb_inpatient_occupancy", - "fieldtype": "Section Break", - "label": "Inpatient Occupancy" - }, - { - "fieldname": "admission_service_unit_type", - "fieldtype": "Link", - "label": "Admission Service Unit Type", - "options": "Healthcare Service Unit Type", - "read_only": 1 - }, - { - "fieldname": "inpatient_occupancies", - "fieldtype": "Table", - "options": "Inpatient Occupancy", - "permlevel": 2 - }, - { - "fieldname": "btn_transfer", - "fieldtype": "Button", - "label": "Transfer" - }, - { - "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")", - "fieldname": "sb_discharge_note", - "fieldtype": "Section Break", - "label": "Discharge Notes" - }, - { - "fieldname": "discharge_note", - "fieldtype": "Text Editor", - "permlevel": 1 - }, - { - "fetch_from": "admission_encounter.company", - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "options": "Company" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:(doc.status == \"Admitted\")", - "fieldname": "encounter_details_section", - "fieldtype": "Section Break", - "label": "Encounter Impression", - "permlevel": 1 - }, - { - "fieldname": "column_break_29", - "fieldtype": "Column Break" - }, - { - "fieldname": "diagnosis", - "fieldtype": "Table MultiSelect", - "label": "Diagnosis", - "options": "Patient Encounter Diagnosis", - "permlevel": 1 - }, - { - "fieldname": "followup_date", - "fieldtype": "Date", - "label": "Follow Up Date" - }, - { - "collapsible": 1, - "depends_on": "eval:(doc.status == \"Discharge Scheduled\" || doc.status == \"Discharged\")", - "fieldname": "sb_discharge_details", - "fieldtype": "Section Break", - "label": "Discharge Detials" - }, - { - "fieldname": "discharge_instructions", - "fieldtype": "Small Text", - "label": "Discharge Instructions" - }, - { - "fieldname": "discharge_ordered_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Discharge Ordered Date", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "rehabilitation_section", - "fieldtype": "Section Break", - "label": "Rehabilitation", - "permlevel": 1 - }, - { - "fieldname": "therapy_plan", - "fieldtype": "Link", - "hidden": 1, - "label": "Therapy Plan", - "options": "Therapy Plan", - "permlevel": 1, - "read_only": 1 - }, - { - "fieldname": "therapies", - "fieldtype": "Table", - "options": "Therapy Plan Detail", - "permlevel": 1 - }, - { - "fieldname": "discharge_datetime", - "fieldtype": "Datetime", - "label": "Discharge Date", - "permlevel": 2 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-03-18 15:59:17.318988", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Record", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1, - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "role": "Physician", - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "report": 1, - "role": "Nursing User" - }, - { - "email": 1, - "export": 1, - "permlevel": 2, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "permlevel": 2, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1 - }, - { - "email": 1, - "export": 1, - "permlevel": 2, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py deleted file mode 100644 index 2e26677db5..0000000000 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ /dev/null @@ -1,291 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe import _ -from frappe.desk.reportview import get_match_cond -from frappe.model.document import Document -from frappe.utils import get_datetime, get_link_to_form, getdate, now_datetime, today - - -class InpatientRecord(Document): - def after_insert(self): - frappe.db.set_value('Patient', self.patient, 'inpatient_record', self.name) - frappe.db.set_value('Patient', self.patient, 'inpatient_status', self.status) - - if self.admission_encounter: # Update encounter - frappe.db.set_value('Patient Encounter', self.admission_encounter, 'inpatient_record', self.name) - frappe.db.set_value('Patient Encounter', self.admission_encounter, 'inpatient_status', self.status) - - def validate(self): - self.validate_dates() - self.validate_already_scheduled_or_admitted() - if self.status == "Discharged": - frappe.db.set_value("Patient", self.patient, "inpatient_status", None) - frappe.db.set_value("Patient", self.patient, "inpatient_record", None) - - def validate_dates(self): - if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ - (getdate(self.discharge_ordered_date) < getdate(self.scheduled_date)): - frappe.throw(_('Expected and Discharge dates cannot be less than Admission Schedule date')) - - for entry in self.inpatient_occupancies: - if entry.check_in and entry.check_out and \ - get_datetime(entry.check_in) > get_datetime(entry.check_out): - frappe.throw(_('Row #{0}: Check Out datetime cannot be less than Check In datetime').format(entry.idx)) - - def validate_already_scheduled_or_admitted(self): - query = """ - select name, status - from `tabInpatient Record` - where (status = 'Admitted' or status = 'Admission Scheduled') - and name != %(name)s and patient = %(patient)s - """ - - ip_record = frappe.db.sql(query,{ - "name": self.name, - "patient": self.patient - }, as_dict = 1) - - if ip_record: - msg = _(("Already {0} Patient {1} with Inpatient Record ").format(ip_record[0].status, self.patient) \ - + """ {0}""".format(ip_record[0].name)) - frappe.throw(msg) - - @frappe.whitelist() - def admit(self, service_unit, check_in, expected_discharge=None): - admit_patient(self, service_unit, check_in, expected_discharge) - - @frappe.whitelist() - def discharge(self): - discharge_patient(self) - - @frappe.whitelist() - def transfer(self, service_unit, check_in, leave_from): - if leave_from: - patient_leave_service_unit(self, check_in, leave_from) - if service_unit: - transfer_patient(self, service_unit, check_in) - - -@frappe.whitelist() -def schedule_inpatient(args): - admission_order = json.loads(args) # admission order via Encounter - if not admission_order or not admission_order['patient'] or not admission_order['admission_encounter']: - frappe.throw(_('Missing required details, did not create Inpatient Record')) - - inpatient_record = frappe.new_doc('Inpatient Record') - - # Admission order details - set_details_from_ip_order(inpatient_record, admission_order) - - # Patient details - patient = frappe.get_doc('Patient', admission_order['patient']) - inpatient_record.patient = patient.name - inpatient_record.patient_name = patient.patient_name - inpatient_record.gender = patient.sex - inpatient_record.blood_group = patient.blood_group - inpatient_record.dob = patient.dob - inpatient_record.mobile = patient.mobile - inpatient_record.email = patient.email - inpatient_record.phone = patient.phone - inpatient_record.scheduled_date = today() - - # Set encounter detials - encounter = frappe.get_doc('Patient Encounter', admission_order['admission_encounter']) - if encounter and encounter.symptoms: # Symptoms - set_ip_child_records(inpatient_record, 'chief_complaint', encounter.symptoms) - - if encounter and encounter.diagnosis: # Diagnosis - set_ip_child_records(inpatient_record, 'diagnosis', encounter.diagnosis) - - if encounter and encounter.drug_prescription: # Medication - set_ip_child_records(inpatient_record, 'drug_prescription', encounter.drug_prescription) - - if encounter and encounter.lab_test_prescription: # Lab Tests - set_ip_child_records(inpatient_record, 'lab_test_prescription', encounter.lab_test_prescription) - - if encounter and encounter.procedure_prescription: # Procedure Prescription - set_ip_child_records(inpatient_record, 'procedure_prescription', encounter.procedure_prescription) - - if encounter and encounter.therapies: # Therapies - inpatient_record.therapy_plan = encounter.therapy_plan - set_ip_child_records(inpatient_record, 'therapies', encounter.therapies) - - inpatient_record.status = 'Admission Scheduled' - inpatient_record.save(ignore_permissions = True) - - -@frappe.whitelist() -def schedule_discharge(args): - discharge_order = json.loads(args) - inpatient_record_id = frappe.db.get_value('Patient', discharge_order['patient'], 'inpatient_record') - if inpatient_record_id: - inpatient_record = frappe.get_doc('Inpatient Record', inpatient_record_id) - check_out_inpatient(inpatient_record) - set_details_from_ip_order(inpatient_record, discharge_order) - inpatient_record.status = 'Discharge Scheduled' - inpatient_record.save(ignore_permissions = True) - frappe.db.set_value('Patient', discharge_order['patient'], 'inpatient_status', inpatient_record.status) - frappe.db.set_value('Patient Encounter', inpatient_record.discharge_encounter, 'inpatient_status', inpatient_record.status) - - -def set_details_from_ip_order(inpatient_record, ip_order): - for key in ip_order: - inpatient_record.set(key, ip_order[key]) - - -def set_ip_child_records(inpatient_record, inpatient_record_child, encounter_child): - for item in encounter_child: - table = inpatient_record.append(inpatient_record_child) - for df in table.meta.get('fields'): - table.set(df.fieldname, item.get(df.fieldname)) - - -def check_out_inpatient(inpatient_record): - if inpatient_record.inpatient_occupancies: - for inpatient_occupancy in inpatient_record.inpatient_occupancies: - if inpatient_occupancy.left != 1: - inpatient_occupancy.left = True - inpatient_occupancy.check_out = now_datetime() - frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant") - - -def discharge_patient(inpatient_record): - validate_inpatient_invoicing(inpatient_record) - inpatient_record.discharge_datetime = now_datetime() - inpatient_record.status = "Discharged" - - inpatient_record.save(ignore_permissions = True) - - -def validate_inpatient_invoicing(inpatient_record): - if frappe.db.get_single_value("Healthcare Settings", "allow_discharge_despite_unbilled_services"): - return - - pending_invoices = get_pending_invoices(inpatient_record) - - if pending_invoices: - message = _("Cannot mark Inpatient Record as Discharged since there are unbilled services. ") - - formatted_doc_rows = '' - - for doctype, docnames in pending_invoices.items(): - formatted_doc_rows += """ - {0} - {1} - """.format(doctype, docnames) - - message += """ - - - - - - {2} -
{0}{1}
- """.format(_("Healthcare Service"), _("Documents"), formatted_doc_rows) - - frappe.throw(message, title=_("Unbilled Services"), is_minimizable=True, wide=True) - - -def get_pending_invoices(inpatient_record): - pending_invoices = {} - if inpatient_record.inpatient_occupancies: - service_unit_names = False - for inpatient_occupancy in inpatient_record.inpatient_occupancies: - if not inpatient_occupancy.invoiced: - if service_unit_names: - service_unit_names += ", " + inpatient_occupancy.service_unit - else: - service_unit_names = inpatient_occupancy.service_unit - if service_unit_names: - pending_invoices["Inpatient Occupancy"] = service_unit_names - - docs = ["Patient Appointment", "Patient Encounter", "Lab Test", "Clinical Procedure"] - - for doc in docs: - doc_name_list = get_unbilled_inpatient_docs(doc, inpatient_record) - if doc_name_list: - pending_invoices = get_pending_doc(doc, doc_name_list, pending_invoices) - - return pending_invoices - - -def get_pending_doc(doc, doc_name_list, pending_invoices): - if doc_name_list: - doc_ids = False - for doc_name in doc_name_list: - doc_link = get_link_to_form(doc, doc_name.name) - if doc_ids: - doc_ids += ", " + doc_link - else: - doc_ids = doc_link - if doc_ids: - pending_invoices[doc] = doc_ids - - return pending_invoices - - -def get_unbilled_inpatient_docs(doc, inpatient_record): - return frappe.db.get_list(doc, filters = {'patient': inpatient_record.patient, - 'inpatient_record': inpatient_record.name, 'docstatus': 1, 'invoiced': 0}) - - -def admit_patient(inpatient_record, service_unit, check_in, expected_discharge=None): - inpatient_record.admitted_datetime = check_in - inpatient_record.status = 'Admitted' - inpatient_record.expected_discharge = expected_discharge - - inpatient_record.set('inpatient_occupancies', []) - transfer_patient(inpatient_record, service_unit, check_in) - - frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_status', 'Admitted') - frappe.db.set_value('Patient', inpatient_record.patient, 'inpatient_record', inpatient_record.name) - - -def transfer_patient(inpatient_record, service_unit, check_in): - item_line = inpatient_record.append('inpatient_occupancies', {}) - item_line.service_unit = service_unit - item_line.check_in = check_in - - inpatient_record.save(ignore_permissions = True) - - frappe.db.set_value("Healthcare Service Unit", service_unit, "occupancy_status", "Occupied") - - -def patient_leave_service_unit(inpatient_record, check_out, leave_from): - if inpatient_record.inpatient_occupancies: - for inpatient_occupancy in inpatient_record.inpatient_occupancies: - if inpatient_occupancy.left != 1 and inpatient_occupancy.service_unit == leave_from: - inpatient_occupancy.left = True - inpatient_occupancy.check_out = check_out - frappe.db.set_value("Healthcare Service Unit", inpatient_occupancy.service_unit, "occupancy_status", "Vacant") - inpatient_record.save(ignore_permissions = True) - - -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_leave_from(doctype, txt, searchfield, start, page_len, filters): - docname = filters['docname'] - - query = '''select io.service_unit - from `tabInpatient Occupancy` io, `tabInpatient Record` ir - where io.parent = '{docname}' and io.parentfield = 'inpatient_occupancies' - and io.left!=1 and io.parent = ir.name''' - - return frappe.db.sql(query.format(**{ - "docname": docname, - "searchfield": searchfield, - "mcond": get_match_cond(doctype) - }), { - 'txt': "%%%s%%" % txt, - '_txt': txt.replace("%", ""), - 'start': start, - 'page_len': page_len - }) diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py deleted file mode 100644 index 0f4adce4ed..0000000000 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record_dashboard.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'inpatient_record', - 'transactions': [ - { - 'label': _('Appointments and Encounters'), - 'items': ['Patient Appointment', 'Patient Encounter'] - }, - { - 'label': _('Lab Tests and Vital Signs'), - 'items': ['Lab Test', 'Clinical Procedure', 'Sample Collection', 'Vital Signs'] - } - ] - } diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py deleted file mode 100644 index 58934e6a03..0000000000 --- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import now_datetime, today -from frappe.utils.make_random import get_random - -from erpnext.healthcare.doctype.inpatient_record.inpatient_record import ( - admit_patient, - discharge_patient, - schedule_discharge, -) -from erpnext.healthcare.doctype.lab_test.test_lab_test import create_patient_encounter -from erpnext.healthcare.utils import get_encounters_to_invoice - - -class TestInpatientRecord(unittest.TestCase): - def test_admit_and_discharge(self): - frappe.db.sql("""delete from `tabInpatient Record`""") - patient = create_patient() - # Schedule Admission - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save(ignore_permissions = True) - self.assertEqual(ip_record.name, frappe.db.get_value("Patient", patient, "inpatient_record")) - self.assertEqual(ip_record.status, frappe.db.get_value("Patient", patient, "inpatient_status")) - - # Admit - service_unit = get_healthcare_service_unit() - admit_patient(ip_record, service_unit, now_datetime()) - self.assertEqual("Admitted", frappe.db.get_value("Patient", patient, "inpatient_status")) - self.assertEqual("Occupied", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status")) - - # Discharge - schedule_discharge(frappe.as_json({'patient': patient})) - self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status")) - - ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) - # Validate Pending Invoices - self.assertRaises(frappe.ValidationError, ip_record.discharge) - mark_invoiced_inpatient_occupancy(ip_record1) - - discharge_patient(ip_record1) - - self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) - self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) - - def test_allow_discharge_despite_unbilled_services(self): - frappe.db.sql("""delete from `tabInpatient Record`""") - setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=1) - patient = create_patient() - # Schedule Admission - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save(ignore_permissions = True) - - # Admit - service_unit = get_healthcare_service_unit() - admit_patient(ip_record, service_unit, now_datetime()) - - # Discharge - schedule_discharge(frappe.as_json({"patient": patient})) - self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status")) - - ip_record = frappe.get_doc("Inpatient Record", ip_record.name) - # Should not validate Pending Invoices - ip_record.discharge() - - self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_record")) - self.assertEqual(None, frappe.db.get_value("Patient", patient, "inpatient_status")) - - setup_inpatient_settings(key="allow_discharge_despite_unbilled_services", value=0) - - def test_do_not_bill_patient_encounters_for_inpatients(self): - frappe.db.sql("""delete from `tabInpatient Record`""") - setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=1) - patient = create_patient() - # Schedule Admission - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save(ignore_permissions = True) - - # Admit - service_unit = get_healthcare_service_unit() - admit_patient(ip_record, service_unit, now_datetime()) - - # Patient Encounter - patient_encounter = create_patient_encounter() - encounters = get_encounters_to_invoice(patient, "_Test Company") - encounter_ids = [entry.reference_name for entry in encounters] - self.assertFalse(patient_encounter.name in encounter_ids) - - # Discharge - schedule_discharge(frappe.as_json({"patient": patient})) - self.assertEqual("Vacant", frappe.db.get_value("Healthcare Service Unit", service_unit, "occupancy_status")) - - ip_record = frappe.get_doc("Inpatient Record", ip_record.name) - mark_invoiced_inpatient_occupancy(ip_record) - discharge_patient(ip_record) - setup_inpatient_settings(key="do_not_bill_inpatient_encounters", value=0) - - def test_validate_overlap_admission(self): - frappe.db.sql("""delete from `tabInpatient Record`""") - patient = create_patient() - - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save(ignore_permissions = True) - ip_record_new = create_inpatient(patient) - ip_record_new.expected_length_of_stay = 0 - self.assertRaises(frappe.ValidationError, ip_record_new.save) - - service_unit = get_healthcare_service_unit() - admit_patient(ip_record, service_unit, now_datetime()) - ip_record_new = create_inpatient(patient) - self.assertRaises(frappe.ValidationError, ip_record_new.save) - frappe.db.sql("""delete from `tabInpatient Record`""") - -def mark_invoiced_inpatient_occupancy(ip_record): - if ip_record.inpatient_occupancies: - for inpatient_occupancy in ip_record.inpatient_occupancies: - inpatient_occupancy.invoiced = 1 - ip_record.save(ignore_permissions = True) - - -def setup_inpatient_settings(key, value): - settings = frappe.get_single("Healthcare Settings") - settings.set(key, value) - settings.save() - - -def create_inpatient(patient): - patient_obj = frappe.get_doc('Patient', patient) - inpatient_record = frappe.new_doc('Inpatient Record') - inpatient_record.patient = patient - inpatient_record.patient_name = patient_obj.patient_name - inpatient_record.gender = patient_obj.sex - inpatient_record.blood_group = patient_obj.blood_group - inpatient_record.dob = patient_obj.dob - inpatient_record.mobile = patient_obj.mobile - inpatient_record.email = patient_obj.email - inpatient_record.phone = patient_obj.phone - inpatient_record.inpatient = "Scheduled" - inpatient_record.scheduled_date = today() - inpatient_record.company = "_Test Company" - return inpatient_record - - -def get_healthcare_service_unit(unit_name=None): - if not unit_name: - service_unit = get_random("Healthcare Service Unit", filters={"inpatient_occupancy": 1, "company": "_Test Company"}) - else: - service_unit = frappe.db.exists("Healthcare Service Unit", {"healthcare_service_unit_name": unit_name}) - - if not service_unit: - service_unit = frappe.new_doc("Healthcare Service Unit") - service_unit.healthcare_service_unit_name = unit_name or "_Test Service Unit Ip Occupancy" - service_unit.company = "_Test Company" - service_unit.service_unit_type = get_service_unit_type() - service_unit.inpatient_occupancy = 1 - service_unit.occupancy_status = "Vacant" - service_unit.is_group = 0 - service_unit_parent_name = frappe.db.exists({ - "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "_Test All Healthcare Service Units", - "is_group": 1 - }) - if not service_unit_parent_name: - parent_service_unit = frappe.new_doc("Healthcare Service Unit") - parent_service_unit.healthcare_service_unit_name = "_Test All Healthcare Service Units" - parent_service_unit.is_group = 1 - parent_service_unit.save(ignore_permissions = True) - service_unit.parent_healthcare_service_unit = parent_service_unit.name - else: - service_unit.parent_healthcare_service_unit = service_unit_parent_name[0][0] - service_unit.save(ignore_permissions = True) - return service_unit.name - return service_unit - - -def get_service_unit_type(): - service_unit_type = get_random("Healthcare Service Unit Type", filters={"inpatient_occupancy": 1}) - - if not service_unit_type: - service_unit_type = frappe.new_doc("Healthcare Service Unit Type") - service_unit_type.service_unit_type = "_Test Service Unit Type Ip Occupancy" - service_unit_type.inpatient_occupancy = 1 - service_unit_type.save(ignore_permissions = True) - return service_unit_type.name - return service_unit_type - - -def create_patient(): - patient = frappe.db.exists('Patient', '_Test IPD Patient') - if not patient: - patient = frappe.new_doc('Patient') - patient.first_name = '_Test IPD Patient' - patient.sex = 'Female' - patient.save(ignore_permissions=True) - patient = patient.name - return patient diff --git a/erpnext/healthcare/doctype/lab_prescription/__init__.py b/erpnext/healthcare/doctype/lab_prescription/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json b/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json deleted file mode 100644 index 0720bb4eec..0000000000 --- a/erpnext/healthcare/doctype/lab_prescription/lab_prescription.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-09-16 16:53:06.882970", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "lab_test_code", - "lab_test_name", - "invoiced", - "column_break_4", - "lab_test_comment", - "lab_test_created" - ], - "fields": [ - { - "fieldname": "lab_test_code", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Lab Test", - "options": "Lab Test Template", - "reqd": 1 - }, - { - "fetch_from": "lab_test_code.lab_test_name", - "fieldname": "lab_test_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Lab Test Name" - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "no_copy": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "lab_test_comment", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Comments" - }, - { - "default": "0", - "fieldname": "lab_test_created", - "fieldtype": "Check", - "hidden": 1, - "label": "Test Created", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1, - "search_index": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-02-26 17:03:00.255560", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Prescription", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_prescription/lab_prescription.py b/erpnext/healthcare/doctype/lab_prescription/lab_prescription.py deleted file mode 100644 index a3f9aa24c4..0000000000 --- a/erpnext/healthcare/doctype/lab_prescription/lab_prescription.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class LabPrescription(Document): - pass diff --git a/erpnext/healthcare/doctype/lab_test/__init__.py b/erpnext/healthcare/doctype/lab_test/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.js b/erpnext/healthcare/doctype/lab_test/lab_test.js deleted file mode 100644 index bb7976ccfa..0000000000 --- a/erpnext/healthcare/doctype/lab_test/lab_test.js +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (c) 2016, ESS and contributors -// For license information, please see license.txt - -cur_frm.cscript.custom_refresh = function (doc) { - cur_frm.toggle_display('sb_sensitivity', doc.sensitivity_toggle); - cur_frm.toggle_display('organisms_section', doc.descriptive_toggle); - cur_frm.toggle_display('sb_descriptive', doc.descriptive_toggle); - cur_frm.toggle_display('sb_normal', doc.normal_toggle); -}; - -frappe.ui.form.on('Lab Test', { - setup: function (frm) { - frm.get_field('normal_test_items').grid.editable_fields = [ - { fieldname: 'lab_test_name', columns: 3 }, - { fieldname: 'lab_test_event', columns: 2 }, - { fieldname: 'result_value', columns: 2 }, - { fieldname: 'lab_test_uom', columns: 1 }, - { fieldname: 'normal_range', columns: 2 } - ]; - frm.get_field('descriptive_test_items').grid.editable_fields = [ - { fieldname: 'lab_test_particulars', columns: 3 }, - { fieldname: 'result_value', columns: 7 } - ]; - }, - refresh: function (frm) { - refresh_field('normal_test_items'); - refresh_field('descriptive_test_items'); - if (frm.doc.__islocal) { - frm.add_custom_button(__('Get from Patient Encounter'), function () { - get_lab_test_prescribed(frm); - }); - } - if (frappe.defaults.get_default('lab_test_approval_required') && frappe.user.has_role('LabTest Approver')) { - if (frm.doc.docstatus === 1 && frm.doc.status !== 'Approved' && frm.doc.status !== 'Rejected') { - frm.add_custom_button(__('Approve'), function () { - status_update(1, frm); - }, __('Actions')); - frm.add_custom_button(__('Reject'), function () { - status_update(0, frm); - }, __('Actions')); - } - } - - if (frm.doc.docstatus === 1 && frm.doc.sms_sent === 0 && frm.doc.status !== 'Rejected' ) { - frm.add_custom_button(__('Send SMS'), function () { - frappe.call({ - method: 'erpnext.healthcare.doctype.healthcare_settings.healthcare_settings.get_sms_text', - args: { doc: frm.doc.name }, - callback: function (r) { - if (!r.exc) { - var emailed = r.message.emailed; - var printed = r.message.printed; - make_dialog(frm, emailed, printed); - } - } - }); - }); - } - - } -}); - -frappe.ui.form.on('Lab Test', 'patient', function (frm) { - if (frm.doc.patient) { - frappe.call({ - 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { patient: frm.doc.patient }, - callback: function (data) { - var age = null; - if (data.message.dob) { - age = calculate_age(data.message.dob); - } - let values = { - 'patient_age': age, - 'patient_sex': data.message.sex, - 'email': data.message.email, - 'mobile': data.message.mobile, - 'report_preference': data.message.report_preference - }; - frm.set_value(values); - } - }); - } -}); - -frappe.ui.form.on('Normal Test Result', { - normal_test_items_remove: function () { - frappe.msgprint(__('Not permitted, configure Lab Test Template as required')); - cur_frm.reload_doc(); - } -}); - -frappe.ui.form.on('Descriptive Test Result', { - descriptive_test_items_remove: function () { - frappe.msgprint(__('Not permitted, configure Lab Test Template as required')); - cur_frm.reload_doc(); - } -}); - -var status_update = function (approve, frm) { - var doc = frm.doc; - var status = null; - if (approve == 1) { - status = 'Approved'; - } - else { - status = 'Rejected'; - } - frappe.call({ - method: 'erpnext.healthcare.doctype.lab_test.lab_test.update_status', - args: { status: status, name: doc.name }, - callback: function () { - cur_frm.reload_doc(); - } - }); -}; - -var get_lab_test_prescribed = function (frm) { - if (frm.doc.patient) { - frappe.call({ - method: 'erpnext.healthcare.doctype.lab_test.lab_test.get_lab_test_prescribed', - args: { patient: frm.doc.patient }, - callback: function (r) { - show_lab_tests(frm, r.message); - } - }); - } - else { - frappe.msgprint(__('Please select Patient to get Lab Tests')); - } -}; - -var show_lab_tests = function (frm, lab_test_list) { - var d = new frappe.ui.Dialog({ - title: __('Lab Tests'), - fields: [{ - fieldtype: 'HTML', fieldname: 'lab_test' - }] - }); - var html_field = d.fields_dict.lab_test.$wrapper; - html_field.empty(); - $.each(lab_test_list, function (x, y) { - var row = $(repl( - '
\ -
%(lab_test)s
\ -
%(practitioner_name)s
%(encounter)s
\ -
%(date)s
\ -
\ - \ -
\ -

', - { name: y[0], lab_test: y[1], encounter: y[2], invoiced: y[3], practitioner: y[4], practitioner_name: y[5], date: y[6] }) - ).appendTo(html_field); - - row.find("a").click(function () { - frm.doc.template = $(this).attr('data-lab-test'); - frm.doc.prescription = $(this).attr('data-name'); - frm.doc.practitioner = $(this).attr('data-practitioner'); - frm.set_df_property('template', 'read_only', 1); - frm.set_df_property('patient', 'read_only', 1); - frm.set_df_property('practitioner', 'read_only', 1); - frm.doc.invoiced = 0; - if ($(this).attr('data-invoiced') === 1) { - frm.doc.invoiced = 1; - } - refresh_field('invoiced'); - refresh_field('template'); - d.hide(); - return false; - }); - }); - if (!lab_test_list.length) { - var msg = __('No Lab Tests found for the Patient {0}', [frm.doc.patient_name.bold()]); - html_field.empty(); - $(repl('
%(msg)s
', { msg: msg })).appendTo(html_field); - } - d.show(); -}; - -var make_dialog = function (frm, emailed, printed) { - var number = frm.doc.mobile; - - var dialog = new frappe.ui.Dialog({ - title: 'Send SMS', - width: 400, - fields: [ - { fieldname: 'result_format', fieldtype: 'Select', label: 'Result Format', options: ['Emailed', 'Printed'] }, - { fieldname: 'number', fieldtype: 'Data', label: 'Mobile Number', reqd: 1 }, - { fieldname: 'message', fieldtype: 'Small Text', label: 'Message', reqd: 1 } - ], - primary_action_label: __('Send'), - primary_action: function () { - var values = dialog.fields_dict; - if (!values) { - return; - } - send_sms(values, frm); - dialog.hide(); - } - }); - if (frm.doc.report_preference === 'Print') { - dialog.set_values({ - 'result_format': 'Printed', - 'number': number, - 'message': printed - }); - } else { - dialog.set_values({ - 'result_format': 'Emailed', - 'number': number, - 'message': emailed - }); - } - var fd = dialog.fields_dict; - $(fd.result_format.input).change(function () { - if (dialog.get_value('result_format') === 'Emailed') { - dialog.set_values({ - 'number': number, - 'message': emailed - }); - } else { - dialog.set_values({ - 'number': number, - 'message': printed - }); - } - }); - dialog.show(); -}; - -var send_sms = function (vals, frm) { - var number = vals.number.value; - var message = vals.message.last_value; - - if (!number || !message) { - frappe.throw(__('Did not send SMS, missing patient mobile number or message content.')); - } - frappe.call({ - method: 'frappe.core.doctype.sms_settings.sms_settings.send_sms', - args: { - receiver_list: [number], - msg: message - }, - callback: function (r) { - if (r.exc) { - frappe.msgprint(r.exc); - } else { - frm.reload_doc(); - } - } - }); -}; - -var calculate_age = function (dob) { - var ageMS = Date.parse(Date()) - Date.parse(dob); - var age = new Date(); - age.setTime(ageMS); - var years = age.getFullYear() - 1970; - return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; -}; diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.json b/erpnext/healthcare/doctype/lab_test/lab_test.json deleted file mode 100644 index ac61fea3ad..0000000000 --- a/erpnext/healthcare/doctype/lab_test/lab_test.json +++ /dev/null @@ -1,610 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2016-03-29 17:34:47.509094", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "naming_series", - "template", - "lab_test_name", - "lab_test_group", - "medical_code", - "department", - "column_break_26", - "company", - "status", - "submitted_date", - "result_date", - "approved_date", - "expected_result_date", - "expected_result_time", - "printed_on", - "invoiced", - "sb_first", - "patient", - "patient_name", - "patient_age", - "patient_sex", - "inpatient_record", - "report_preference", - "email", - "mobile", - "c_b", - "practitioner", - "practitioner_name", - "requesting_department", - "employee", - "employee_name", - "employee_designation", - "user", - "sample", - "sb_normal", - "lab_test_html", - "normal_test_items", - "sb_descriptive", - "descriptive_test_items", - "organisms_section", - "organism_test_items", - "sb_sensitivity", - "sensitivity_test_items", - "sb_comments", - "lab_test_comment", - "sb_customresult", - "custom_result", - "worksheet_section", - "worksheet_instructions", - "result_legend_section", - "legend_print_position", - "result_legend", - "section_break_50", - "email_sent", - "sms_sent", - "printed", - "normal_toggle", - "descriptive_toggle", - "sensitivity_toggle", - "amended_from", - "prescription" - ], - "fields": [ - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-LAB-.YYYY.-", - "print_hide": 1, - "report_hide": 1, - "reqd": 1 - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "no_copy": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fetch_from": "inpatient_record.patient", - "fieldname": "patient", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1, - "search_index": 1, - "set_only_once": 1 - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "label": "Age", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "patient_sex", - "fieldtype": "Link", - "label": "Gender", - "options": "Gender", - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Requesting Practitioner", - "no_copy": 1, - "options": "Healthcare Practitioner", - "search_index": 1 - }, - { - "fetch_from": "patient.email", - "fieldname": "email", - "fieldtype": "Data", - "hidden": 1, - "label": "Email", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fetch_from": "patient.mobile", - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 1, - "label": "Mobile", - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "search_index": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "options": "Company", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "c_b", - "fieldtype": "Column Break", - "print_hide": 1 - }, - { - "fetch_from": "template.department", - "fieldname": "department", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Department", - "options": "Medical Department", - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Status", - "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "submitted_date", - "fieldtype": "Datetime", - "hidden": 1, - "label": "Submitted Date", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "approved_date", - "fieldtype": "Datetime", - "hidden": 1, - "label": "Approved Date", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "sample", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_filter": 1, - "label": "Sample ID", - "options": "Sample Collection", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "default": "Today", - "fieldname": "expected_result_date", - "fieldtype": "Date", - "hidden": 1, - "label": "Expected Result Date", - "read_only": 1 - }, - { - "fieldname": "expected_result_time", - "fieldtype": "Time", - "hidden": 1, - "label": "Expected Result Time", - "read_only": 1 - }, - { - "fieldname": "result_date", - "fieldtype": "Date", - "label": "Result Date", - "read_only": 1, - "search_index": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "printed_on", - "fieldtype": "Datetime", - "label": "Printed on", - "read_only": 1 - }, - { - "fieldname": "employee", - "fieldtype": "Link", - "label": "Employee (Lab Technician)", - "no_copy": 1, - "options": "Employee", - "print_hide": 1, - "report_hide": 1 - }, - { - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Data", - "label": "Lab Technician Name", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fetch_from": "employee.designation", - "fieldname": "employee_designation", - "fieldtype": "Data", - "label": "Lab Technician Designation", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "user", - "fieldtype": "Link", - "hidden": 1, - "label": "User", - "no_copy": 1, - "options": "User", - "print_hide": 1, - "report_hide": 1 - }, - { - "fetch_from": "patient.report_preference", - "fieldname": "report_preference", - "fieldtype": "Data", - "label": "Report Preference", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "sb_first", - "fieldtype": "Section Break" - }, - { - "fieldname": "lab_test_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Test Name", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "search_index": 1 - }, - { - "fieldname": "template", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Test Template", - "options": "Lab Test Template", - "print_hide": 1, - "report_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "lab_test_group", - "fieldtype": "Link", - "hidden": 1, - "label": "Test Group", - "options": "Item Group", - "print_hide": 1, - "report_hide": 1 - }, - { - "fetch_from": "template.medical_code", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code", - "read_only": 1 - }, - { - "fieldname": "sb_normal", - "fieldtype": "Section Break", - "label": "Compound Test Result" - }, - { - "fieldname": "normal_test_items", - "fieldtype": "Table", - "label": "Normal Test Result", - "options": "Normal Test Result", - "print_hide": 1 - }, - { - "fieldname": "lab_test_html", - "fieldtype": "HTML" - }, - { - "depends_on": "descriptive_toggle", - "fieldname": "organisms_section", - "fieldtype": "Section Break", - "label": "Organism Test Result" - }, - { - "fieldname": "sb_sensitivity", - "fieldtype": "Section Break", - "label": "Sensitivity Test Result" - }, - { - "fieldname": "sensitivity_test_items", - "fieldtype": "Table", - "label": "Sensitivity Test Result", - "options": "Sensitivity Test Result", - "print_hide": 1, - "report_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "sb_comments", - "fieldtype": "Section Break", - "label": "Comments" - }, - { - "fieldname": "lab_test_comment", - "fieldtype": "Text", - "ignore_xss_filter": 1, - "label": "Comments", - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "sb_customresult", - "fieldtype": "Section Break", - "label": "Custom Result" - }, - { - "fieldname": "custom_result", - "fieldtype": "Text Editor", - "ignore_xss_filter": 1, - "label": "Custom Result", - "print_hide": 1 - }, - { - "default": "0", - "fieldname": "email_sent", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "sms_sent", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "printed", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "normal_toggle", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "sensitivity_toggle", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Lab Test", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "prescription", - "fieldtype": "Link", - "hidden": 1, - "label": "Prescription", - "no_copy": 1, - "options": "Lab Prescription", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "column_break_26", - "fieldtype": "Column Break" - }, - { - "fetch_from": "practitioner.department", - "fieldname": "requesting_department", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Requesting Department", - "options": "Medical Department", - "read_only": 1 - }, - { - "fetch_from": "practitioner.practitioner_name", - "fieldname": "practitioner_name", - "fieldtype": "Data", - "label": "Requesting Practitioner", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "result_legend_section", - "fieldtype": "Section Break", - "label": "Result Legend Print" - }, - { - "fieldname": "legend_print_position", - "fieldtype": "Select", - "label": "Print Position", - "options": "\nBottom\nTop\nBoth", - "print_hide": 1 - }, - { - "fieldname": "result_legend", - "fieldtype": "Text Editor", - "label": "Result Legend", - "print_hide": 1 - }, - { - "fieldname": "section_break_50", - "fieldtype": "Section Break" - }, - { - "fieldname": "worksheet_instructions", - "fieldtype": "Text Editor", - "label": "Worksheet Instructions", - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "worksheet_section", - "fieldtype": "Section Break", - "label": "Worksheet Print" - }, - { - "fieldname": "descriptive_test_items", - "fieldtype": "Table", - "label": "Descriptive Test Result", - "options": "Descriptive Test Result", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "sb_descriptive", - "fieldtype": "Section Break", - "label": "Descriptive Test Result" - }, - { - "default": "0", - "fieldname": "descriptive_toggle", - "fieldtype": "Check", - "hidden": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "organism_test_items", - "fieldtype": "Table", - "label": "Organism Test Result", - "options": "Organism Test Result", - "print_hide": 1 - } - ], - "is_submittable": 1, - "links": [], - "modified": "2020-11-30 11:04:17.195848", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "LabTest Approver", - "share": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient,practitioner,lab_test_name,sample", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py deleted file mode 100644 index 7db497c24d..0000000000 --- a/erpnext/healthcare/doctype/lab_test/lab_test.py +++ /dev/null @@ -1,352 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import get_link_to_form, getdate - - -class LabTest(Document): - def validate(self): - if not self.is_new(): - self.set_secondary_uom_result() - - def on_submit(self): - self.validate_result_values() - self.db_set('submitted_date', getdate()) - self.db_set('status', 'Completed') - - def on_cancel(self): - self.db_set('status', 'Cancelled') - self.reload() - - def on_update(self): - if self.sensitivity_test_items: - sensitivity = sorted(self.sensitivity_test_items, key=lambda x: x.antibiotic_sensitivity) - for i, item in enumerate(sensitivity): - item.idx = i + 1 - self.sensitivity_test_items = sensitivity - - def after_insert(self): - if self.prescription: - frappe.db.set_value('Lab Prescription', self.prescription, 'lab_test_created', 1) - if frappe.db.get_value('Lab Prescription', self.prescription, 'invoiced'): - self.invoiced = True - if self.template: - self.load_test_from_template() - self.reload() - - def load_test_from_template(self): - lab_test = self - create_test_from_template(lab_test) - self.reload() - - def set_secondary_uom_result(self): - for item in self.normal_test_items: - if item.result_value and item.secondary_uom and item.conversion_factor: - try: - item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor) - except Exception: - item.secondary_uom_result = '' - frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated').format(item.idx), title = _('Warning')) - - def validate_result_values(self): - if self.normal_test_items: - for item in self.normal_test_items: - if not item.result_value and not item.allow_blank and item.require_result_value: - frappe.throw(_('Row #{0}: Please enter the result value for {1}').format( - item.idx, frappe.bold(item.lab_test_name)), title=_('Mandatory Results')) - - if self.descriptive_test_items: - for item in self.descriptive_test_items: - if not item.result_value and not item.allow_blank and item.require_result_value: - frappe.throw(_('Row #{0}: Please enter the result value for {1}').format( - item.idx, frappe.bold(item.lab_test_particulars)), title=_('Mandatory Results')) - - -def create_test_from_template(lab_test): - template = frappe.get_doc('Lab Test Template', lab_test.template) - patient = frappe.get_doc('Patient', lab_test.patient) - - lab_test.lab_test_name = template.lab_test_name - lab_test.result_date = getdate() - lab_test.department = template.department - lab_test.lab_test_group = template.lab_test_group - lab_test.legend_print_position = template.legend_print_position - lab_test.result_legend = template.result_legend - lab_test.worksheet_instructions = template.worksheet_instructions - - lab_test = create_sample_collection(lab_test, template, patient, None) - lab_test = load_result_format(lab_test, template, None, None) - -@frappe.whitelist() -def update_status(status, name): - if name and status: - frappe.db.set_value('Lab Test', name, { - 'status': status, - 'approved_date': getdate() - }) - -@frappe.whitelist() -def create_multiple(doctype, docname): - if not doctype or not docname: - frappe.throw(_('Sales Invoice or Patient Encounter is required to create Lab Tests'), title=_('Insufficient Data')) - - lab_test_created = False - if doctype == 'Sales Invoice': - lab_test_created = create_lab_test_from_invoice(docname) - elif doctype == 'Patient Encounter': - lab_test_created = create_lab_test_from_encounter(docname) - - if lab_test_created: - frappe.msgprint(_('Lab Test(s) {0} created successfully').format(lab_test_created), indicator='green') - else: - frappe.msgprint(_('No Lab Tests created')) - -def create_lab_test_from_encounter(encounter): - lab_test_created = False - encounter = frappe.get_doc('Patient Encounter', encounter) - - if encounter and encounter.lab_test_prescription: - patient = frappe.get_doc('Patient', encounter.patient) - for item in encounter.lab_test_prescription: - if not item.lab_test_created: - template = get_lab_test_template(item.lab_test_code) - if template: - lab_test = create_lab_test_doc(item.invoiced, encounter.practitioner, patient, template, encounter.company) - lab_test.save(ignore_permissions = True) - frappe.db.set_value('Lab Prescription', item.name, 'lab_test_created', 1) - if not lab_test_created: - lab_test_created = lab_test.name - else: - lab_test_created += ', ' + lab_test.name - return lab_test_created - - -def create_lab_test_from_invoice(sales_invoice): - lab_tests_created = False - invoice = frappe.get_doc('Sales Invoice', sales_invoice) - if invoice and invoice.patient: - patient = frappe.get_doc('Patient', invoice.patient) - for item in invoice.items: - lab_test_created = 0 - if item.reference_dt == 'Lab Prescription': - lab_test_created = frappe.db.get_value('Lab Prescription', item.reference_dn, 'lab_test_created') - elif item.reference_dt == 'Lab Test': - lab_test_created = 1 - if lab_test_created != 1: - template = get_lab_test_template(item.item_code) - if template: - lab_test = create_lab_test_doc(True, invoice.ref_practitioner, patient, template, invoice.company) - if item.reference_dt == 'Lab Prescription': - lab_test.prescription = item.reference_dn - lab_test.save(ignore_permissions = True) - if item.reference_dt != 'Lab Prescription': - frappe.db.set_value('Sales Invoice Item', item.name, 'reference_dt', 'Lab Test') - frappe.db.set_value('Sales Invoice Item', item.name, 'reference_dn', lab_test.name) - if not lab_tests_created: - lab_tests_created = lab_test.name - else: - lab_tests_created += ', ' + lab_test.name - return lab_tests_created - -def get_lab_test_template(item): - template_id = frappe.db.exists('Lab Test Template', {'item': item}) - if template_id: - return frappe.get_doc('Lab Test Template', template_id) - return False - -def create_lab_test_doc(invoiced, practitioner, patient, template, company): - lab_test = frappe.new_doc('Lab Test') - lab_test.invoiced = invoiced - lab_test.practitioner = practitioner - lab_test.patient = patient.name - lab_test.patient_age = patient.get_age() - lab_test.patient_sex = patient.sex - lab_test.email = patient.email - lab_test.mobile = patient.mobile - lab_test.report_preference = patient.report_preference - lab_test.department = template.department - lab_test.template = template.name - lab_test.lab_test_group = template.lab_test_group - lab_test.result_date = getdate() - lab_test.company = company - return lab_test - -def create_normals(template, lab_test): - lab_test.normal_toggle = 1 - normal = lab_test.append('normal_test_items') - normal.lab_test_name = template.lab_test_name - normal.lab_test_uom = template.lab_test_uom - normal.secondary_uom = template.secondary_uom - normal.conversion_factor = template.conversion_factor - normal.normal_range = template.lab_test_normal_range - normal.require_result_value = 1 - normal.allow_blank = 0 - normal.template = template.name - -def create_compounds(template, lab_test, is_group): - lab_test.normal_toggle = 1 - for normal_test_template in template.normal_test_templates: - normal = lab_test.append('normal_test_items') - if is_group: - normal.lab_test_event = normal_test_template.lab_test_event - else: - normal.lab_test_name = normal_test_template.lab_test_event - - normal.lab_test_uom = normal_test_template.lab_test_uom - normal.secondary_uom = normal_test_template.secondary_uom - normal.conversion_factor = normal_test_template.conversion_factor - normal.normal_range = normal_test_template.normal_range - normal.require_result_value = 1 - normal.allow_blank = normal_test_template.allow_blank - normal.template = template.name - -def create_descriptives(template, lab_test): - lab_test.descriptive_toggle = 1 - if template.sensitivity: - lab_test.sensitivity_toggle = 1 - for descriptive_test_template in template.descriptive_test_templates: - descriptive = lab_test.append('descriptive_test_items') - descriptive.lab_test_particulars = descriptive_test_template.particulars - descriptive.require_result_value = 1 - descriptive.allow_blank = descriptive_test_template.allow_blank - descriptive.template = template.name - -def create_sample_doc(template, patient, invoice, company = None): - if template.sample: - sample_exists = frappe.db.exists({ - 'doctype': 'Sample Collection', - 'patient': patient.name, - 'docstatus': 0, - 'sample': template.sample - }) - - if sample_exists: - # update sample collection by adding quantity - sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0]) - quantity = int(sample_collection.sample_qty) + int(template.sample_qty) - if template.sample_details: - sample_details = sample_collection.sample_details + '\n-\n' + _('Test :') - sample_details += (template.get('lab_test_name') or template.get('template')) + '\n' - sample_details += _('Collection Details:') + '\n\t' + template.sample_details - frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_details', sample_details) - - frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_qty', quantity) - - else: - # Create Sample Collection for template, copy vals from Invoice - sample_collection = frappe.new_doc('Sample Collection') - if invoice: - sample_collection.invoiced = True - - sample_collection.patient = patient.name - sample_collection.patient_age = patient.get_age() - sample_collection.patient_sex = patient.sex - sample_collection.sample = template.sample - sample_collection.sample_uom = template.sample_uom - sample_collection.sample_qty = template.sample_qty - sample_collection.company = company - - if template.sample_details: - sample_collection.sample_details = _('Test :') + (template.get('lab_test_name') or template.get('template')) + '\n' + 'Collection Detials:\n\t' + template.sample_details - sample_collection.save(ignore_permissions=True) - - return sample_collection - -def create_sample_collection(lab_test, template, patient, invoice): - if frappe.get_cached_value('Healthcare Settings', None, 'create_sample_collection_for_lab_test'): - sample_collection = create_sample_doc(template, patient, invoice, lab_test.company) - if sample_collection: - lab_test.sample = sample_collection.name - sample_collection_doc = get_link_to_form('Sample Collection', sample_collection.name) - frappe.msgprint(_('Sample Collection {0} has been created').format(sample_collection_doc), - title=_('Sample Collection'), indicator='green') - return lab_test - -def load_result_format(lab_test, template, prescription, invoice): - if template.lab_test_template_type == 'Single': - create_normals(template, lab_test) - - elif template.lab_test_template_type == 'Compound': - create_compounds(template, lab_test, False) - - elif template.lab_test_template_type == 'Descriptive': - create_descriptives(template, lab_test) - - elif template.lab_test_template_type == 'Grouped': - # Iterate for each template in the group and create one result for all. - for lab_test_group in template.lab_test_groups: - # Template_in_group = None - if lab_test_group.lab_test_template: - template_in_group = frappe.get_doc('Lab Test Template', lab_test_group.lab_test_template) - if template_in_group: - if template_in_group.lab_test_template_type == 'Single': - create_normals(template_in_group, lab_test) - - elif template_in_group.lab_test_template_type == 'Compound': - normal_heading = lab_test.append('normal_test_items') - normal_heading.lab_test_name = template_in_group.lab_test_name - normal_heading.require_result_value = 0 - normal_heading.allow_blank = 1 - normal_heading.template = template_in_group.name - create_compounds(template_in_group, lab_test, True) - - elif template_in_group.lab_test_template_type == 'Descriptive': - descriptive_heading = lab_test.append('descriptive_test_items') - descriptive_heading.lab_test_name = template_in_group.lab_test_name - descriptive_heading.require_result_value = 0 - descriptive_heading.allow_blank = 1 - descriptive_heading.template = template_in_group.name - create_descriptives(template_in_group, lab_test) - - else: # Lab Test Group - Add New Line - normal = lab_test.append('normal_test_items') - normal.lab_test_name = lab_test_group.group_event - normal.lab_test_uom = lab_test_group.group_test_uom - normal.secondary_uom = lab_test_group.secondary_uom - normal.conversion_factor = lab_test_group.conversion_factor - normal.normal_range = lab_test_group.group_test_normal_range - normal.allow_blank = lab_test_group.allow_blank - normal.require_result_value = 1 - normal.template = template.name - - if template.lab_test_template_type != 'No Result': - if prescription: - lab_test.prescription = prescription - if invoice: - frappe.db.set_value('Lab Prescription', prescription, 'invoiced', True) - lab_test.save(ignore_permissions=True) # Insert the result - return lab_test - -@frappe.whitelist() -def get_employee_by_user_id(user_id): - emp_id = frappe.db.exists('Employee', { 'user_id': user_id }) - if emp_id: - return frappe.get_doc('Employee', emp_id) - return None - - -@frappe.whitelist() -def get_lab_test_prescribed(patient): - return frappe.db.sql( - ''' - select - lp.name, - lp.lab_test_code, - lp.parent, - lp.invoiced, - pe.practitioner, - pe.practitioner_name, - pe.encounter_date - from - `tabPatient Encounter` pe, `tabLab Prescription` lp - where - pe.patient=%s - and lp.parent=pe.name - and lp.lab_test_created=0 - ''', (patient)) diff --git a/erpnext/healthcare/doctype/lab_test/lab_test_list.js b/erpnext/healthcare/doctype/lab_test/lab_test_list.js deleted file mode 100644 index 7b5b9d922a..0000000000 --- a/erpnext/healthcare/doctype/lab_test/lab_test_list.js +++ /dev/null @@ -1,71 +0,0 @@ -/* -(c) ESS 2015-16 -*/ -frappe.listview_settings['Lab Test'] = { - add_fields: ['name', 'status', 'invoiced'], - filters: [['docstatus', '=', '1']], - get_indicator: function (doc) { - if (doc.status === 'Approved') { - return [__('Approved'), 'green', 'status, =, Approved']; - } else if (doc.status === 'Rejected') { - return [__('Rejected'), 'orange', 'status, =, Rejected']; - } else if (doc.status === 'Completed') { - return [__('Completed'), 'green', 'status, =, Completed']; - } else if (doc.status === 'Cancelled') { - return [__('Cancelled'), 'red', 'status, =, Cancelled']; - } - }, - onload: function (listview) { - listview.page.add_menu_item(__('Create Multiple'), function () { - create_multiple_dialog(listview); - }); - } -}; - -var create_multiple_dialog = function (listview) { - var dialog = new frappe.ui.Dialog({ - title: 'Create Multiple Lab Tests', - width: 100, - fields: [ - { fieldtype: 'Link', label: 'Patient', fieldname: 'patient', options: 'Patient', reqd: 1 }, - { - fieldtype: 'Select', label: 'Invoice / Patient Encounter', fieldname: 'doctype', - options: '\nSales Invoice\nPatient Encounter', reqd: 1 - }, - { - fieldtype: 'Dynamic Link', fieldname: 'docname', options: 'doctype', reqd: 1, - get_query: function () { - return { - filters: { - 'patient': dialog.get_value('patient'), - 'docstatus': 1 - } - }; - } - } - ], - primary_action_label: __('Create'), - primary_action: function () { - frappe.call({ - method: 'erpnext.healthcare.doctype.lab_test.lab_test.create_multiple', - args: { - 'doctype': dialog.get_value('doctype'), - 'docname': dialog.get_value('docname') - }, - callback: function (data) { - if (!data.exc) { - if (!data.message) { - frappe.msgprint(__('No Lab Tests created')); - } - listview.refresh(); - } - }, - freeze: true, - freeze_message: __('Creating Lab Tests...') - }); - dialog.hide(); - } - }); - - dialog.show(); -}; diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.py b/erpnext/healthcare/doctype/lab_test/test_lab_test.py deleted file mode 100644 index da10bd86bd..0000000000 --- a/erpnext/healthcare/doctype/lab_test/test_lab_test.py +++ /dev/null @@ -1,215 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import getdate, nowtime - -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( - get_income_account, - get_receivable_account, -) -from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient -from erpnext.healthcare.doctype.patient_medical_record.test_patient_medical_record import ( - create_lab_test_template as create_blood_test_template, -) - - -class TestLabTest(unittest.TestCase): - def test_lab_test_item(self): - lab_template = create_lab_test_template() - self.assertTrue(frappe.db.exists('Item', lab_template.item)) - self.assertEqual(frappe.db.get_value('Item Price', {'item_code':lab_template.item}, 'price_list_rate'), lab_template.lab_test_rate) - - lab_template.disabled = 1 - lab_template.save() - self.assertEqual(frappe.db.get_value('Item', lab_template.item, 'disabled'), 1) - - lab_template.reload() - - lab_template.disabled = 0 - lab_template.save() - - def test_descriptive_lab_test(self): - lab_template = create_lab_test_template() - - # blank result value not allowed as per template - lab_test = create_lab_test(lab_template) - lab_test.descriptive_test_items[0].result_value = 12 - lab_test.descriptive_test_items[2].result_value = 1 - lab_test.save() - self.assertRaises(frappe.ValidationError, lab_test.submit) - - def test_sample_collection(self): - frappe.db.set_value('Healthcare Settings', 'Healthcare Settings', 'create_sample_collection_for_lab_test', 1) - lab_template = create_lab_test_template() - - lab_test = create_lab_test(lab_template) - lab_test.descriptive_test_items[0].result_value = 12 - lab_test.descriptive_test_items[1].result_value = 1 - lab_test.descriptive_test_items[2].result_value = 2.3 - lab_test.save() - - # check sample collection created - self.assertTrue(frappe.db.exists('Sample Collection', {'sample': lab_template.sample})) - - frappe.db.set_value('Healthcare Settings', 'Healthcare Settings', 'create_sample_collection_for_lab_test', 0) - lab_test = create_lab_test(lab_template) - lab_test.descriptive_test_items[0].result_value = 12 - lab_test.descriptive_test_items[1].result_value = 1 - lab_test.descriptive_test_items[2].result_value = 2.3 - lab_test.save() - - # sample collection should not be created - lab_test.reload() - self.assertEqual(lab_test.sample, None) - - def test_create_lab_tests_from_sales_invoice(self): - sales_invoice = create_sales_invoice() - create_multiple('Sales Invoice', sales_invoice.name) - sales_invoice.reload() - self.assertIsNotNone(sales_invoice.items[0].reference_dn) - self.assertIsNotNone(sales_invoice.items[1].reference_dn) - - def test_create_lab_tests_from_patient_encounter(self): - patient_encounter = create_patient_encounter() - create_multiple('Patient Encounter', patient_encounter.name) - patient_encounter.reload() - self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created) - self.assertTrue(patient_encounter.lab_test_prescription[0].lab_test_created) - - -def create_lab_test_template(test_sensitivity=0, sample_collection=1): - medical_department = create_medical_department() - if frappe.db.exists('Lab Test Template', 'Insulin Resistance'): - return frappe.get_doc('Lab Test Template', 'Insulin Resistance') - template = frappe.new_doc('Lab Test Template') - template.lab_test_name = 'Insulin Resistance' - template.lab_test_template_type = 'Descriptive' - template.lab_test_code = 'Insulin Resistance' - template.lab_test_group = 'Services' - template.department = medical_department - template.is_billable = 1 - template.lab_test_description = 'Insulin Resistance' - template.lab_test_rate = 2000 - - for entry in ['FBS', 'Insulin', 'IR']: - template.append('descriptive_test_templates', { - 'particulars': entry, - 'allow_blank': 1 if entry=='IR' else 0 - }) - - if test_sensitivity: - template.sensitivity = 1 - - if sample_collection: - template.sample = create_lab_test_sample() - template.sample_qty = 5.0 - - template.save() - return template - -def create_medical_department(): - medical_department = frappe.db.exists('Medical Department', '_Test Medical Department') - if not medical_department: - medical_department = frappe.new_doc('Medical Department') - medical_department.department = '_Test Medical Department' - medical_department.save() - medical_department = medical_department.name - - return medical_department - -def create_lab_test(lab_template): - patient = create_patient() - lab_test = frappe.new_doc('Lab Test') - lab_test.template = lab_template.name - lab_test.patient = patient - lab_test.patient_sex = 'Female' - lab_test.save() - - return lab_test - -def create_lab_test_sample(): - blood_sample = frappe.db.exists('Lab Test Sample', 'Blood Sample') - if blood_sample: - return blood_sample - - sample = frappe.new_doc('Lab Test Sample') - sample.sample = 'Blood Sample' - sample.sample_uom = 'U/ml' - sample.save() - - return sample.name - -def create_sales_invoice(): - patient = create_patient() - medical_department = create_medical_department() - insulin_resistance_template = create_lab_test_template() - blood_test_template = create_blood_test_template(medical_department) - - sales_invoice = frappe.new_doc('Sales Invoice') - sales_invoice.patient = patient - sales_invoice.customer = frappe.db.get_value('Patient', patient, 'customer') - sales_invoice.due_date = getdate() - sales_invoice.company = '_Test Company' - sales_invoice.debit_to = get_receivable_account('_Test Company') - - tests = [insulin_resistance_template, blood_test_template] - for entry in tests: - sales_invoice.append('items', { - 'item_code': entry.item, - 'item_name': entry.lab_test_name, - 'description': entry.lab_test_description, - 'qty': 1, - 'uom': 'Nos', - 'conversion_factor': 1, - 'income_account': get_income_account(None, '_Test Company'), - 'rate': entry.lab_test_rate, - 'amount': entry.lab_test_rate - }) - - sales_invoice.set_missing_values() - - sales_invoice.submit() - return sales_invoice - -def create_patient_encounter(): - patient = create_patient() - medical_department = create_medical_department() - insulin_resistance_template = create_lab_test_template() - blood_test_template = create_blood_test_template(medical_department) - - patient_encounter = frappe.new_doc('Patient Encounter') - patient_encounter.patient = patient - patient_encounter.practitioner = create_practitioner() - patient_encounter.encounter_date = getdate() - patient_encounter.encounter_time = nowtime() - - tests = [insulin_resistance_template, blood_test_template] - for entry in tests: - patient_encounter.append('lab_test_prescription', { - 'lab_test_code': entry.item, - 'lab_test_name': entry.lab_test_name - }) - - patient_encounter.submit() - return patient_encounter - - -def create_practitioner(): - practitioner = frappe.db.exists('Healthcare Practitioner', '_Test Healthcare Practitioner') - - if not practitioner: - practitioner = frappe.new_doc('Healthcare Practitioner') - practitioner.first_name = '_Test Healthcare Practitioner' - practitioner.gender = 'Female' - practitioner.op_consulting_charge = 500 - practitioner.inpatient_visit_charge = 500 - practitioner.save(ignore_permissions=True) - practitioner = practitioner.name - - return practitioner diff --git a/erpnext/healthcare/doctype/lab_test_group_template/__init__.py b/erpnext/healthcare/doctype/lab_test_group_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json deleted file mode 100644 index 2767f7ec77..0000000000 --- a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-03-29 17:37:29.913583", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "template_or_new_line", - "lab_test_template", - "lab_test_rate", - "lab_test_description", - "group_event", - "group_test_uom", - "secondary_uom", - "conversion_factor", - "allow_blank", - "column_break_8", - "group_test_normal_range" - ], - "fields": [ - { - "default": "Add Test", - "fieldname": "template_or_new_line", - "fieldtype": "Select", - "options": "Add Test\nAdd New Line", - "print_hide": 1, - "report_hide": 1 - }, - { - "depends_on": "eval:doc.template_or_new_line == 'Add Test'", - "fieldname": "lab_test_template", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Test Name", - "options": "Lab Test Template" - }, - { - "fetch_from": "lab_test_template.lab_test_rate", - "fieldname": "lab_test_rate", - "fieldtype": "Currency", - "label": "Rate", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fetch_from": "lab_test_template.lab_test_description", - "fieldname": "lab_test_description", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Description", - "read_only": 1 - }, - { - "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", - "fieldname": "group_event", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Event" - }, - { - "depends_on": "eval:doc.template_or_new_line =='Add New Line'", - "fieldname": "group_test_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "UOM", - "options": "Lab Test UOM" - }, - { - "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", - "fieldname": "group_test_normal_range", - "fieldtype": "Long Text", - "ignore_xss_filter": 1, - "label": "Normal Range" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.template_or_new_line =='Add New Line'", - "fieldname": "secondary_uom", - "fieldtype": "Link", - "label": "Secondary UOM", - "options": "Lab Test UOM" - }, - { - "depends_on": "secondary_uom", - "fieldname": "conversion_factor", - "fieldtype": "Float", - "label": "Conversion Factor", - "mandatory_depends_on": "secondary_uom" - }, - { - "default": "0", - "depends_on": "eval:doc.template_or_new_line == 'Add New Line'", - "fieldname": "allow_blank", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Allow Blank" - } - ], - "istable": 1, - "links": [], - "modified": "2020-07-30 12:36:03.082391", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Group Template", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py b/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py deleted file mode 100644 index 2e3c4093c7..0000000000 --- a/erpnext/healthcare/doctype/lab_test_group_template/lab_test_group_template.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class LabTestGroupTemplate(Document): - pass diff --git a/erpnext/healthcare/doctype/lab_test_sample/__init__.py b/erpnext/healthcare/doctype/lab_test_sample/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.js b/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.js deleted file mode 100644 index a5f4b4de99..0000000000 --- a/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Lab Test Sample', { -}); diff --git a/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.json b/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.json deleted file mode 100644 index 2830038eaf..0000000000 --- a/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:sample", - "beta": 1, - "creation": "2016-04-04 17:35:44.823951", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "sample", - "sample_uom" - ], - "fields": [ - { - "fieldname": "sample", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Sample", - "reqd": 1, - "unique": 1 - }, - { - "bold": 1, - "fieldname": "sample_uom", - "fieldtype": "Link", - "in_list_view": 1, - "label": "UOM", - "options": "Lab Test UOM" - } - ], - "links": [], - "modified": "2020-01-29 23:02:02.249839", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Sample", - "owner": "Administrator", - "permissions": [ - { - "create": 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": "Laboratory User", - "share": 1 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "sample", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.py b/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.py deleted file mode 100644 index 3a765fe243..0000000000 --- a/erpnext/healthcare/doctype/lab_test_sample/lab_test_sample.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class LabTestSample(Document): - pass diff --git a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.py b/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.py deleted file mode 100644 index 8896572d78..0000000000 --- a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestLabTestSample(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/lab_test_template/__init__.py b/erpnext/healthcare/doctype/lab_test_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js deleted file mode 100644 index 2e41f518f0..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.js +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) 2016, ESS -// License: ESS license.txt - -frappe.ui.form.on('Lab Test Template', { - lab_test_name: function(frm) { - if (!frm.doc.lab_test_code) - frm.set_value('lab_test_code', frm.doc.lab_test_name); - if (!frm.doc.lab_test_description) - frm.set_value('lab_test_description', frm.doc.lab_test_name); - }, - refresh : function(frm) { - // Restrict Special, Grouped type templates in Child Test Groups - frm.set_query('lab_test_template', 'lab_test_groups', function() { - return { - filters: { - lab_test_template_type: ['in', ['Single','Compound']] - } - }; - }); - }, - medical_code: function(frm) { - frm.set_query('medical_code', function() { - return { - filters: { - medical_code_standard: frm.doc.medical_code_standard - } - }; - }); - } -}); - -cur_frm.cscript.custom_refresh = function(doc) { - cur_frm.set_df_property('lab_test_code', 'read_only', doc.__islocal ? 0 : 1); - - if (!doc.__islocal) { - cur_frm.add_custom_button(__('Change Template Code'), function() { - change_template_code(doc); - }); - } -}; - -let change_template_code = function(doc) { - let d = new frappe.ui.Dialog({ - title:__('Change Template Code'), - fields:[ - { - 'fieldtype': 'Data', - 'label': 'Lab Test Template Code', - 'fieldname': 'lab_test_code', - reqd: 1 - } - ], - primary_action: function() { - let values = d.get_values(); - if (values) { - frappe.call({ - 'method': 'erpnext.healthcare.doctype.lab_test_template.lab_test_template.change_test_code_from_template', - 'args': {lab_test_code: values.lab_test_code, doc: doc}, - callback: function (data) { - frappe.set_route('Form', 'Lab Test Template', data.message); - } - }); - } - d.hide(); - }, - primary_action_label: __('Change Template Code') - }); - d.show(); - - d.set_values({ - 'lab_test_code': doc.lab_test_code - }); -}; - -frappe.ui.form.on('Lab Test Template', 'lab_test_name', function(frm) { - frm.doc.change_in_item = 1; -}); - -frappe.ui.form.on('Lab Test Template', 'lab_test_rate', function(frm) { - frm.doc.change_in_item = 1; -}); - -frappe.ui.form.on('Lab Test Template', 'lab_test_group', function(frm) { - frm.doc.change_in_item = 1; -}); - -frappe.ui.form.on('Lab Test Template', 'lab_test_description', function(frm) { - frm.doc.change_in_item = 1; -}); - -frappe.ui.form.on('Lab Test Groups', 'template_or_new_line', function (frm, cdt, cdn) { - let child = locals[cdt][cdn]; - if (child.template_or_new_line == 'Add New Line') { - frappe.model.set_value(cdt, cdn, 'lab_test_template', ''); - frappe.model.set_value(cdt, cdn, 'lab_test_description', ''); - } -}); diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json deleted file mode 100644 index c3fc842047..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.json +++ /dev/null @@ -1,356 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:lab_test_code", - "beta": 1, - "creation": "2016-03-29 17:35:36.761223", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "lab_test_name", - "item", - "lab_test_code", - "lab_test_group", - "department", - "column_break_3", - "disabled", - "lab_test_template_type", - "is_billable", - "lab_test_rate", - "section_break_description", - "lab_test_description", - "section_break_normal", - "lab_test_uom", - "secondary_uom", - "conversion_factor", - "column_break_10", - "lab_test_normal_range", - "section_break_compound", - "normal_test_templates", - "section_break_special", - "sensitivity", - "descriptive_test_templates", - "section_break_group", - "lab_test_groups", - "sb_sample_collection", - "sample", - "sample_uom", - "sample_qty", - "column_break_33", - "sample_details", - "medical_coding_section", - "medical_code", - "medical_code_standard", - "worksheet_section", - "worksheet_instructions", - "result_legend_section", - "legend_print_position", - "result_legend", - "change_in_item" - ], - "fields": [ - { - "fieldname": "lab_test_name", - "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Test Name", - "no_copy": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "item", - "fieldtype": "Link", - "label": "Item", - "no_copy": 1, - "options": "Item", - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "lab_test_code", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Code", - "no_copy": 1, - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "lab_test_group", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Item Group", - "options": "Item Group", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "department", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Department", - "options": "Medical Department", - "reqd": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "description": "Single: Results which require only a single input.\n
\nCompound: Results which require multiple event inputs.\n
\nDescriptive: Tests which have multiple result components with manual result entry.\n
\nGrouped: Test templates which are a group of other test templates.\n
\nNo Result: Tests with no results, can be ordered and billed but no Lab Test will be created. e.g.. Sub Tests for Grouped results", - "fieldname": "lab_test_template_type", - "fieldtype": "Select", - "in_standard_filter": 1, - "label": "Result Format", - "options": "\nSingle\nCompound\nDescriptive\nGrouped\nNo Result" - }, - { - "default": "1", - "depends_on": "eval:doc.lab_test_template_type != 'Grouped'", - "description": "If unchecked, the item will not be available in Sales Invoices for billing but can be used in group test creation. ", - "fieldname": "is_billable", - "fieldtype": "Check", - "label": "Is Billable", - "search_index": 1 - }, - { - "depends_on": "eval:doc.is_billable == 1", - "description": "This value is updated in the Default Sales Price List.", - "fieldname": "lab_test_rate", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Rate", - "mandatory_depends_on": "eval:doc.is_billable == 1" - }, - { - "collapsible": 1, - "fieldname": "medical_coding_section", - "fieldtype": "Section Break", - "label": "Medical Coding" - }, - { - "depends_on": "medical_code_standard", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code" - }, - { - "fieldname": "medical_code_standard", - "fieldtype": "Link", - "label": "Medical Code Standard", - "options": "Medical Code Standard" - }, - { - "depends_on": "eval:doc.lab_test_template_type == 'Single'", - "fieldname": "section_break_normal", - "fieldtype": "Section Break", - "label": "Lab Routine" - }, - { - "fieldname": "lab_test_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "UOM", - "options": "Lab Test UOM" - }, - { - "fieldname": "lab_test_normal_range", - "fieldtype": "Long Text", - "ignore_xss_filter": 1, - "label": "Normal Range" - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:doc.lab_test_template_type == 'Compound'", - "fieldname": "section_break_compound", - "fieldtype": "Section Break", - "label": "Compound" - }, - { - "fieldname": "normal_test_templates", - "fieldtype": "Table", - "options": "Normal Test Template" - }, - { - "depends_on": "eval:doc.lab_test_template_type == 'Descriptive'", - "fieldname": "section_break_special", - "fieldtype": "Section Break", - "label": "Descriptive Test" - }, - { - "default": "0", - "fieldname": "sensitivity", - "fieldtype": "Check", - "label": "Sensitivity" - }, - { - "depends_on": "eval:doc.lab_test_template_type == 'Grouped'", - "fieldname": "section_break_group", - "fieldtype": "Section Break", - "label": "Group Tests" - }, - { - "fieldname": "lab_test_groups", - "fieldtype": "Table", - "options": "Lab Test Group Template" - }, - { - "collapsible": 1, - "fieldname": "section_break_description", - "fieldtype": "Section Break", - "label": "Description " - }, - { - "fieldname": "lab_test_description", - "fieldtype": "Text Editor", - "ignore_xss_filter": 1, - "label": "Description", - "no_copy": 1 - }, - { - "fieldname": "sb_sample_collection", - "fieldtype": "Section Break", - "label": "Sample Collection" - }, - { - "fieldname": "sample", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Sample", - "options": "Lab Test Sample" - }, - { - "fetch_from": "sample.sample_uom", - "fieldname": "sample_uom", - "fieldtype": "Data", - "label": "UOM", - "read_only": 1 - }, - { - "default": "0", - "fieldname": "change_in_item", - "fieldtype": "Check", - "hidden": 1, - "label": "Change In Item", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled" - }, - { - "default": "0", - "fieldname": "sample_qty", - "fieldtype": "Float", - "label": "Quantity" - }, - { - "fieldname": "sample_details", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Collection Details" - }, - { - "collapsible": 1, - "description": "Information to help easily interpret the test report, will be printed as part of the Lab Test result.", - "fieldname": "result_legend_section", - "fieldtype": "Section Break", - "label": "Result Legend Print" - }, - { - "fieldname": "result_legend", - "fieldtype": "Text Editor", - "label": "Result Legend" - }, - { - "fieldname": "legend_print_position", - "fieldtype": "Select", - "label": "Print Position", - "options": "Bottom\nTop\nBoth" - }, - { - "fieldname": "secondary_uom", - "fieldtype": "Link", - "label": "Secondary UOM", - "options": "Lab Test UOM" - }, - { - "depends_on": "secondary_uom", - "fieldname": "conversion_factor", - "fieldtype": "Float", - "label": "Conversion Factor", - "mandatory_depends_on": "secondary_uom" - }, - { - "description": "Instructions to be printed on the worksheet", - "fieldname": "worksheet_instructions", - "fieldtype": "Text Editor", - "label": "Worksheet Instructions" - }, - { - "collapsible": 1, - "fieldname": "worksheet_section", - "fieldtype": "Section Break", - "label": "Worksheet Print" - }, - { - "fieldname": "descriptive_test_templates", - "fieldtype": "Table", - "options": "Descriptive Test Template" - }, - { - "fieldname": "column_break_33", - "fieldtype": "Column Break" - } - ], - "links": [], - "modified": "2020-07-30 14:32:40.449818", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Template", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "share": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "lab_test_code,lab_test_name,lab_test_template_type", - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "lab_test_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py deleted file mode 100644 index c052693d04..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.rename_doc import rename_doc - - -class LabTestTemplate(Document): - def after_insert(self): - if not self.item: - create_item_from_template(self) - - def validate(self): - if self.is_billable and (not self.lab_test_rate or self.lab_test_rate <= 0.0): - frappe.throw(_('Standard Selling Rate should be greater than zero.')) - - self.validate_conversion_factor() - self.enable_disable_item() - - def on_update(self): - # If change_in_item update Item and Price List - if self.change_in_item and self.is_billable and self.item: - self.update_item() - item_price = self.item_price_exists() - if not item_price: - if self.lab_test_rate and self.lab_test_rate > 0.0: - price_list_name = frappe.db.get_value('Price List', {'selling': 1}) - make_item_price(self.lab_test_code, price_list_name, self.lab_test_rate) - else: - frappe.db.set_value('Item Price', item_price, 'price_list_rate', self.lab_test_rate) - - self.db_set('change_in_item', 0) - - elif not self.is_billable and self.item: - frappe.db.set_value('Item', self.item, 'disabled', 1) - - self.reload() - - def on_trash(self): - # Remove template reference from item and disable item - if self.item: - try: - item = self.item - self.db_set('item', '') - frappe.delete_doc('Item', item) - except Exception: - frappe.throw(_('Not permitted. Please disable the Lab Test Template')) - - 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(self): - item = frappe.get_doc('Item', self.item) - if item: - item.update({ - 'item_name': self.lab_test_name, - 'item_group': self.lab_test_group, - 'disabled': 0, - 'standard_rate': self.lab_test_rate, - 'description': self.lab_test_description - }) - item.flags.ignore_mandatory = True - item.save(ignore_permissions=True) - - def item_price_exists(self): - item_price = frappe.db.exists({'doctype': 'Item Price', 'item_code': self.lab_test_code}) - if item_price: - return item_price[0][0] - return False - - def validate_conversion_factor(self): - if self.lab_test_template_type == 'Single' and self.secondary_uom and not self.conversion_factor: - frappe.throw(_('Conversion Factor is mandatory')) - if self.lab_test_template_type == 'Compound': - for item in self.normal_test_templates: - if item.secondary_uom and not item.conversion_factor: - frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(item.idx)) - if self.lab_test_template_type == 'Grouped': - for group in self.lab_test_groups: - if group.template_or_new_line == 'Add New Line' and group.secondary_uom and not group.conversion_factor: - frappe.throw(_('Row #{0}: Conversion Factor is mandatory').format(group.idx)) - - -def create_item_from_template(doc): - uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom') - # Insert item - item = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': doc.lab_test_code, - 'item_name':doc.lab_test_name, - 'item_group': doc.lab_test_group, - 'description':doc.lab_test_description, - 'is_sales_item': 1, - 'is_service_item': 1, - 'is_purchase_item': 0, - 'is_stock_item': 0, - 'include_item_in_manufacturing': 0, - 'show_in_website': 0, - 'is_pro_applicable': 0, - 'disabled': 0 if doc.is_billable and not doc.disabled else doc.disabled, - 'stock_uom': uom - }).insert(ignore_permissions=True, ignore_mandatory=True) - - # Insert item price - if doc.is_billable and doc.lab_test_rate != 0.0: - price_list_name = frappe.db.get_value('Price List', {'selling': 1}) - if doc.lab_test_rate: - make_item_price(item.name, price_list_name, doc.lab_test_rate) - else: - make_item_price(item.name, price_list_name, 0.0) - # Set item in the template - frappe.db.set_value('Lab Test Template', doc.name, 'item', item.name) - - doc.reload() - -def make_item_price(item, price_list_name, item_price): - 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_test_code_from_template(lab_test_code, doc): - doc = frappe._dict(json.loads(doc)) - - if frappe.db.exists({'doctype': 'Item', 'item_code': lab_test_code}): - frappe.throw(_('Lab Test Item {0} already exist').format(lab_test_code)) - else: - rename_doc('Item', doc.name, lab_test_code, ignore_permissions=True) - frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_code', lab_test_code) - frappe.db.set_value('Lab Test Template', doc.name, 'lab_test_name', lab_test_code) - rename_doc('Lab Test Template', doc.name, lab_test_code, ignore_permissions=True) - return lab_test_code diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py deleted file mode 100644 index a4f9d4b714..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_dashboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'template', - 'transactions': [ - { - 'label': _('Lab Tests'), - 'items': ['Lab Test'] - } - ] - } diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js b/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js deleted file mode 100644 index 08fc2cddda..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template_list.js +++ /dev/null @@ -1,7 +0,0 @@ -/* -(c) ESS 2015-16 -*/ -frappe.listview_settings['Lab Test Template'] = { - add_fields: ['lab_test_name', 'lab_test_code', 'lab_test_rate'], - filters: [['disabled', '=', 'No']] -}; diff --git a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.py deleted file mode 100644 index 8d8ac64750..0000000000 --- a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Lab Test Template') - -class TestLabTestTemplate(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/lab_test_uom/__init__.py b/erpnext/healthcare/doctype/lab_test_uom/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.js b/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.js deleted file mode 100644 index 2107e79c0f..0000000000 --- a/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2016, ESS and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Lab Test UOM', { -}); diff --git a/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.json b/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.json deleted file mode 100644 index a6d5224987..0000000000 --- a/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:lab_test_uom", - "beta": 1, - "creation": "2016-03-29 17:28:08.630148", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lab_test_uom", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Lab Test UOM", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "uom_description", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-04 11:02:53.202718", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test UOM", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "lab_test_uom", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "lab_test_uom", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.py b/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.py deleted file mode 100644 index ce11c0ffce..0000000000 --- a/erpnext/healthcare/doctype/lab_test_uom/lab_test_uom.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class LabTestUOM(Document): - pass diff --git a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.py b/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.py deleted file mode 100644 index 9fe2de51a6..0000000000 --- a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Lab Test UOM') - -class TestLabTestUOM(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/medical_code/__init__.py b/erpnext/healthcare/doctype/medical_code/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/medical_code/medical_code.js b/erpnext/healthcare/doctype/medical_code/medical_code.js deleted file mode 100644 index 0422d77435..0000000000 --- a/erpnext/healthcare/doctype/medical_code/medical_code.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Medical Code', { -}); diff --git a/erpnext/healthcare/doctype/medical_code/medical_code.json b/erpnext/healthcare/doctype/medical_code/medical_code.json deleted file mode 100644 index 5d69830907..0000000000 --- a/erpnext/healthcare/doctype/medical_code/medical_code.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "allow_rename": 1, - "beta": 1, - "creation": "2017-06-21 13:02:56.122897", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "medical_code_standard", - "code", - "description" - ], - "fields": [ - { - "fieldname": "medical_code_standard", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Medical Code Standard", - "options": "Medical Code Standard", - "reqd": 1 - }, - { - "fieldname": "code", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Code", - "reqd": 1, - "unique": 1 - }, - { - "bold": 1, - "fieldname": "description", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Description" - } - ], - "links": [], - "modified": "2020-06-29 14:02:30.980032", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Medical Code", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "search_fields": "code, description", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/medical_code/medical_code.py b/erpnext/healthcare/doctype/medical_code/medical_code.py deleted file mode 100644 index 4ed3d31e90..0000000000 --- a/erpnext/healthcare/doctype/medical_code/medical_code.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class MedicalCode(Document): - def autoname(self): - self.name = self.medical_code_standard+" "+self.code diff --git a/erpnext/healthcare/doctype/medical_code/test_medical_code.py b/erpnext/healthcare/doctype/medical_code/test_medical_code.py deleted file mode 100644 index 7584b050a6..0000000000 --- a/erpnext/healthcare/doctype/medical_code/test_medical_code.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestMedicalCode(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/medical_code_standard/__init__.py b/erpnext/healthcare/doctype/medical_code_standard/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.js b/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.js deleted file mode 100644 index 4bf6d3ea16..0000000000 --- a/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Medical Code Standard', { -}); diff --git a/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.json b/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.json deleted file mode 100644 index 886938d12f..0000000000 --- a/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:medical_code", - "beta": 1, - "creation": "2017-06-21 13:07:00.463176", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "medical_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Medical Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-08-31 14:15:40.820693", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Medical Code Standard", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.py b/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.py deleted file mode 100644 index be0ea8922e..0000000000 --- a/erpnext/healthcare/doctype/medical_code_standard/medical_code_standard.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class MedicalCodeStandard(Document): - pass diff --git a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.py b/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.py deleted file mode 100644 index 3b046e360f..0000000000 --- a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestMedicalCodeStandard(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/medical_department/__init__.py b/erpnext/healthcare/doctype/medical_department/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/medical_department/medical_department.js b/erpnext/healthcare/doctype/medical_department/medical_department.js deleted file mode 100644 index 25aeeb85e2..0000000000 --- a/erpnext/healthcare/doctype/medical_department/medical_department.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Medical Department', { -}); diff --git a/erpnext/healthcare/doctype/medical_department/medical_department.json b/erpnext/healthcare/doctype/medical_department/medical_department.json deleted file mode 100644 index 40f14caf72..0000000000 --- a/erpnext/healthcare/doctype/medical_department/medical_department.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:department", - "beta": 1, - "creation": "2017-02-27 13:38:30.806362", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "department", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-08-31 13:41:59.611698", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Medical Department", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "department", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "department", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/medical_department/medical_department.py b/erpnext/healthcare/doctype/medical_department/medical_department.py deleted file mode 100644 index 7be5a1ade3..0000000000 --- a/erpnext/healthcare/doctype/medical_department/medical_department.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class MedicalDepartment(Document): - pass diff --git a/erpnext/healthcare/doctype/medical_department/test_medical_department.py b/erpnext/healthcare/doctype/medical_department/test_medical_department.py deleted file mode 100644 index 6435a23e92..0000000000 --- a/erpnext/healthcare/doctype/medical_department/test_medical_department.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Medical Department') - -class TestMedicalDepartment(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/normal_test_result/__init__.py b/erpnext/healthcare/doctype/normal_test_result/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json deleted file mode 100644 index c8f43d3a54..0000000000 --- a/erpnext/healthcare/doctype/normal_test_result/normal_test_result.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-02-22 15:06:08.295224", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "lab_test_name", - "lab_test_event", - "result_value", - "lab_test_uom", - "secondary_uom_result", - "secondary_uom", - "conversion_factor", - "column_break_10", - "allow_blank", - "normal_range", - "lab_test_comment", - "bold", - "italic", - "underline", - "template", - "require_result_value" - ], - "fields": [ - { - "fieldname": "lab_test_name", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Test Name", - "read_only": 1 - }, - { - "fieldname": "lab_test_event", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Event", - "read_only": 1 - }, - { - "depends_on": "eval:doc.require_result_value", - "fieldname": "result_value", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Result Value" - }, - { - "depends_on": "eval:doc.require_result_value", - "fieldname": "lab_test_uom", - "fieldtype": "Link", - "label": "UOM", - "options": "Lab Test UOM", - "read_only": 1 - }, - { - "depends_on": "eval:doc.require_result_value", - "fieldname": "normal_range", - "fieldtype": "Long Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Normal Range", - "read_only": 1 - }, - { - "depends_on": "eval:doc.require_result_value", - "fieldname": "lab_test_comment", - "fieldtype": "Data", - "hidden": 1, - "in_list_view": 1, - "label": "Comment", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "template", - "fieldtype": "Link", - "hidden": 1, - "label": "Template", - "options": "Lab Test Template", - "print_hide": 1, - "report_hide": 1 - }, - { - "depends_on": "eval:doc.require_result_value", - "fieldname": "secondary_uom", - "fieldtype": "Link", - "label": "Secondary UOM", - "options": "Lab Test UOM", - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "secondary_uom", - "fieldname": "conversion_factor", - "fieldtype": "Float", - "label": "Conversion Factor", - "mandatory_depends_on": "secondary_uom", - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "eval:doc.require_result_value && doc.result_value", - "fieldname": "secondary_uom_result", - "fieldtype": "Data", - "label": "Secondary UOM Result", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "allow_on_submit": 1, - "default": "0", - "depends_on": "eval:doc.require_result_value", - "fieldname": "bold", - "fieldtype": "Check", - "label": "Bold", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "allow_on_submit": 1, - "default": "0", - "depends_on": "eval:doc.require_result_value", - "fieldname": "italic", - "fieldtype": "Check", - "label": "Italic", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "allow_on_submit": 1, - "default": "0", - "depends_on": "eval:doc.require_result_value", - "fieldname": "underline", - "fieldtype": "Check", - "label": "Underline", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "require_result_value", - "fieldtype": "Check", - "hidden": 1, - "label": "Require Result Value", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "default": "1", - "depends_on": "eval:doc.require_result_value", - "fieldname": "allow_blank", - "fieldtype": "Check", - "label": "Allow Blank", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-07-08 16:03:17.522893", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Normal Test Result", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py b/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py deleted file mode 100644 index e72f7269fc..0000000000 --- a/erpnext/healthcare/doctype/normal_test_result/normal_test_result.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class NormalTestResult(Document): - pass diff --git a/erpnext/healthcare/doctype/normal_test_template/__init__.py b/erpnext/healthcare/doctype/normal_test_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json b/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json deleted file mode 100644 index 8dd6476ea8..0000000000 --- a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2016-02-22 16:09:54.310628", - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "heading_text", - "lab_test_event", - "allow_blank", - "lab_test_uom", - "secondary_uom", - "conversion_factor", - "column_break_5", - "normal_range" - ], - "fields": [ - { - "fieldname": "heading_text", - "fieldtype": "Heading", - "ignore_xss_filter": 1, - "label": "Test" - }, - { - "fieldname": "lab_test_event", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Event" - }, - { - "fieldname": "lab_test_uom", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "UOM", - "options": "Lab Test UOM" - }, - { - "fieldname": "normal_range", - "fieldtype": "Long Text", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Normal Range" - }, - { - "fieldname": "column_break_5", - "fieldtype": "Column Break" - }, - { - "fieldname": "secondary_uom", - "fieldtype": "Link", - "label": "Secondary UOM", - "options": "Lab Test UOM" - }, - { - "depends_on": "secondary_uom", - "fieldname": "conversion_factor", - "fieldtype": "Float", - "label": "Conversion Factor", - "mandatory_depends_on": "secondary_uom" - }, - { - "default": "0", - "fieldname": "allow_blank", - "fieldtype": "Check", - "label": "Allow Blank" - } - ], - "istable": 1, - "links": [], - "modified": "2020-06-23 13:28:40.156224", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Normal Test Template", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC" -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.py b/erpnext/healthcare/doctype/normal_test_template/normal_test_template.py deleted file mode 100644 index 9df48559cd..0000000000 --- a/erpnext/healthcare/doctype/normal_test_template/normal_test_template.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class NormalTestTemplate(Document): - pass diff --git a/erpnext/healthcare/doctype/organism/__init__.py b/erpnext/healthcare/doctype/organism/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/organism/organism.js b/erpnext/healthcare/doctype/organism/organism.js deleted file mode 100644 index fbcb0942e9..0000000000 --- a/erpnext/healthcare/doctype/organism/organism.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Organism', { -}); diff --git a/erpnext/healthcare/doctype/organism/organism.json b/erpnext/healthcare/doctype/organism/organism.json deleted file mode 100644 index 88a7686777..0000000000 --- a/erpnext/healthcare/doctype/organism/organism.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:organism", - "beta": 1, - "creation": "2019-09-06 16:29:07.797960", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "organism", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Organism", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "abbr", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Abbr", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-10-04 19:45:33.353753", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Organism", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "organism, abbr", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "organism", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/organism/organism.py b/erpnext/healthcare/doctype/organism/organism.py deleted file mode 100644 index dfb9c0dd0d..0000000000 --- a/erpnext/healthcare/doctype/organism/organism.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class Organism(Document): - pass diff --git a/erpnext/healthcare/doctype/organism/test_organism.py b/erpnext/healthcare/doctype/organism/test_organism.py deleted file mode 100644 index 9d7ea0ba33..0000000000 --- a/erpnext/healthcare/doctype/organism/test_organism.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestOrganism(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/organism_test_item/__init__.py b/erpnext/healthcare/doctype/organism_test_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json deleted file mode 100644 index 56d0a4d905..0000000000 --- a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2019-09-06 16:37:59.698996", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "organism", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Organism", - "length": 0, - "no_copy": 0, - "options": "Organism", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "colony_population", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Colony Population", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "colony_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Colony UOM", - "length": 0, - "no_copy": 0, - "options": "Lab Test UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-10-04 19:48:04.104234", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Organism Test Item", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py b/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py deleted file mode 100644 index 6bb8e8f4c0..0000000000 --- a/erpnext/healthcare/doctype/organism_test_item/organism_test_item.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class OrganismTestItem(Document): - pass diff --git a/erpnext/healthcare/doctype/organism_test_result/__init__.py b/erpnext/healthcare/doctype/organism_test_result/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json deleted file mode 100644 index 8b238de4cd..0000000000 --- a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2019-09-06 16:37:59.698996", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "organism", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Organism", - "length": 0, - "no_copy": 0, - "options": "Organism", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "colony_population", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Colony Population", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "colony_uom", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Colony UOM", - "length": 0, - "no_copy": 0, - "options": "Lab Test UOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-10-04 19:48:04.104234", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Organism Test Result", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py b/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py deleted file mode 100644 index 3428403e3c..0000000000 --- a/erpnext/healthcare/doctype/organism_test_result/organism_test_result.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class OrganismTestResult(Document): - pass diff --git a/erpnext/healthcare/doctype/patient/__init__.py b/erpnext/healthcare/doctype/patient/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js deleted file mode 100644 index 9266467155..0000000000 --- a/erpnext/healthcare/doctype/patient/patient.js +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Patient', { - refresh: function (frm) { - frm.set_query('patient', 'patient_relation', function () { - return { - filters: [ - ['Patient', 'name', '!=', frm.doc.name] - ] - }; - }); - frm.set_query('customer_group', {'is_group': 0}); - frm.set_query('default_price_list', { 'selling': 1}); - - if (frappe.defaults.get_default('patient_name_by') != 'Naming Series') { - frm.toggle_display('naming_series', false); - } else { - erpnext.toggle_naming_series(); - } - - if (frappe.defaults.get_default('collect_registration_fee') && frm.doc.status == 'Disabled') { - frm.add_custom_button(__('Invoice Patient Registration'), function () { - invoice_registration(frm); - }); - } - - if (frm.doc.patient_name && frappe.user.has_role('Physician')) { - frm.add_custom_button(__('Patient Progress'), function() { - frappe.route_options = {'patient': frm.doc.name}; - frappe.set_route('patient-progress'); - }, __('View')); - - frm.add_custom_button(__('Patient History'), function() { - frappe.route_options = {'patient': frm.doc.name}; - frappe.set_route('patient_history'); - }, __('View')); - } - - frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Patient'}; - frm.toggle_display(['address_html', 'contact_html'], !frm.is_new()); - - if (!frm.is_new()) { - if ((frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) { - frm.add_custom_button(__('Medical Record'), function () { - create_medical_record(frm); - }, 'Create'); - frm.toggle_enable(['customer'], 0); - } - frappe.contacts.render_address_and_contact(frm); - erpnext.utils.set_party_dashboard_indicators(frm); - } else { - frappe.contacts.clear_address_and_contact(frm); - } - }, - - onload: function (frm) { - if (frm.doc.dob) { - $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`); - } else { - $(frm.fields_dict['age_html'].wrapper).html(''); - } - } -}); - -frappe.ui.form.on('Patient', 'dob', function(frm) { - if (frm.doc.dob) { - let today = new Date(); - let birthDate = new Date(frm.doc.dob); - if (today < birthDate) { - frappe.msgprint(__('Please select a valid Date')); - frappe.model.set_value(frm.doctype,frm.docname, 'dob', ''); - } else { - let age_str = get_age(frm.doc.dob); - $(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`); - } - } else { - $(frm.fields_dict['age_html'].wrapper).html(''); - } -}); - -frappe.ui.form.on('Patient Relation', { - patient_relation_add: function(frm){ - frm.fields_dict['patient_relation'].grid.get_field('patient').get_query = function(doc){ - let patient_list = []; - if(!doc.__islocal) patient_list.push(doc.name); - $.each(doc.patient_relation, function(idx, val){ - if (val.patient) patient_list.push(val.patient); - }); - return { filters: [['Patient', 'name', 'not in', patient_list]] }; - }; - } -}); - -let create_medical_record = function (frm) { - frappe.route_options = { - 'patient': frm.doc.name, - 'status': 'Open', - 'reference_doctype': 'Patient Medical Record', - 'reference_owner': frm.doc.owner - }; - frappe.new_doc('Patient Medical Record'); -}; - -let get_age = function (birth) { - let ageMS = Date.parse(Date()) - Date.parse(birth); - let age = new Date(); - age.setTime(ageMS); - let years = age.getFullYear() - 1970; - return years + ' Year(s) ' + age.getMonth() + ' Month(s) ' + age.getDate() + ' Day(s)'; -}; - -let create_vital_signs = function (frm) { - if (!frm.doc.name) { - frappe.throw(__('Please save the patient first')); - } - frappe.route_options = { - 'patient': frm.doc.name, - }; - frappe.new_doc('Vital Signs'); -}; - -let create_encounter = function (frm) { - if (!frm.doc.name) { - frappe.throw(__('Please save the patient first')); - } - frappe.route_options = { - 'patient': frm.doc.name, - }; - frappe.new_doc('Patient Encounter'); -}; - -let invoice_registration = function (frm) { - frappe.call({ - doc: frm.doc, - method: 'invoice_patient_registration', - callback: function(data) { - if (!data.exc) { - if (data.message.invoice) { - frappe.set_route('Form', 'Sales Invoice', data.message.invoice); - } - cur_frm.reload_doc(); - } - } - }); -}; diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json deleted file mode 100644 index 4092a6a768..0000000000 --- a/erpnext/healthcare/doctype/patient/patient.json +++ /dev/null @@ -1,542 +0,0 @@ -{ - "actions": [], - "allow_events_in_timeline": 1, - "allow_import": 1, - "allow_rename": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2017-01-23 14:03:49.084370", - "description": "Patient", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "basic_info", - "naming_series", - "first_name", - "middle_name", - "last_name", - "patient_name", - "sex", - "blood_group", - "dob", - "age_html", - "image", - "column_break_14", - "status", - "uid", - "inpatient_record", - "inpatient_status", - "report_preference", - "mobile", - "phone", - "email", - "invite_user", - "user_id", - "address_contacts", - "address_html", - "column_break_22", - "contact_html", - "customer_details_section", - "customer", - "customer_group", - "territory", - "column_break_24", - "default_currency", - "default_price_list", - "language", - "personal_and_social_history", - "occupation", - "column_break_25", - "marital_status", - "sb_relation", - "patient_relation", - "allergy_medical_and_surgical_history", - "allergies", - "medication", - "column_break_20", - "medical_history", - "surgical_history", - "risk_factors", - "tobacco_past_use", - "tobacco_current_use", - "alcohol_past_use", - "alcohol_current_use", - "column_break_32", - "surrounding_factors", - "other_risk_factors", - "more_info", - "patient_details" - ], - "fields": [ - { - "fieldname": "basic_info", - "fieldtype": "Section Break", - "label": "Patient Demographics", - "oldfieldtype": "Section Break", - "options": "fa fa-user" - }, - { - "fieldname": "inpatient_status", - "fieldtype": "Select", - "in_preview": 1, - "label": "Inpatient Status", - "no_copy": 1, - "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled", - "read_only": 1 - }, - { - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "no_copy": 1, - "options": "Inpatient Record", - "read_only": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-PAT-.YYYY.-", - "print_hide": 1, - "report_hide": 1, - "set_only_once": 1 - }, - { - "bold": 1, - "fieldname": "patient_name", - "fieldtype": "Data", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Full Name", - "no_copy": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "sex", - "fieldtype": "Link", - "in_preview": 1, - "label": "Gender", - "options": "Gender", - "reqd": 1 - }, - { - "bold": 1, - "fieldname": "blood_group", - "fieldtype": "Select", - "in_preview": 1, - "label": "Blood Group", - "no_copy": 1, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative" - }, - { - "bold": 1, - "fieldname": "dob", - "fieldtype": "Date", - "in_preview": 1, - "label": "Date of birth", - "no_copy": 1 - }, - { - "fieldname": "age_html", - "fieldtype": "HTML", - "label": "Age", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "status", - "fieldtype": "Select", - "in_filter": 1, - "in_list_view": 1, - "label": "Status", - "no_copy": 1, - "options": "Active\nDisabled", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "in_preview": 1, - "label": "Image", - "no_copy": 1, - "print_hide": 1, - "width": "50%" - }, - { - "fieldname": "column_break_14", - "fieldtype": "Column Break" - }, - { - "description": "If \"Link Customer to Patient\" is checked in Healthcare Settings and an existing Customer is not selected then, a Customer will be created for this Patient for recording transactions in Accounts module.", - "fieldname": "customer", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Customer", - "no_copy": 1, - "options": "Customer", - "set_only_once": 1 - }, - { - "fieldname": "report_preference", - "fieldtype": "Select", - "label": "Report Preference", - "options": "\nEmail\nPrint" - }, - { - "bold": 1, - "fieldname": "mobile", - "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Mobile", - "no_copy": 1, - "options": "Phone" - }, - { - "bold": 1, - "fieldname": "email", - "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Email", - "no_copy": 1, - "options": "Email" - }, - { - "fieldname": "phone", - "fieldtype": "Data", - "in_filter": 1, - "label": "Phone", - "no_copy": 1, - "options": "Phone" - }, - { - "collapsible": 1, - "fieldname": "sb_relation", - "fieldtype": "Section Break", - "label": "Patient Relation" - }, - { - "fieldname": "patient_relation", - "fieldtype": "Table", - "label": "Patient Relation", - "options": "Patient Relation" - }, - { - "collapsible": 1, - "fieldname": "allergy_medical_and_surgical_history", - "fieldtype": "Section Break", - "label": "Allergies, Medical and Surgical History" - }, - { - "fieldname": "allergies", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Allergies", - "no_copy": 1 - }, - { - "fieldname": "medication", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Medication", - "no_copy": 1 - }, - { - "fieldname": "column_break_20", - "fieldtype": "Column Break" - }, - { - "fieldname": "medical_history", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Medical History", - "no_copy": 1 - }, - { - "fieldname": "surgical_history", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Surgical History", - "no_copy": 1 - }, - { - "collapsible": 1, - "fieldname": "personal_and_social_history", - "fieldtype": "Section Break", - "label": "Personal and Social History" - }, - { - "fieldname": "occupation", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "label": "Occupation", - "no_copy": 1 - }, - { - "fieldname": "column_break_25", - "fieldtype": "Column Break" - }, - { - "fieldname": "marital_status", - "fieldtype": "Select", - "label": "Marital Status", - "no_copy": 1, - "options": "\nSingle\nMarried\nDivorced\nWidow" - }, - { - "collapsible": 1, - "fieldname": "risk_factors", - "fieldtype": "Section Break", - "label": "Risk Factors" - }, - { - "fieldname": "tobacco_past_use", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "label": "Tobacco Consumption (Past)", - "no_copy": 1 - }, - { - "fieldname": "tobacco_current_use", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "label": "Tobacco Consumption (Present)", - "no_copy": 1 - }, - { - "fieldname": "alcohol_past_use", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "label": "Alcohol Consumption (Past)", - "no_copy": 1 - }, - { - "fieldname": "alcohol_current_use", - "fieldtype": "Data", - "ignore_user_permissions": 1, - "label": "Alcohol Consumption (Present)", - "no_copy": 1 - }, - { - "fieldname": "column_break_32", - "fieldtype": "Column Break" - }, - { - "fieldname": "surrounding_factors", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Occupational Hazards and Environmental Factors", - "no_copy": 1 - }, - { - "fieldname": "other_risk_factors", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Other Risk Factors", - "no_copy": 1 - }, - { - "collapsible": 1, - "collapsible_depends_on": "patient_details", - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "oldfieldtype": "Section Break", - "options": "fa fa-file-text" - }, - { - "description": "Additional information regarding the patient", - "fieldname": "patient_details", - "fieldtype": "Text", - "ignore_xss_filter": 1, - "label": "Patient Details", - "no_copy": 1 - }, - { - "fieldname": "default_currency", - "fieldtype": "Link", - "label": "Billing Currency", - "options": "Currency" - }, - { - "fieldname": "last_name", - "fieldtype": "Data", - "label": "Last Name", - "no_copy": 1 - }, - { - "fieldname": "first_name", - "fieldtype": "Data", - "label": "First Name", - "no_copy": 1, - "oldfieldtype": "Data", - "reqd": 1 - }, - { - "fieldname": "middle_name", - "fieldtype": "Data", - "label": "Middle Name (optional)", - "no_copy": 1 - }, - { - "collapsible": 1, - "fieldname": "customer_details_section", - "fieldtype": "Section Break", - "label": "Customer Details" - }, - { - "fieldname": "customer_group", - "fieldtype": "Link", - "label": "Customer Group", - "options": "Customer Group" - }, - { - "fieldname": "territory", - "fieldtype": "Link", - "label": "Territory", - "options": "Territory" - }, - { - "fieldname": "column_break_24", - "fieldtype": "Column Break" - }, - { - "fieldname": "default_price_list", - "fieldtype": "Link", - "label": "Default Price List", - "options": "Price List" - }, - { - "fieldname": "language", - "fieldtype": "Link", - "label": "Print Language", - "options": "Language" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "address_contacts", - "fieldtype": "Section Break", - "label": "Address and Contact", - "options": "fa fa-map-marker" - }, - { - "fieldname": "address_html", - "fieldtype": "HTML", - "label": "Address HTML", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_22", - "fieldtype": "Column Break" - }, - { - "fieldname": "contact_html", - "fieldtype": "HTML", - "label": "Contact HTML", - "no_copy": 1, - "read_only": 1 - }, - { - "allow_in_quick_entry": 1, - "default": "1", - "fieldname": "invite_user", - "fieldtype": "Check", - "label": "Invite as User", - "no_copy": 1, - "read_only_depends_on": "eval: doc.user_id" - }, - { - "fieldname": "user_id", - "fieldtype": "Read Only", - "label": "User ID", - "no_copy": 1, - "options": "User" - }, - { - "allow_in_quick_entry": 1, - "bold": 1, - "fieldname": "uid", - "fieldtype": "Data", - "in_standard_filter": 1, - "label": "Identification Number (UID)", - "unique": 1 - } - ], - "icon": "fa fa-user", - "image_field": "image", - "links": [], - "max_attachments": 50, - "modified": "2021-03-14 13:21:09.759906", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient", - "name_case": "Title Case", - "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": "Physician", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "search_fields": "patient_name,mobile,email,phone,uid", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "ASC", - "title_field": "patient_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py deleted file mode 100644 index 970c000d00..0000000000 --- a/erpnext/healthcare/doctype/patient/patient.py +++ /dev/null @@ -1,282 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import dateutil -import frappe -from frappe import _ -from frappe.contacts.address_and_contact import load_address_and_contact -from frappe.contacts.doctype.contact.contact import get_default_contact -from frappe.model.document import Document -from frappe.model.naming import set_name_by_naming_series -from frappe.utils import cint, cstr, getdate -from frappe.utils.nestedset import get_root_of - -from erpnext import get_default_currency -from erpnext.accounts.party import get_dashboard_info -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( - get_income_account, - get_receivable_account, - send_registration_sms, -) - - -class Patient(Document): - def onload(self): - '''Load address and contacts in `__onload`''' - load_address_and_contact(self) - self.load_dashboard_info() - - def validate(self): - self.set_full_name() - - def before_insert(self): - self.set_missing_customer_details() - - def after_insert(self): - if frappe.db.get_single_value('Healthcare Settings', 'collect_registration_fee'): - frappe.db.set_value('Patient', self.name, 'status', 'Disabled') - else: - send_registration_sms(self) - self.reload() # self.notify_update() - - def on_update(self): - if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient'): - if self.customer: - customer = frappe.get_doc('Customer', self.customer) - if self.customer_group: - customer.customer_group = self.customer_group - if self.territory: - customer.territory = self.territory - customer.customer_name = self.patient_name - customer.default_price_list = self.default_price_list - customer.default_currency = self.default_currency - customer.language = self.language - customer.ignore_mandatory = True - customer.save(ignore_permissions=True) - else: - create_customer(self) - - self.set_contact() # add or update contact - - if not self.user_id and self.email and self.invite_user: - self.create_website_user() - - def load_dashboard_info(self): - if self.customer: - info = get_dashboard_info('Customer', self.customer, None) - self.set_onload('dashboard_info', info) - - def set_full_name(self): - if self.last_name: - self.patient_name = ' '.join(filter(None, [self.first_name, self.last_name])) - else: - self.patient_name = self.first_name - - def set_missing_customer_details(self): - if not self.customer_group: - self.customer_group = frappe.db.get_single_value('Selling Settings', 'customer_group') or get_root_of('Customer Group') - if not self.territory: - self.territory = frappe.db.get_single_value('Selling Settings', 'territory') or get_root_of('Territory') - if not self.default_price_list: - self.default_price_list = frappe.db.get_single_value('Selling Settings', 'selling_price_list') - - if not self.customer_group or not self.territory or not self.default_price_list: - frappe.msgprint(_('Please set defaults for Customer Group, Territory and Selling Price List in Selling Settings'), alert=True) - - if not self.default_currency: - self.default_currency = get_default_currency() - if not self.language: - self.language = frappe.db.get_single_value('System Settings', 'language') - - def create_website_user(self): - if self.email and not frappe.db.exists('User', self.email): - user = frappe.get_doc({ - 'doctype': 'User', - 'first_name': self.first_name, - 'last_name': self.last_name, - 'email': self.email, - 'user_type': 'Website User', - 'gender': self.sex, - 'phone': self.phone, - 'mobile_no': self.mobile, - 'birth_date': self.dob - }) - user.flags.ignore_permissions = True - user.enabled = True - user.send_welcome_email = True - user.add_roles('Patient') - frappe.db.set_value(self.doctype, self.name, 'user_id', user.name) - - def autoname(self): - patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by') - if patient_name_by == 'Patient Name': - self.name = self.get_patient_name() - else: - set_name_by_naming_series(self) - - def get_patient_name(self): - self.set_full_name() - name = self.patient_name - if frappe.db.get_value('Patient', name): - count = frappe.db.sql("""select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabPatient - where name like %s""", "%{0} - %".format(name), as_list=1)[0][0] - count = cint(count) + 1 - return "{0} - {1}".format(name, cstr(count)) - - return name - - @property - def age(self): - if not self.dob: - return - dob = getdate(self.dob) - age = dateutil.relativedelta.relativedelta(getdate(), dob) - return age - - def get_age(self): - age = self.age - if not age: - return - age_str = f'{str(age.years)} {_("Years(s)")} {str(age.months)} {_("Month(s)")} {str(age.days)} {_("Day(s)")}' - return age_str - - @frappe.whitelist() - def invoice_patient_registration(self): - if frappe.db.get_single_value('Healthcare Settings', 'registration_fee'): - company = frappe.defaults.get_user_default('company') - if not company: - company = frappe.db.get_single_value('Global Defaults', 'default_company') - - sales_invoice = make_invoice(self.name, company) - sales_invoice.save(ignore_permissions=True) - frappe.db.set_value('Patient', self.name, 'status', 'Active') - send_registration_sms(self) - - return {'invoice': sales_invoice.name} - - def set_contact(self): - if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}): - old_doc = self.get_doc_before_save() - if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone: - self.update_contact() - else: - self.reload() - if self.email or self.mobile or self.phone: - contact = frappe.get_doc({ - 'doctype': 'Contact', - 'first_name': self.first_name, - 'middle_name': self.middle_name, - 'last_name': self.last_name, - 'gender': self.sex, - 'is_primary_contact': 1 - }) - contact.append('links', dict(link_doctype='Patient', link_name=self.name)) - if self.customer: - contact.append('links', dict(link_doctype='Customer', link_name=self.customer)) - - contact.insert(ignore_permissions=True) - self.update_contact(contact) # update email, mobile and phone - - def update_contact(self, contact=None): - if not contact: - contact_name = get_default_contact(self.doctype, self.name) - if contact_name: - contact = frappe.get_doc('Contact', contact_name) - - if contact: - if self.email and self.email != contact.email_id: - for email in contact.email_ids: - email.is_primary = True if email.email_id == self.email else False - contact.add_email(self.email, is_primary=True) - contact.set_primary_email() - - if self.mobile and self.mobile != contact.mobile_no: - for mobile in contact.phone_nos: - mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False - contact.add_phone(self.mobile, is_primary_mobile_no=True) - contact.set_primary('mobile_no') - - if self.phone and self.phone != contact.phone: - for phone in contact.phone_nos: - phone.is_primary_phone = True if phone.phone == self.phone else False - contact.add_phone(self.phone, is_primary_phone=True) - contact.set_primary('phone') - - contact.flags.ignore_validate = True # disable hook TODO: safe? - contact.save(ignore_permissions=True) - - -def create_customer(doc): - customer = frappe.get_doc({ - 'doctype': 'Customer', - 'customer_name': doc.patient_name, - 'customer_group': doc.customer_group or frappe.db.get_single_value('Selling Settings', 'customer_group'), - 'territory' : doc.territory or frappe.db.get_single_value('Selling Settings', 'territory'), - 'customer_type': 'Individual', - 'default_currency': doc.default_currency, - 'default_price_list': doc.default_price_list, - 'language': doc.language - }).insert(ignore_permissions=True, ignore_mandatory=True) - - frappe.db.set_value('Patient', doc.name, 'customer', customer.name) - frappe.msgprint(_('Customer {0} is created.').format(customer.name), alert=True) - -def make_invoice(patient, company): - uom = frappe.db.exists('UOM', 'Nos') or frappe.db.get_single_value('Stock Settings', 'stock_uom') - sales_invoice = frappe.new_doc('Sales Invoice') - sales_invoice.customer = frappe.db.get_value('Patient', patient, 'customer') - sales_invoice.due_date = getdate() - sales_invoice.company = company - sales_invoice.is_pos = 0 - sales_invoice.debit_to = get_receivable_account(company) - - item_line = sales_invoice.append('items') - item_line.item_name = 'Registration Fee' - item_line.description = 'Registration Fee' - item_line.qty = 1 - item_line.uom = uom - item_line.conversion_factor = 1 - item_line.income_account = get_income_account(None, company) - item_line.rate = frappe.db.get_single_value('Healthcare Settings', 'registration_fee') - item_line.amount = item_line.rate - sales_invoice.set_missing_values() - return sales_invoice - -@frappe.whitelist() -def get_patient_detail(patient): - patient_dict = frappe.db.sql("""select * from tabPatient where name=%s""", (patient), as_dict=1) - if not patient_dict: - frappe.throw(_('Patient not found')) - vital_sign = frappe.db.sql("""select * from `tabVital Signs` where patient=%s - order by signs_date desc limit 1""", (patient), as_dict=1) - - details = patient_dict[0] - if vital_sign: - details.update(vital_sign[0]) - return details - -def get_timeline_data(doctype, name): - ''' - Return Patient's timeline data from medical records - Also include the associated Customer timeline data - ''' - patient_timeline_data = dict(frappe.db.sql(''' - SELECT - unix_timestamp(communication_date), count(*) - FROM - `tabPatient Medical Record` - WHERE - patient=%s - and `communication_date` > date_sub(curdate(), interval 1 year) - GROUP BY communication_date''', name)) - - customer = frappe.db.get_value(doctype, name, 'customer') - if customer: - from erpnext.accounts.party import get_timeline_data - customer_timeline_data = get_timeline_data('Customer', customer) - patient_timeline_data.update(customer_timeline_data) - - return patient_timeline_data diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py deleted file mode 100644 index a388b74f61..0000000000 --- a/erpnext/healthcare/doctype/patient/patient_dashboard.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'heatmap': True, - 'heatmap_message': _('This is based on transactions against this Patient. See timeline below for details'), - 'fieldname': 'patient', - 'non_standard_fieldnames': { - 'Payment Entry': 'party' - }, - 'transactions': [ - { - 'label': _('Appointments and Encounters'), - 'items': ['Patient Appointment', 'Vital Signs', 'Patient Encounter'] - }, - { - 'label': _('Lab Tests and Vital Signs'), - 'items': ['Lab Test', 'Sample Collection'] - }, - { - 'label': _('Rehab and Physiotherapy'), - 'items': ['Patient Assessment', 'Therapy Session', 'Therapy Plan'] - }, - { - 'label': _('Surgery'), - 'items': ['Clinical Procedure'] - }, - { - 'label': _('Admissions'), - 'items': ['Inpatient Record', 'Inpatient Medication Order'] - }, - { - 'label': _('Billing and Payments'), - 'items': ['Sales Invoice', 'Payment Entry'] - } - ] - } diff --git a/erpnext/healthcare/doctype/patient/test_patient.py b/erpnext/healthcare/doctype/patient/test_patient.py deleted file mode 100644 index 4b8c732646..0000000000 --- a/erpnext/healthcare/doctype/patient/test_patient.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe - -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient - - -class TestPatient(unittest.TestCase): - def test_customer_created(self): - frappe.db.sql("""delete from `tabPatient`""") - frappe.db.set_value('Healthcare Settings', None, 'link_customer_to_patient', 1) - patient = create_patient() - self.assertTrue(frappe.db.get_value('Patient', patient, 'customer')) - - def test_patient_registration(self): - frappe.db.sql("""delete from `tabPatient`""") - settings = frappe.get_single('Healthcare Settings') - settings.collect_registration_fee = 1 - settings.registration_fee = 500 - settings.save() - - patient = create_patient() - patient = frappe.get_doc('Patient', patient) - self.assertEqual(patient.status, 'Disabled') - - # check sales invoice and patient status - result = patient.invoice_patient_registration() - self.assertTrue(frappe.db.exists('Sales Invoice', result.get('invoice'))) - self.assertTrue(patient.status, 'Active') - - settings.collect_registration_fee = 0 - settings.save() diff --git a/erpnext/healthcare/doctype/patient_appointment/__init__.py b/erpnext/healthcare/doctype/patient_appointment/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js deleted file mode 100644 index 49847d5bc8..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ /dev/null @@ -1,657 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt -frappe.provide('erpnext.queries'); -frappe.ui.form.on('Patient Appointment', { - setup: function(frm) { - frm.custom_make_buttons = { - 'Vital Signs': 'Vital Signs', - 'Patient Encounter': 'Patient Encounter' - }; - }, - - onload: function(frm) { - if (frm.is_new()) { - frm.set_value('appointment_time', null); - frm.disable_save(); - } - }, - - refresh: function(frm) { - frm.set_query('patient', function() { - return { - filters: { 'status': 'Active' } - }; - }); - - frm.set_query('practitioner', function() { - if (frm.doc.department) { - return { - filters: { - 'department': frm.doc.department - } - }; - } - }); - - frm.set_query('service_unit', function() { - return { - query: 'erpnext.controllers.queries.get_healthcare_service_units', - filters: { - company: frm.doc.company, - inpatient_record: frm.doc.inpatient_record - } - }; - }); - - frm.set_query('therapy_plan', function() { - return { - filters: { - 'patient': frm.doc.patient - } - }; - }); - - frm.trigger('set_therapy_type_filter'); - - if (frm.is_new()) { - frm.page.set_primary_action(__('Check Availability'), function() { - if (!frm.doc.patient) { - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Please select Patient first'), - indicator: 'red' - }); - } else { - frappe.call({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', - args: { 'patient': frm.doc.patient }, - callback: function(data) { - if (data.message == true) { - if (frm.doc.mode_of_payment && frm.doc.paid_amount) { - check_and_set_availability(frm); - } - if (!frm.doc.mode_of_payment) { - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Please select a Mode of Payment first'), - indicator: 'red' - }); - } - if (!frm.doc.paid_amount) { - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Please set the Paid Amount first'), - indicator: 'red' - }); - } - } else { - check_and_set_availability(frm); - } - } - }); - } - }); - } else { - frm.page.set_primary_action(__('Save'), () => frm.save()); - } - - if (frm.doc.patient) { - frm.add_custom_button(__('Patient History'), function() { - frappe.route_options = { 'patient': frm.doc.patient }; - frappe.set_route('patient_history'); - }, __('View')); - } - - if (frm.doc.status == 'Open' || (frm.doc.status == 'Scheduled' && !frm.doc.__islocal)) { - frm.add_custom_button(__('Cancel'), function() { - update_status(frm, 'Cancelled'); - }); - frm.add_custom_button(__('Reschedule'), function() { - check_and_set_availability(frm); - }); - - if (frm.doc.procedure_template) { - frm.add_custom_button(__('Clinical Procedure'), function() { - frappe.model.open_mapped_doc({ - method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.make_procedure', - frm: frm, - }); - }, __('Create')); - } else if (frm.doc.therapy_type) { - frm.add_custom_button(__('Therapy Session'), function() { - frappe.model.open_mapped_doc({ - method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session', - frm: frm, - }) - }, 'Create'); - } else { - frm.add_custom_button(__('Patient Encounter'), function() { - frappe.model.open_mapped_doc({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.make_encounter', - frm: frm, - }); - }, __('Create')); - } - - frm.add_custom_button(__('Vital Signs'), function() { - create_vital_signs(frm); - }, __('Create')); - } - }, - - patient: function(frm) { - if (frm.doc.patient) { - frm.trigger('toggle_payment_fields'); - frappe.call({ - method: 'frappe.client.get', - args: { - doctype: 'Patient', - name: frm.doc.patient - }, - callback: function(data) { - let age = null; - if (data.message.dob) { - age = calculate_age(data.message.dob); - } - frappe.model.set_value(frm.doctype, frm.docname, 'patient_age', age); - } - }); - } else { - frm.set_value('patient_name', ''); - frm.set_value('patient_sex', ''); - frm.set_value('patient_age', ''); - frm.set_value('inpatient_record', ''); - } - }, - - practitioner: function(frm) { - if (frm.doc.practitioner) { - frm.events.set_payment_details(frm); - } - }, - - appointment_type: function(frm) { - if (frm.doc.appointment_type) { - frm.events.set_payment_details(frm); - } - }, - - set_payment_details: function(frm) { - frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing').then(val => { - if (val) { - frappe.call({ - method: 'erpnext.healthcare.utils.get_service_item_and_practitioner_charge', - args: { - doc: frm.doc - }, - callback: function(data) { - if (data.message) { - frappe.model.set_value(frm.doctype, frm.docname, 'paid_amount', data.message.practitioner_charge); - frappe.model.set_value(frm.doctype, frm.docname, 'billing_item', data.message.service_item); - } - } - }); - } - }); - }, - - therapy_plan: function(frm) { - frm.trigger('set_therapy_type_filter'); - }, - - set_therapy_type_filter: function(frm) { - if (frm.doc.therapy_plan) { - frm.call('get_therapy_types').then(r => { - frm.set_query('therapy_type', function() { - return { - filters: { - 'name': ['in', r.message] - } - }; - }); - }); - } - }, - - therapy_type: function(frm) { - if (frm.doc.therapy_type) { - frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => { - if (r.default_duration) { - frm.set_value('duration', r.default_duration) - } - }); - } - }, - - get_procedure_from_encounter: function(frm) { - get_prescribed_procedure(frm); - }, - - toggle_payment_fields: function(frm) { - frappe.call({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd', - args: { 'patient': frm.doc.patient }, - callback: function(data) { - if (data.message.fee_validity) { - // if fee validity exists and automated appointment invoicing is enabled, - // show payment fields as non-mandatory - frm.toggle_display('mode_of_payment', 0); - frm.toggle_display('paid_amount', 0); - frm.toggle_display('billing_item', 0); - frm.toggle_reqd('mode_of_payment', 0); - frm.toggle_reqd('paid_amount', 0); - frm.toggle_reqd('billing_item', 0); - } else if (data.message) { - frm.toggle_display('mode_of_payment', 1); - frm.toggle_display('paid_amount', 1); - frm.toggle_display('billing_item', 1); - frm.toggle_reqd('mode_of_payment', 1); - frm.toggle_reqd('paid_amount', 1); - frm.toggle_reqd('billing_item', 1); - } else { - // if automated appointment invoicing is disabled, hide fields - frm.toggle_display('mode_of_payment', data.message ? 1 : 0); - frm.toggle_display('paid_amount', data.message ? 1 : 0); - frm.toggle_display('billing_item', data.message ? 1 : 0); - frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0); - frm.toggle_reqd('paid_amount', data.message ? 1 : 0); - frm.toggle_reqd('billing_item', data.message ? 1 : 0); - } - } - }); - }, - - 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' - }); - } - } - }); - } - } -}); - -let check_and_set_availability = function(frm) { - let selected_slot = null; - let service_unit = null; - let duration = null; - - show_availability(); - - function show_empty_state(practitioner, appointment_date) { - frappe.msgprint({ - title: __('Not Available'), - message: __('Healthcare Practitioner {0} not available on {1}', [practitioner.bold(), appointment_date.bold()]), - indicator: 'red' - }); - } - - function show_availability() { - let selected_practitioner = ''; - let d = new frappe.ui.Dialog({ - title: __('Available slots'), - fields: [ - { fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department' }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner' }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date' }, - { fieldtype: 'Section Break' }, - { fieldtype: 'HTML', fieldname: 'available_slots' } - - ], - primary_action_label: __('Book'), - primary_action: function() { - frm.set_value('appointment_time', selected_slot); - if (!frm.doc.duration) { - frm.set_value('duration', duration); - } - frm.set_value('practitioner', d.get_value('practitioner')); - frm.set_value('department', d.get_value('department')); - frm.set_value('appointment_date', d.get_value('appointment_date')); - if (service_unit) { - frm.set_value('service_unit', service_unit); - } - d.hide(); - frm.enable_save(); - frm.save(); - d.get_primary_btn().attr('disabled', true); - } - }); - - d.set_values({ - 'department': frm.doc.department, - 'practitioner': frm.doc.practitioner, - 'appointment_date': frm.doc.appointment_date - }); - - d.fields_dict['department'].df.onchange = () => { - d.set_values({ - 'practitioner': '' - }); - let department = d.get_value('department'); - if (department) { - d.fields_dict.practitioner.get_query = function() { - return { - filters: { - 'department': department - } - }; - }; - } - }; - - // disable dialog action initially - d.get_primary_btn().attr('disabled', true); - - // Field Change Handler - - let fd = d.fields_dict; - - d.fields_dict['appointment_date'].df.onchange = () => { - show_slots(d, fd); - }; - d.fields_dict['practitioner'].df.onchange = () => { - if (d.get_value('practitioner') && d.get_value('practitioner') != selected_practitioner) { - selected_practitioner = d.get_value('practitioner'); - show_slots(d, fd); - } - }; - d.show(); - } - - function show_slots(d, fd) { - if (d.get_value('appointment_date') && d.get_value('practitioner')) { - fd.available_slots.html(''); - frappe.call({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_availability_data', - args: { - practitioner: d.get_value('practitioner'), - date: d.get_value('appointment_date') - }, - callback: (r) => { - let data = r.message; - if (data.slot_details.length > 0) { - let $wrapper = d.fields_dict.available_slots.$wrapper; - - // make buttons for each slot - let slot_html = get_slots(data.slot_details); - - $wrapper - .css('margin-bottom', 0) - .addClass('text-center') - .html(slot_html); - - // highlight button when clicked - $wrapper.on('click', 'button', function() { - let $btn = $(this); - $wrapper.find('button').removeClass('btn-outline-primary'); - $btn.addClass('btn-outline-primary'); - selected_slot = $btn.attr('data-name'); - service_unit = $btn.attr('data-service-unit'); - duration = $btn.attr('data-duration'); - // enable primary action 'Book' - d.get_primary_btn().attr('disabled', null); - }); - - } else { - // fd.available_slots.html('Please select a valid date.'.bold()) - show_empty_state(d.get_value('practitioner'), d.get_value('appointment_date')); - } - }, - freeze: true, - freeze_message: __('Fetching Schedule...') - }); - } else { - fd.available_slots.html(__('Appointment date and Healthcare Practitioner are Mandatory').bold()); - } - } - - function get_slots(slot_details) { - let slot_html = ''; - let appointment_count = 0; - let disabled = false; - let start_str, slot_start_time, slot_end_time, interval, count, count_class, tool_tip, available_slots; - - slot_details.forEach((slot_info) => { - slot_html += `
- ${__('Practitioner Schedule:')} ${slot_info.slot_name}
- ${__('Service Unit:')} ${slot_info.service_unit} `; - - if (slot_info.service_unit_capacity) { - slot_html += `
${__('Maximum Capacity:')} ${slot_info.service_unit_capacity} `; - } - - slot_html += '


'; - - slot_html += slot_info.avail_slot.map(slot => { - appointment_count = 0; - disabled = false; - start_str = slot.from_time; - slot_start_time = moment(slot.from_time, 'HH:mm:ss'); - slot_end_time = moment(slot.to_time, 'HH:mm:ss'); - interval = (slot_end_time - slot_start_time) / 60000 | 0; - - // iterate in all booked appointments, update the start time and duration - slot_info.appointments.forEach((booked) => { - let booked_moment = moment(booked.appointment_time, 'HH:mm:ss'); - let end_time = booked_moment.clone().add(booked.duration, 'minutes'); - - // Deal with 0 duration appointments - if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_end_time)) { - if (booked.duration == 0) { - disabled = true; - return false; - } - } - - // Check for overlaps considering appointment duration - if (slot_info.allow_overlap != 1) { - if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { - // There is an overlap - disabled = true; - return false; - } - } else { - if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) { - appointment_count++; - } - if (appointment_count >= slot_info.service_unit_capacity) { - // There is an overlap - disabled = true; - return false; - } - } - }); - - if (slot_info.allow_overlap == 1 && slot_info.service_unit_capacity > 1) { - available_slots = slot_info.service_unit_capacity - appointment_count; - count = `${(available_slots > 0 ? available_slots : __('Full'))}`; - count_class = `${(available_slots > 0 ? 'badge-success' : 'badge-danger')}`; - tool_tip =`${available_slots} ${__('slots available for booking')}`; - } - return ` - `; - }).join(""); - - if (slot_info.service_unit_capacity) { - slot_html += `
${__('Each slot indicates the capacity currently available for booking')}`; - } - slot_html += `

`; - }); - - return slot_html; - } -}; - -let get_prescribed_procedure = function(frm) { - if (frm.doc.patient) { - frappe.call({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_procedure_prescribed', - args: { patient: frm.doc.patient }, - callback: function(r) { - if (r.message && r.message.length) { - show_procedure_templates(frm, r.message); - } else { - frappe.msgprint({ - title: __('Not Found'), - message: __('No Prescribed Procedures found for the selected Patient') - }); - } - } - }); - } else { - frappe.msgprint({ - title: __('Not Allowed'), - message: __('Please select a Patient first') - }); - } -}; - -let show_procedure_templates = function(frm, result) { - let d = new frappe.ui.Dialog({ - title: __('Prescribed Procedures'), - fields: [ - { - fieldtype: 'HTML', fieldname: 'procedure_template' - } - ] - }); - let html_field = d.fields_dict.procedure_template.$wrapper; - html_field.empty(); - $.each(result, function(x, y) { - let row = $(repl('
\ -
%(encounter)s
%(consulting_practitioner)s
%(encounter_date)s
\ -
%(procedure_template)s
%(practitioner)s
%(date)s
\ -

', { - name: y[0], procedure_template: y[1], - encounter: y[2], consulting_practitioner: y[3], encounter_date: y[4], - practitioner: y[5] ? y[5] : '', date: y[6] ? y[6] : '', department: y[7] ? y[7] : '' - })).appendTo(html_field); - row.find("a").click(function() { - frm.doc.procedure_template = $(this).attr('data-procedure-template'); - frm.doc.procedure_prescription = $(this).attr('data-name'); - frm.doc.practitioner = $(this).attr('data-practitioner'); - frm.doc.appointment_date = $(this).attr('data-date'); - frm.doc.department = $(this).attr('data-department'); - refresh_field('procedure_template'); - refresh_field('procedure_prescription'); - refresh_field('appointment_date'); - refresh_field('practitioner'); - refresh_field('department'); - d.hide(); - return false; - }); - }); - if (!result) { - let msg = __('There are no procedure prescribed for ') + frm.doc.patient; - $(repl('
%(msg)s
', { msg: msg })).appendTo(html_field); - } - 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('
\ -
%(encounter)s
%(practitioner)s
%(date)s
\ -
%(therapy)s
\ -

', { - therapy: y[0], - name: y[1], encounter: y[2], practitioner: y[3], date: y[4], - department: y[6] ? y[6] : '', therapy_plan: y[5] - })).appendTo(html_field); - - row.find("a").click(function() { - frm.doc.therapy_type = $(this).attr("data-therapy"); - frm.doc.practitioner = $(this).attr("data-practitioner"); - frm.doc.department = $(this).attr("data-department"); - frm.doc.therapy_plan = $(this).attr("data-therapy-plan"); - frm.refresh_field("therapy_type"); - frm.refresh_field("practitioner"); - frm.refresh_field("department"); - frm.refresh_field("therapy-plan"); - frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => { - if (r.default_duration) { - frm.set_value('duration', r.default_duration) - } - }); - d.hide(); - return false; - }); - }); - d.show(); -}; - -let create_vital_signs = function(frm) { - if (!frm.doc.patient) { - frappe.throw(__('Please select patient')); - } - frappe.route_options = { - 'patient': frm.doc.patient, - 'appointment': frm.doc.name, - 'company': frm.doc.company - }; - frappe.new_doc('Vital Signs'); -}; - -let update_status = function(frm, status) { - let doc = frm.doc; - frappe.confirm(__('Are you sure you want to cancel this appointment?'), - function() { - frappe.call({ - method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status', - args: { appointment_id: doc.name, status: status }, - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); - } - } - }); - } - ); -}; - -let calculate_age = function(birth) { - let ageMS = Date.parse(Date()) - Date.parse(birth); - let age = new Date(); - age.setTime(ageMS); - let years = age.getFullYear() - 1970; - return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; -}; diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json deleted file mode 100644 index 28d3a6dadf..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ /dev/null @@ -1,403 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2017-05-04 11:52:40.941507", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "naming_series", - "title", - "status", - "patient", - "patient_name", - "patient_sex", - "patient_age", - "inpatient_record", - "column_break_1", - "company", - "practitioner", - "practitioner_name", - "department", - "service_unit", - "section_break_12", - "appointment_type", - "duration", - "procedure_template", - "get_procedure_from_encounter", - "procedure_prescription", - "therapy_plan", - "therapy_type", - "get_prescribed_therapies", - "column_break_17", - "appointment_date", - "appointment_time", - "appointment_datetime", - "section_break_16", - "mode_of_payment", - "billing_item", - "invoiced", - "column_break_2", - "paid_amount", - "ref_sales_invoice", - "section_break_3", - "referring_practitioner", - "reminded", - "column_break_36", - "notes" - ], - "fields": [ - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1, - "search_index": 1, - "set_only_once": 1 - }, - { - "fieldname": "appointment_type", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Appointment Type", - "options": "Appointment Type", - "set_only_once": 1 - }, - { - "fetch_from": "appointment_type.default_duration", - "fieldname": "duration", - "fieldtype": "Int", - "in_filter": 1, - "label": "Duration (In Minutes)", - "set_only_once": 1 - }, - { - "fieldname": "column_break_1", - "fieldtype": "Column Break", - "read_only": 1 - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "status", - "fieldtype": "Select", - "in_filter": 1, - "in_list_view": 1, - "label": "Status", - "options": "\nScheduled\nOpen\nClosed\nCancelled", - "read_only": 1, - "search_index": 1 - }, - { - "depends_on": "eval:doc.patient;", - "fieldname": "procedure_template", - "fieldtype": "Link", - "label": "Clinical Procedure Template", - "options": "Clinical Procedure Template", - "set_only_once": 1 - }, - { - "depends_on": "eval:doc.__islocal && doc.patient", - "fieldname": "get_procedure_from_encounter", - "fieldtype": "Button", - "label": "Get Prescribed Clinical Procedures" - }, - { - "fieldname": "procedure_prescription", - "fieldtype": "Link", - "hidden": 1, - "label": "Procedure Prescription", - "no_copy": 1, - "options": "Procedure Prescription", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "service_unit", - "fieldtype": "Link", - "label": "Service Unit", - "options": "Healthcare Service Unit", - "read_only": 1 - }, - { - "depends_on": "eval:doc.practitioner;", - "fieldname": "section_break_12", - "fieldtype": "Section Break", - "label": "Appointment Details" - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "reqd": 1, - "search_index": 1, - "set_only_once": 1 - }, - { - "fetch_from": "practitioner.department", - "fieldname": "department", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Department", - "options": "Medical Department", - "search_index": 1, - "set_only_once": 1 - }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, - { - "fieldname": "appointment_date", - "fieldtype": "Date", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Date", - "read_only": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "appointment_time", - "fieldtype": "Time", - "in_list_view": 1, - "label": "Time", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "section_break_16", - "fieldtype": "Section Break", - "label": "Payments" - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fetch_from": "patient.sex", - "fieldname": "patient_sex", - "fieldtype": "Link", - "label": "Gender", - "no_copy": 1, - "options": "Gender", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "label": "Patient Age", - "read_only": 1 - }, - { - "fieldname": "appointment_datetime", - "fieldtype": "Datetime", - "hidden": 1, - "label": "Appointment Datetime", - "print_hide": 1, - "read_only": 1, - "report_hide": 1, - "search_index": 1 - }, - { - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "label": "Mode of Payment", - "options": "Mode of Payment", - "read_only_depends_on": "invoiced" - }, - { - "fieldname": "paid_amount", - "fieldtype": "Currency", - "label": "Paid Amount", - "read_only_depends_on": "invoiced" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "read_only": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "no_copy": 1, - "options": "Company", - "reqd": 1, - "set_only_once": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_3", - "fieldtype": "Section Break", - "label": "More Info" - }, - { - "fieldname": "notes", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Notes" - }, - { - "fieldname": "referring_practitioner", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Referring Practitioner", - "options": "Healthcare Practitioner" - }, - { - "default": "0", - "fieldname": "reminded", - "fieldtype": "Check", - "hidden": 1, - "label": "Reminded", - "print_hide": 1, - "report_hide": 1 - }, - { - "depends_on": "eval:doc.patient && doc.therapy_plan;", - "fieldname": "therapy_type", - "fieldtype": "Link", - "label": "Therapy", - "options": "Therapy Type", - "set_only_once": 1 - }, - { - "depends_on": "eval:doc.patient && doc.therapy_plan && doc.__islocal;", - "fieldname": "get_prescribed_therapies", - "fieldtype": "Button", - "label": "Get Prescribed Therapies" - }, - { - "depends_on": "eval: doc.patient;", - "fieldname": "therapy_plan", - "fieldtype": "Link", - "label": "Therapy Plan", - "options": "Therapy Plan", - "set_only_once": 1 - }, - { - "fieldname": "ref_sales_invoice", - "fieldtype": "Link", - "label": "Reference Sales Invoice", - "options": "Sales Invoice", - "read_only": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-APP-.YYYY.-", - "set_only_once": 1 - }, - { - "fieldname": "billing_item", - "fieldtype": "Link", - "label": "Billing Item", - "options": "Item", - "read_only": 1 - }, - { - "fieldname": "column_break_36", - "fieldtype": "Column Break" - }, - { - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "fetch_from": "practitioner.practitioner_name", - "fieldname": "practitioner_name", - "fieldtype": "Data", - "label": "Practitioner Name", - "read_only": 1 - } - ], - "links": [], - "modified": "2021-08-30 09:00:41.329387", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Appointment", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient, practitioner, department, appointment_date, appointment_time", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1, - "track_seen": 1 -} diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py deleted file mode 100755 index dcbcda09d8..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ /dev/null @@ -1,559 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import datetime -import json - -import frappe -from frappe import _ -from frappe.core.doctype.sms_settings.sms_settings import send_sms -from frappe.model.document import Document -from frappe.model.mapper import get_mapped_doc -from frappe.utils import flt, get_link_to_form, get_time, getdate - -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( - get_income_account, - get_receivable_account, -) -from erpnext.healthcare.utils import ( - check_fee_validity, - get_service_item_and_practitioner_charge, - manage_fee_validity, -) -from erpnext.hr.doctype.employee.employee import is_holiday - - -class MaximumCapacityError(frappe.ValidationError): - pass -class OverlapError(frappe.ValidationError): - pass - -class PatientAppointment(Document): - def validate(self): - self.validate_overlaps() - self.validate_service_unit() - self.set_appointment_datetime() - self.validate_customer_created() - self.set_status() - self.set_title() - - def after_insert(self): - self.update_prescription_details() - self.set_payment_details() - invoice_appointment(self) - self.update_fee_validity() - send_confirmation_msg(self) - - def set_title(self): - self.title = _('{0} with {1}').format(self.patient_name or self.patient, - self.practitioner_name or self.practitioner) - - def set_status(self): - today = getdate() - appointment_date = getdate(self.appointment_date) - - # If appointment is created for today set status as Open else Scheduled - if appointment_date == today: - self.status = 'Open' - elif appointment_date > today: - self.status = 'Scheduled' - - def validate_overlaps(self): - end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \ - + datetime.timedelta(minutes=flt(self.duration)) - - # all appointments for both patient and practitioner overlapping the duration of this appointment - overlapping_appointments = frappe.db.sql(""" - SELECT - name, practitioner, patient, appointment_time, duration, service_unit - FROM - `tabPatient Appointment` - WHERE - appointment_date=%(appointment_date)s AND name!=%(name)s AND status NOT IN ("Closed", "Cancelled") AND - (practitioner=%(practitioner)s OR patient=%(patient)s) AND - ((appointment_time<%(appointment_time)s AND appointment_time + INTERVAL duration MINUTE>%(appointment_time)s) OR - (appointment_time>%(appointment_time)s AND appointment_time<%(end_time)s) OR - (appointment_time=%(appointment_time)s)) - """, - { - 'appointment_date': self.appointment_date, - 'name': self.name, - 'practitioner': self.practitioner, - 'patient': self.patient, - 'appointment_time': self.appointment_time, - 'end_time':end_time.time() - }, - as_dict = True - ) - - if not overlapping_appointments: - return # No overlaps, nothing to validate! - - if self.service_unit: # validate service unit capacity if overlap enabled - allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', self.service_unit, - ['overlap_appointments', 'service_unit_capacity']) - if allow_overlap: - service_unit_appointments = list(filter(lambda appointment: appointment['service_unit'] == self.service_unit and - appointment['patient'] != self.patient, overlapping_appointments)) # if same patient already booked, it should be an overlap - if len(service_unit_appointments) >= (service_unit_capacity or 1): - frappe.throw(_("Not allowed, {} cannot exceed maximum capacity {}") - .format(frappe.bold(self.service_unit), frappe.bold(service_unit_capacity or 1)), MaximumCapacityError) - else: # service_unit_appointments within capacity, remove from overlapping_appointments - overlapping_appointments = [appointment for appointment in overlapping_appointments if appointment not in service_unit_appointments] - - if overlapping_appointments: - frappe.throw(_("Not allowed, cannot overlap appointment {}") - .format(frappe.bold(', '.join([appointment['name'] for appointment in overlapping_appointments]))), OverlapError) - - - def validate_service_unit(self): - if self.inpatient_record and self.service_unit: - from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( - get_current_healthcare_service_unit, - ) - - is_inpatient_occupancy_unit = frappe.db.get_value('Healthcare Service Unit', self.service_unit, - 'inpatient_occupancy') - service_unit = get_current_healthcare_service_unit(self.inpatient_record) - if is_inpatient_occupancy_unit and service_unit != self.service_unit: - msg = _('Patient {0} is not admitted in the service unit {1}').format(frappe.bold(self.patient), frappe.bold(self.service_unit)) + '
' - msg += _('Appointment for service units with Inpatient Occupancy can only be created against the unit where patient has been admitted.') - frappe.throw(msg, title=_('Invalid Healthcare Service Unit')) - - - def set_appointment_datetime(self): - self.appointment_datetime = "%s %s" % (self.appointment_date, self.appointment_time or "00:00:00") - - def set_payment_details(self): - if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): - details = get_service_item_and_practitioner_charge(self) - self.db_set('billing_item', details.get('service_item')) - if not self.paid_amount: - self.db_set('paid_amount', details.get('practitioner_charge')) - - def validate_customer_created(self): - if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): - if not frappe.db.get_value('Patient', self.patient, 'customer'): - msg = _("Please set a Customer linked to the Patient") - msg += " {0}".format(self.patient) - frappe.throw(msg, title=_('Customer Not Found')) - - def update_prescription_details(self): - if self.procedure_prescription: - frappe.db.set_value('Procedure Prescription', self.procedure_prescription, 'appointment_booked', 1) - if self.procedure_template: - comments = frappe.db.get_value('Procedure Prescription', self.procedure_prescription, 'comments') - if comments: - frappe.db.set_value('Patient Appointment', self.name, 'notes', comments) - - def update_fee_validity(self): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - - fee_validity = manage_fee_validity(self) - if fee_validity: - frappe.msgprint(_('{0}: {1} has fee validity till {2}').format(self.patient, - frappe.bold(self.patient_name), fee_validity.valid_till)) - - @frappe.whitelist() - def get_therapy_types(self): - if not self.therapy_plan: - return - - therapy_types = [] - doc = frappe.get_doc('Therapy Plan', self.therapy_plan) - for entry in doc.therapy_plan_details: - therapy_types.append(entry.therapy_type) - - return therapy_types - - -@frappe.whitelist() -def check_payment_fields_reqd(patient): - automate_invoicing = frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') - free_follow_ups = frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') - if automate_invoicing: - if free_follow_ups: - fee_validity = frappe.db.exists('Fee Validity', {'patient': patient, 'status': 'Pending'}) - if fee_validity: - return {'fee_validity': fee_validity} - return True - return False - -def invoice_appointment(appointment_doc): - automate_invoicing = frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing') - appointment_invoiced = frappe.db.get_value('Patient Appointment', appointment_doc.name, 'invoiced') - enable_free_follow_ups = frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') - if enable_free_follow_ups: - fee_validity = check_fee_validity(appointment_doc) - if fee_validity and fee_validity.status == 'Completed': - fee_validity = None - elif not fee_validity: - if frappe.db.exists('Fee Validity Reference', {'appointment': appointment_doc.name}): - return - else: - fee_validity = None - - if automate_invoicing and not appointment_invoiced and not fee_validity: - create_sales_invoice(appointment_doc) - - -def create_sales_invoice(appointment_doc): - sales_invoice = frappe.new_doc('Sales Invoice') - sales_invoice.patient = appointment_doc.patient - sales_invoice.customer = frappe.get_value('Patient', appointment_doc.patient, 'customer') - sales_invoice.appointment = appointment_doc.name - sales_invoice.due_date = getdate() - sales_invoice.company = appointment_doc.company - sales_invoice.debit_to = get_receivable_account(appointment_doc.company) - - item = sales_invoice.append('items', {}) - item = get_appointment_item(appointment_doc, item) - - # Add payments if payment details are supplied else proceed to create invoice as Unpaid - if appointment_doc.mode_of_payment and appointment_doc.paid_amount: - sales_invoice.is_pos = 1 - payment = sales_invoice.append('payments', {}) - payment.mode_of_payment = appointment_doc.mode_of_payment - payment.amount = appointment_doc.paid_amount - - sales_invoice.set_missing_values(for_validate=True) - sales_invoice.flags.ignore_mandatory = True - sales_invoice.save(ignore_permissions=True) - sales_invoice.submit() - frappe.msgprint(_('Sales Invoice {0} created').format(sales_invoice.name), alert=True) - frappe.db.set_value('Patient Appointment', appointment_doc.name, { - 'invoiced': 1, - 'ref_sales_invoice': sales_invoice.name - }) - - -def check_is_new_patient(patient, name=None): - filters = {'patient': patient, 'status': ('!=','Cancelled')} - if name: - filters['name'] = ('!=', name) - - has_previous_appointment = frappe.db.exists('Patient Appointment', filters) - return not has_previous_appointment - - -def get_appointment_item(appointment_doc, item): - details = get_service_item_and_practitioner_charge(appointment_doc) - charge = appointment_doc.paid_amount or details.get('practitioner_charge') - item.item_code = details.get('service_item') - item.description = _('Consulting Charges: {0}').format(appointment_doc.practitioner) - item.income_account = get_income_account(appointment_doc.practitioner, appointment_doc.company) - item.cost_center = frappe.get_cached_value('Company', appointment_doc.company, 'cost_center') - item.rate = charge - item.amount = charge - item.qty = 1 - item.reference_dt = 'Patient Appointment' - item.reference_dn = appointment_doc.name - return item - - -def cancel_appointment(appointment_id): - appointment = frappe.get_doc('Patient Appointment', appointment_id) - if appointment.invoiced: - sales_invoice = check_sales_invoice_exists(appointment) - if sales_invoice and cancel_sales_invoice(sales_invoice): - msg = _('Appointment {0} and Sales Invoice {1} cancelled').format(appointment.name, sales_invoice.name) - else: - msg = _('Appointment Cancelled. Please review and cancel the invoice {0}').format(sales_invoice.name) - else: - fee_validity = manage_fee_validity(appointment) - msg = _('Appointment Cancelled.') - if fee_validity: - msg += _('Fee Validity {0} updated.').format(fee_validity.name) - - frappe.msgprint(msg) - - -def cancel_sales_invoice(sales_invoice): - if frappe.db.get_single_value('Healthcare Settings', 'automate_appointment_invoicing'): - if len(sales_invoice.items) == 1: - sales_invoice.cancel() - return True - return False - - -def check_sales_invoice_exists(appointment): - sales_invoice = frappe.db.get_value('Sales Invoice Item', { - 'reference_dt': 'Patient Appointment', - 'reference_dn': appointment.name - }, 'parent') - - if sales_invoice: - sales_invoice = frappe.get_doc('Sales Invoice', sales_invoice) - return sales_invoice - return False - - -@frappe.whitelist() -def get_availability_data(date, practitioner): - """ - Get availability data of 'practitioner' on 'date' - :param date: Date to check in schedule - :param practitioner: Name of the practitioner - :return: dict containing a list of available slots, list of appointments and time of appointments - """ - - date = getdate(date) - weekday = date.strftime('%A') - - practitioner_doc = frappe.get_doc('Healthcare Practitioner', practitioner) - - check_employee_wise_availability(date, practitioner_doc) - - if practitioner_doc.practitioner_schedules: - slot_details = get_available_slots(practitioner_doc, date) - else: - frappe.throw(_('{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner master').format( - practitioner), title=_('Practitioner Schedule Not Found')) - - - if not slot_details: - # TODO: return available slots in nearby dates - frappe.throw(_('Healthcare Practitioner not available on {0}').format(weekday), title=_('Not Available')) - - return {'slot_details': slot_details} - - -def check_employee_wise_availability(date, practitioner_doc): - employee = None - if practitioner_doc.employee: - employee = practitioner_doc.employee - elif practitioner_doc.user_id: - employee = frappe.db.get_value('Employee', {'user_id': practitioner_doc.user_id}, 'name') - - if employee: - # check holiday - if is_holiday(employee, date): - frappe.throw(_('{0} is a holiday'.format(date)), title=_('Not Available')) - - # check leave status - leave_record = frappe.db.sql("""select half_day from `tabLeave Application` - where employee = %s and %s between from_date and to_date - and docstatus = 1""", (employee, date), as_dict=True) - if leave_record: - if leave_record[0].half_day: - frappe.throw(_('{0} is on a Half day Leave on {1}').format(practitioner_doc.name, date), title=_('Not Available')) - else: - frappe.throw(_('{0} is on Leave on {1}').format(practitioner_doc.name, date), title=_('Not Available')) - - -def get_available_slots(practitioner_doc, date): - available_slots = slot_details = [] - weekday = date.strftime('%A') - practitioner = practitioner_doc.name - - for schedule_entry in practitioner_doc.practitioner_schedules: - validate_practitioner_schedules(schedule_entry, practitioner) - practitioner_schedule = frappe.get_doc('Practitioner Schedule', schedule_entry.schedule) - - if practitioner_schedule: - available_slots = [] - for time_slot in practitioner_schedule.time_slots: - if weekday == time_slot.day: - available_slots.append(time_slot) - - if available_slots: - appointments = [] - allow_overlap = 0 - service_unit_capacity = 0 - # fetch all appointments to practitioner by service unit - filters = { - 'practitioner': practitioner, - 'service_unit': schedule_entry.service_unit, - 'appointment_date': date, - 'status': ['not in',['Cancelled']] - } - - if schedule_entry.service_unit: - slot_name = f'{schedule_entry.schedule}' - allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, ['overlap_appointments', 'service_unit_capacity']) - if not allow_overlap: - # fetch all appointments to service unit - filters.pop('practitioner') - else: - slot_name = schedule_entry.schedule - # fetch all appointments to practitioner without service unit - filters['practitioner'] = practitioner - filters.pop('service_unit') - - appointments = frappe.get_all( - 'Patient Appointment', - filters=filters, - fields=['name', 'appointment_time', 'duration', 'status']) - - slot_details.append({'slot_name': slot_name, 'service_unit': schedule_entry.service_unit, 'avail_slot': available_slots, - 'appointments': appointments, 'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity}) - - return slot_details - - -def validate_practitioner_schedules(schedule_entry, practitioner): - if schedule_entry.schedule: - if not schedule_entry.service_unit: - frappe.throw(_('Practitioner {0} does not have a Service Unit set against the Practitioner Schedule {1}.').format( - get_link_to_form('Healthcare Practitioner', practitioner), frappe.bold(schedule_entry.schedule)), - title=_('Service Unit Not Found')) - - else: - frappe.throw(_('Practitioner {0} does not have a Practitioner Schedule assigned.').format( - get_link_to_form('Healthcare Practitioner', practitioner)), - title=_('Practitioner Schedule Not Found')) - - -@frappe.whitelist() -def update_status(appointment_id, status): - frappe.db.set_value('Patient Appointment', appointment_id, 'status', status) - appointment_booked = True - if status == 'Cancelled': - appointment_booked = False - cancel_appointment(appointment_id) - - procedure_prescription = frappe.db.get_value('Patient Appointment', appointment_id, 'procedure_prescription') - if procedure_prescription: - frappe.db.set_value('Procedure Prescription', procedure_prescription, 'appointment_booked', appointment_booked) - - -def send_confirmation_msg(doc): - if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_confirmation'): - message = frappe.db.get_single_value('Healthcare Settings', 'appointment_confirmation_msg') - try: - send_message(doc, message) - except Exception: - frappe.log_error(frappe.get_traceback(), _('Appointment Confirmation Message Not Sent')) - frappe.msgprint(_('Appointment Confirmation Message Not Sent'), indicator='orange') - - -@frappe.whitelist() -def make_encounter(source_name, target_doc=None): - doc = get_mapped_doc('Patient Appointment', source_name, { - 'Patient Appointment': { - 'doctype': 'Patient Encounter', - 'field_map': [ - ['appointment', 'name'], - ['patient', 'patient'], - ['practitioner', 'practitioner'], - ['medical_department', 'department'], - ['patient_sex', 'patient_sex'], - ['invoiced', 'invoiced'], - ['company', 'company'] - ] - } - }, target_doc) - return doc - - -def send_appointment_reminder(): - if frappe.db.get_single_value('Healthcare Settings', 'send_appointment_reminder'): - remind_before = datetime.datetime.strptime(frappe.db.get_single_value('Healthcare Settings', 'remind_before'), '%H:%M:%S') - reminder_dt = datetime.datetime.now() + datetime.timedelta( - hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second) - - appointment_list = frappe.db.get_all('Patient Appointment', { - 'appointment_datetime': ['between', (datetime.datetime.now(), reminder_dt)], - 'reminded': 0, - 'status': ['!=', 'Cancelled'] - }) - - for appointment in appointment_list: - doc = frappe.get_doc('Patient Appointment', appointment.name) - message = frappe.db.get_single_value('Healthcare Settings', 'appointment_reminder_msg') - send_message(doc, message) - frappe.db.set_value('Patient Appointment', doc.name, 'reminded', 1) - -def send_message(doc, message): - patient_mobile = frappe.db.get_value('Patient', doc.patient, 'mobile') - if patient_mobile: - context = {'doc': doc, 'alert': doc, 'comments': None} - if doc.get('_comments'): - context['comments'] = json.loads(doc.get('_comments')) - - # jinja to string convertion happens here - message = frappe.render_template(message, context) - number = [patient_mobile] - try: - send_sms(number, message) - except Exception as e: - frappe.msgprint(_('SMS not sent, please check SMS Settings'), alert=True) - -@frappe.whitelist() -def get_events(start, end, filters=None): - """Returns events for Gantt / Calendar view rendering. - - :param start: Start date-time. - :param end: End date-time. - :param filters: Filters (JSON). - """ - from frappe.desk.calendar import get_event_conditions - conditions = get_event_conditions('Patient Appointment', filters) - - data = frappe.db.sql(""" - select - `tabPatient Appointment`.name, `tabPatient Appointment`.patient, - `tabPatient Appointment`.practitioner, `tabPatient Appointment`.status, - `tabPatient Appointment`.duration, - timestamp(`tabPatient Appointment`.appointment_date, `tabPatient Appointment`.appointment_time) as 'start', - `tabAppointment Type`.color - from - `tabPatient Appointment` - left join `tabAppointment Type` on `tabPatient Appointment`.appointment_type=`tabAppointment Type`.name - where - (`tabPatient Appointment`.appointment_date between %(start)s and %(end)s) - and `tabPatient Appointment`.status != 'Cancelled' and `tabPatient Appointment`.docstatus < 2 {conditions}""".format(conditions=conditions), - {"start": start, "end": end}, as_dict=True, update={"allDay": 0}) - - for item in data: - item.end = item.start + datetime.timedelta(minutes = item.duration) - - return data - - -@frappe.whitelist() -def get_procedure_prescribed(patient): - return frappe.db.sql( - """ - SELECT - pp.name, pp.procedure, pp.parent, ct.practitioner, - ct.encounter_date, pp.practitioner, pp.date, pp.department - FROM - `tabPatient Encounter` ct, `tabProcedure Prescription` pp - WHERE - ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0 - ORDER BY - ct.creation desc - """, {'patient': patient} - ) - - -@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.medical_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(): - # update the status of appointments daily - appointments = frappe.get_all('Patient Appointment', { - 'status': ('not in', ['Closed', 'Cancelled']) - }, as_dict=1) - - for appointment in appointments: - frappe.get_doc('Patient Appointment', appointment.name).set_status() diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js deleted file mode 100644 index 2249d2a205..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js +++ /dev/null @@ -1,14 +0,0 @@ - -frappe.views.calendar["Patient Appointment"] = { - field_map: { - "start": "start", - "end": "end", - "id": "name", - "title": "patient", - "allDay": "allDay", - "eventColor": "color" - }, - order_by: "appointment_date", - gantt: true, - get_events_method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_events" -}; diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py deleted file mode 100644 index 43c63c96e6..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_dashboard.py +++ /dev/null @@ -1,18 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'appointment', - 'non_standard_fieldnames': { - 'Patient Medical Record': 'reference_name' - }, - 'transactions': [ - { - 'label': _('Consultations'), - 'items': ['Patient Encounter', 'Vital Signs', 'Patient Medical Record'] - } - ] - } diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_list.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_list.js deleted file mode 100644 index 721887b459..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_list.js +++ /dev/null @@ -1,16 +0,0 @@ -/* -(c) ESS 2015-16 -*/ -frappe.listview_settings['Patient Appointment'] = { - filters: [["status", "=", "Open"]], - get_indicator: function(doc) { - var colors = { - "Open": "orange", - "Scheduled": "yellow", - "Closed": "green", - "Cancelled": "red", - "Expired": "grey" - }; - return [__(doc.status), colors[doc.status], "status,=," + doc.status]; - } -}; diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py deleted file mode 100644 index 8ca30b8cbe..0000000000 --- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py +++ /dev/null @@ -1,487 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import add_days, now_datetime, nowdate - -from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile -from erpnext.healthcare.doctype.patient_appointment.patient_appointment import ( - check_is_new_patient, - check_payment_fields_reqd, - make_encounter, - update_status, -) - - -class TestPatientAppointment(unittest.TestCase): - def setUp(self): - frappe.db.sql("""delete from `tabPatient Appointment`""") - frappe.db.sql("""delete from `tabFee Validity`""") - frappe.db.sql("""delete from `tabPatient Encounter`""") - make_pos_profile() - frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test %'""") - frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test Service Unit Type%'""") - - def test_status(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) - appointment = create_appointment(patient, practitioner, nowdate()) - self.assertEqual(appointment.status, 'Open') - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2)) - self.assertEqual(appointment.status, 'Scheduled') - encounter = create_encounter(appointment) - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') - encounter.cancel() - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') - - def test_start_encounter(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1) - appointment.reload() - self.assertEqual(appointment.invoiced, 1) - encounter = make_encounter(appointment.name) - self.assertTrue(encounter) - self.assertEqual(encounter.company, appointment.company) - self.assertEqual(encounter.practitioner, appointment.practitioner) - self.assertEqual(encounter.patient, appointment.patient) - # invoiced flag mapped from appointment - self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced')) - - def test_auto_invoicing(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0) - appointment = create_appointment(patient, practitioner, nowdate()) - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 0) - - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), invoice=1) - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'), 1) - sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') - self.assertTrue(sales_invoice_name) - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'company'), appointment.company) - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'patient'), appointment.patient) - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) - - def test_auto_invoicing_based_on_department(self): - patient, practitioner = create_healthcare_docs() - medical_department = create_medical_department() - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - appointment_type = create_appointment_type({'medical_department': medical_department}) - - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), - invoice=1, appointment_type=appointment_type.name, department=medical_department) - appointment.reload() - - self.assertEqual(appointment.invoiced, 1) - self.assertEqual(appointment.billing_item, 'HLC-SI-001') - self.assertEqual(appointment.paid_amount, 200) - - sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') - self.assertTrue(sales_invoice_name) - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount) - - def test_auto_invoicing_according_to_appointment_type_charge(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - - item = create_healthcare_service_items() - items = [{ - 'op_consulting_charge_item': item, - 'op_consulting_charge': 300 - }] - appointment_type = create_appointment_type(args={ - 'name': 'Generic Appointment Type charge', - 'items': items - }) - - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2), - invoice=1, appointment_type=appointment_type.name) - appointment.reload() - - self.assertEqual(appointment.invoiced, 1) - self.assertEqual(appointment.billing_item, item) - self.assertEqual(appointment.paid_amount, 300) - - sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') - self.assertTrue(sales_invoice_name) - - def test_appointment_cancel(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) - appointment = create_appointment(patient, practitioner, nowdate()) - fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner}) - # fee validity created - self.assertTrue(fee_validity) - - # first follow up appointment - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1)) - self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1) - - update_status(appointment.name, 'Cancelled') - # check fee validity updated - self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 0) - - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1), invoice=1) - update_status(appointment.name, 'Cancelled') - # check invoice cancelled - sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent') - self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'status'), 'Cancelled') - - def test_appointment_booking_for_admission_service_unit(self): - from erpnext.healthcare.doctype.inpatient_record.inpatient_record import ( - admit_patient, - discharge_patient, - schedule_discharge, - ) - from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import ( - create_inpatient, - get_healthcare_service_unit, - mark_invoiced_inpatient_occupancy, - ) - - frappe.db.sql("""delete from `tabInpatient Record`""") - patient, practitioner = create_healthcare_docs() - patient = create_patient() - # Schedule Admission - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save(ignore_permissions = True) - - # Admit - service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') - admit_patient(ip_record, service_unit, now_datetime()) - - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) - self.assertEqual(appointment.service_unit, service_unit) - - # Discharge - schedule_discharge(frappe.as_json({'patient': patient})) - ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) - mark_invoiced_inpatient_occupancy(ip_record1) - discharge_patient(ip_record1) - - def test_invalid_healthcare_service_unit_validation(self): - from erpnext.healthcare.doctype.inpatient_record.inpatient_record import ( - admit_patient, - discharge_patient, - schedule_discharge, - ) - from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import ( - create_inpatient, - get_healthcare_service_unit, - mark_invoiced_inpatient_occupancy, - ) - - frappe.db.sql("""delete from `tabInpatient Record`""") - patient, practitioner = create_healthcare_docs() - patient = create_patient() - # Schedule Admission - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save(ignore_permissions = True) - - # Admit - service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') - admit_patient(ip_record, service_unit, now_datetime()) - - appointment_service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy for Appointment') - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0) - self.assertRaises(frappe.exceptions.ValidationError, appointment.save) - - # Discharge - schedule_discharge(frappe.as_json({'patient': patient})) - ip_record1 = frappe.get_doc("Inpatient Record", ip_record.name) - mark_invoiced_inpatient_occupancy(ip_record1) - discharge_patient(ip_record1) - - def test_payment_should_be_mandatory_for_new_patient_appointment(self): - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1) - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - frappe.db.set_value('Healthcare Settings', None, 'max_visits', 3) - frappe.db.set_value('Healthcare Settings', None, 'valid_days', 30) - - patient = create_patient() - assert check_is_new_patient(patient) - payment_required = check_payment_fields_reqd(patient) - assert payment_required is True - - def test_sales_invoice_should_be_generated_for_new_patient_appointment(self): - patient, practitioner = create_healthcare_docs() - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - invoice_count = frappe.db.count('Sales Invoice') - - assert check_is_new_patient(patient) - create_appointment(patient, practitioner, nowdate()) - new_invoice_count = frappe.db.count('Sales Invoice') - - assert new_invoice_count == invoice_count + 1 - - def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self): - patient, practitioner = create_healthcare_docs() - create_appointment(patient, practitioner, nowdate()) - - patient, new_practitioner = create_healthcare_docs(id=5) - create_appointment(patient, new_practitioner, nowdate()) - - roles = [{"doctype": "Has Role", "role": "Physician"}] - user = create_user(roles=roles) - new_practitioner = frappe.get_doc('Healthcare Practitioner', new_practitioner) - new_practitioner.user_id = user.email - new_practitioner.save() - - frappe.set_user(user.name) - appointments = frappe.get_list('Patient Appointment') - assert len(appointments) == 1 - - frappe.set_user("Administrator") - appointments = frappe.get_list('Patient Appointment') - assert len(appointments) == 2 - - def test_overlap_appointment(self): - from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError - patient, practitioner = create_healthcare_docs(id=1) - patient_1, practitioner_1 = create_healthcare_docs(id=2) - service_unit = create_service_unit(id=0) - service_unit_1 = create_service_unit(id=1) - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) # valid - - # patient and practitioner cannot have overlapping appointments - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit, save=0) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit_1, save=0) # diff service unit - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient, practitioner, nowdate(), save=0) # with no service unit link - self.assertRaises(OverlapError, appointment.save) - - # patient cannot have overlapping appointments with other practitioners - appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit, save=0) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient, practitioner_1, nowdate(), save=0) - self.assertRaises(OverlapError, appointment.save) - - # practitioner cannot have overlapping appointments with other patients - appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit, save=0) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0) - self.assertRaises(OverlapError, appointment.save) - appointment = create_appointment(patient_1, practitioner, nowdate(), save=0) - self.assertRaises(OverlapError, appointment.save) - - def test_service_unit_capacity(self): - from erpnext.healthcare.doctype.patient_appointment.patient_appointment import ( - MaximumCapacityError, - OverlapError, - ) - practitioner = create_practitioner() - capacity = 3 - overlap_service_unit_type = create_service_unit_type(id=10, allow_appointments=1, overlap_appointments=1) - overlap_service_unit = create_service_unit(id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity) - - for i in range(0, capacity): - patient = create_patient(id=i) - create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) # overlap - self.assertRaises(OverlapError, appointment.save) - - patient = create_patient(id=capacity) - appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) - self.assertRaises(MaximumCapacityError, appointment.save) - - -def create_healthcare_docs(id=0): - patient = create_patient(id) - practitioner = create_practitioner(id) - - return patient, practitioner - - -def create_patient(id=0): - if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}): - patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name']) - return patient - - patient = frappe.new_doc('Patient') - patient.first_name = f'_Test Patient {str(id)}' - patient.sex = 'Female' - patient.save(ignore_permissions=True) - - return patient.name - - -def create_medical_department(id=0): - if frappe.db.exists('Medical Department', f'_Test Medical Department {str(id)}'): - return f'_Test Medical Department {str(id)}' - - medical_department = frappe.new_doc('Medical Department') - medical_department.department = f'_Test Medical Department {str(id)}' - medical_department.save(ignore_permissions=True) - - return medical_department.name - - -def create_practitioner(id=0, medical_department=None): - if frappe.db.exists('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}): - practitioner = frappe.db.get_value('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}, ['name']) - return practitioner - - practitioner = frappe.new_doc('Healthcare Practitioner') - practitioner.first_name = f'_Test Healthcare Practitioner {str(id)}' - practitioner.gender = 'Female' - practitioner.department = medical_department or create_medical_department(id) - practitioner.op_consulting_charge = 500 - practitioner.inpatient_visit_charge = 500 - practitioner.save(ignore_permissions=True) - - return practitioner.name - - -def create_encounter(appointment): - if appointment: - encounter = frappe.new_doc('Patient Encounter') - encounter.appointment = appointment.name - encounter.patient = appointment.patient - encounter.practitioner = appointment.practitioner - encounter.encounter_date = appointment.appointment_date - encounter.encounter_time = appointment.appointment_time - encounter.company = appointment.company - encounter.save() - encounter.submit() - - return encounter - - -def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0, - service_unit=None, appointment_type=None, save=1, department=None): - item = create_healthcare_service_items() - frappe.db.set_value('Healthcare Settings', None, 'inpatient_visit_charge_item', item) - frappe.db.set_value('Healthcare Settings', None, 'op_consulting_charge_item', item) - appointment = frappe.new_doc('Patient Appointment') - appointment.patient = patient - appointment.practitioner = practitioner - appointment.department = department or '_Test Medical Department' - appointment.appointment_date = appointment_date - appointment.company = '_Test Company' - appointment.duration = 15 - - if service_unit: - appointment.service_unit = service_unit - if invoice: - appointment.mode_of_payment = 'Cash' - if appointment_type: - appointment.appointment_type = appointment_type - if procedure_template: - appointment.procedure_template = create_clinical_procedure_template().get('name') - if save: - appointment.save(ignore_permissions=True) - - return appointment - - -def create_healthcare_service_items(): - if frappe.db.exists('Item', 'HLC-SI-001'): - return 'HLC-SI-001' - - item = frappe.new_doc('Item') - item.item_code = 'HLC-SI-001' - item.item_name = 'Consulting Charges' - item.item_group = 'Services' - item.is_stock_item = 0 - item.stock_uom = 'Nos' - item.save() - - return item.name - - -def create_clinical_procedure_template(): - if frappe.db.exists('Clinical Procedure Template', 'Knee Surgery and Rehab'): - return frappe.get_doc('Clinical Procedure Template', 'Knee Surgery and Rehab') - - template = frappe.new_doc('Clinical Procedure Template') - template.template = 'Knee Surgery and Rehab' - template.item_code = 'Knee Surgery and Rehab' - template.item_group = 'Services' - template.is_billable = 1 - template.description = 'Knee Surgery and Rehab' - template.rate = 50000 - template.save() - - return template - - -def create_appointment_type(args=None): - if not args: - args = frappe.local.form_dict - - name = args.get('name') or 'Test Appointment Type wise Charge' - - if frappe.db.exists('Appointment Type', name): - return frappe.get_doc('Appointment Type', name) - - else: - item = create_healthcare_service_items() - items = [{ - 'medical_department': args.get('medical_department') or '_Test Medical Department', - 'op_consulting_charge_item': item, - 'op_consulting_charge': 200 - }] - return frappe.get_doc({ - 'doctype': 'Appointment Type', - 'appointment_type': args.get('name') or 'Test Appointment Type wise Charge', - 'default_duration': args.get('default_duration') or 20, - 'color': args.get('color') or '#7575ff', - 'price_list': args.get('price_list') or frappe.db.get_value("Price List", {"selling": 1}), - 'items': args.get('items') or items - }).insert() - -def create_user(email=None, roles=None): - if not email: - email = '{}@frappe.com'.format(frappe.utils.random_string(10)) - user = frappe.db.exists('User', email) - if not user: - user = frappe.get_doc({ - "doctype": "User", - "email": email, - "first_name": "test_user", - "password": "password", - "roles": roles, - }).insert() - return user - - -def create_service_unit_type(id=0, allow_appointments=1, overlap_appointments=0): - if frappe.db.exists('Healthcare Service Unit Type', f'_Test Service Unit Type {str(id)}'): - return f'_Test Service Unit Type {str(id)}' - - service_unit_type = frappe.new_doc('Healthcare Service Unit Type') - service_unit_type.service_unit_type = f'_Test Service Unit Type {str(id)}' - service_unit_type.allow_appointments = allow_appointments - service_unit_type.overlap_appointments = overlap_appointments - service_unit_type.save(ignore_permissions=True) - - return service_unit_type.name - - -def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0): - if frappe.db.exists('Healthcare Service Unit', f'_Test Service Unit {str(id)}'): - return f'_Test service_unit {str(id)}' - - service_unit = frappe.new_doc('Healthcare Service Unit') - service_unit.is_group = 0 - service_unit.healthcare_service_unit_name= f'_Test Service Unit {str(id)}' - service_unit.service_unit_type = service_unit_type or create_service_unit_type(id) - service_unit.service_unit_capacity = service_unit_capacity - service_unit.save(ignore_permissions=True) - - return service_unit.name diff --git a/erpnext/healthcare/doctype/patient_assessment/__init__.py b/erpnext/healthcare/doctype/patient_assessment/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js deleted file mode 100644 index f28d32c22c..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js +++ /dev/null @@ -1,88 +0,0 @@ -// 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); - } - frm.fields_dict.assessment_sheet.grid.update_docfield_property( - 'score', 'options', 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); - } -}); diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json deleted file mode 100644 index eb0021ff75..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "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", - "company", - "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", - "reqd": 1 - }, - { - "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" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "options": "Company" - } - ], - "is_submittable": 1, - "links": [], - "modified": "2020-06-25 00:25:13.208400", - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py deleted file mode 100644 index 90cb30035d..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py b/erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py deleted file mode 100644 index 0ffbd1f504..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment_detail/__init__.py b/erpnext/healthcare/doctype/patient_assessment_detail/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json b/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json deleted file mode 100644 index 179f441044..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py b/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py deleted file mode 100644 index 4da679b889..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/__init__.py b/erpnext/healthcare/doctype/patient_assessment_parameter/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js deleted file mode 100644 index 2c5d270d57..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js +++ /dev/null @@ -1,8 +0,0 @@ -// 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) { - - // } -}); diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json deleted file mode 100644 index 098bdefea7..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py deleted file mode 100644 index 783c537848..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py b/erpnext/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py deleted file mode 100644 index f06fffb1ef..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment_sheet/__init__.py b/erpnext/healthcare/doctype/patient_assessment_sheet/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json b/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json deleted file mode 100644 index 64e4aef7cf..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py b/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py deleted file mode 100644 index 4686e9e261..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment_template/__init__.py b/erpnext/healthcare/doctype/patient_assessment_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js deleted file mode 100644 index 40419362a4..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js +++ /dev/null @@ -1,8 +0,0 @@ -// 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) { - - // } -}); diff --git a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json deleted file mode 100644 index de006b1805..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.py b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.py deleted file mode 100644 index e0d8fca37f..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py b/erpnext/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py deleted file mode 100644 index 7d639cb6af..0000000000 --- a/erpnext/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/patient_encounter/__init__.py b/erpnext/healthcare/doctype/patient_encounter/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js deleted file mode 100644 index c3466260d2..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ /dev/null @@ -1,397 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Patient Encounter', { - setup: function(frm) { - frm.get_field('therapies').grid.editable_fields = [ - {fieldname: 'therapy_type', columns: 8}, - {fieldname: 'no_of_sessions', columns: 2} - ]; - frm.get_field('drug_prescription').grid.editable_fields = [ - {fieldname: 'drug_code', columns: 2}, - {fieldname: 'drug_name', columns: 2}, - {fieldname: 'dosage', columns: 2}, - {fieldname: 'period', columns: 2} - ]; - frm.get_field('lab_test_prescription').grid.editable_fields = [ - {fieldname: 'lab_test_code', columns: 2}, - {fieldname: 'lab_test_name', columns: 4}, - {fieldname: 'lab_test_comment', columns: 4} - ]; - }, - - refresh: function(frm) { - refresh_field('drug_prescription'); - refresh_field('lab_test_prescription'); - - if (!frm.doc.__islocal) { - if (frm.doc.docstatus === 1) { - if (frm.doc.inpatient_status == 'Admission Scheduled' || frm.doc.inpatient_status == 'Admitted') { - frm.add_custom_button(__('Schedule Discharge'), function() { - schedule_discharge(frm); - }); - } else if (frm.doc.inpatient_status != 'Discharge Scheduled') { - frm.add_custom_button(__('Schedule Admission'), function() { - schedule_inpatient(frm); - }); - } - } - - frm.add_custom_button(__('Patient History'), function() { - if (frm.doc.patient) { - frappe.route_options = {'patient': frm.doc.patient}; - frappe.set_route('patient_history'); - } else { - frappe.msgprint(__('Please select Patient')); - } - },'View'); - - frm.add_custom_button(__('Vital Signs'), function() { - create_vital_signs(frm); - },'Create'); - - frm.add_custom_button(__('Medical Record'), function() { - create_medical_record(frm); - },'Create'); - - frm.add_custom_button(__('Clinical Procedure'), function() { - create_procedure(frm); - },'Create'); - - if (frm.doc.drug_prescription && frm.doc.inpatient_record && frm.doc.inpatient_status === "Admitted") { - frm.add_custom_button(__('Inpatient Medication Order'), function() { - frappe.model.open_mapped_doc({ - method: 'erpnext.healthcare.doctype.patient_encounter.patient_encounter.make_ip_medication_order', - frm: frm - }); - }, 'Create'); - } - } - - frm.set_query('patient', function() { - return { - filters: {'status': 'Active'} - }; - }); - - frm.set_query('drug_code', 'drug_prescription', function() { - return { - filters: { - is_stock_item: 1 - } - }; - }); - - frm.set_query('lab_test_code', 'lab_test_prescription', function() { - return { - filters: { - is_billable: 1 - } - }; - }); - - frm.set_query('appointment', function() { - return { - filters: { - // Scheduled filter for demo ... - status:['in',['Open','Scheduled']] - } - }; - }); - - frm.set_df_property('patient', 'read_only', frm.doc.appointment ? 1 : 0); - }, - - appointment: function(frm) { - frm.events.set_appointment_fields(frm); - }, - - patient: function(frm) { - frm.events.set_patient_info(frm); - }, - - practitioner: function(frm) { - if (!frm.doc.practitioner) { - frm.set_value('practitioner_name', ''); - } - }, - set_appointment_fields: function(frm) { - if (frm.doc.appointment) { - frappe.call({ - method: 'frappe.client.get', - args: { - doctype: 'Patient Appointment', - name: frm.doc.appointment - }, - callback: function(data) { - let values = { - 'patient':data.message.patient, - 'type': data.message.appointment_type, - 'practitioner': data.message.practitioner, - 'invoiced': data.message.invoiced, - 'company': data.message.company - }; - frm.set_value(values); - frm.set_df_property('patient', 'read_only', 1); - } - }); - } - else { - let values = { - 'patient': '', - 'patient_name': '', - 'type': '', - 'practitioner': '', - 'invoiced': 0, - 'patient_sex': '', - 'patient_age': '', - 'inpatient_record': '', - 'inpatient_status': '' - }; - frm.set_value(values); - frm.set_df_property('patient', 'read_only', 0); - } - }, - - set_patient_info: function(frm) { - if (frm.doc.patient) { - frappe.call({ - method: 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { - patient: frm.doc.patient - }, - callback: function(data) { - let age = ''; - if (data.message.dob) { - age = calculate_age(data.message.dob); - } - let values = { - 'patient_age': age, - 'patient_name':data.message.patient_name, - 'patient_sex': data.message.sex, - 'inpatient_record': data.message.inpatient_record, - 'inpatient_status': data.message.inpatient_status - }; - frm.set_value(values); - } - }); - } else { - let values = { - 'patient_age': '', - 'patient_name':'', - 'patient_sex': '', - 'inpatient_record': '', - 'inpatient_status': '' - }; - frm.set_value(values); - } - }, - - get_applicable_treatment_plans: function(frm) { - frappe.call({ - method: 'get_applicable_treatment_plans', - doc: frm.doc, - args: {'encounter': frm.doc}, - freeze: true, - freeze_message: __('Fetching Treatment Plans'), - callback: function() { - new frappe.ui.form.MultiSelectDialog({ - doctype: "Treatment Plan Template", - target: this.cur_frm, - setters: { - medical_department: "", - }, - action(selections) { - frappe.call({ - method: 'set_treatment_plans', - doc: frm.doc, - args: selections, - }).then(() => { - frm.refresh_field('drug_prescription'); - frm.refresh_field('procedure_prescription'); - frm.refresh_field('lab_test_prescription'); - frm.refresh_field('therapies'); - }); - cur_dialog.hide(); - } - }); - - - } - }); - }, - -}); - -var schedule_inpatient = function(frm) { - var dialog = new frappe.ui.Dialog({ - title: 'Patient Admission', - fields: [ - {fieldtype: 'Link', label: 'Medical Department', fieldname: 'medical_department', options: 'Medical Department', reqd: 1}, - {fieldtype: 'Link', label: 'Healthcare Practitioner (Primary)', fieldname: 'primary_practitioner', options: 'Healthcare Practitioner', reqd: 1}, - {fieldtype: 'Link', label: 'Healthcare Practitioner (Secondary)', fieldname: 'secondary_practitioner', options: 'Healthcare Practitioner'}, - {fieldtype: 'Column Break'}, - {fieldtype: 'Date', label: 'Admission Ordered For', fieldname: 'admission_ordered_for', default: 'Today'}, - {fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'}, - {fieldtype: 'Int', label: 'Expected Length of Stay', fieldname: 'expected_length_of_stay'}, - {fieldtype: 'Section Break'}, - {fieldtype: 'Long Text', label: 'Admission Instructions', fieldname: 'admission_instruction'} - ], - primary_action_label: __('Order Admission'), - primary_action : function() { - var args = { - patient: frm.doc.patient, - admission_encounter: frm.doc.name, - referring_practitioner: frm.doc.practitioner, - company: frm.doc.company, - medical_department: dialog.get_value('medical_department'), - primary_practitioner: dialog.get_value('primary_practitioner'), - secondary_practitioner: dialog.get_value('secondary_practitioner'), - admission_ordered_for: dialog.get_value('admission_ordered_for'), - admission_service_unit_type: dialog.get_value('service_unit_type'), - expected_length_of_stay: dialog.get_value('expected_length_of_stay'), - admission_instruction: dialog.get_value('admission_instruction') - } - frappe.call({ - method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_inpatient', - args: { - args: args - }, - callback: function(data) { - if (!data.exc) { - frm.reload_doc(); - } - }, - freeze: true, - freeze_message: __('Scheduling Patient Admission') - }); - frm.refresh_fields(); - dialog.hide(); - } - }); - - dialog.set_values({ - 'medical_department': frm.doc.medical_department, - 'primary_practitioner': frm.doc.practitioner, - }); - - dialog.fields_dict['service_unit_type'].get_query = function() { - return { - filters: { - 'inpatient_occupancy': 1, - 'allow_appointments': 0 - } - }; - }; - - dialog.show(); - dialog.$wrapper.find('.modal-dialog').css('width', '800px'); -}; - -var schedule_discharge = function(frm) { - var dialog = new frappe.ui.Dialog ({ - title: 'Inpatient Discharge', - fields: [ - {fieldtype: 'Date', label: 'Discharge Ordered Date', fieldname: 'discharge_ordered_date', default: 'Today', read_only: 1}, - {fieldtype: 'Date', label: 'Followup Date', fieldname: 'followup_date'}, - {fieldtype: 'Column Break'}, - {fieldtype: 'Small Text', label: 'Discharge Instructions', fieldname: 'discharge_instructions'}, - {fieldtype: 'Section Break', label:'Discharge Summary'}, - {fieldtype: 'Long Text', label: 'Discharge Note', fieldname: 'discharge_note'} - ], - primary_action_label: __('Order Discharge'), - primary_action : function() { - var args = { - patient: frm.doc.patient, - discharge_encounter: frm.doc.name, - discharge_practitioner: frm.doc.practitioner, - discharge_ordered_date: dialog.get_value('discharge_ordered_date'), - followup_date: dialog.get_value('followup_date'), - discharge_instructions: dialog.get_value('discharge_instructions'), - discharge_note: dialog.get_value('discharge_note') - } - frappe.call ({ - method: 'erpnext.healthcare.doctype.inpatient_record.inpatient_record.schedule_discharge', - args: {args}, - callback: function(data) { - if(!data.exc){ - frm.reload_doc(); - } - }, - freeze: true, - freeze_message: 'Scheduling Inpatient Discharge' - }); - frm.refresh_fields(); - dialog.hide(); - } - }); - - dialog.show(); - dialog.$wrapper.find('.modal-dialog').css('width', '800px'); -}; - -let create_medical_record = function(frm) { - if (!frm.doc.patient) { - frappe.throw(__('Please select patient')); - } - frappe.route_options = { - 'patient': frm.doc.patient, - 'status': 'Open', - 'reference_doctype': 'Patient Medical Record', - 'reference_owner': frm.doc.owner - }; - frappe.new_doc('Patient Medical Record'); -}; - -let create_vital_signs = function(frm) { - if (!frm.doc.patient) { - frappe.throw(__('Please select patient')); - } - frappe.route_options = { - 'patient': frm.doc.patient, - 'encounter': frm.doc.name, - 'company': frm.doc.company - }; - frappe.new_doc('Vital Signs'); -}; - -let create_procedure = function(frm) { - if (!frm.doc.patient) { - frappe.throw(__('Please select patient')); - } - frappe.route_options = { - 'patient': frm.doc.patient, - 'medical_department': frm.doc.medical_department, - 'company': frm.doc.company - }; - frappe.new_doc('Clinical Procedure'); -}; - -frappe.ui.form.on('Drug Prescription', { - dosage: function(frm, cdt, cdn){ - frappe.model.set_value(cdt, cdn, 'update_schedule', 1); - let child = locals[cdt][cdn]; - if (child.dosage) { - frappe.model.set_value(cdt, cdn, 'interval_uom', 'Day'); - frappe.model.set_value(cdt, cdn, 'interval', 1); - } - }, - period: function(frm, cdt, cdn) { - frappe.model.set_value(cdt, cdn, 'update_schedule', 1); - }, - interval_uom: function(frm, cdt, cdn) { - frappe.model.set_value(cdt, cdn, 'update_schedule', 1); - let child = locals[cdt][cdn]; - if (child.interval_uom == 'Hour') { - frappe.model.set_value(cdt, cdn, 'dosage', null); - } - } -}); - -let calculate_age = function(birth) { - let ageMS = Date.parse(Date()) - Date.parse(birth); - let age = new Date(); - age.setTime(ageMS); - let years = age.getFullYear() - 1970; - return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; -}; diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json deleted file mode 100644 index 994597dca7..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ /dev/null @@ -1,368 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2016-04-21 10:53:44.637684", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "naming_series", - "title", - "appointment", - "appointment_type", - "patient", - "patient_name", - "patient_sex", - "patient_age", - "inpatient_record", - "inpatient_status", - "column_break_6", - "company", - "encounter_date", - "encounter_time", - "practitioner", - "practitioner_name", - "medical_department", - "invoiced", - "sb_symptoms", - "symptoms", - "symptoms_in_print", - "get_applicable_treatment_plans", - "physical_examination", - "diagnosis", - "diagnosis_in_print", - "codification", - "codification_table", - "sb_drug_prescription", - "drug_prescription", - "sb_test_prescription", - "lab_test_prescription", - "sb_procedures", - "procedure_prescription", - "rehabilitation_section", - "therapy_plan", - "therapies", - "section_break_33", - "encounter_comment", - "sb_refs", - "amended_from" - ], - "fields": [ - { - "allow_on_submit": 1, - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "HLC-ENC-.YYYY.-", - "set_only_once": 1 - }, - { - "fieldname": "appointment", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Appointment", - "options": "Patient Appointment", - "search_index": 1, - "set_only_once": 1 - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "label": "Age", - "read_only": 1 - }, - { - "fieldname": "patient_sex", - "fieldtype": "Link", - "label": "Gender", - "options": "Gender", - "read_only": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "reqd": 1 - }, - { - "default": "Today", - "fieldname": "encounter_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Encounter Date", - "reqd": 1 - }, - { - "fieldname": "encounter_time", - "fieldtype": "Time", - "label": "Encounter Time", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "sb_symptoms", - "fieldtype": "Section Break", - "label": "Encounter Impression" - }, - { - "fieldname": "symptoms", - "fieldtype": "Table MultiSelect", - "ignore_xss_filter": 1, - "label": "Symptoms", - "no_copy": 1, - "options": "Patient Encounter Symptom" - }, - { - "default": "0", - "depends_on": "eval: doc.symptoms != ''", - "fieldname": "symptoms_in_print", - "fieldtype": "Check", - "label": "In print", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "physical_examination", - "fieldtype": "Column Break" - }, - { - "fieldname": "diagnosis", - "fieldtype": "Table MultiSelect", - "ignore_xss_filter": 1, - "label": "Diagnosis", - "no_copy": 1, - "options": "Patient Encounter Diagnosis" - }, - { - "default": "1", - "depends_on": "eval: doc.diagnosis != ''", - "fieldname": "diagnosis_in_print", - "fieldtype": "Check", - "label": "In print", - "no_copy": 1, - "print_hide": 1, - "report_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "codification", - "fieldtype": "Section Break", - "label": "Medical Coding" - }, - { - "fieldname": "codification_table", - "fieldtype": "Table", - "label": "Medical Codes", - "options": "Codification Table" - }, - { - "fieldname": "sb_drug_prescription", - "fieldtype": "Section Break", - "label": "Medications" - }, - { - "fieldname": "drug_prescription", - "fieldtype": "Table", - "label": "Drug Prescription", - "options": "Drug Prescription" - }, - { - "fieldname": "sb_test_prescription", - "fieldtype": "Section Break", - "label": "Investigations" - }, - { - "fieldname": "lab_test_prescription", - "fieldtype": "Table", - "label": "Lab Tests", - "options": "Lab Prescription" - }, - { - "fieldname": "sb_procedures", - "fieldtype": "Section Break", - "label": "Procedures" - }, - { - "fieldname": "procedure_prescription", - "fieldtype": "Table", - "label": "Clinical Procedures", - "no_copy": 1, - "options": "Procedure Prescription" - }, - { - "fieldname": "encounter_comment", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Review Details", - "no_copy": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Patient Encounter", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "rehabilitation_section", - "fieldtype": "Section Break", - "label": "Rehabilitation" - }, - { - "fieldname": "therapies", - "fieldtype": "Table", - "label": "Therapies", - "options": "Therapy Plan Detail" - }, - { - "fieldname": "section_break_33", - "fieldtype": "Section Break" - }, - { - "fieldname": "therapy_plan", - "fieldtype": "Link", - "hidden": 1, - "label": "Therapy Plan", - "options": "Therapy Plan", - "read_only": 1 - }, - { - "fieldname": "appointment_type", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Appointment Type", - "no_copy": 1, - "options": "Appointment Type", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fetch_from": "practitioner.department", - "fieldname": "medical_department", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Department", - "options": "Medical Department", - "read_only": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "inpatient_status", - "fieldtype": "Data", - "label": "Inpatient Status", - "read_only": 1 - }, - { - "fieldname": "sb_refs", - "fieldtype": "Section Break" - }, - { - "fetch_from": "practitioner.practitioner_name", - "fieldname": "practitioner_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Practitioner Name", - "read_only": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "eval:doc.patient", - "fieldname": "get_applicable_treatment_plans", - "fieldtype": "Button", - "label": "Get Applicable Treatment Plans" - } - ], - "is_submittable": 1, - "links": [], - "modified": "2021-07-27 11:39:12.347704", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Encounter", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient, practitioner, medical_department, encounter_date, encounter_time", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1, - "track_seen": 1 -} diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py deleted file mode 100644 index 2daa6c145c..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ /dev/null @@ -1,182 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.mapper import get_mapped_doc -from frappe.utils import add_days, getdate - - -class PatientEncounter(Document): - def validate(self): - self.set_title() - - def on_update(self): - if self.appointment: - frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') - - def on_submit(self): - if self.therapies: - create_therapy_plan(self) - - def on_cancel(self): - if self.appointment: - frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') - - if self.inpatient_record and self.drug_prescription: - delete_ip_medication_order(self) - - def set_title(self): - self.title = _('{0} with {1}').format(self.patient_name or self.patient, - self.practitioner_name or self.practitioner)[:100] - - @frappe.whitelist() - @staticmethod - def get_applicable_treatment_plans(encounter): - patient = frappe.get_doc('Patient', encounter['patient']) - - plan_filters = {} - plan_filters['name'] = ['in', []] - - age = patient.age - if age: - plan_filters['patient_age_from'] = ['<=', age.years] - plan_filters['patient_age_to'] = ['>=', age.years] - - gender = patient.sex - if gender: - plan_filters['gender'] = ['in', [gender, None]] - - diagnosis = encounter.get('diagnosis') - if diagnosis: - diagnosis = [_diagnosis['diagnosis'] for _diagnosis in encounter['diagnosis']] - filters = [ - ['diagnosis', 'in', diagnosis], - ['parenttype', '=', 'Treatment Plan Template'], - ] - diagnosis = frappe.get_list('Patient Encounter Diagnosis', filters=filters, fields='*') - plan_names = [_diagnosis['parent'] for _diagnosis in diagnosis] - plan_filters['name'][1].extend(plan_names) - - symptoms = encounter.get('symptoms') - if symptoms: - symptoms = [symptom['complaint'] for symptom in encounter['symptoms']] - filters = [ - ['complaint', 'in', symptoms], - ['parenttype', '=', 'Treatment Plan Template'], - ] - symptoms = frappe.get_list('Patient Encounter Symptom', filters=filters, fields='*') - plan_names = [symptom['parent'] for symptom in symptoms] - plan_filters['name'][1].extend(plan_names) - - if not plan_filters['name'][1]: - plan_filters.pop('name') - - plans = frappe.get_list('Treatment Plan Template', fields='*', filters=plan_filters) - - return plans - - @frappe.whitelist() - def set_treatment_plans(self, treatment_plans=None): - for treatment_plan in treatment_plans: - self.set_treatment_plan(treatment_plan) - - def set_treatment_plan(self, plan): - plan_items = frappe.get_list('Treatment Plan Template Item', filters={'parent': plan}, fields='*') - for plan_item in plan_items: - self.set_treatment_plan_item(plan_item) - - drugs = frappe.get_list('Drug Prescription', filters={'parent': plan}, fields='*') - for drug in drugs: - self.append('drug_prescription', drug) - - self.save() - - def set_treatment_plan_item(self, plan_item): - if plan_item.type == 'Clinical Procedure Template': - self.append('procedure_prescription', { - 'procedure': plan_item.template - }) - - if plan_item.type == 'Lab Test Template': - self.append('lab_test_prescription', { - 'lab_test_code': plan_item.template - }) - - if plan_item.type == 'Therapy Type': - self.append('therapies', { - 'therapy_type': plan_item.template - }) - - -@frappe.whitelist() -def make_ip_medication_order(source_name, target_doc=None): - def set_missing_values(source, target): - target.start_date = source.encounter_date - for entry in source.drug_prescription: - if entry.drug_code: - dosage = frappe.get_doc('Prescription Dosage', entry.dosage) - dates = get_prescription_dates(entry.period, target.start_date) - for date in dates: - for dose in dosage.dosage_strength: - order = target.append('medication_orders') - order.drug = entry.drug_code - order.drug_name = entry.drug_name - order.dosage = dose.strength - order.instructions = entry.comment - order.dosage_form = entry.dosage_form - order.date = date - order.time = dose.strength_time - target.end_date = dates[-1] - - doc = get_mapped_doc('Patient Encounter', source_name, { - 'Patient Encounter': { - 'doctype': 'Inpatient Medication Order', - 'field_map': { - 'name': 'patient_encounter', - 'patient': 'patient', - 'patient_name': 'patient_name', - 'patient_age': 'patient_age', - 'inpatient_record': 'inpatient_record', - 'practitioner': 'practitioner', - 'start_date': 'encounter_date' - }, - } - }, target_doc, set_missing_values) - - return doc - - -def get_prescription_dates(period, start_date): - prescription_duration = frappe.get_doc('Prescription Duration', period) - days = prescription_duration.get_days() - dates = [start_date] - for i in range(1, days): - dates.append(add_days(getdate(start_date), i)) - return dates - - -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 delete_ip_medication_order(encounter): - record = frappe.db.exists('Inpatient Medication Order', {'patient_encounter': encounter.name}) - if record: - frappe.delete_doc('Inpatient Medication Order', record, force=1) diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py deleted file mode 100644 index 3b64d98871..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter_dashboard.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'encounter', - 'non_standard_fieldnames': { - 'Patient Medical Record': 'reference_name', - 'Inpatient Medication Order': 'patient_encounter' - }, - 'transactions': [ - { - 'label': _('Records'), - 'items': ['Vital Signs', 'Patient Medical Record'] - }, - { - 'label': _('Orders'), - 'items': ['Inpatient Medication Order'] - } - ], - 'disable_create_buttons': ['Inpatient Medication Order'] - } diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter_list.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter_list.js deleted file mode 100644 index d8f63bd0fa..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter_list.js +++ /dev/null @@ -1,6 +0,0 @@ -/* -(c) ESS 2015-16 -*/ -frappe.listview_settings['Patient Encounter'] = { - filters:[["docstatus","!=","2"]] -}; diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py deleted file mode 100644 index fa643a31d8..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe - -from erpnext.healthcare.doctype.patient_encounter.patient_encounter import PatientEncounter - - -class TestPatientEncounter(unittest.TestCase): - def setUp(self): - try: - gender_m = frappe.get_doc({ - 'doctype': 'Gender', - 'gender': 'MALE' - }).insert() - gender_f = frappe.get_doc({ - 'doctype': 'Gender', - 'gender': 'FEMALE' - }).insert() - except frappe.exceptions.DuplicateEntryError: - gender_m = frappe.get_doc({ - 'doctype': 'Gender', - 'gender': 'MALE' - }) - gender_f = frappe.get_doc({ - 'doctype': 'Gender', - 'gender': 'FEMALE' - }) - - self.patient_male = frappe.get_doc({ - 'doctype': 'Patient', - 'first_name': 'John', - 'sex': gender_m.gender, - }).insert() - self.patient_female = frappe.get_doc({ - 'doctype': 'Patient', - 'first_name': 'Curie', - 'sex': gender_f.gender, - }).insert() - self.practitioner = frappe.get_doc({ - 'doctype': 'Healthcare Practitioner', - 'first_name': 'Doc', - 'sex': 'MALE', - }).insert() - try: - self.care_plan_male = frappe.get_doc({ - 'doctype': 'Treatment Plan Template', - 'template_name': 'test plan - m', - 'gender': gender_m.gender, - }).insert() - self.care_plan_female = frappe.get_doc({ - 'doctype': 'Treatment Plan Template', - 'template_name': 'test plan - f', - 'gender': gender_f.gender, - }).insert() - except frappe.exceptions.DuplicateEntryError: - self.care_plan_male = frappe.get_doc({ - 'doctype': 'Treatment Plan Template', - 'template_name': 'test plan - m', - 'gender': gender_m.gender, - }) - self.care_plan_female = frappe.get_doc({ - 'doctype': 'Treatment Plan Template', - 'template_name': 'test plan - f', - 'gender': gender_f.gender, - }) - - def test_treatment_plan_template_filter(self): - encounter = frappe.get_doc({ - 'doctype': 'Patient Encounter', - 'patient': self.patient_male.name, - 'practitioner': self.practitioner.name, - }).insert() - plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict()) - self.assertEqual(plans[0]['name'], self.care_plan_male.template_name) - - encounter = frappe.get_doc({ - 'doctype': 'Patient Encounter', - 'patient': self.patient_female.name, - 'practitioner': self.practitioner.name, - }).insert() - plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict()) - self.assertEqual(plans[0]['name'], self.care_plan_female.template_name) diff --git a/erpnext/healthcare/doctype/patient_encounter_diagnosis/__init__.py b/erpnext/healthcare/doctype/patient_encounter_diagnosis/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json b/erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json deleted file mode 100644 index 00ca309d63..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "actions": [], - "beta": 1, - "creation": "2020-02-26 16:48:16.835105", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "diagnosis" - ], - "fields": [ - { - "fieldname": "diagnosis", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Diagnosis", - "options": "Diagnosis", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-02-26 16:58:16.480583", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Encounter Diagnosis", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py b/erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py deleted file mode 100644 index e4d2069a50..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter_diagnosis/patient_encounter_diagnosis.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 PatientEncounterDiagnosis(Document): - pass diff --git a/erpnext/healthcare/doctype/patient_encounter_symptom/__init__.py b/erpnext/healthcare/doctype/patient_encounter_symptom/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json b/erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json deleted file mode 100644 index bc92145867..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "actions": [], - "beta": 1, - "creation": "2020-02-26 16:47:00.525657", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "complaint" - ], - "fields": [ - { - "fieldname": "complaint", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Complaint", - "options": "Complaint", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-02-26 16:57:37.997481", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Encounter Symptom", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py b/erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py deleted file mode 100644 index 47f2a2be7e..0000000000 --- a/erpnext/healthcare/doctype/patient_encounter_symptom/patient_encounter_symptom.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 PatientEncounterSymptom(Document): - pass diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py b/erpnext/healthcare/doctype/patient_history_custom_document_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json deleted file mode 100644 index 3025c7b06d..0000000000 --- a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "actions": [], - "creation": "2020-11-25 13:40:23.054469", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "document_type", - "date_fieldname", - "add_edit_fields", - "selected_fields" - ], - "fields": [ - { - "fieldname": "document_type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Document Type", - "options": "DocType", - "reqd": 1 - }, - { - "fieldname": "selected_fields", - "fieldtype": "Code", - "label": "Selected Fields", - "read_only": 1 - }, - { - "fieldname": "add_edit_fields", - "fieldtype": "Button", - "in_list_view": 1, - "label": "Add / Edit Fields" - }, - { - "fieldname": "date_fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Date Fieldname", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2020-11-30 13:54:37.474671", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient History Custom Document Type", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py b/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py deleted file mode 100644 index 34e15dc46a..0000000000 --- a/erpnext/healthcare/doctype/patient_history_custom_document_type/patient_history_custom_document_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 PatientHistoryCustomDocumentType(Document): - pass diff --git a/erpnext/healthcare/doctype/patient_history_settings/__init__.py b/erpnext/healthcare/doctype/patient_history_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js deleted file mode 100644 index 453da6a12b..0000000000 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.js +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Patient History Settings', { - refresh: function(frm) { - frm.set_query('document_type', 'custom_doctypes', () => { - return { - filters: { - custom: 1, - is_submittable: 1, - module: 'Healthcare', - } - }; - }); - }, - - field_selector: function(frm, doc, standard=1) { - let document_fields = []; - if (doc.selected_fields) - document_fields = (JSON.parse(doc.selected_fields)).map(f => f.fieldname); - - frm.call({ - method: 'get_doctype_fields', - doc: frm.doc, - args: { - document_type: doc.document_type, - fields: document_fields - }, - freeze: true, - callback: function(r) { - if (r.message) { - let doctype = 'Patient History Custom Document Type'; - if (standard) - doctype = 'Patient History Standard Document Type'; - - frm.events.show_field_selector_dialog(frm, doc, doctype, r.message); - } - } - }); - }, - - show_field_selector_dialog: function(frm, doc, doctype, doc_fields) { - let d = new frappe.ui.Dialog({ - title: __('{0} Fields', [__(doc.document_type)]), - fields: [ - { - label: __('Select Fields'), - fieldtype: 'MultiCheck', - fieldname: 'fields', - options: doc_fields, - columns: 2 - } - ] - }); - - d.$body.prepend(` - ` - ); - - frappe.utils.setup_search(d.$body, '.unit-checkbox', '.label-area'); - - d.set_primary_action(__('Save'), () => { - let values = d.get_values().fields; - - let selected_fields = []; - - frappe.model.with_doctype(doc.document_type, function() { - for (let idx in values) { - let value = values[idx]; - - let field = frappe.get_meta(doc.document_type).fields.filter((df) => df.fieldname == value)[0]; - if (field) { - selected_fields.push({ - label: field.label, - fieldname: field.fieldname, - fieldtype: field.fieldtype - }); - } - } - - d.refresh(); - frappe.model.set_value(doctype, doc.name, 'selected_fields', JSON.stringify(selected_fields)); - }); - - d.hide(); - }); - - d.show(); - }, - - get_date_field_for_dt: function(frm, row) { - frm.call({ - method: 'get_date_field_for_dt', - doc: frm.doc, - args: { - document_type: row.document_type - }, - callback: function(data) { - if (data.message) { - frappe.model.set_value('Patient History Custom Document Type', - row.name, 'date_fieldname', data.message); - } - } - }); - } -}); - -frappe.ui.form.on('Patient History Custom Document Type', { - document_type: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.document_type) { - frm.events.get_date_field_for_dt(frm, row); - } - }, - - add_edit_fields: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.document_type) { - frm.events.field_selector(frm, row, 0); - } - } -}); - -frappe.ui.form.on('Patient History Standard Document Type', { - add_edit_fields: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.document_type) { - frm.events.field_selector(frm, row); - } - } -}); diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json deleted file mode 100644 index 143e2c91eb..0000000000 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "actions": [], - "creation": "2020-11-25 13:41:37.675518", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "standard_doctypes", - "section_break_2", - "custom_doctypes" - ], - "fields": [ - { - "fieldname": "section_break_2", - "fieldtype": "Section Break" - }, - { - "fieldname": "custom_doctypes", - "fieldtype": "Table", - "label": "Custom Document Types", - "options": "Patient History Custom Document Type" - }, - { - "fieldname": "standard_doctypes", - "fieldtype": "Table", - "label": "Standard Document Types", - "options": "Patient History Standard Document Type", - "read_only": 1 - } - ], - "index_web_pages_for_search": 1, - "issingle": 1, - "links": [], - "modified": "2020-11-25 13:43:38.511771", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient History Settings", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py deleted file mode 100644 index b763591d3a..0000000000 --- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py +++ /dev/null @@ -1,194 +0,0 @@ -# -*- 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 json - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import cint, cstr - -from erpnext.healthcare.page.patient_history.patient_history import get_patient_history_doctypes - - -class PatientHistorySettings(Document): - def validate(self): - self.validate_submittable_doctypes() - self.validate_date_fieldnames() - - def validate_submittable_doctypes(self): - for entry in self.custom_doctypes: - if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')): - msg = _('Row #{0}: Document Type {1} is not submittable.').format( - entry.idx, frappe.bold(entry.document_type)) - msg += _('Patient Medical Record can only be created for submittable document types.') - frappe.throw(msg) - - def validate_date_fieldnames(self): - for entry in self.custom_doctypes: - field = frappe.get_meta(entry.document_type).get_field(entry.date_fieldname) - if not field: - frappe.throw(_('Row #{0}: No such Field named {1} found in the Document Type {2}.').format( - entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) - - if field.fieldtype not in ['Date', 'Datetime']: - frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format( - entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type))) - - @frappe.whitelist() - def get_doctype_fields(self, document_type, fields): - multicheck_fields = [] - doc_fields = frappe.get_meta(document_type).fields - - for field in doc_fields: - if field.fieldtype not in frappe.model.no_value_fields or \ - field.fieldtype in frappe.model.table_fields and not field.hidden: - multicheck_fields.append({ - 'label': field.label, - 'value': field.fieldname, - 'checked': 1 if field.fieldname in fields else 0 - }) - - return multicheck_fields - - @frappe.whitelist() - def get_date_field_for_dt(self, document_type): - meta = frappe.get_meta(document_type) - date_fields = meta.get('fields', { - 'fieldtype': ['in', ['Date', 'Datetime']] - }) - - if date_fields: - return date_fields[0].get('fieldname') - -def create_medical_record(doc, method=None): - medical_record_required = validate_medical_record_required(doc) - if not medical_record_required: - return - - if frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }): - return - - subject = set_subject_field(doc) - date_field = get_date_field(doc.doctype) - medical_record = frappe.new_doc('Patient Medical Record') - medical_record.patient = doc.patient - medical_record.subject = subject - medical_record.status = 'Open' - medical_record.communication_date = doc.get(date_field) - medical_record.reference_doctype = doc.doctype - medical_record.reference_name = doc.name - medical_record.reference_owner = doc.owner - medical_record.save(ignore_permissions=True) - - -def update_medical_record(doc, method=None): - medical_record_required = validate_medical_record_required(doc) - if not medical_record_required: - return - - medical_record_id = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }) - - if medical_record_id: - subject = set_subject_field(doc) - frappe.db.set_value('Patient Medical Record', medical_record_id[0][0], 'subject', subject) - else: - create_medical_record(doc) - - -def delete_medical_record(doc, method=None): - medical_record_required = validate_medical_record_required(doc) - if not medical_record_required: - return - - record = frappe.db.exists('Patient Medical Record', { 'reference_name': doc.name }) - if record: - frappe.delete_doc('Patient Medical Record', record, force=1) - - -def set_subject_field(doc): - from frappe.utils.formatters import format_value - - meta = frappe.get_meta(doc.doctype) - subject = '' - patient_history_fields = get_patient_history_fields(doc) - - for entry in patient_history_fields: - fieldname = entry.get('fieldname') - if entry.get('fieldtype') == 'Table' and doc.get(fieldname): - formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname)) - subject += frappe.bold(_(entry.get('label')) + ':') + '
' + cstr(formatted_value) + '
' - - else: - if doc.get(fieldname): - formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc) - subject += frappe.bold(_(entry.get('label')) + ':') + cstr(formatted_value) + '
' - - return subject - - -def get_date_field(doctype): - dt = get_patient_history_config_dt(doctype) - - return frappe.db.get_value(dt, { 'document_type': doctype }, 'date_fieldname') - - -def get_patient_history_fields(doc): - dt = get_patient_history_config_dt(doc.doctype) - patient_history_fields = frappe.db.get_value(dt, { 'document_type': doc.doctype }, 'selected_fields') - - if patient_history_fields: - return json.loads(patient_history_fields) - - -def get_formatted_value_for_table_field(items, df): - child_meta = frappe.get_meta(df.options) - - table_head = '' - table_row = '' - html = '' - create_head = True - for item in items: - table_row += '' - for cdf in child_meta.fields: - if cdf.in_list_view: - if create_head: - table_head += '' + cdf.label + '' - if item.get(cdf.fieldname): - table_row += '' + str(item.get(cdf.fieldname)) + '' - else: - table_row += '' - create_head = False - table_row += '' - - html += "" + table_head + table_row + "
" - - return html - - -def get_patient_history_config_dt(doctype): - if frappe.db.get_value('DocType', doctype, 'custom'): - return 'Patient History Custom Document Type' - else: - return 'Patient History Standard Document Type' - - -def validate_medical_record_required(doc): - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard \ - or get_module(doc) != 'Healthcare': - return False - - if doc.doctype not in get_patient_history_doctypes(): - return False - - return True - -def get_module(doc): - module = doc.meta.module - if not module: - module = frappe.db.get_value('DocType', doc.doctype, 'module') - - return module diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py deleted file mode 100644 index c37a2adc36..0000000000 --- a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import json -import unittest - -import frappe -from frappe.utils import getdate, strip_html - -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_patient - - -class TestPatientHistorySettings(unittest.TestCase): - def setUp(self): - dt = create_custom_doctype() - settings = frappe.get_single("Patient History Settings") - settings.append("custom_doctypes", { - "document_type": dt.name, - "date_fieldname": "date", - "selected_fields": json.dumps([{ - "label": "Date", - "fieldname": "date", - "fieldtype": "Date" - }, - { - "label": "Rating", - "fieldname": "rating", - "fieldtype": "Rating" - }, - { - "label": "Feedback", - "fieldname": "feedback", - "fieldtype": "Small Text" - }]) - }) - settings.save() - - def test_custom_doctype_medical_record(self): - # tests for medical record creation of standard doctypes in test_patient_medical_record.py - patient = create_patient() - doc = create_doc(patient) - # check for medical record - medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name}) - self.assertTrue(medical_rec) - - medical_rec = frappe.get_doc("Patient Medical Record", medical_rec) - expected_subject = "Date:{0}Rating:3Feedback:Test Patient History Settings".format( - frappe.utils.format_date(getdate())) - self.assertEqual(strip_html(medical_rec.subject), expected_subject) - self.assertEqual(medical_rec.patient, patient) - self.assertEqual(medical_rec.communication_date, getdate()) - - -def create_custom_doctype(): - if not frappe.db.exists("DocType", "Test Patient Feedback"): - doc = frappe.get_doc({ - "doctype": "DocType", - "module": "Healthcare", - "custom": 1, - "is_submittable": 1, - "fields": [{ - "label": "Date", - "fieldname": "date", - "fieldtype": "Date" - }, - { - "label": "Patient", - "fieldname": "patient", - "fieldtype": "Link", - "options": "Patient" - }, - { - "label": "Rating", - "fieldname": "rating", - "fieldtype": "Rating" - }, - { - "label": "Feedback", - "fieldname": "feedback", - "fieldtype": "Small Text" - }], - "permissions": [{ - "role": "System Manager", - "read": 1 - }], - "name": "Test Patient Feedback", - }) - doc.insert() - return doc - else: - return frappe.get_doc("DocType", "Test Patient Feedback") - - -def create_doc(patient): - doc = frappe.get_doc({ - "doctype": "Test Patient Feedback", - "patient": patient, - "date": getdate(), - "rating": 3, - "feedback": "Test Patient History Settings" - }).insert() - doc.submit() - - return doc diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py b/erpnext/healthcare/doctype/patient_history_standard_document_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json deleted file mode 100644 index b43099c4ea..0000000000 --- a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "actions": [], - "creation": "2020-11-25 13:39:36.014814", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "document_type", - "date_fieldname", - "add_edit_fields", - "selected_fields" - ], - "fields": [ - { - "fieldname": "document_type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Document Type", - "options": "DocType", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "selected_fields", - "fieldtype": "Code", - "label": "Selected Fields", - "read_only": 1 - }, - { - "fieldname": "add_edit_fields", - "fieldtype": "Button", - "in_list_view": 1, - "label": "Add / Edit Fields" - }, - { - "fieldname": "date_fieldname", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Date Fieldname", - "read_only": 1, - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2020-11-30 13:54:56.773325", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient History Standard Document Type", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py b/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py deleted file mode 100644 index b7dd09bc10..0000000000 --- a/erpnext/healthcare/doctype/patient_history_standard_document_type/patient_history_standard_document_type.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 PatientHistoryStandardDocumentType(Document): - pass diff --git a/erpnext/healthcare/doctype/patient_medical_record/__init__.py b/erpnext/healthcare/doctype/patient_medical_record/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.js b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.js deleted file mode 100644 index 93ff70e643..0000000000 --- a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Patient Medical Record', { -}); diff --git a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json deleted file mode 100644 index ed82355f33..0000000000 --- a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.json +++ /dev/null @@ -1,155 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2016-06-09 11:30:44.972056", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "naming_series", - "patient", - "status", - "column_break_2", - "attach", - "section_break_4", - "subject", - "section_break_8", - "communication_date", - "reference_doctype", - "reference_name", - "column_break_9", - "reference_owner", - "user" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-PMR-.YYYY.-", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Patient", - "options": "Patient", - "search_index": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "fieldname": "attach", - "fieldtype": "Attach", - "label": "Attach Medical Record" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "fieldname": "subject", - "fieldtype": "Text Editor", - "ignore_xss_filter": 1, - "label": "Subject" - }, - { - "fieldname": "status", - "fieldtype": "Select", - "label": "Status", - "options": "Open\nClose", - "read_only": 1 - }, - { - "default": "Today", - "fieldname": "communication_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Datetime", - "read_only": 1 - }, - { - "fieldname": "reference_doctype", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Reference DocType", - "options": "DocType", - "read_only": 1, - "search_index": 1 - }, - { - "fieldname": "reference_name", - "fieldtype": "Dynamic Link", - "in_list_view": 1, - "label": "Reference Name", - "options": "reference_doctype", - "read_only": 1, - "search_index": 1 - }, - { - "fetch_from": "reference_name.owner", - "fieldname": "reference_owner", - "fieldtype": "Data", - "label": "Reference Owner", - "no_copy": 1, - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "default": "__user", - "fieldname": "user", - "fieldtype": "Link", - "label": "User", - "options": "User", - "print_hide": 1, - "read_only": 1, - "report_hide": 1 - }, - { - "fieldname": "column_break_9", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_8", - "fieldtype": "Section Break" - } - ], - "in_create": 1, - "links": [], - "modified": "2020-04-29 12:26:57.679402", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Medical Record", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient, subject, communication_date, reference_doctype, reference_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.py deleted file mode 100644 index ac2cffa3e8..0000000000 --- a/erpnext/healthcare/doctype/patient_medical_record/patient_medical_record.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe.model.document import Document - - -class PatientMedicalRecord(Document): - def after_insert(self): - if self.reference_doctype == "Patient Medical Record" : - frappe.db.set_value("Patient Medical Record", self.name, "reference_name", self.name) diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py deleted file mode 100644 index 099146c7ee..0000000000 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import nowdate - -from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import ( - create_appointment, - create_encounter, - create_healthcare_docs, - create_medical_department, -) - - -class TestPatientMedicalRecord(unittest.TestCase): - def setUp(self): - frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) - frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) - make_pos_profile() - - def test_medical_record(self): - patient, practitioner = create_healthcare_docs() - medical_department = create_medical_department() - appointment = create_appointment(patient, practitioner, nowdate(), invoice=1) - encounter = create_encounter(appointment) - - # check for encounter - medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': encounter.name}) - self.assertTrue(medical_rec) - - vital_signs = create_vital_signs(appointment) - # check for vital signs - medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': vital_signs.name}) - self.assertTrue(medical_rec) - - appointment = create_appointment(patient, practitioner, nowdate(), invoice=1, procedure_template=1) - procedure = create_procedure(appointment) - procedure.start_procedure() - procedure.complete_procedure() - # check for clinical procedure - medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': procedure.name}) - self.assertTrue(medical_rec) - - template = create_lab_test_template(medical_department) - lab_test = create_lab_test(template.name, patient) - # check for lab test - medical_rec = frappe.db.exists('Patient Medical Record', {'status': 'Open', 'reference_name': lab_test.name}) - self.assertTrue(medical_rec) - - -def create_procedure(appointment): - if appointment: - procedure = frappe.new_doc('Clinical Procedure') - procedure.procedure_template = appointment.procedure_template - procedure.appointment = appointment.name - procedure.patient = appointment.patient - procedure.practitioner = appointment.practitioner - procedure.medical_department = appointment.department - procedure.start_dt = appointment.appointment_date - procedure.start_time = appointment.appointment_time - procedure.save() - procedure.submit() - return procedure - -def create_vital_signs(appointment): - vital_signs = frappe.new_doc('Vital Signs') - vital_signs.patient = appointment.patient - vital_signs.signs_date = appointment.appointment_date - vital_signs.signs_time = appointment.appointment_time - vital_signs.temperature = 38.5 - vital_signs.save() - vital_signs.submit() - return vital_signs - -def create_lab_test_template(medical_department): - if frappe.db.exists('Lab Test Template', 'Blood Test'): - return frappe.get_doc('Lab Test Template', 'Blood Test') - - template = frappe.new_doc('Lab Test Template') - template.lab_test_name = 'Blood Test' - template.lab_test_code = 'Blood Test' - template.lab_test_group = 'Services' - template.department = medical_department - template.is_billable = 1 - template.lab_test_rate = 2000 - template.save() - return template - -def create_lab_test(template, patient): - lab_test = frappe.new_doc('Lab Test') - lab_test.patient = patient - lab_test.patient_sex = frappe.db.get_value('Patient', patient, 'sex') - lab_test.template = template - lab_test.save() - lab_test.submit() - return lab_test diff --git a/erpnext/healthcare/doctype/patient_relation/__init__.py b/erpnext/healthcare/doctype/patient_relation/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/patient_relation/patient_relation.json b/erpnext/healthcare/doctype/patient_relation/patient_relation.json deleted file mode 100644 index 376f7f76d6..0000000000 --- a/erpnext/healthcare/doctype/patient_relation/patient_relation.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2017-04-26 15:40:11.561855", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "patient", - "relation", - "description" - ], - "fields": [ - { - "fieldname": "relation", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Relation", - "options": "\nFather\nMother\nSpouse\nSiblings\nFamily\nOther", - "search_index": 1 - }, - { - "fieldname": "patient", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1 - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Description" - } - ], - "istable": 1, - "links": [], - "modified": "2020-01-29 12:45:40.081899", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Relation", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_relation/patient_relation.py b/erpnext/healthcare/doctype/patient_relation/patient_relation.py deleted file mode 100644 index 17bc20940d..0000000000 --- a/erpnext/healthcare/doctype/patient_relation/patient_relation.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class PatientRelation(Document): - pass diff --git a/erpnext/healthcare/doctype/practitioner_schedule/__init__.py b/erpnext/healthcare/doctype/practitioner_schedule/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.js b/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.js deleted file mode 100644 index 7cb7c4b65e..0000000000 --- a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.js +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Practitioner Schedule', { - refresh: function(frm) { - cur_frm.fields_dict["time_slots"].grid.wrapper.find('.grid-add-row').hide(); - cur_frm.fields_dict["time_slots"].grid.add_custom_button(__('Add Time Slots'), () => { - let d = new frappe.ui.Dialog({ - fields: [ - {fieldname: 'days', label: __('Select Days'), fieldtype: 'MultiSelect', - options:[ - {value:'Sunday', label:__('Sunday')}, - {value:'Monday', label:__('Monday')}, - {value:'Tuesday', label:__('Tuesday')}, - {value:'Wednesday', label:__('Wednesday')}, - {value:'Thursday', label:__('Thursday')}, - {value:'Friday', label:__('Friday')}, - {value:'Saturday', label:__('Saturday')}, - ], reqd: 1}, - {fieldname: 'from_time', label: __('From'), fieldtype: 'Time', - 'default': '09:00:00', reqd: 1}, - {fieldname: 'to_time', label: __('To'), fieldtype: 'Time', - 'default': '12:00:00', reqd: 1}, - {fieldname: 'duration', label: __('Appointment Duration (mins)'), - fieldtype:'Int', 'default': 15, reqd: 1}, - ], - primary_action_label: __('Add Timeslots'), - primary_action: () => { - let values = d.get_values(); - if (values) { - let slot_added = false; - values.days.split(',').forEach(function(day){ - day = $.trim(day); - if (['Sunday', 'Monday', 'Tuesday', 'Wednesday', - 'Thursday', 'Friday', 'Saturday'].includes(day)){ - add_slots(day); - } - }); - - function check_overlap_or_add_slot(week_day, cur_time, end_time, add_slots_to_child){ - let overlap = false; - while (cur_time < end_time) { - let add_to_child = true; - let to_time = cur_time.clone().add(values.duration, 'minutes'); - if (to_time <= end_time) { - if (frm.doc.time_slots){ - frm.doc.time_slots.forEach(function(slot) { - if (slot.day == week_day){ - let slot_from_moment = moment(slot.from_time, 'HH:mm:ss'); - let slot_to_moment = moment(slot.to_time, 'HH:mm:ss'); - if (cur_time.isSame(slot_from_moment) || cur_time.isBetween(slot_from_moment, slot_to_moment) || - to_time.isSame(slot_to_moment) || to_time.isBetween(slot_from_moment, slot_to_moment)) { - overlap = true; - if (add_slots_to_child) { - frappe.show_alert({ - message:__('Time slot skiped, the slot {0} to {1} overlap exisiting slot {2} to {3}', - [cur_time.format('HH:mm:ss'), to_time.format('HH:mm:ss'), slot.from_time, slot.to_time]), - indicator:'orange' - }); - add_to_child = false; - } - } - } - }); - } - // add a new timeslot - if (add_to_child && add_slots_to_child) { - frm.add_child('time_slots', { - from_time: cur_time.format('HH:mm:ss'), - to_time: to_time.format('HH:mm:ss'), - day: week_day - }); - slot_added = true; - } - } - cur_time = to_time; - } - return overlap; - } - - function add_slots(week_day) { - let cur_time = moment(values.from_time, 'HH:mm:ss'); - let end_time = moment(values.to_time, 'HH:mm:ss'); - if (check_overlap_or_add_slot(week_day, cur_time, end_time, false)) { - frappe.confirm(__('Schedules for {0} overlaps, do you want to proceed after skiping overlaped slots ?', [week_day]), - function() { - // if Yes - check_overlap_or_add_slot(week_day, cur_time, end_time, true); - }, - function() { - // if No - frappe.show_alert({ - message: __('Slots for {0} are not added to the schedule', [week_day]), - indicator: 'red' - }); - } - ); - } else { - check_overlap_or_add_slot(week_day, cur_time, end_time, true); - } - } - - frm.refresh_field('time_slots'); - - if (slot_added) { - frappe.show_alert({ - message: __('Time slots added'), - indicator: 'green' - }); - } - } - }, - }); - d.show(); - }); - } -}); diff --git a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json b/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json deleted file mode 100644 index a21825ea8e..0000000000 --- a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "actions": [], - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:schedule_name", - "beta": 1, - "creation": "2017-05-03 17:28:03.926787", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "disabled", - "schedule_details_section", - "schedule_name", - "time_slots" - ], - "fields": [ - { - "fieldname": "schedule_name", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Schedule Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "time_slots", - "fieldtype": "Table", - "label": "Time Slots", - "options": "Healthcare Schedule Time Slot" - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled", - "print_hide": 1 - }, - { - "fieldname": "schedule_details_section", - "fieldtype": "Section Break", - "label": "Schedule Details" - } - ], - "links": [], - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Practitioner Schedule", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "share": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.py b/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.py deleted file mode 100644 index 7fa31e5fb6..0000000000 --- a/erpnext/healthcare/doctype/practitioner_schedule/practitioner_schedule.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class PractitionerSchedule(Document): - def autoname(self): - self.name = self.schedule_name diff --git a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py b/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py deleted file mode 100644 index 1ecaa47248..0000000000 --- a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestPractitionerSchedule(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/practitioner_service_unit_schedule/__init__.py b/erpnext/healthcare/doctype/practitioner_service_unit_schedule/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json b/erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json deleted file mode 100644 index 4c283aaf1e..0000000000 --- a/erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2017-11-16 12:19:17.163786", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "schedule", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Schedule", - "length": 0, - "no_copy": 0, - "options": "Practitioner Schedule", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "service_unit", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Service Unit", - "length": 0, - "no_copy": 0, - "options": "Healthcare Service Unit", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-11-04 03:33:07.936958", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Practitioner Service Unit Schedule", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py b/erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py deleted file mode 100644 index 4eba1fbf6b..0000000000 --- a/erpnext/healthcare/doctype/practitioner_service_unit_schedule/practitioner_service_unit_schedule.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class PractitionerServiceUnitSchedule(Document): - pass diff --git a/erpnext/healthcare/doctype/prescription_dosage/__init__.py b/erpnext/healthcare/doctype/prescription_dosage/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.js b/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.js deleted file mode 100644 index 94b444cbaa..0000000000 --- a/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Prescription Dosage', { -}); diff --git a/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.json b/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.json deleted file mode 100644 index 9fb0dbc13c..0000000000 --- a/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:dosage", - "beta": 1, - "creation": "2016-09-16 15:49:25.327610", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dosage", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Dosage", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dosage_strength", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "options": "Dosage Strength", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-10-05 11:20:47.558464", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Prescription Dosage", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "dosage", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.py b/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.py deleted file mode 100644 index 19f9b70bb6..0000000000 --- a/erpnext/healthcare/doctype/prescription_dosage/prescription_dosage.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class PrescriptionDosage(Document): - pass diff --git a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.py b/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.py deleted file mode 100644 index cabfd35e23..0000000000 --- a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestPrescriptionDosage(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/prescription_duration/__init__.py b/erpnext/healthcare/doctype/prescription_duration/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/prescription_duration/prescription_duration.js b/erpnext/healthcare/doctype/prescription_duration/prescription_duration.js deleted file mode 100644 index dd5887c929..0000000000 --- a/erpnext/healthcare/doctype/prescription_duration/prescription_duration.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Prescription Duration', { -}); diff --git a/erpnext/healthcare/doctype/prescription_duration/prescription_duration.json b/erpnext/healthcare/doctype/prescription_duration/prescription_duration.json deleted file mode 100644 index c4f6c5f10d..0000000000 --- a/erpnext/healthcare/doctype/prescription_duration/prescription_duration.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "", - "beta": 1, - "creation": "2016-09-16 15:50:28.895789", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "number", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Number", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "period", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Period", - "length": 0, - "no_copy": 0, - "options": "Hour\nDay\nWeek\nMonth", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-08-31 13:42:51.325725", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Prescription Duration", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "", - "show_name_in_global_search": 0, - "sort_field": "", - "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/prescription_duration/prescription_duration.py b/erpnext/healthcare/doctype/prescription_duration/prescription_duration.py deleted file mode 100644 index 988276da74..0000000000 --- a/erpnext/healthcare/doctype/prescription_duration/prescription_duration.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document -from frappe.utils import cstr - - -class PrescriptionDuration(Document): - def autoname(self): - self.name = " ".join(filter(None, - [cstr(self.get(f)).strip() for f in ["number", "period"]])) - def get_days(self): - days = 0 - duration = self - if(duration.period == 'Day'): - days = duration.number - if(duration.period == 'Hour'): - days = (duration.number)/24 - if(duration.period == 'Week'): - days = (duration.number*7) - if(duration.period == 'Month'): - days = (duration.number*30) - return days - def get_weeks(self): - weeks = 0 - duration = self - if(duration.period == 'Day'): - weeks = (duration.number)/7 - #if(duration.period == 'Hour'): - # weeks = (duration.number)/x - if(duration.period == 'Week'): - weeks = duration.number - if(duration.period == 'Month'): - weeks = duration.number*4 - return weeks - def get_months(self): - months = 0 - duration = self - if(duration.period == 'Day'): - months = (duration.number)/30 - #if(duration.period == 'Hour'): - # months = (duration.number)/x - if(duration.period == 'Week'): - months = (duration.number)/4 - if(duration.period == 'Month'): - months = duration.number - return months - def get_hours(self): - hours = 0 - duration = self - if(duration.period == 'Day'): - hours = (duration.number*24) - if(duration.period == 'Hour'): - hours = duration.number - if(duration.period == 'Week'): - hours = (duration.number*24)*7 - if(duration.period == 'Month'): - hours = (duration.number*24)*30 - return hours - def get_minutes(self): - minutes = 0 - duration = self - if(duration.period == 'Day'): - minutes = (duration.number*1440) - if(duration.period == 'Hour'): - minutes = (duration.number*60) - if(duration.period == 'Week'): - minutes = (duration.number*10080) - if(duration.period == 'Month'): - minutes = (duration.number*43800) - return minutes diff --git a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.py b/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.py deleted file mode 100644 index 197bb3e7fb..0000000000 --- a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestPrescriptionDuration(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/procedure_prescription/__init__.py b/erpnext/healthcare/doctype/procedure_prescription/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json b/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json deleted file mode 100644 index e4c01d79c1..0000000000 --- a/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "beta": 1, - "creation": "2017-11-17 15:52:48.324157", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "procedure", - "procedure_name", - "department", - "practitioner", - "date", - "comments", - "appointment_booked", - "procedure_created", - "invoiced" - ], - "fields": [ - { - "fieldname": "procedure", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Clinical Procedure", - "options": "Clinical Procedure Template", - "reqd": 1 - }, - { - "fetch_from": "procedure.template", - "fieldname": "procedure_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Procedure Name" - }, - { - "fetch_from": "procedure.medical_department", - "fieldname": "department", - "fieldtype": "Link", - "label": "Department", - "options": "Medical Department" - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Referring Practitioner", - "options": "Healthcare Practitioner" - }, - { - "fieldname": "date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Date" - }, - { - "fieldname": "comments", - "fieldtype": "Data", - "label": "Comments" - }, - { - "default": "0", - "fieldname": "appointment_booked", - "fieldtype": "Check", - "hidden": 1, - "label": "Appointment Booked", - "search_index": 1 - }, - { - "default": "0", - "fieldname": "procedure_created", - "fieldtype": "Check", - "hidden": 1, - "label": "Procedure Created", - "no_copy": 1, - "search_index": 1 - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "read_only": 1, - "search_index": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-02-26 15:42:33.988081", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Procedure Prescription", - "owner": "Administrator", - "permissions": [], - "restrict_to_domain": "Healthcare", - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.py b/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.py deleted file mode 100644 index f4d29fa6a3..0000000000 --- a/erpnext/healthcare/doctype/procedure_prescription/procedure_prescription.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class ProcedurePrescription(Document): - pass diff --git a/erpnext/healthcare/doctype/sample_collection/__init__.py b/erpnext/healthcare/doctype/sample_collection/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.js b/erpnext/healthcare/doctype/sample_collection/sample_collection.js deleted file mode 100644 index ddf8285bc6..0000000000 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.js +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2016, ESS and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Sample Collection', { - refresh: function(frm) { - if (frappe.defaults.get_default('create_sample_collection_for_lab_test')) { - frm.add_custom_button(__('View Lab Tests'), function() { - frappe.route_options = {'sample': frm.doc.name}; - frappe.set_route('List', 'Lab Test'); - }); - } - } -}); - -frappe.ui.form.on('Sample Collection', 'patient', function(frm) { - if(frm.doc.patient){ - frappe.call({ - 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { - patient: frm.doc.patient - }, - callback: function (data) { - var age = null; - if (data.message.dob){ - age = calculate_age(data.message.dob); - } - frappe.model.set_value(frm.doctype,frm.docname, 'patient_age', age); - frappe.model.set_value(frm.doctype,frm.docname, 'patient_sex', data.message.sex); - } - }); - } -}); - -var calculate_age = function(birth) { - var ageMS = Date.parse(Date()) - Date.parse(birth); - var age = new Date(); - age.setTime(ageMS); - var years = age.getFullYear() - 1970; - return `${years} ${__('Years(s)')} ${age.getMonth()} ${__('Month(s)')} ${age.getDate()} ${__('Day(s)')}`; -}; diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.json b/erpnext/healthcare/doctype/sample_collection/sample_collection.json deleted file mode 100644 index 83383e3445..0000000000 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.json +++ /dev/null @@ -1,256 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2016-04-05 15:58:18.076977", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "patient_details_section", - "naming_series", - "patient", - "patient_name", - "patient_age", - "patient_sex", - "column_break_4", - "inpatient_record", - "company", - "invoiced", - "section_break_6", - "sample", - "sample_uom", - "sample_qty", - "column_break_10", - "collected_by", - "collected_time", - "num_print", - "section_break_15", - "sample_details", - "amended_from" - ], - "fields": [ - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "bold": 1, - "fieldname": "naming_series", - "fieldtype": "Select", - "hide_days": 1, - "hide_seconds": 1, - "label": "Series", - "no_copy": 1, - "options": "HLC-SC-.YYYY.-", - "print_hide": 1, - "reqd": 1 - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "hide_days": 1, - "hide_seconds": 1, - "label": "Invoiced", - "no_copy": 1, - "read_only": 1, - "search_index": 1 - }, - { - "fetch_from": "inpatient_record.patient", - "fieldname": "patient", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "hide_days": 1, - "hide_seconds": 1, - "label": "Age", - "read_only": 1 - }, - { - "fetch_from": "patient.sex", - "fieldname": "patient_sex", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Gender", - "options": "Gender", - "read_only": 1 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "in_standard_filter": 1, - "label": "Company", - "options": "Company" - }, - { - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Sample Details" - }, - { - "fieldname": "sample", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Sample", - "options": "Lab Test Sample", - "reqd": 1, - "search_index": 1 - }, - { - "fetch_from": "sample.sample_uom", - "fieldname": "sample_uom", - "fieldtype": "Data", - "hide_days": 1, - "hide_seconds": 1, - "in_list_view": 1, - "label": "UOM", - "read_only": 1 - }, - { - "fieldname": "column_break_10", - "fieldtype": "Column Break", - "hide_days": 1, - "hide_seconds": 1 - }, - { - "fieldname": "collected_by", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "ignore_user_permissions": 1, - "label": "Collected By", - "options": "User" - }, - { - "fieldname": "collected_time", - "fieldtype": "Datetime", - "hide_days": 1, - "hide_seconds": 1, - "label": "Collected On" - }, - { - "allow_on_submit": 1, - "default": "1", - "description": "Number of prints required for labelling the samples", - "fieldname": "num_print", - "fieldtype": "Int", - "hide_days": 1, - "hide_seconds": 1, - "label": "No. of prints", - "print_hide": 1, - "report_hide": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "hide_days": 1, - "hide_seconds": 1, - "label": "Amended From", - "no_copy": 1, - "options": "Sample Collection", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "section_break_15", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1 - }, - { - "default": "0", - "fieldname": "sample_qty", - "fieldtype": "Float", - "hide_days": 1, - "hide_seconds": 1, - "in_list_view": 1, - "label": "Quantity" - }, - { - "fieldname": "sample_details", - "fieldtype": "Long Text", - "hide_days": 1, - "hide_seconds": 1, - "ignore_xss_filter": 1, - "label": "Collection Details" - }, - { - "fieldname": "patient_details_section", - "fieldtype": "Section Break", - "label": "Patient Details" - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - } - ], - "is_submittable": 1, - "links": [], - "modified": "2020-07-30 16:53:13.076104", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Sample Collection", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient, sample", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "patient", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/sample_collection/sample_collection.py b/erpnext/healthcare/doctype/sample_collection/sample_collection.py deleted file mode 100644 index 7de6ac08ca..0000000000 --- a/erpnext/healthcare/doctype/sample_collection/sample_collection.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.utils import flt - - -class SampleCollection(Document): - def validate(self): - if flt(self.sample_qty) <= 0: - frappe.throw(_('Sample Quantity cannot be negative or 0'), title=_('Invalid Quantity')) diff --git a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.py b/erpnext/healthcare/doctype/sample_collection/test_sample_collection.py deleted file mode 100644 index 0b16173dd5..0000000000 --- a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Sample Collection') - -class TestSampleCollection(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/sensitivity/__init__.py b/erpnext/healthcare/doctype/sensitivity/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/sensitivity/sensitivity.js b/erpnext/healthcare/doctype/sensitivity/sensitivity.js deleted file mode 100644 index f9c9002fe6..0000000000 --- a/erpnext/healthcare/doctype/sensitivity/sensitivity.js +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Sensitivity', { -}); diff --git a/erpnext/healthcare/doctype/sensitivity/sensitivity.json b/erpnext/healthcare/doctype/sensitivity/sensitivity.json deleted file mode 100644 index eddfda9056..0000000000 --- a/erpnext/healthcare/doctype/sensitivity/sensitivity.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:sensitivity", - "beta": 1, - "creation": "2016-02-23 11:12:54.623249", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sensitivity", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 1, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Sensitivity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-10-05 11:19:12.110308", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Sensitivity", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Laboratory User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Healthcare Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "search_fields": "sensitivity", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "sensitivity", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/sensitivity/sensitivity.py b/erpnext/healthcare/doctype/sensitivity/sensitivity.py deleted file mode 100644 index f61781d3f8..0000000000 --- a/erpnext/healthcare/doctype/sensitivity/sensitivity.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class Sensitivity(Document): - pass diff --git a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.py b/erpnext/healthcare/doctype/sensitivity/test_sensitivity.py deleted file mode 100644 index c772c72faf..0000000000 --- a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Sensitivity') - -class TestSensitivity(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/sensitivity_test_result/__init__.py b/erpnext/healthcare/doctype/sensitivity_test_result/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json deleted file mode 100644 index 768c17710f..0000000000 --- a/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2016-02-22 15:18:01.769903", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "antibiotic", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Antibiotic", - "length": 0, - "no_copy": 0, - "options": "Antibiotic", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "antibiotic_sensitivity", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Sensitivity", - "length": 0, - "no_copy": 0, - "options": "Sensitivity", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-10-05 11:08:06.327972", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Sensitivity Test Result", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Healthcare", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py b/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py deleted file mode 100644 index 53f7acc4af..0000000000 --- a/erpnext/healthcare/doctype/sensitivity_test_result/sensitivity_test_result.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class SensitivityTestResult(Document): - pass diff --git a/erpnext/healthcare/doctype/therapy_plan/__init__.py b/erpnext/healthcare/doctype/therapy_plan/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py deleted file mode 100644 index 4f96f6a706..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe -from frappe.utils import flt, getdate, nowdate - -from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import ( - create_appointment, - create_healthcare_docs, - create_medical_department, - create_patient, -) -from erpnext.healthcare.doctype.therapy_plan.therapy_plan import ( - make_sales_invoice, - make_therapy_session, -) -from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type - - -class TestTherapyPlan(unittest.TestCase): - def test_creation_on_encounter_submission(self): - patient, practitioner = create_healthcare_docs() - medical_department = create_medical_department() - 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.assertEqual(plan.status, 'Not Started') - - session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') - frappe.get_doc(session).submit() - self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') - - session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') - frappe.get_doc(session).submit() - self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') - - patient, practitioner = create_healthcare_docs() - appointment = create_appointment(patient, practitioner, nowdate()) - - session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) - session = frappe.get_doc(session) - session.submit() - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') - session.cancel() - self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open') - - def test_therapy_plan_from_template(self): - patient = create_patient() - template = create_therapy_plan_template() - # check linked item - self.assertTrue(frappe.db.exists('Therapy Plan Template', {'linked_item': 'Complete Rehab'})) - - plan = create_therapy_plan(template) - # invoice - si = make_sales_invoice(plan.name, patient, '_Test Company', template) - si.save() - - therapy_plan_template_amt = frappe.db.get_value('Therapy Plan Template', template, 'total_amount') - self.assertEqual(si.items[0].amount, therapy_plan_template_amt) - - -def create_therapy_plan(template=None): - patient = create_patient() - therapy_type = create_therapy_type() - plan = frappe.new_doc('Therapy Plan') - plan.patient = patient - plan.start_date = getdate() - - if template: - plan.therapy_plan_template = template - plan = plan.set_therapy_details_from_template() - else: - 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 - -def create_therapy_plan_template(): - template_name = frappe.db.exists('Therapy Plan Template', 'Complete Rehab') - if not template_name: - therapy_type = create_therapy_type() - template = frappe.new_doc('Therapy Plan Template') - template.plan_name = template.item_code = template.item_name = 'Complete Rehab' - template.item_group = 'Services' - rate = frappe.db.get_value('Therapy Type', therapy_type.name, 'rate') - template.append('therapy_types', { - 'therapy_type': therapy_type.name, - 'no_of_sessions': 2, - 'rate': rate, - 'amount': 2 * flt(rate) - }) - template.save() - template_name = template.name - - return template_name diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js deleted file mode 100644 index 42e231dc66..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js +++ /dev/null @@ -1,133 +0,0 @@ -// 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.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, - company: frm.doc.company - }, - 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')); - } - - if (frm.doc.therapy_plan_template && !frm.doc.invoiced) { - frm.add_custom_button(__('Sales Invoice'), function() { - frm.trigger('make_sales_invoice'); - }, __('Create')); - } - } - - if (frm.doc.therapy_plan_template) { - frm.fields_dict.therapy_plan_details.grid.update_docfield_property( - 'therapy_type', 'read_only', 1 - ); - frm.fields_dict.therapy_plan_details.grid.update_docfield_property( - 'no_of_sessions', 'read_only', 1 - ); - } - }, - - make_sales_invoice: function(frm) { - frappe.call({ - args: { - 'reference_name': frm.doc.name, - 'patient': frm.doc.patient, - 'company': frm.doc.company, - 'therapy_plan_template': frm.doc.therapy_plan_template - }, - method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_sales_invoice', - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route('Form', doclist[0].doctype, doclist[0].name); - } - }); - }, - - therapy_plan_template: function(frm) { - if (frm.doc.therapy_plan_template) { - frappe.call({ - method: 'set_therapy_details_from_template', - doc: frm.doc, - freeze: true, - freeze_message: __('Fetching Template Details'), - callback: function() { - refresh_field('therapy_plan_details'); - } - }); - } - }, - - show_progress_for_therapies: function(frm) { - let bars = []; - let message = ''; - - // 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%'; - } - 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'); - } -}); diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json deleted file mode 100644 index c03e9de332..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "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", - "invoiced", - "column_break_4", - "company", - "status", - "start_date", - "section_break_3", - "therapy_plan_template", - "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", - "read_only_depends_on": "therapy_plan_template", - "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 - }, - { - "fieldname": "company", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Company", - "options": "Company", - "reqd": 1 - }, - { - "fieldname": "therapy_plan_template", - "fieldtype": "Link", - "label": "Therapy Plan Template", - "options": "Therapy Plan Template", - "set_only_once": 1 - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - } - ], - "links": [], - "modified": "2020-11-04 18:13:13.564999", - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py deleted file mode 100644 index 6d63f39189..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- 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.utils import flt, today - - -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 = 0 - total_sessions_completed = 0 - for entry in self.therapy_plan_details: - if entry.no_of_sessions: - total_sessions += entry.no_of_sessions - if entry.sessions_completed: - total_sessions_completed += entry.sessions_completed - - self.db_set('total_sessions', total_sessions) - self.db_set('total_sessions_completed', total_sessions_completed) - - @frappe.whitelist() - def set_therapy_details_from_template(self): - # Add therapy types in the child table - self.set('therapy_plan_details', []) - therapy_plan_template = frappe.get_doc('Therapy Plan Template', self.therapy_plan_template) - - for data in therapy_plan_template.therapy_types: - self.append('therapy_plan_details', { - 'therapy_type': data.therapy_type, - 'no_of_sessions': data.no_of_sessions - }) - return self - - -@frappe.whitelist() -def make_therapy_session(therapy_plan, patient, therapy_type, company, appointment=None): - therapy_type = frappe.get_doc('Therapy Type', therapy_type) - - therapy_session = frappe.new_doc('Therapy Session') - therapy_session.therapy_plan = therapy_plan - therapy_session.company = company - 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 - therapy_session.appointment = appointment - - if frappe.flags.in_test: - therapy_session.start_date = today() - return therapy_session.as_dict() - - -@frappe.whitelist() -def make_sales_invoice(reference_name, patient, company, therapy_plan_template): - from erpnext.stock.get_item_details import get_item_details - si = frappe.new_doc('Sales Invoice') - si.company = company - si.patient = patient - si.customer = frappe.db.get_value('Patient', patient, 'customer') - - item = frappe.db.get_value('Therapy Plan Template', therapy_plan_template, 'linked_item') - price_list, price_list_currency = frappe.db.get_values('Price List', {'selling': 1}, ['name', 'currency'])[0] - args = { - 'doctype': 'Sales Invoice', - 'item_code': item, - 'company': company, - 'customer': si.customer, - 'selling_price_list': price_list, - 'price_list_currency': price_list_currency, - 'plc_conversion_rate': 1.0, - 'conversion_rate': 1.0 - } - - item_line = si.append('items', {}) - item_details = get_item_details(args) - item_line.item_code = item - item_line.qty = 1 - item_line.rate = item_details.price_list_rate - item_line.amount = flt(item_line.rate) * flt(item_line.qty) - item_line.reference_dt = 'Therapy Plan' - item_line.reference_dn = reference_name - item_line.description = item_details.description - - si.set_missing_values(for_validate = True) - return si diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py deleted file mode 100644 index 25c8df1d6b..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'therapy_plan', - 'non_standard_fieldnames': { - 'Sales Invoice': 'reference_dn' - }, - 'transactions': [ - { - 'label': _('Therapy Sessions'), - 'items': ['Therapy Session'] - }, - { - 'label': _('Billing'), - 'items': ['Sales Invoice'] - } - ], - 'disable_create_buttons': ['Sales Invoice'] - } diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js deleted file mode 100644 index 63967aff33..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js +++ /dev/null @@ -1,11 +0,0 @@ -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]; - } -}; diff --git a/erpnext/healthcare/doctype/therapy_plan_detail/__init__.py b/erpnext/healthcare/doctype/therapy_plan_detail/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json deleted file mode 100644 index 77f08af07d..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "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", - "no_copy": 1, - "read_only": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-11-04 18:15:52.173450", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Therapy Plan Detail", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py deleted file mode 100644 index 1842fc2197..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/therapy_plan_template/__init__.py b/erpnext/healthcare/doctype/therapy_plan_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py b/erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py deleted file mode 100644 index cd3d5686bc..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template/test_therapy_plan_template.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 TestTherapyPlanTemplate(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js deleted file mode 100644 index 86de1928e2..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Therapy Plan Template', { - refresh: function(frm) { - frm.set_query('therapy_type', 'therapy_types', () => { - return { - filters: { - 'is_billable': 1 - } - }; - }); - }, - - set_totals: function(frm) { - let total_sessions = 0; - let total_amount = 0.0; - frm.doc.therapy_types.forEach((d) => { - if (d.no_of_sessions) total_sessions += cint(d.no_of_sessions); - if (d.amount) total_amount += flt(d.amount); - }); - frm.set_value('total_sessions', total_sessions); - frm.set_value('total_amount', total_amount); - frm.refresh_fields(); - } -}); - -frappe.ui.form.on('Therapy Plan Template Detail', { - therapy_type: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - frappe.call('frappe.client.get', { - doctype: 'Therapy Type', - name: row.therapy_type - }).then((res) => { - row.rate = res.message.rate; - if (!row.no_of_sessions) - row.no_of_sessions = 1; - row.amount = flt(row.rate) * cint(row.no_of_sessions); - frm.refresh_field('therapy_types'); - frm.trigger('set_totals'); - }); - }, - - no_of_sessions: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - row.amount = flt(row.rate) * cint(row.no_of_sessions); - frm.refresh_field('therapy_types'); - frm.trigger('set_totals'); - }, - - rate: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - row.amount = flt(row.rate) * cint(row.no_of_sessions); - frm.refresh_field('therapy_types'); - frm.trigger('set_totals'); - } -}); diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json deleted file mode 100644 index 48fc896257..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "actions": [], - "autoname": "field:plan_name", - "creation": "2020-09-22 17:51:38.861055", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "plan_name", - "linked_item_details_section", - "item_code", - "item_name", - "item_group", - "column_break_6", - "description", - "linked_item", - "therapy_types_section", - "therapy_types", - "section_break_11", - "total_sessions", - "column_break_13", - "total_amount" - ], - "fields": [ - { - "fieldname": "plan_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Plan Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "therapy_types_section", - "fieldtype": "Section Break", - "label": "Therapy Types" - }, - { - "fieldname": "therapy_types", - "fieldtype": "Table", - "label": "Therapy Types", - "options": "Therapy Plan Template Detail", - "reqd": 1 - }, - { - "fieldname": "linked_item", - "fieldtype": "Link", - "label": "Linked Item", - "options": "Item", - "read_only": 1 - }, - { - "fieldname": "linked_item_details_section", - "fieldtype": "Section Break", - "label": "Linked Item Details" - }, - { - "fieldname": "item_code", - "fieldtype": "Data", - "label": "Item Code", - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name", - "reqd": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "label": "Item Group", - "options": "Item Group", - "reqd": 1 - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Item Description" - }, - { - "fieldname": "total_amount", - "fieldtype": "Currency", - "label": "Total Amount", - "read_only": 1 - }, - { - "fieldname": "section_break_11", - "fieldtype": "Section Break" - }, - { - "fieldname": "total_sessions", - "fieldtype": "Int", - "label": "Total Sessions", - "read_only": 1 - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2020-10-08 00:56:58.062105", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Therapy Plan 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 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py deleted file mode 100644 index f5512be207..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- 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.utils import cint, flt - -from erpnext.healthcare.doctype.therapy_type.therapy_type import make_item_price - - -class TherapyPlanTemplate(Document): - def after_insert(self): - self.create_item_from_template() - - def validate(self): - self.set_totals() - - def on_update(self): - doc_before_save = self.get_doc_before_save() - if not doc_before_save: return - if doc_before_save.item_name != self.item_name or doc_before_save.item_group != self.item_group \ - or doc_before_save.description != self.description: - self.update_item() - - if doc_before_save.therapy_types != self.therapy_types: - self.update_item_price() - - def set_totals(self): - total_sessions = 0 - total_amount = 0 - - for entry in self.therapy_types: - total_sessions += cint(entry.no_of_sessions) - total_amount += flt(entry.amount) - - self.total_sessions = total_sessions - self.total_amount = total_amount - - def create_item_from_template(self): - uom = frappe.db.exists('UOM', 'Nos') or frappe.db.get_single_value('Stock Settings', 'stock_uom') - - item = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': self.item_code, - 'item_name': self.item_name, - 'item_group': self.item_group, - 'description': self.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, - 'stock_uom': uom - }).insert(ignore_permissions=True, ignore_mandatory=True) - - make_item_price(item.name, self.total_amount) - self.db_set('linked_item', item.name) - - def update_item(self): - item_doc = frappe.get_doc('Item', {'item_code': self.linked_item}) - item_doc.item_name = self.item_name - item_doc.item_group = self.item_group - item_doc.description = self.description - item_doc.ignore_mandatory = True - item_doc.save(ignore_permissions=True) - - def update_item_price(self): - item_price = frappe.get_doc('Item Price', {'item_code': self.linked_item}) - item_price.item_name = self.item_name - item_price.price_list_rate = self.total_amount - item_price.ignore_mandatory = True - item_price.save(ignore_permissions=True) diff --git a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py b/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py deleted file mode 100644 index def5c482d1..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template/therapy_plan_template_dashboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'therapy_plan_template', - 'transactions': [ - { - 'label': _('Therapy Plans'), - 'items': ['Therapy Plan'] - } - ] - } diff --git a/erpnext/healthcare/doctype/therapy_plan_template_detail/__init__.py b/erpnext/healthcare/doctype/therapy_plan_template_detail/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json b/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json deleted file mode 100644 index 5553a118f8..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "actions": [], - "creation": "2020-10-07 23:04:44.373381", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "therapy_type", - "no_of_sessions", - "rate", - "amount" - ], - "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" - }, - { - "fieldname": "rate", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Rate" - }, - { - "fieldname": "amount", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Amount", - "read_only": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-10-07 23:46:54.296322", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Therapy Plan Template Detail", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py b/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py deleted file mode 100644 index 104c1bf28b..0000000000 --- a/erpnext/healthcare/doctype/therapy_plan_template_detail/therapy_plan_template_detail.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- 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 TherapyPlanTemplateDetail(Document): - pass diff --git a/erpnext/healthcare/doctype/therapy_session/__init__.py b/erpnext/healthcare/doctype/therapy_session/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/therapy_session/test_therapy_session.py b/erpnext/healthcare/doctype/therapy_session/test_therapy_session.py deleted file mode 100644 index e4afacf3f0..0000000000 --- a/erpnext/healthcare/doctype/therapy_session/test_therapy_session.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- 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 diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js deleted file mode 100644 index fbfa774c91..0000000000 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.js +++ /dev/null @@ -1,171 +0,0 @@ -// 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} - ]; - - frm.set_query('service_unit', function() { - return { - filters: { - 'is_group': false, - 'allow_appointments': true, - 'company': frm.doc.company - } - }; - }); - - frm.set_query('appointment', function() { - - return { - filters: { - 'status': ['in', ['Open', 'Scheduled']] - } - }; - }); - }, - - refresh: function(frm) { - if (frm.doc.therapy_plan) { - frm.trigger('filter_therapy_types'); - } - - if (!frm.doc.__islocal) { - frm.dashboard.add_indicator(__('Counts Targeted: {0}', [frm.doc.total_counts_targeted]), 'blue'); - frm.dashboard.add_indicator(__('Counts Completed: {0}', [frm.doc.total_counts_completed]), - (frm.doc.total_counts_completed < frm.doc.total_counts_targeted) ? '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'); - - frappe.db.get_value('Therapy Plan', {'name': frm.doc.therapy_plan}, 'therapy_plan_template', (r) => { - if (r && !r.therapy_plan_template) { - frm.add_custom_button(__('Sales Invoice'), function() { - frappe.model.open_mapped_doc({ - method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.invoice_therapy_session', - frm: frm, - }); - }, 'Create'); - } - }); - } - }, - - therapy_plan: function(frm) { - if (frm.doc.therapy_plan) { - frm.trigger('filter_therapy_types'); - } - }, - - filter_therapy_types: function(frm) { - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'Therapy Plan', - name: frm.doc.therapy_plan - }, - callback: function(data) { - let therapy_types = (data.message.therapy_plan_details || []).map(function(d){ return d.therapy_type; }); - frm.set_query('therapy_type', function() { - return { - filters: { 'therapy_type': ['in', therapy_types]} - }; - }); - } - }); - }, - - patient: function(frm) { - if (frm.doc.patient) { - frappe.call({ - 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { - patient: frm.doc.patient - }, - callback: function (data) { - let age = ''; - if (data.message.dob) { - age = calculate_age(data.message.dob); - } else if (data.message.age) { - age = data.message.age; - if (data.message.age_as_on) { - age = __('{0} as on {1}', [age, data.message.age_as_on]); - } - } - frm.set_value('patient_age', age); - frm.set_value('gender', data.message.sex); - frm.set_value('patient_name', data.message.patient_name); - } - }); - } else { - frm.set_value('patient_age', ''); - frm.set_value('gender', ''); - frm.set_value('patient_name', ''); - } - }, - - appointment: function(frm) { - if (frm.doc.appointment) { - frappe.call({ - 'method': 'frappe.client.get', - args: { - doctype: 'Patient Appointment', - name: frm.doc.appointment - }, - callback: function(data) { - let values = { - 'patient':data.message.patient, - 'therapy_type': data.message.therapy_type, - 'therapy_plan': data.message.therapy_plan, - 'practitioner': data.message.practitioner, - 'department': data.message.department, - 'start_date': data.message.appointment_date, - 'start_time': data.message.appointment_time, - 'service_unit': data.message.service_unit, - 'company': data.message.company, - 'duration': data.message.duration - }; - frm.set_value(values); - } - }); - } - }, - - 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.set_value('service_unit', data.message.healthcare_service_unit); - frm.set_value('department', data.message.medical_department); - 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'); - } - }); - } - } -}); diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json deleted file mode 100644 index 0bb2b0ef2a..0000000000 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json +++ /dev/null @@ -1,264 +0,0 @@ -{ - "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_name", - "patient_age", - "gender", - "column_break_5", - "company", - "therapy_plan", - "therapy_type", - "practitioner", - "department", - "details_section", - "medical_code", - "duration", - "rate", - "location", - "column_break_12", - "service_unit", - "start_date", - "start_time", - "invoiced", - "exercises_section", - "exercises", - "section_break_23", - "total_counts_targeted", - "column_break_25", - "total_counts_completed", - "amended_from" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-THP-.YYYY.-" - }, - { - "fieldname": "appointment", - "fieldtype": "Link", - "label": "Appointment", - "options": "Patient Appointment", - "set_only_once": 1 - }, - { - "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", - "reqd": 1 - }, - { - "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", - "reqd": 1 - }, - { - "fieldname": "start_time", - "fieldtype": "Time", - "label": "Start Time" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "invoiced", - "fieldtype": "Check", - "label": "Invoiced", - "read_only": 1 - }, - { - "fieldname": "patient_age", - "fieldtype": "Data", - "label": "Patient Age", - "read_only": 1 - }, - { - "fieldname": "total_counts_targeted", - "fieldtype": "Int", - "label": "Total Counts Targeted", - "read_only": 1 - }, - { - "fieldname": "total_counts_completed", - "fieldtype": "Int", - "label": "Total Counts Completed", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "section_break_23", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_25", - "fieldtype": "Column Break" - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fetch_from": "therapy_type.medical_code", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code", - "read_only": 1 - } - ], - "is_submittable": 1, - "links": [], - "modified": "2020-11-04 18:14:25.999939", - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.py b/erpnext/healthcare/doctype/therapy_session/therapy_session.py deleted file mode 100644 index 915e6e42f4..0000000000 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.py +++ /dev/null @@ -1,149 +0,0 @@ -# -*- 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 datetime - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.mapper import get_mapped_doc -from frappe.utils import flt, get_link_to_form, get_time, getdate - -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import ( - get_income_account, - get_receivable_account, -) - - -class TherapySession(Document): - def validate(self): - self.validate_duplicate() - self.set_total_counts() - - def validate_duplicate(self): - end_time = datetime.datetime.combine(getdate(self.start_date), get_time(self.start_time)) \ - + datetime.timedelta(minutes=flt(self.duration)) - - overlaps = frappe.db.sql(""" - select - name - from - `tabTherapy Session` - where - start_date=%s and name!=%s and docstatus!=2 - and (practitioner=%s or patient=%s) and - ((start_time<%s and start_time + INTERVAL duration MINUTE>%s) or - (start_time>%s and start_time<%s) or - (start_time=%s)) - """, (self.start_date, self.name, self.practitioner, self.patient, - self.start_time, end_time.time(), self.start_time, end_time.time(), self.start_time)) - - if overlaps: - overlapping_details = _('Therapy Session overlaps with {0}').format(get_link_to_form('Therapy Session', overlaps[0][0])) - frappe.throw(overlapping_details, title=_('Therapy Sessions Overlapping')) - - def on_submit(self): - self.update_sessions_count_in_therapy_plan() - - def on_update(self): - if self.appointment: - frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Closed') - - def on_cancel(self): - if self.appointment: - frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') - - 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() - - def set_total_counts(self): - target_total = 0 - counts_completed = 0 - for entry in self.exercises: - if entry.counts_target: - target_total += entry.counts_target - if entry.counts_completed: - counts_completed += entry.counts_completed - - self.db_set('total_counts_targeted', target_total) - self.db_set('total_counts_completed', counts_completed) - - -@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 - - -@frappe.whitelist() -def invoice_therapy_session(source_name, target_doc=None): - def set_missing_values(source, target): - target.customer = frappe.db.get_value('Patient', source.patient, 'customer') - target.due_date = getdate() - target.debit_to = get_receivable_account(source.company) - item = target.append('items', {}) - item = get_therapy_item(source, item) - target.set_missing_values(for_validate=True) - - doc = get_mapped_doc('Therapy Session', source_name, { - 'Therapy Session': { - 'doctype': 'Sales Invoice', - 'field_map': [ - ['patient', 'patient'], - ['referring_practitioner', 'practitioner'], - ['company', 'company'], - ['due_date', 'start_date'] - ] - } - }, target_doc, set_missing_values) - - return doc - - -def get_therapy_item(therapy, item): - item.item_code = frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item') - item.description = _('Therapy Session Charges: {0}').format(therapy.practitioner) - item.income_account = get_income_account(therapy.practitioner, therapy.company) - item.cost_center = frappe.get_cached_value('Company', therapy.company, 'cost_center') - item.rate = therapy.rate - item.amount = therapy.rate - item.qty = 1 - item.reference_dt = 'Therapy Session' - item.reference_dn = therapy.name - return item diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py b/erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py deleted file mode 100644 index b8a37820ba..0000000000 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py +++ /dev/null @@ -1,15 +0,0 @@ -from __future__ import unicode_literals - -from frappe import _ - - -def get_data(): - return { - 'fieldname': 'therapy_session', - 'transactions': [ - { - 'label': _('Assessments'), - 'items': ['Patient Assessment'] - } - ] - } diff --git a/erpnext/healthcare/doctype/therapy_type/__init__.py b/erpnext/healthcare/doctype/therapy_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py deleted file mode 100644 index 23d542236b..0000000000 --- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -import frappe - - -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.assertEqual(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', therapy_type) - - 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() - else: - exercise_type = frappe.get_doc('Exercise Type', exercise_type) - - return exercise_type diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.js b/erpnext/healthcare/doctype/therapy_type/therapy_type.js deleted file mode 100644 index 6e155dc21f..0000000000 --- a/erpnext/healthcare/doctype/therapy_type/therapy_type.js +++ /dev/null @@ -1,103 +0,0 @@ -// 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); - }, - - medical_code: function(frm) { - frm.set_query("medical_code", function() { - return { - filters: { - medical_code_standard: frm.doc.medical_code_standard - } - }; - }); - } -}); - -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 - }); -}; diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.json b/erpnext/healthcare/doctype/therapy_type/therapy_type.json deleted file mode 100644 index f365b1df03..0000000000 --- a/erpnext/healthcare/doctype/therapy_type/therapy_type.json +++ /dev/null @@ -1,234 +0,0 @@ -{ - "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", - "medical_coding_section", - "medical_code_standard", - "medical_code", - "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" - }, - { - "collapsible": 1, - "fieldname": "medical_coding_section", - "fieldtype": "Section Break", - "label": "Medical Coding", - "options": "Medical Coding" - }, - { - "fieldname": "medical_code_standard", - "fieldtype": "Link", - "label": "Medical Code Standard", - "options": "Medical Code Standard" - }, - { - "depends_on": "medical_code_standard", - "fieldname": "medical_code", - "fieldtype": "Link", - "label": "Medical Code", - "options": "Medical Code" - } - ], - "links": [], - "modified": "2020-06-29 14:18:50.669951", - "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 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.py b/erpnext/healthcare/doctype/therapy_type/therapy_type.py deleted file mode 100644 index 3517ef2c5a..0000000000 --- a/erpnext/healthcare/doctype/therapy_type/therapy_type.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- 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 json - -import frappe -from frappe import _ -from frappe.model.document import Document -from frappe.model.rename_doc import rename_doc -from frappe.utils import cint - - -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_rate = 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) - - @frappe.whitelist() - 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 diff --git a/erpnext/healthcare/doctype/treatment_plan_template/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_records.json b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json deleted file mode 100644 index d661b4304f..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template/test_records.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "doctype": "Treatment Plan Template", - "template_name": "Chemo", - "patient_age_from": 21 - } -] diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py deleted file mode 100644 index b8a1dd7786..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt - -# import frappe -import unittest - - -class TestTreatmentPlanTemplate(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js deleted file mode 100644 index 986c3cb6e4..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Treatment Plan Template', { - refresh: function (frm) { - frm.set_query('type', 'items', function () { - return { - filters: { - 'name': ['in', ['Lab Test Template', 'Clinical Procedure Template', 'Therapy Type']], - } - }; - }); - }, -}); diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json deleted file mode 100644 index 85a312fb17..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "actions": [], - "autoname": "field:template_name", - "creation": "2021-06-10 10:14:17.901273", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "section_break_1", - "template_name", - "description", - "practitioners", - "disabled", - "column_break_1", - "medical_department", - "goal", - "order_group", - "section_break_8", - "patient_age_from", - "complaints", - "gender", - "column_break_12", - "patient_age_to", - "diagnosis", - "plan_items_section", - "items", - "drugs" - ], - "fields": [ - { - "fieldname": "section_break_1", - "fieldtype": "Section Break", - "label": "Plan Details" - }, - { - "fieldname": "medical_department", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Medical Department", - "options": "Medical Department" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Description" - }, - { - "fieldname": "goal", - "fieldtype": "Small Text", - "label": "Goal" - }, - { - "fieldname": "practitioners", - "fieldtype": "Table MultiSelect", - "label": "Practitioners", - "options": "Treatment Plan Template Practitioner" - }, - { - "fieldname": "order_group", - "fieldtype": "Link", - "label": "Order Group", - "options": "Patient Encounter", - "read_only": 1 - }, - { - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "label": "Plan Conditions" - }, - { - "fieldname": "template_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Template Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "patient_age_from", - "fieldtype": "Int", - "label": "Patient Age From", - "non_negative": 1 - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - }, - { - "fieldname": "patient_age_to", - "fieldtype": "Int", - "label": "Patient Age To", - "non_negative": 1 - }, - { - "fieldname": "gender", - "fieldtype": "Link", - "label": "Gender", - "options": "Gender" - }, - { - "fieldname": "complaints", - "fieldtype": "Table MultiSelect", - "label": "Complaints", - "options": "Patient Encounter Symptom" - }, - { - "fieldname": "diagnosis", - "fieldtype": "Table MultiSelect", - "label": "Diagnosis", - "options": "Patient Encounter Diagnosis" - }, - { - "fieldname": "plan_items_section", - "fieldtype": "Section Break", - "label": "Plan Items" - }, - { - "fieldname": "items", - "fieldtype": "Table", - "label": "Items", - "options": "Treatment Plan Template Item" - }, - { - "fieldname": "drugs", - "fieldtype": "Table", - "label": "Drugs", - "options": "Drug Prescription" - }, - { - "default": "0", - "fieldname": "disabled", - "fieldtype": "Check", - "label": "Disabled" - }, - { - "fieldname": "column_break_1", - "fieldtype": "Column Break" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-08-18 02:41:58.354296", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Treatment Plan 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": "Physician", - "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 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "template_name", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py deleted file mode 100644 index dbe0e9ae5f..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -import frappe -from frappe import _ -from frappe.model.document import Document - - -class TreatmentPlanTemplate(Document): - def validate(self): - self.validate_age() - - def validate_age(self): - if self.patient_age_from and self.patient_age_from < 0: - frappe.throw(_('Patient Age From cannot be less than 0')) - if self.patient_age_to and self.patient_age_to < 0: - frappe.throw(_('Patient Age To cannot be less than 0')) - if self.patient_age_to and self.patient_age_from and \ - self.patient_age_to < self.patient_age_from: - frappe.throw(_('Patient Age To cannot be less than Patient Age From')) diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js deleted file mode 100644 index 7ab31dff79..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js +++ /dev/null @@ -1,10 +0,0 @@ -frappe.listview_settings['Treatment Plan Template'] = { - get_indicator: function(doc) { - var colors = { - 1: 'gray', - 0: 'blue', - }; - let label = doc.disabled == 1 ? 'Disabled' : 'Enabled'; - return [__(label), colors[doc.disabled], 'disable,=,' + doc.disabled]; - } -}; diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json deleted file mode 100644 index 20a9d6793a..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "actions": [], - "creation": "2021-06-10 11:47:29.194795", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "type", - "template", - "qty", - "instructions" - ], - "fields": [ - { - "fieldname": "type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Type", - "options": "DocType", - "reqd": 1 - }, - { - "fieldname": "template", - "fieldtype": "Dynamic Link", - "in_list_view": 1, - "label": "Template", - "options": "type", - "reqd": 1 - }, - { - "default": "1", - "fieldname": "qty", - "fieldtype": "Int", - "label": "Qty" - }, - { - "fieldname": "instructions", - "fieldtype": "Small Text", - "in_list_view": 1, - "label": "Instructions" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-08-17 11:19:03.515441", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Treatment Plan Template Item", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py deleted file mode 100644 index 8b8d89f07f..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class TreatmentPlanTemplateItem(Document): - pass diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json deleted file mode 100644 index 04da387f7b..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "creation": "2021-06-10 10:37:56.669416", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "practitioner" - ], - "fields": [ - { - "fieldname": "practitioner", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Practitioner", - "options": "Healthcare Practitioner", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2021-06-11 16:05:06.733299", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Treatment Plan Template Practitioner", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py deleted file mode 100644 index c2d08bcc1a..0000000000 --- a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class TreatmentPlanTemplatePractitioner(Document): - pass diff --git a/erpnext/healthcare/doctype/vital_signs/__init__.py b/erpnext/healthcare/doctype/vital_signs/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.py b/erpnext/healthcare/doctype/vital_signs/test_vital_signs.py deleted file mode 100644 index 22b52fb482..0000000000 --- a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -# test_records = frappe.get_test_records('Vital Signs') - -class TestVitalSigns(unittest.TestCase): - pass diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.js b/erpnext/healthcare/doctype/vital_signs/vital_signs.js deleted file mode 100644 index 78509e0323..0000000000 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.js +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2016, ESS LLP and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Vital Signs', { - height: function(frm) { - if (frm.doc.height && frm.doc.weight) { - calculate_bmi(frm); - } - }, - - weight: function(frm) { - if (frm.doc.height && frm.doc.weight) { - calculate_bmi(frm); - } - }, - - bp_systolic: function(frm) { - if (frm.doc.bp_systolic && frm.doc.bp_diastolic) { - set_bp(frm); - } - }, - - bp_diastolic: function(frm) { - if (frm.doc.bp_systolic && frm.doc.bp_diastolic) { - set_bp(frm); - } - } -}); - -let calculate_bmi = function(frm){ - // Reference https://en.wikipedia.org/wiki/Body_mass_index - // bmi = weight (in Kg) / height * height (in Meter) - let bmi = (frm.doc.weight / (frm.doc.height * frm.doc.height)).toFixed(2); - let bmi_note = null; - - if (bmi<18.5) { - bmi_note = 'Underweight'; - } else if (bmi>=18.5 && bmi<25) { - bmi_note = 'Normal'; - } else if (bmi>=25 && bmi<30) { - bmi_note = 'Overweight'; - } else if (bmi>=30) { - bmi_note = 'Obese'; - } - frappe.model.set_value(frm.doctype,frm.docname, 'bmi', bmi); - frappe.model.set_value(frm.doctype,frm.docname, 'nutrition_note', bmi_note); -}; - -let set_bp = function(frm){ - let bp = frm.doc.bp_systolic+ '/' + frm.doc.bp_diastolic + ' mmHg'; - frappe.model.set_value(frm.doctype,frm.docname, 'bp', bp); -}; diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.json b/erpnext/healthcare/doctype/vital_signs/vital_signs.json deleted file mode 100644 index 15ab5047bc..0000000000 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "actions": [], - "allow_copy": 1, - "allow_import": 1, - "autoname": "naming_series:", - "beta": 1, - "creation": "2017-02-02 11:00:24.853005", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "naming_series", - "title", - "patient", - "patient_name", - "inpatient_record", - "appointment", - "encounter", - "column_break_2", - "company", - "signs_date", - "signs_time", - "sb_vs", - "temperature", - "pulse", - "respiratory_rate", - "tongue", - "abdomen", - "column_break_8", - "reflexes", - "bp_systolic", - "bp_diastolic", - "bp", - "vital_signs_note", - "sb_nutrition_values", - "height", - "weight", - "bmi", - "column_break_14", - "nutrition_note", - "sb_references", - "amended_from" - ], - "fields": [ - { - "fetch_from": "patient.inpatient_record", - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "read_only": 1 - }, - { - "fetch_from": "inpatient_record.patient", - "fieldname": "patient", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Patient", - "options": "Patient", - "reqd": 1 - }, - { - "fetch_from": "patient.patient_name", - "fieldname": "patient_name", - "fieldtype": "Data", - "label": "Patient Name", - "read_only": 1 - }, - { - "fieldname": "appointment", - "fieldtype": "Link", - "in_filter": 1, - "label": "Patient Appointment", - "no_copy": 1, - "options": "Patient Appointment", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "encounter", - "fieldtype": "Link", - "in_filter": 1, - "label": "Patient Encounter", - "no_copy": 1, - "options": "Patient Encounter", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "default": "Today", - "fieldname": "signs_date", - "fieldtype": "Date", - "label": "Date", - "reqd": 1 - }, - { - "fieldname": "signs_time", - "fieldtype": "Time", - "label": "Time", - "reqd": 1 - }, - { - "fieldname": "sb_vs", - "fieldtype": "Section Break", - "label": "Vital Signs" - }, - { - "description": "Presence of a fever (temp > 38.5 \u00b0C/101.3 \u00b0F or sustained temp > 38 \u00b0C/100.4 \u00b0F)", - "fieldname": "temperature", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Body Temperature" - }, - { - "description": "Adults' pulse rate is anywhere between 50 and 80 beats per minute.", - "fieldname": "pulse", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Heart Rate / Pulse" - }, - { - "description": "Normal reference range for an adult is 16\u201320 breaths/minute (RCP 2012)", - "fieldname": "respiratory_rate", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Respiratory rate" - }, - { - "fieldname": "tongue", - "fieldtype": "Select", - "label": "Tongue", - "options": "\nCoated\nVery Coated\nNormal\nFurry\nCuts" - }, - { - "fieldname": "abdomen", - "fieldtype": "Select", - "label": "Abdomen", - "options": "\nNormal\nBloated\nFull\nFluid\nConstipated" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "fieldname": "reflexes", - "fieldtype": "Select", - "label": "Reflexes", - "options": "\nNormal\nHyper\nVery Hyper\nOne Sided" - }, - { - "fieldname": "bp_systolic", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Blood Pressure (systolic)" - }, - { - "fieldname": "bp_diastolic", - "fieldtype": "Data", - "ignore_xss_filter": 1, - "in_list_view": 1, - "label": "Blood Pressure (diastolic)" - }, - { - "description": "Normal resting blood pressure in an adult is approximately 120 mmHg systolic, and 80 mmHg diastolic, abbreviated \"120/80 mmHg\"", - "fieldname": "bp", - "fieldtype": "Data", - "label": "Blood Pressure", - "read_only": 1 - }, - { - "fieldname": "vital_signs_note", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Notes" - }, - { - "fieldname": "sb_nutrition_values", - "fieldtype": "Section Break", - "label": "Nutrition Values" - }, - { - "fieldname": "height", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Height (In Meter)" - }, - { - "fieldname": "weight", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Weight (In Kilogram)" - }, - { - "default": "0.00", - "fieldname": "bmi", - "fieldtype": "Float", - "in_list_view": 1, - "label": "BMI", - "read_only": 1 - }, - { - "fieldname": "column_break_14", - "fieldtype": "Column Break" - }, - { - "fieldname": "nutrition_note", - "fieldtype": "Small Text", - "ignore_xss_filter": 1, - "label": "Notes" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company" - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "label": "Amended From", - "no_copy": 1, - "options": "Vital Signs", - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "sb_references", - "fieldtype": "Section Break" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "HLC-VTS-.YYYY.-", - "reqd": 1 - }, - { - "allow_on_submit": 1, - "columns": 5, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "label": "Title", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - } - ], - "is_submittable": 1, - "links": [], - "modified": "2020-05-17 22:23:24.632286", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Vital Signs", - "owner": "Administrator", - "permissions": [ - { - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Physician", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Nursing User", - "share": 1, - "submit": 1, - "write": 1 - } - ], - "restrict_to_domain": "Healthcare", - "search_fields": "patient, signs_date", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title", - "track_changes": 1, - "track_seen": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/vital_signs/vital_signs.py b/erpnext/healthcare/doctype/vital_signs/vital_signs.py deleted file mode 100644 index 29dbeb470d..0000000000 --- a/erpnext/healthcare/doctype/vital_signs/vital_signs.py +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document - - -class VitalSigns(Document): - def validate(self): - self.set_title() - - def set_title(self): - self.title = _('{0} on {1}').format(self.patient_name or self.patient, - frappe.utils.format_date(self.signs_date))[:100] diff --git a/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json b/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json deleted file mode 100644 index 2fea6682ed..0000000000 --- a/erpnext/healthcare/healthcare_dashboard/healthcare/healthcare.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "cards": [ - { - "card": "Total Patients" - }, - { - "card": "Total Patients Admitted" - }, - { - "card": "Open Appointments" - }, - { - "card": "Appointments to Bill" - } - ], - "charts": [ - { - "chart": "Patient Appointments", - "width": "Full" - }, - { - "chart": "In-Patient Status", - "width": "Half" - }, - { - "chart": "Clinical Procedures Status", - "width": "Half" - }, - { - "chart": "Lab Tests", - "width": "Half" - }, - { - "chart": "Clinical Procedures", - "width": "Half" - }, - { - "chart": "Symptoms", - "width": "Half" - }, - { - "chart": "Diagnoses", - "width": "Half" - }, - { - "chart": "Department wise Patient Appointments", - "width": "Full" - } - ], - "creation": "2020-07-14 18:17:54.823311", - "dashboard_name": "Healthcare", - "docstatus": 0, - "doctype": "Dashboard", - "idx": 0, - "is_default": 0, - "is_standard": 1, - "modified": "2020-07-22 15:36:34.220387", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare", - "owner": "Administrator" -} \ No newline at end of file diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json deleted file mode 100644 index 0aa8f9a027..0000000000 --- a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "allow_roles": [ - { - "role": "Healthcare Administrator" - } - ], - "creation": "2020-05-19 10:32:43.025852", - "docstatus": 0, - "doctype": "Module Onboarding", - "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare", - "idx": 0, - "is_complete": 0, - "modified": "2021-01-30 19:22:20.273766", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare", - "owner": "Administrator", - "steps": [ - { - "step": "Create Patient" - }, - { - "step": "Create Practitioner Schedule" - }, - { - "step": "Introduction to Healthcare Practitioner" - }, - { - "step": "Create Healthcare Practitioner" - }, - { - "step": "Explore Healthcare Settings" - }, - { - "step": "Explore Clinical Procedure Templates" - } - ], - "subtitle": "Patients, Practitioner Schedules, Settings, and more.", - "success_message": "The Healthcare Module is all set up!", - "title": "Let's Set Up the Healthcare Module." -} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json b/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json deleted file mode 100644 index 3e4d4e27df..0000000000 --- a/erpnext/healthcare/number_card/appointments_to_bill/appointments_to_bill.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "creation": "2020-07-14 18:17:54.792773", - "docstatus": 0, - "doctype": "Number Card", - "document_type": "Patient Appointment", - "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[[\"Patient Appointment\",\"invoiced\",\"=\",0,false]]", - "function": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "label": "Appointments To Bill", - "modified": "2020-07-22 13:27:58.038577", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Appointments to Bill", - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily", - "type": "Document Type" -} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/open_appointments/open_appointments.json b/erpnext/healthcare/number_card/open_appointments/open_appointments.json deleted file mode 100644 index 8d121cc58a..0000000000 --- a/erpnext/healthcare/number_card/open_appointments/open_appointments.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "creation": "2020-07-14 18:17:54.771092", - "docstatus": 0, - "doctype": "Number Card", - "document_type": "Patient Appointment", - "dynamic_filters_json": "[[\"Patient Appointment\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]", - "filters_json": "[[\"Patient Appointment\",\"status\",\"=\",\"Open\",false]]", - "function": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "label": "Open Appointments", - "modified": "2020-07-22 13:27:09.542122", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Open Appointments", - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily", - "type": "Document Type" -} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/total_patients/total_patients.json b/erpnext/healthcare/number_card/total_patients/total_patients.json deleted file mode 100644 index 75441a6842..0000000000 --- a/erpnext/healthcare/number_card/total_patients/total_patients.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "creation": "2020-07-14 18:17:54.727946", - "docstatus": 0, - "doctype": "Number Card", - "document_type": "Patient", - "filters_json": "[[\"Patient\",\"status\",\"=\",\"Active\",false]]", - "function": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "label": "Total Patients", - "modified": "2020-07-22 13:26:02.643534", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Total Patients", - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily", - "type": "Document Type" -} \ No newline at end of file diff --git a/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json b/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json deleted file mode 100644 index 69a967df93..0000000000 --- a/erpnext/healthcare/number_card/total_patients_admitted/total_patients_admitted.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "creation": "2020-07-14 18:17:54.749754", - "docstatus": 0, - "doctype": "Number Card", - "document_type": "Patient", - "filters_json": "[[\"Patient\",\"inpatient_status\",\"=\",\"Admitted\",false]]", - "function": "Count", - "idx": 0, - "is_public": 1, - "is_standard": 1, - "label": "Total Patients Admitted", - "modified": "2020-07-22 13:26:20.027788", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Total Patients Admitted", - "owner": "Administrator", - "show_percentage_stats": 1, - "stats_time_interval": "Daily", - "type": "Document Type" -} \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json deleted file mode 100644 index 3f25a9d676..0000000000 --- a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "action": "Create Entry", - "creation": "2020-05-19 10:39:55.728058", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2021-01-30 12:02:22.849260", - "modified_by": "Administrator", - "name": "Create Healthcare Practitioner", - "owner": "Administrator", - "reference_document": "Healthcare Practitioner", - "show_form_tour": 0, - "show_full_form": 1, - "title": "Create Healthcare Practitioner", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json deleted file mode 100644 index b46bb15b48..0000000000 --- a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "action": "Create Entry", - "creation": "2020-05-19 10:32:27.648902", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2021-01-30 00:09:28.786428", - "modified_by": "ruchamahabal2@gmail.com", - "name": "Create Patient", - "owner": "Administrator", - "reference_document": "Patient", - "show_form_tour": 0, - "show_full_form": 1, - "title": "Create Patient", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json deleted file mode 100644 index 7ce122d5c0..0000000000 --- a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "action": "Create Entry", - "creation": "2020-05-19 10:41:19.065753", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2021-01-30 00:09:28.794602", - "modified_by": "ruchamahabal2@gmail.com", - "name": "Create Practitioner Schedule", - "owner": "Administrator", - "reference_document": "Practitioner Schedule", - "show_form_tour": 0, - "show_full_form": 1, - "title": "Create Practitioner Schedule", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json deleted file mode 100644 index dfe9f71a76..0000000000 --- a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "action": "Show Form Tour", - "creation": "2020-05-19 11:40:51.963741", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2021-01-30 19:22:08.257160", - "modified_by": "Administrator", - "name": "Explore Clinical Procedure Templates", - "owner": "Administrator", - "reference_document": "Clinical Procedure Template", - "show_form_tour": 0, - "show_full_form": 0, - "title": "Explore Clinical Procedure Templates", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json deleted file mode 100644 index 2d952f3093..0000000000 --- a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "action": "Show Form Tour", - "creation": "2020-05-19 11:14:33.044989", - "docstatus": 0, - "doctype": "Onboarding Step", - "idx": 0, - "is_complete": 0, - "is_single": 1, - "is_skipped": 0, - "modified": "2021-01-30 19:22:07.275735", - "modified_by": "Administrator", - "name": "Explore Healthcare Settings", - "owner": "Administrator", - "reference_document": "Healthcare Settings", - "show_form_tour": 0, - "show_full_form": 0, - "title": "Explore Healthcare Settings", - "validate_action": 1 -} \ No newline at end of file diff --git a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json deleted file mode 100644 index baa8358c06..0000000000 --- a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "action": "Show Form Tour", - "creation": "2020-05-19 10:43:56.231679", - "docstatus": 0, - "doctype": "Onboarding Step", - "field": "schedule", - "idx": 0, - "is_complete": 0, - "is_single": 0, - "is_skipped": 0, - "modified": "2021-01-30 00:09:28.807129", - "modified_by": "ruchamahabal2@gmail.com", - "name": "Introduction to Healthcare Practitioner", - "owner": "Administrator", - "reference_document": "Healthcare Practitioner", - "show_form_tour": 0, - "show_full_form": 0, - "title": "Introduction to Healthcare Practitioner", - "validate_action": 0 -} \ No newline at end of file diff --git a/erpnext/healthcare/page/__init__.py b/erpnext/healthcare/page/__init__.py deleted file mode 100644 index baffc48825..0000000000 --- a/erpnext/healthcare/page/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import unicode_literals diff --git a/erpnext/healthcare/page/patient_history/__init__.py b/erpnext/healthcare/page/patient_history/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/page/patient_history/patient_history.css b/erpnext/healthcare/page/patient_history/patient_history.css deleted file mode 100644 index 74b5e7eb91..0000000000 --- a/erpnext/healthcare/page/patient_history/patient_history.css +++ /dev/null @@ -1,151 +0,0 @@ -#page-medical_record .label { - display: inline-block; - margin-right: 7px; -} - -#page-medical_record .list-row { - border: none; - padding: 0px; - cursor: pointer; -} - -.patient-image-container { - margin-top: 17px; - } - -.patient-image { - display: inline-block; - width: 100%; - height: 0; - padding: 50% 0px; - background-size: cover; - background-repeat: no-repeat; - background-position: center center; - border-radius: 4px; -} - -.patient-name { - font-size: 20px; - margin-top: 25px; -} - -.medical_record-label { - max-width: 100px; - margin-bottom: -4px; -} - -.medical_record-row > * { - z-index: -999; -} - -.date-indicator { - background:none; - font-size:12px; - vertical-align:middle; - font-weight:bold; - color:#6c7680; -} -.date-indicator::after { - margin:0 -4px 0 12px; - content:''; - display:inline-block; - height:8px; - width:8px; - border-radius:8px; - background: #d1d8dd; -} - -.date-indicator.blue { - color: #5e64ff; -} - -.div-bg-color { - background: #fafbfc; -} - -.bg-color-white { - background: #FFFFFF; -} - -.d-flex { - display: flex; -} - -.width-full { - width: 100%; -} - -.p-3 { - padding: 16px; -} - -.mt-2 { - margin-top: 8px; -} - -.mr-3 { - margin-right: 16px; -} - -.Box { - background-color: #fff; - border: 1px solid #d1d5da; - border-radius: 3px; -} - -.flex-column { - flex-direction: column; -} - -.avatar { - display: inline-block; - overflow: hidden; - line-height: 1; - vertical-align: middle; - border-radius: 3px; -} - -.py-3 { - padding-top: 16px; - padding-bottom: 16px; -} - -.border-bottom { - border-bottom: 1px #e1e4e8 solid; -} - -.date-indicator.blue::after { - background: #5e64ff; -} - -.medical_record-message { - border-left: 1px solid #d1d8dd; - padding: 15px; - padding-right: 30px; -} - -.medical_record-date { - padding: 15px; - padding-right: 0px; -} - -.patient-history-filter { - margin-left: 35px; - width: 25%; -} - -#page-medical_record .plot-wrapper { - padding: 20px 15px; - border-bottom: 1px solid #d1d8dd; - text-align: center; -} - -#page-medical_record .plot { - height: 140px ; - width: 97% ; - margin: auto; -} - -#page-medical_record .list-filters { - display: none ; -} diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html deleted file mode 100644 index d16b38637c..0000000000 --- a/erpnext/healthcare/page/patient_history/patient_history.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
-
-
-
-
-
- -
-
-
-
-
-
-
- -
-
diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js deleted file mode 100644 index ed2dc52cb1..0000000000 --- a/erpnext/healthcare/page/patient_history/patient_history.js +++ /dev/null @@ -1,455 +0,0 @@ -frappe.provide('frappe.patient_history'); -frappe.pages['patient_history'].on_page_load = function(wrapper) { - frappe.ui.make_app_page({ - parent: wrapper, - title: __('Patient History') - }); - - let patient_history = new PatientHistory(wrapper); - $(wrapper).bind('show', ()=> { - patient_history.show(); - }); -}; - -class PatientHistory { - constructor(wrapper) { - this.wrapper = $(wrapper); - this.page = wrapper.page; - this.sidebar = this.wrapper.find('.layout-side-section'); - this.main_section = this.wrapper.find('.layout-main-section'); - this.start = 0; - } - - show() { - frappe.breadcrumbs.add('Healthcare'); - this.sidebar.empty(); - - let me = this; - let patient = frappe.ui.form.make_control({ - parent: me.sidebar, - df: { - fieldtype: 'Link', - options: 'Patient', - fieldname: 'patient', - placeholder: __('Select Patient'), - only_select: true, - change: () => { - me.patient_id = ''; - if (me.patient_id != patient.get_value() && patient.get_value()) { - me.start = 0; - me.patient_id = patient.get_value(); - me.make_patient_profile(); - } - } - } - }); - patient.refresh(); - - if (frappe.route_options && !this.patient_id) { - patient.set_value(frappe.route_options.patient); - this.patient_id = frappe.route_options.patient; - } - - this.sidebar.find('[data-fieldname="patient"]').append('
'); - } - - make_patient_profile() { - this.page.set_title(__('Patient History')); - this.main_section.empty().append(frappe.render_template('patient_history')); - this.setup_filters(); - this.setup_documents(); - this.show_patient_info(); - this.setup_buttons(); - this.show_patient_vital_charts('bp', 'mmHg', 'Blood Pressure'); - } - - setup_filters() { - $('.doctype-filter').empty(); - let me = this; - - frappe.xcall( - 'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes' - ).then(document_types => { - let doctype_filter = frappe.ui.form.make_control({ - parent: $('.doctype-filter'), - df: { - fieldtype: 'MultiSelectList', - fieldname: 'document_type', - placeholder: __('Select Document Type'), - change: () => { - me.start = 0; - me.page.main.find('.patient_documents_list').html(''); - this.setup_documents(doctype_filter.get_value(), date_range_field.get_value()); - }, - get_data: () => { - return document_types.map(document_type => { - return { - description: document_type, - value: document_type - }; - }); - }, - } - }); - doctype_filter.refresh(); - - $('.date-filter').empty(); - let date_range_field = frappe.ui.form.make_control({ - df: { - fieldtype: 'DateRange', - fieldname: 'date_range', - placeholder: __('Date Range'), - input_class: 'input-xs', - change: () => { - let selected_date_range = date_range_field.get_value(); - if (selected_date_range && selected_date_range.length === 2) { - me.start = 0; - me.page.main.find('.patient_documents_list').html(''); - this.setup_documents(doctype_filter.get_value(), date_range_field.get_value()); - } - } - }, - parent: $('.date-filter') - }); - date_range_field.refresh(); - }); - } - - setup_documents(document_types="", selected_date_range="") { - let filters = { - name: this.patient_id, - start: this.start, - page_length: 20 - }; - if (document_types) - filters['document_types'] = document_types; - if (selected_date_range) - filters['date_range'] = selected_date_range; - - let me = this; - frappe.call({ - 'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed', - args: filters, - callback: function(r) { - let data = r.message; - if (data.length) { - me.add_to_records(data); - } else { - me.page.main.find('.patient_documents_list').append(` -
-

${__('No more records..')}

-
`); - me.page.main.find('.btn-get-records').hide(); - } - } - }); - } - - add_to_records(data) { - let details = ""; - let i; - for (i=0; i - ${data[i].reference_name} - `; - - details += ` -
-
`; - - if (data[i].imgsrc) { - details += ` - - `; - } else { - details += ` -
- ${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'} -
-
`; - } - - details += `
-
- `+time_line_heading+` - - ${data[i].date_sep} - -
-
- ${label} -
-
- - -
-
- - -
-
-
-
`; - } - } - - this.page.main.find('.patient_documents_list').append(details); - this.start += data.length; - - if (data.length === 20) { - this.page.main.find(".btn-get-records").show(); - } else { - this.page.main.find(".btn-get-records").hide(); - this.page.main.find(".patient_documents_list").append(` -
-

${__('No more records..')}

-
`); - } - } - - add_date_separator(data) { - let date = frappe.datetime.str_to_obj(data.communication_date); - let pdate = ''; - let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), - frappe.datetime.obj_to_str(date)); - - if (diff < 1) { - pdate = __('Today'); - } else if (diff < 2) { - pdate = __('Yesterday'); - } else { - pdate = __('on {0}', [frappe.datetime.global_date_format(date)]); - } - data.date_sep = pdate; - return data; - } - - show_patient_info() { - this.get_patient_info().then(() => { - $('.patient-info').empty().append(frappe.render_template('patient_history_sidebar', { - patient_image: this.patient.image, - patient_name: this.patient.patient_name, - patient_gender: this.patient.sex, - patient_mobile: this.patient.mobile - })); - this.show_patient_details(); - }); - } - - show_patient_details() { - let me = this; - frappe.call({ - 'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail', - args: { - patient: me.patient_id - }, - callback: function(r) { - let data = r.message; - let details = ``; - - if (data.occupation) details += `

${__('Occupation')} : ${data.occupation}`; - if (data.blood_group) details += `
${__('Blood Group')} : ${data.blood_group}`; - if (data.allergies) details += `

${__('Allerigies')} : ${data.allergies.replace("\n", ", ")}`; - if (data.medication) details += `
${__('Medication')} : ${data.medication.replace("\n", ", ")}`; - if (data.alcohol_current_use) details += `

${__('Alcohol use')} : ${data.alcohol_current_use}`; - if (data.alcohol_past_use) details += `
${__('Alcohol past use')} : ${data.alcohol_past_use}`; - if (data.tobacco_current_use) details += `
${__('Tobacco use')} : ${data.tobacco_current_use}`; - if (data.tobacco_past_use) details += `
${__('Tobacco past use')} : ${data.tobacco_past_use}`; - if (data.medical_history) details += `

${__('Medical history')} : ${data.medical_history.replace("\n", ", ")}`; - if (data.surgical_history) details += `
${__('Surgical history')} : ${data.surgical_history.replace("\n", ", ")}`; - if (data.surrounding_factors) details += `

${__('Occupational hazards')} : ${data.surrounding_factors.replace("\n", ", ")}`; - if (data.other_risk_factors) details += `
${__('Other risk factors')} : ${data.other_risk_factors.replace("\n", ", ")}`; - if (data.patient_details) details += `

${__('More info')} : ${data.patient_details.replace("\n", ", ")}`; - - if (details) { - details = `
` + details + `
`; - } - - me.sidebar.find('.patient-details').html(details); - } - }); - } - - get_patient_info() { - return frappe.xcall('frappe.client.get', { - doctype: 'Patient', - name: this.patient_id, - }).then((patient) => { - if (patient) { - this.patient = patient; - } - }); - } - - setup_buttons() { - let me = this; - this.page.main.on("click", ".btn-show-chart", function() { - let btn_id = $(this).attr("data-show-chart-id"), scale_unit = $(this).attr("data-pts"); - let title = $(this).attr("data-title"); - me.show_patient_vital_charts(btn_id, scale_unit, title); - }); - - this.page.main.on('click', '.btn-more', function() { - let doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname'); - if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') { - me.page.main.find('.'+docname).hide(); - me.page.main.find('.'+docname).parent().find('.document-html').show(); - } else { - if (doctype && docname) { - let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date', 'naming_series']; - frappe.call({ - method: 'erpnext.healthcare.utils.render_doc_as_html', - args: { - doctype: doctype, - docname: docname, - exclude_fields: exclude - }, - freeze: true, - callback: function(r) { - if (r.message) { - me.page.main.find('.' + docname).hide(); - - me.page.main.find('.' + docname).parent().find('.document-html').html( - `${r.message.html} -
-
- - -
- `); - - me.page.main.find('.' + docname).parent().find('.document-html').attr('hidden', false); - me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1'); - } - } - }); - } - } - }); - - this.page.main.on('click', '.btn-less', function() { - let docname = $(this).attr('data-docname'); - me.page.main.find('.' + docname).parent().find('.document-id').show(); - me.page.main.find('.' + docname).parent().find('.document-html').hide(); - }); - - me.page.main.on('click', '.btn-get-records', function() { - this.setup_documents(); - }); - } - - show_patient_vital_charts(btn_id, scale_unit, title) { - let me = this; - - frappe.call({ - method: 'erpnext.healthcare.utils.get_patient_vitals', - args: { - patient: me.patient_id - }, - callback: function(r) { - if (r.message) { - let show_chart_btns_html = ` - `; - - me.page.main.find('.show_chart_btns').html(show_chart_btns_html); - let data = r.message; - let labels = [], datasets = []; - let bp_systolic = [], bp_diastolic = [], temperature = []; - let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = []; - - for (let i=0; i (d + '').toUpperCase(), - formatTooltipY: d => d + ' ' + scale_unit, - } - }); - me.page.main.find('.header-separator').show(); - } else { - me.page.main.find('.patient_vital_charts').html(''); - me.page.main.find('.show_chart_btns').html(''); - me.page.main.find('.header-separator').hide(); - } - } - }); - } -} diff --git a/erpnext/healthcare/page/patient_history/patient_history.json b/erpnext/healthcare/page/patient_history/patient_history.json deleted file mode 100644 index b3892a41c6..0000000000 --- a/erpnext/healthcare/page/patient_history/patient_history.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "content": null, - "creation": "2018-08-08 17:09:13.816199", - "docstatus": 0, - "doctype": "Page", - "icon": "", - "idx": 0, - "modified": "2018-08-08 17:09:55.969424", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "patient_history", - "owner": "Administrator", - "page_name": "patient_history", - "restrict_to_domain": "Healthcare", - "roles": [ - { - "role": "Healthcare Administrator" - }, - { - "role": "Physician" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0, - "title": "Patient History" -} \ No newline at end of file diff --git a/erpnext/healthcare/page/patient_history/patient_history.py b/erpnext/healthcare/page/patient_history/patient_history.py deleted file mode 100644 index 77d8846f37..0000000000 --- a/erpnext/healthcare/page/patient_history/patient_history.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, ESS LLP and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe.utils import cint - - -@frappe.whitelist() -def get_feed(name, document_types=None, date_range=None, start=0, page_length=20): - """get feed""" - filters = get_filters(name, document_types, date_range) - - result = frappe.db.get_all('Patient Medical Record', - fields=['name', 'owner', 'communication_date', - 'reference_doctype', 'reference_name', 'subject'], - filters=filters, - order_by='communication_date DESC', - limit=cint(page_length), - start=cint(start) - ) - - return result - - -def get_filters(name, document_types=None, date_range=None): - filters = {'patient': name} - if document_types: - document_types = json.loads(document_types) - if len(document_types): - filters['reference_doctype'] = ['IN', document_types] - - if date_range: - try: - date_range = json.loads(date_range) - if date_range: - filters['communication_date'] = ['between', [date_range[0], date_range[1]]] - except json.decoder.JSONDecodeError: - pass - - return filters - - -@frappe.whitelist() -def get_feed_for_dt(doctype, docname): - """get feed""" - result = frappe.db.get_all('Patient Medical Record', - fields=['name', 'owner', 'communication_date', - 'reference_doctype', 'reference_name', 'subject'], - filters={ - 'reference_doctype': doctype, - 'reference_name': docname - }, - order_by='communication_date DESC' - ) - - return result - - -@frappe.whitelist() -def get_patient_history_doctypes(): - document_types = [] - settings = frappe.get_single("Patient History Settings") - - for entry in settings.standard_doctypes: - document_types.append(entry.document_type) - - for entry in settings.custom_doctypes: - document_types.append(entry.document_type) - - return document_types diff --git a/erpnext/healthcare/page/patient_history/patient_history_sidebar.html b/erpnext/healthcare/page/patient_history/patient_history_sidebar.html deleted file mode 100644 index fc7eab0540..0000000000 --- a/erpnext/healthcare/page/patient_history/patient_history_sidebar.html +++ /dev/null @@ -1,20 +0,0 @@ -
-
- {% if patient_image %} -
- {% endif %} -
-
- {% if patient_name %} -

{{patient_name}}

- {% endif %} - {% if patient_gender %} -

{%=__("Gender: ") %} {{patient_gender}}

- {% endif %} - {% if patient_mobile %} -

{%=__("Contact: ") %} {{patient_mobile}}

- {% endif %} -
-
-
-
diff --git a/erpnext/healthcare/page/patient_progress/__init__.py b/erpnext/healthcare/page/patient_progress/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.css b/erpnext/healthcare/page/patient_progress/patient_progress.css deleted file mode 100644 index 737b2e0ea2..0000000000 --- a/erpnext/healthcare/page/patient_progress/patient_progress.css +++ /dev/null @@ -1,171 +0,0 @@ -/* sidebar */ - -.layout-side-section .frappe-control[data-fieldname='patient'] { - max-width: 300px; -} - -.patient-image-container { - margin-top: 17px; -} - -.patient-image { - display: inline-block; - width: 100%; - height: 0; - padding: 50% 0px; - background-size: cover; - background-repeat: no-repeat; - background-position: center center; - border-radius: 4px; -} - -.patient-details { - margin: -5px 5px; -} - -.important-links { - margin: 30px 5px; -} - -.patient-name { - font-size: 20px; - margin-top: 25px; -} - -/* heatmap */ - -.heatmap-container { - height: 170px; -} - -.patient-heatmap { - width: 80%; - display: inline-block; -} - -.patient-heatmap .chart-container { - margin-left: 30px; -} - -.patient-heatmap .frappe-chart { - margin-top: 5px; -} - -.patient-heatmap .frappe-chart .chart-legend { - display: none; -} - -.heatmap-container .chart-filter { - z-index: 1; - position: relative; - top: 5px; - margin-right: 10px; -} - -/* percentage chart */ - -.percentage-chart-container { - height: 130px; -} - -.percentage-chart-container .chart-filter { - position: relative; - top: 5px; - margin-right: 10px; -} - -.therapy-session-percentage-chart .frappe-chart { - position: absolute; - top: 5px; -} - -/* line charts */ - -.date-field .clearfix { - display: none; -} - -.date-field .help-box { - display: none; -} - -.date-field .frappe-control { - margin-bottom: 0px !important; -} - -.date-field .form-group { - margin-bottom: 0px !important; -} - -/* common */ - -text.title { - text-transform: uppercase; - font-size: 11px; - margin-left: 20px; - margin-top: 20px; - display: block; -} - -.chart-filter-search { - margin-left: 35px; - width: 25%; -} - -.chart-column-container { - margin: 5px 0; -} - -.progress-graphs .progress-container { - margin-bottom: var(--margin-xl); -} - -.line-chart-container .frappe-chart { - margin-top: -20px; -} - -.line-chart-container { - margin-bottom: 20px; -} - -.chart-control { - align-self: center; - display: flex; - flex-direction: row-reverse; - margin-top: -25px; -} - -.chart-control > * { - margin-right: 10px; -} - -/* mobile */ - -@media (max-width: 991px) { - .patient-progress-sidebar { - display: flex; - } - - .percentage-chart-container { - border-top: 1px solid #d1d8dd; - } - - .percentage-chart-container .chart-filter { - z-index: 1; - position: relative; - top: 12px; - margin-right: 10px; - } - - .patient-progress-sidebar .important-links { - margin: 0; - } - - .patient-progress-sidebar .patient-details { - width: 50%; - } - - .chart-filter-search { - width: 40%; - } -} diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html deleted file mode 100644 index ee60065618..0000000000 --- a/erpnext/healthcare/page/patient_progress/patient_progress.html +++ /dev/null @@ -1,69 +0,0 @@ -
-
-
- - -
-
-
- -
-
- Therapy Progress -
-
-
- -
-
-
-
-
-
- -
-
- Assessment Results -
-
-
- -
-
-
-
-
-
- -
-
- Therapy Type and Assessment Correlation -
-
-
- -
-
-
-
-
-
- -
-
- Assessment Parameter Wise Progress -
-
-
- -
-
-
-
-
-
-
-
-
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.js b/erpnext/healthcare/page/patient_progress/patient_progress.js deleted file mode 100644 index 3f06f1feba..0000000000 --- a/erpnext/healthcare/page/patient_progress/patient_progress.js +++ /dev/null @@ -1,536 +0,0 @@ -frappe.pages['patient-progress'].on_page_load = function(wrapper) { - - frappe.ui.make_app_page({ - parent: wrapper, - title: __('Patient Progress') - }); - - let patient_progress = new PatientProgress(wrapper); - $(wrapper).bind('show', ()=> { - patient_progress.show(); - }); -}; - -class PatientProgress { - - constructor(wrapper) { - this.wrapper = $(wrapper); - this.page = wrapper.page; - this.sidebar = this.wrapper.find('.layout-side-section'); - this.main_section = this.wrapper.find('.layout-main-section'); - } - - show() { - frappe.breadcrumbs.add('Healthcare'); - this.sidebar.empty(); - - let me = this; - let patient = frappe.ui.form.make_control({ - parent: me.sidebar, - df: { - fieldtype: 'Link', - options: 'Patient', - fieldname: 'patient', - placeholder: __('Select Patient'), - only_select: true, - change: () => { - me.patient_id = ''; - if (me.patient_id != patient.get_value() && patient.get_value()) { - me.start = 0; - me.patient_id = patient.get_value(); - me.make_patient_profile(); - } - } - } - }); - patient.refresh(); - - if (frappe.route_options && !this.patient) { - patient.set_value(frappe.route_options.patient); - this.patient_id = frappe.route_options.patient; - } - - this.sidebar.find('[data-fieldname="patient"]').append('
'); - } - - make_patient_profile() { - this.page.set_title(__('Patient Progress')); - this.main_section.empty().append(frappe.render_template('patient_progress')); - this.render_patient_details(); - this.render_heatmap(); - this.render_percentage_chart('therapy_type', 'Therapy Type Distribution'); - this.create_percentage_chart_filters(); - this.show_therapy_progress(); - this.show_assessment_results(); - this.show_therapy_assessment_correlation(); - this.show_assessment_parameter_progress(); - } - - get_patient_info() { - return frappe.xcall('frappe.client.get', { - doctype: 'Patient', - name: this.patient_id - }).then((patient) => { - if (patient) { - this.patient = patient; - } - }); - } - - get_therapy_sessions_count() { - return frappe.xcall( - 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_count', { - patient: this.patient_id, - } - ).then(data => { - if (data) { - this.total_therapy_sessions = data.total_therapy_sessions; - this.therapy_sessions_this_month = data.therapy_sessions_this_month; - } - }); - } - - render_patient_details() { - this.get_patient_info().then(() => { - this.get_therapy_sessions_count().then(() => { - $('.patient-info').empty().append(frappe.render_template('patient_progress_sidebar', { - patient_image: this.patient.image, - patient_name: this.patient.patient_name, - patient_gender: this.patient.sex, - patient_mobile: this.patient.mobile, - total_therapy_sessions: this.total_therapy_sessions, - therapy_sessions_this_month: this.therapy_sessions_this_month - })); - - this.setup_patient_profile_links(); - }); - }); - } - - setup_patient_profile_links() { - this.wrapper.find('.patient-profile-link').on('click', () => { - frappe.set_route('Form', 'Patient', this.patient_id); - }); - - this.wrapper.find('.therapy-plan-link').on('click', () => { - frappe.route_options = { - 'patient': this.patient_id, - 'docstatus': 1 - }; - frappe.set_route('List', 'Therapy Plan'); - }); - - this.wrapper.find('.patient-history').on('click', () => { - frappe.route_options = { - 'patient': this.patient_id - }; - frappe.set_route('patient_history'); - }); - } - - render_heatmap() { - this.heatmap = new frappe.Chart('.patient-heatmap', { - type: 'heatmap', - countLabel: 'Interactions', - data: {}, - discreteDomains: 1, - radius: 3, - height: 150 - }); - - this.update_heatmap_data(); - this.create_heatmap_chart_filters(); - } - - update_heatmap_data(date_from) { - frappe.xcall('erpnext.healthcare.page.patient_progress.patient_progress.get_patient_heatmap_data', { - patient: this.patient_id, - date: date_from || frappe.datetime.year_start(), - }).then((data) => { - this.heatmap.update( {dataPoints: data} ); - }); - } - - create_heatmap_chart_filters() { - this.get_patient_info().then(() => { - let filters = [ - { - label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()), - options: frappe.dashboard_utils.get_years_since_creation(this.patient.creation), - action: (selected_item) => { - this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item)); - } - }, - ]; - frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-container'); - }); - } - - render_percentage_chart(field, title) { - // REDESIGN-TODO: chart seems to be broken. Enable this once fixed. - this.wrapper.find('.percentage-chart-container').hide(); - // frappe.xcall( - // 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', { - // patient: this.patient_id, - // field: field - // } - // ).then(chart => { - // if (chart.labels.length) { - // this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', { - // title: title, - // type: 'percentage', - // data: { - // labels: chart.labels, - // datasets: chart.datasets - // }, - // truncateLegends: 1, - // barOptions: { - // height: 11, - // depth: 1 - // }, - // height: 160, - // maxSlices: 8, - // colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'], - // }); - // } else { - // this.wrapper.find('.percentage-chart-container').hide(); - // } - // }); - } - - create_percentage_chart_filters() { - let filters = [ - { - label: 'Therapy Type', - options: ['Therapy Type', 'Exercise Type'], - fieldnames: ['therapy_type', 'exercise_type'], - action: (selected_item, fieldname) => { - let title = selected_item + ' Distribution'; - this.render_percentage_chart(fieldname, title); - } - }, - ]; - frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-container'); - } - - create_time_span_filters(action_method, parent) { - let chart_control = $(parent).find('.chart-control'); - let filters = [ - { - label: 'Last Month', - options: ['Select Date Range', 'Last Week', 'Last Month', 'Last Quarter', 'Last Year'], - action: (selected_item) => { - if (selected_item === 'Select Date Range') { - this.render_date_range_fields(action_method, chart_control); - } else { - // hide date range field if visible - let date_field = $(parent).find('.date-field'); - if (date_field.is(':visible')) { - date_field.hide(); - } - this[action_method](selected_item); - } - } - } - ]; - frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', chart_control, 1); - } - - render_date_range_fields(action_method, parent) { - let date_field = $(parent).find('.date-field'); - - if (!date_field.length) { - let date_field_wrapper = $( - `
` - ).appendTo(parent); - - let date_range_field = frappe.ui.form.make_control({ - df: { - fieldtype: 'DateRange', - fieldname: 'from_date', - placeholder: 'Date Range', - input_class: 'input-xs', - reqd: 1, - change: () => { - let selected_date_range = date_range_field.get_value(); - if (selected_date_range && selected_date_range.length === 2) { - this[action_method](selected_date_range); - } - } - }, - parent: date_field_wrapper, - render_input: 1 - }); - } else if (!date_field.is(':visible')) { - date_field.show(); - } - } - - show_therapy_progress() { - let me = this; - let therapy_type = frappe.ui.form.make_control({ - parent: $('.therapy-type-search'), - df: { - fieldtype: 'Link', - options: 'Therapy Type', - fieldname: 'therapy_type', - placeholder: __('Select Therapy Type'), - only_select: true, - change: () => { - if (me.therapy_type != therapy_type.get_value() && therapy_type.get_value()) { - me.therapy_type = therapy_type.get_value(); - me.render_therapy_progress_chart(); - } - } - } - }); - therapy_type.refresh(); - this.create_time_span_filters('render_therapy_progress_chart', '.therapy-progress'); - } - - render_therapy_progress_chart(time_span='Last Month') { - if (!this.therapy_type) return; - - frappe.xcall( - 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_progress_data', { - patient: this.patient_id, - therapy_type: this.therapy_type, - time_span: time_span - } - ).then(chart => { - let data = { - labels: chart.labels, - datasets: chart.datasets - } - let parent = '.therapy-progress-line-chart'; - if (!chart.labels.length) { - this.show_null_state(parent); - } else { - if (!this.therapy_line_chart) { - this.therapy_line_chart = new frappe.Chart(parent, { - type: 'axis-mixed', - height: 250, - data: data, - lineOptions: { - regionFill: 1 - }, - axisOptions: { - xIsSeries: 1 - } - }); - } else { - $(parent).find('.chart-container').show(); - $(parent).find('.chart-empty-state').hide(); - this.therapy_line_chart.update(data); - } - } - }); - } - - show_assessment_results() { - let me = this; - let assessment_template = frappe.ui.form.make_control({ - parent: $('.assessment-template-search'), - df: { - fieldtype: 'Link', - options: 'Patient Assessment Template', - fieldname: 'assessment_template', - placeholder: __('Select Assessment Template'), - only_select: true, - change: () => { - if (me.assessment_template != assessment_template.get_value() && assessment_template.get_value()) { - me.assessment_template = assessment_template.get_value(); - me.render_assessment_result_chart(); - } - } - } - }); - assessment_template.refresh(); - this.create_time_span_filters('render_assessment_result_chart', '.assessment-results'); - } - - render_assessment_result_chart(time_span='Last Month') { - if (!this.assessment_template) return; - - frappe.xcall( - 'erpnext.healthcare.page.patient_progress.patient_progress.get_patient_assessment_data', { - patient: this.patient_id, - assessment_template: this.assessment_template, - time_span: time_span - } - ).then(chart => { - let data = { - labels: chart.labels, - datasets: chart.datasets, - yMarkers: [ - { label: 'Max Score', value: chart.max_score } - ], - } - let parent = '.assessment-results-line-chart'; - if (!chart.labels.length) { - this.show_null_state(parent); - } else { - if (!this.assessment_line_chart) { - this.assessment_line_chart = new frappe.Chart(parent, { - type: 'axis-mixed', - height: 250, - data: data, - lineOptions: { - regionFill: 1 - }, - axisOptions: { - xIsSeries: 1 - }, - tooltipOptions: { - formatTooltipY: d => __('{0} out of {1}', [d, chart.max_score]) - } - }); - } else { - $(parent).find('.chart-container').show(); - $(parent).find('.chart-empty-state').hide(); - this.assessment_line_chart.update(data); - } - } - }); - } - - show_therapy_assessment_correlation() { - let me = this; - let assessment = frappe.ui.form.make_control({ - parent: $('.assessment-correlation-template-search'), - df: { - fieldtype: 'Link', - options: 'Patient Assessment Template', - fieldname: 'assessment', - placeholder: __('Select Assessment Template'), - only_select: true, - change: () => { - if (me.assessment != assessment.get_value() && assessment.get_value()) { - me.assessment = assessment.get_value(); - me.render_therapy_assessment_correlation_chart(); - } - } - } - }); - assessment.refresh(); - this.create_time_span_filters('render_therapy_assessment_correlation_chart', '.therapy-assessment-correlation'); - } - - render_therapy_assessment_correlation_chart(time_span='Last Month') { - if (!this.assessment) return; - - frappe.xcall( - 'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_assessment_correlation_data', { - patient: this.patient_id, - assessment_template: this.assessment, - time_span: time_span - } - ).then(chart => { - let data = { - labels: chart.labels, - datasets: chart.datasets, - yMarkers: [ - { label: 'Max Score', value: chart.max_score } - ], - } - let parent = '.therapy-assessment-correlation-chart'; - if (!chart.labels.length) { - this.show_null_state(parent); - } else { - if (!this.correlation_chart) { - this.correlation_chart = new frappe.Chart(parent, { - type: 'axis-mixed', - height: 300, - data: data, - axisOptions: { - xIsSeries: 1 - } - }); - } else { - $(parent).find('.chart-container').show(); - $(parent).find('.chart-empty-state').hide(); - this.correlation_chart.update(data); - } - } - }); - } - - show_assessment_parameter_progress() { - let me = this; - let parameter = frappe.ui.form.make_control({ - parent: $('.assessment-parameter-search'), - df: { - fieldtype: 'Link', - options: 'Patient Assessment Parameter', - fieldname: 'assessment', - placeholder: __('Select Assessment Parameter'), - only_select: true, - change: () => { - if (me.parameter != parameter.get_value() && parameter.get_value()) { - me.parameter = parameter.get_value(); - me.render_assessment_parameter_progress_chart(); - } - } - } - }); - parameter.refresh(); - this.create_time_span_filters('render_assessment_parameter_progress_chart', '.assessment-parameter-progress'); - } - - render_assessment_parameter_progress_chart(time_span='Last Month') { - if (!this.parameter) return; - - frappe.xcall( - 'erpnext.healthcare.page.patient_progress.patient_progress.get_assessment_parameter_data', { - patient: this.patient_id, - parameter: this.parameter, - time_span: time_span - } - ).then(chart => { - let data = { - labels: chart.labels, - datasets: chart.datasets - } - let parent = '.assessment-parameter-progress-chart'; - if (!chart.labels.length) { - this.show_null_state(parent); - } else { - if (!this.parameter_chart) { - this.parameter_chart = new frappe.Chart(parent, { - type: 'line', - height: 250, - data: data, - lineOptions: { - regionFill: 1 - }, - axisOptions: { - xIsSeries: 1 - }, - tooltipOptions: { - formatTooltipY: d => d + '%' - } - }); - } else { - $(parent).find('.chart-container').show(); - $(parent).find('.chart-empty-state').hide(); - this.parameter_chart.update(data); - } - } - }); - } - - show_null_state(parent) { - let null_state = $(parent).find('.chart-empty-state'); - if (null_state.length) { - $(null_state).show(); - } else { - null_state = $( - `
${__( - "No Data..." - )}
` - ); - $(parent).append(null_state); - } - $(parent).find('.chart-container').hide(); - } -} diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.json b/erpnext/healthcare/page/patient_progress/patient_progress.json deleted file mode 100644 index 0175cb9c45..0000000000 --- a/erpnext/healthcare/page/patient_progress/patient_progress.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "content": null, - "creation": "2020-06-12 15:46:23.111928", - "docstatus": 0, - "doctype": "Page", - "idx": 0, - "modified": "2020-07-23 21:45:45.540055", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "patient-progress", - "owner": "Administrator", - "page_name": "patient-progress", - "restrict_to_domain": "Healthcare", - "roles": [ - { - "role": "Healthcare Administrator" - }, - { - "role": "Physician" - }, - { - "role": "Patient" - }, - { - "role": "System Manager" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0, - "title": "Patient Progress" -} \ No newline at end of file diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.py b/erpnext/healthcare/page/patient_progress/patient_progress.py deleted file mode 100644 index c17f10574a..0000000000 --- a/erpnext/healthcare/page/patient_progress/patient_progress.py +++ /dev/null @@ -1,198 +0,0 @@ -import json -from datetime import datetime - -import frappe -from frappe import _ -from frappe.utils import get_timespan_date_range, getdate - - -@frappe.whitelist() -def get_therapy_sessions_count(patient): - total = frappe.db.count('Therapy Session', filters={ - 'docstatus': 1, - 'patient': patient - }) - - month_start = datetime.today().replace(day=1) - this_month = frappe.db.count('Therapy Session', filters={ - 'creation': ['>', month_start], - 'docstatus': 1, - 'patient': patient - }) - - return { - 'total_therapy_sessions': total, - 'therapy_sessions_this_month': this_month - } - - -@frappe.whitelist() -def get_patient_heatmap_data(patient, date): - return dict(frappe.db.sql(""" - SELECT - unix_timestamp(communication_date), count(*) - FROM - `tabPatient Medical Record` - WHERE - communication_date > subdate(%(date)s, interval 1 year) and - communication_date < subdate(%(date)s, interval -1 year) and - patient = %(patient)s - GROUP BY communication_date - ORDER BY communication_date asc""", {'date': date, 'patient': patient})) - - -@frappe.whitelist() -def get_therapy_sessions_distribution_data(patient, field): - if field == 'therapy_type': - result = frappe.db.get_all('Therapy Session', - filters = {'patient': patient, 'docstatus': 1}, - group_by = field, - order_by = field, - fields = [field, 'count(*)'], - as_list = True) - - elif field == 'exercise_type': - data = frappe.db.get_all('Therapy Session', filters={ - 'docstatus': 1, - 'patient': patient - }, as_list=True) - therapy_sessions = [entry[0] for entry in data] - - result = frappe.db.get_all('Exercise', - filters = { - 'parenttype': 'Therapy Session', - 'parent': ['in', therapy_sessions], - 'docstatus': 1 - }, - group_by = field, - order_by = field, - fields = [field, 'count(*)'], - as_list = True) - - return { - 'labels': [r[0] for r in result if r[0] != None], - 'datasets': [{ - 'values': [r[1] for r in result] - }] - } - - -@frappe.whitelist() -def get_therapy_progress_data(patient, therapy_type, time_span): - date_range = get_date_range(time_span) - query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'therapy_type': therapy_type, 'patient': patient} - result = frappe.db.sql(""" - SELECT - start_date, total_counts_targeted, total_counts_completed - FROM - `tabTherapy Session` - WHERE - start_date BETWEEN %(from_date)s AND %(to_date)s and - docstatus = 1 and - therapy_type = %(therapy_type)s and - patient = %(patient)s - ORDER BY start_date""", query_values, as_list=1) - - return { - 'labels': [r[0] for r in result if r[0] != None], - 'datasets': [ - { 'name': _('Targetted'), 'values': [r[1] for r in result if r[0] != None] }, - { 'name': _('Completed'), 'values': [r[2] for r in result if r[0] != None] } - ] - } - -@frappe.whitelist() -def get_patient_assessment_data(patient, assessment_template, time_span): - date_range = get_date_range(time_span) - query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment_template': assessment_template, 'patient': patient} - result = frappe.db.sql(""" - SELECT - assessment_datetime, total_score, total_score_obtained - FROM - `tabPatient Assessment` - WHERE - DATE(assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and - docstatus = 1 and - assessment_template = %(assessment_template)s and - patient = %(patient)s - ORDER BY assessment_datetime""", query_values, as_list=1) - - return { - 'labels': [getdate(r[0]) for r in result if r[0] != None], - 'datasets': [ - { 'name': _('Score Obtained'), 'values': [r[2] for r in result if r[0] != None] } - ], - 'max_score': result[0][1] if result else None - } - -@frappe.whitelist() -def get_therapy_assessment_correlation_data(patient, assessment_template, time_span): - date_range = get_date_range(time_span) - query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment': assessment_template, 'patient': patient} - result = frappe.db.sql(""" - SELECT - therapy.therapy_type, count(*), avg(assessment.total_score_obtained), total_score - FROM - `tabPatient Assessment` assessment INNER JOIN `tabTherapy Session` therapy - ON - assessment.therapy_session = therapy.name - WHERE - DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and - assessment.docstatus = 1 and - assessment.patient = %(patient)s and - assessment.assessment_template = %(assessment)s - GROUP BY therapy.therapy_type - """, query_values, as_list=1) - - return { - 'labels': [r[0] for r in result if r[0] != None], - 'datasets': [ - { 'name': _('Sessions'), 'chartType': 'bar', 'values': [r[1] for r in result if r[0] != None] }, - { 'name': _('Average Score'), 'chartType': 'line', 'values': [round(r[2], 2) for r in result if r[0] != None] } - ], - 'max_score': result[0][1] if result else None - } - -@frappe.whitelist() -def get_assessment_parameter_data(patient, parameter, time_span): - date_range = get_date_range(time_span) - query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'parameter': parameter, 'patient': patient} - results = frappe.db.sql(""" - SELECT - assessment.assessment_datetime, - sheet.score, - template.scale_max - FROM - `tabPatient Assessment Sheet` sheet - INNER JOIN `tabPatient Assessment` assessment - ON sheet.parent = assessment.name - INNER JOIN `tabPatient Assessment Template` template - ON template.name = assessment.assessment_template - WHERE - DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and - assessment.docstatus = 1 and - sheet.parameter = %(parameter)s and - assessment.patient = %(patient)s - ORDER BY - assessment.assessment_datetime asc - """, query_values, as_list=1) - - score_percentages = [] - for r in results: - if r[2] != 0 and r[0] != None: - score = round((int(r[1]) / int(r[2])) * 100, 2) - score_percentages.append(score) - - return { - 'labels': [getdate(r[0]) for r in results if r[0] != None], - 'datasets': [ - { 'name': _('Score'), 'values': score_percentages } - ] - } - -def get_date_range(time_span): - try: - time_span = json.loads(time_span) - return time_span - except json.decoder.JSONDecodeError: - return get_timespan_date_range(time_span.lower()) diff --git a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html deleted file mode 100644 index 4ee65738ba..0000000000 --- a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- {% if patient_image %} -
- {% endif %} -
-
- {% if patient_name %} -

{{patient_name}}

- {% endif %} - {% if patient_gender %} -

{%=__("Gender: ") %} {{patient_gender}}

- {% endif %} - {% if patient_mobile %} -

{%=__("Contact: ") %} {{patient_mobile}}

- {% endif %} - {% if total_therapy_sessions %} -

{%=__("Total Therapy Sessions: ") %} {{total_therapy_sessions}}

- {% endif %} - {% if therapy_sessions_this_month %} -

{%=__("Monthly Therapy Sessions: ") %} {{therapy_sessions_this_month}}

- {% endif %} -
- -
diff --git a/erpnext/healthcare/print_format/__init__.py b/erpnext/healthcare/print_format/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/print_format/encounter_print/__init__.py b/erpnext/healthcare/print_format/encounter_print/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/print_format/encounter_print/encounter_print.json b/erpnext/healthcare/print_format/encounter_print/encounter_print.json deleted file mode 100644 index 3c90adb0a1..0000000000 --- a/erpnext/healthcare/print_format/encounter_print/encounter_print.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "align_labels_right": 0, - "creation": "2017-04-10 14:05:53.355863", - "custom_format": 1, - "disabled": 0, - "doc_type": "Patient Encounter", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {% else %}\n
\n

{{doc.name}}

\n
\n {%- endif %}\n
\n
\n {% if doc.appointment %}\n\t
\n\t\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t: {{doc.appointment}}\n\t\t\t
\n\t\t
\n\t\t{%- endif -%}\n\n
\n\t\t
\n\t\t\t \n\t\t
\n {% if doc.patient %}\n\t\t
\n\t\t\t : {{doc.patient}}\n\t\t
\n {% else %}\n
\n\t\t\t : Patient Name\n\t\t
\n {%- endif -%}\n\t\t
\n\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t : {{doc.patient_age}}\n\t\t\t
\n\t\t
\n\n
\n
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t : {{doc.patient_sex}}\n\t\t\t
\n
\n\n
\n
\n\n
\n\t
\n\t\t \n\t
\n {% if doc.practitioner %}\n\t
\n\t\t\t: {{doc.practitioner}}\n\t
\n {%- endif -%}\n\t
\n\n {% if doc.encounter_date %}\n\t
\n\t\t
\n\t\t\n\t\t
\n\t\t
\n\t\t: {{doc.encounter_date}}\n\t\t
\n
\n\t {%- endif -%}\n {% if doc.encounter_time %}\n\t
\n\t\t
\n\t\t\n\t\t
\n\t\t
\n\t\t: {{doc.encounter_time}}\n\t\t
\n
\n\t {%- endif -%}\n {% if doc.medical_department %}\n\t
\n\t\t
\n\t\t\n\t\t
\n\t\t
\n\t\t: {{doc.visit_department}}\n\t\t
\n
\n {%- endif -%}\n
\n\n
\n\n
\n
\n
\n {% if doc.symptoms_in_print%}\n {% if doc.symptoms %}\n Complaints:\n {{doc.symptoms}}\n \t
\n {%- endif -%}\n {%- endif -%}\n\n {% if doc.diagnosis_in_print%}\n {% if doc.diagnosis %}\n \t Diagnosis:\n {{doc.diagnosis}}\n
\n {%- endif -%}\n {%- endif -%}\n\n
\n\n
\n {% if doc.drug_prescription %}\n
\n Rx,\n \n \n \n\n {%- for row in doc.drug_prescription -%}\n \n \n \t\n \t\n \n \n\t {%- endfor -%}\n \n
\n {%- if row.drug_name -%}{{ row.drug_name }}{%- endif -%}\n \n {%- if row.dosage -%}{{ row.dosage }}{%- endif -%}\n \n {%- if row.period -%}{{ row.period }}{%- endif -%}\n\t\t \n\t\t\t
\n {%- if row.comment -%}{{ row.comment }}{%- endif -%}\n
\n\t\t
\n\n\n {%- endif -%}\n
\n\n\n
\n {% if doc.lab_test_prescription %}\n Investigations,\n \n \n \n\n {%- for row in doc.lab_test_prescription -%}\n \n \n \n \n\n\t {%- endfor -%}\n \n
\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}{%- endif -%}\n \n\t\t\t
\n {%- if row.lab_test_comment -%}{{ row.lab_test_comment }}{%- endif -%}\n
\n\t\t
\n\n\n {%- endif -%}\n
\n
\n {% if doc.encounter_comment %}\n
\n {{doc.encounter_comment}}\n {%- endif -%}\n
\n", - "idx": 0, - "line_breaks": 0, - "modified": "2018-09-04 11:52:54.473702", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Encounter Print", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Jinja", - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/healthcare/print_format/lab_test_print/__init__.py b/erpnext/healthcare/print_format/lab_test_print/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json b/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json deleted file mode 100644 index f7d16769c6..0000000000 --- a/erpnext/healthcare/print_format/lab_test_print/lab_test_print.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "align_labels_right": 0, - "creation": "2017-04-24 15:38:45.332473", - "custom_format": 1, - "disabled": 0, - "doc_type": "Lab Test", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "
\n {% if letter_head and not no_letterhead -%}\n
{{ letter_head }}
\n
\n {%- endif %}\n\n {% if (doc.docstatus != 1) %}\n

WORKSHEET

\n\t
\n\t
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n {% else %}\n\t\t\t{% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t {% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n
\n
\n\n\t
\n

Department of {{ doc.department }}

\n
\n\n\t\n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n \n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n\t\n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }}
\n
\n {% if doc.worksheet_instructions %}\n
\n Instructions\n {{ doc.worksheet_instructions }}\n {%- endif -%}\n
\n {% elif (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"require_test_result_approval\") == '1' and doc.status != \"Approved\") %}\n Lab Tests have to be Approved for Print .. !\n {%- else -%}\n
\n
\n\n
\n
\n \n
\n {% if doc.patient_name %}\n
\n {{ doc.patient_name }}\n
\n {% else %}\n
\n {{ doc.patient }}\n
\n {%- endif -%}\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_age or '' }}\n
\n
\n\n
\n
\n \n
\n
\n {{ doc.patient_sex or '' }}\n
\n
\n\n
\n\n
\n\n
\n
\n \n
\n {% if doc.practitioner_name %}\n
\n {{ doc.practitioner_name }}\n
\n\t\t{% else %}\n\t\t {% if doc.referring_practitioner_name %}\n
\n {{ doc.referring_practitioner_name }}\n
\n\t\t\t{% endif %}\n {%- endif -%}\n
\n\n {% if doc.sample_date %}\n
\n
\n \n
\n
\n {{ doc.sample_date }}\n
\n
\n {%- endif -%}\n\n {% if doc.result_date %}\n
\n
\n \n
\n
\n {{ doc.result_date }}\n
\n
\n {%- endif -%}\n\n
\n\n
\n\n
\n

Department of {{ doc.department }}

\n
\n\n\t
\n\t\t{% if doc.result_legend and (doc.legend_print_position == \"Top\" or doc.legend_print_position == \"Both\")%}\n\t\tResult Legend:\n\t\t{{ doc.result_legend }}\n\t\t{%- endif -%}\n\t
\n\n \n \n {%- if doc.normal_test_items -%}\n \n \n \n \n \n\n {%- if doc.normal_test_items|length > 1 %}\n \n {%- endif -%}\n\n {%- for row in doc.normal_test_items -%}\n \n \n\n \n\n \n \n\n {%- endfor -%}\n {%- endif -%}\n \n
Name of TestResultNormal Range
{{ doc.lab_test_name }}
\n {%- if doc.normal_test_items|length > 1 %}  {%- endif -%}\n {%- if row.lab_test_name -%}{{ row.lab_test_name }}\n {%- else -%}   {%- endif -%}\n {%- if row.lab_test_event -%}   {{ row.lab_test_event }}{%- endif -%}\n \n\t\t\t\t\t{%- if row.result_value -%}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.result_value }}\n {%- if row.lab_test_uom -%} {{ row.lab_test_uom }}{%- endif -%}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t{%- endif -%}\n \n\t\t\t\t\t{%- if row.secondary_uom and row.conversion_factor and row.secondary_uom_result -%}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n {{ row.secondary_uom_result }}\n  {{ row.secondary_uom }}\n\t\t\t\t\t\t{%- if row.italic -%}{% endif %}\n\t\t\t\t\t\t{%- if row.underline -%}{% endif %}\n\t\t\t\t\t\t{%- if row.bold -%}{% endif %}\n\t\t\t\t\t\t \n\t\t\t\t\t{%- endif -%}\n
\n
\n {%- if row.normal_range -%}{{ row.normal_range }}{%- endif -%}\n
\n
\n\n \n \n {%- if doc.descriptive_test_items -%}\n \n \n \n \n \n\t\t\t{% set gr_lab_test_name = {'ltname': ''} %}\n {%- for row in doc.descriptive_test_items -%}\n\t\t\t{%- if row.lab_test_name -%}\n\t\t\t{%- if row.lab_test_name != gr_lab_test_name.ltname -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{% if gr_lab_test_name.update({'ltname': row.lab_test_name}) %} {% endif %}\n\t\t\t{%- endif -%}\n\t\t\t{%- endif -%}\n \n \n \n \n {%- endfor -%}\n {%- endif -%}\n\n\t\t\t{%- if doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.organisms -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n\t\t\t{%- if doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- for row in doc.sensitivity_test_items -%}\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\t{%- endfor -%}\n\t\t\t{%- endif -%}\n\n \n
Name of TestResult
{{ doc.lab_test_name }}
 {{ row.lab_test_name }}
  {{ row.lab_test_particulars }} \n {%- if row.result_value -%}{{ row.result_value }}{%- endif -%}\n
OrganismColony Population
{{ row.organism }} \n\t\t\t\t\t{{ row.colony_population }}\n\t\t\t\t\t{% if row.colony_uom %}\n\t\t\t\t\t\t{{ row.colony_uom }}\n\t\t\t\t\t{% endif %}\n\t\t\t\t
AntibioticSensitivity
{{ row.antibiotic }} {{ row.antibiotic_sensitivity }}
\n
\n {% if doc.custom_result %}\n
\n
{{ doc.custom_result }}
\n {%- endif -%}\n
\n\n
\n {% if doc.lab_test_comment %}\n
\n Comments\n {{ doc.lab_test_comment }}\n {%- endif -%}\n
\n\n
\n {%- if (frappe.db.get_value(\"Healthcare Settings\", \"None\", \"employee_name_and_designation_in_print\") == '1') -%}\n {%- if doc.employee_name -%}\n
{{ doc.employee_name }}
\n {%- endif -%}\n {%- if doc.employee_designation -%}\n
{{ doc.employee_designation }}
\n {%- endif -%}\n {%- else -%}\n {%- if frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") -%}\n
{{ frappe.db.get_value(\"Healthcare Settings\", \"None\", \"custom_signature_in_print\") }}
\n {%- endif -%}\n {%- endif -%}\n
\n\n
\n {% if doc.result_legend and (doc.legend_print_position == \"Bottom\" or doc.legend_print_position == \"Both\" or doc.legend_print_position == \"\")%}\n
\n Result Legend\n {{ doc.result_legend }}\n {%- endif -%}\n
\n {%- endif -%}\n
", - "idx": 0, - "line_breaks": 0, - "modified": "2020-07-08 15:34:28.866798", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Print", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Jinja", - "raw_printing": 0, - "show_section_headings": 0, - "standard": "Yes" -} \ No newline at end of file diff --git a/erpnext/healthcare/print_format/sample_id_print/__init__.py b/erpnext/healthcare/print_format/sample_id_print/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json b/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json deleted file mode 100644 index 4819e6d57a..0000000000 --- a/erpnext/healthcare/print_format/sample_id_print/sample_id_print.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "align_labels_left": 0, - "creation": "2017-02-17 17:40:52.967840", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sample Collection", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "\n{% set column = 0 %}\n\n{% for _ in range(0, doc.num_print) %}\n{% if column == 0 -%}{% endif %}\n\t\n{% if column == 0 %}{% set column = column+1 %}\n{% elif column == 2%} {%- set column = 0 %}\n{% else %}{%- set column = column+1 -%}{%- endif %}\n\t\n{% endfor %}\n
{{doc.name}}
{{doc.patient}}
\n{% if doc.patient_age %}{{doc.patient_age}}, {% endif %} {% if doc.patient_sex %}{{doc.patient_sex}}{% endif %}
{% if doc.collected_time %}{{doc.collected_time}} {% endif %}
{% if doc.collected_by %} {{doc.collected_by}} {% endif %}
", - "idx": 0, - "line_breaks": 0, - "modified": "2017-03-30 18:09:39.537609", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Sample ID Print", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Jinja", - "show_section_headings": 0, - "standard": "Yes" -} diff --git a/erpnext/healthcare/report/__init__.py b/erpnext/healthcare/report/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/report/inpatient_medication_orders/__init__.py b/erpnext/healthcare/report/inpatient_medication_orders/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js deleted file mode 100644 index a10f83760f..0000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["Inpatient Medication Orders"] = { - "filters": [ - { - fieldname: "company", - label: __("Company"), - fieldtype: "Link", - options: "Company", - default: frappe.defaults.get_user_default("Company"), - reqd: 1 - }, - { - fieldname: "from_date", - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), - reqd: 1 - }, - { - fieldname: "to_date", - label: __("To Date"), - fieldtype: "Date", - default: frappe.datetime.now_date(), - reqd: 1 - }, - { - fieldname: "patient", - label: __("Patient"), - fieldtype: "Link", - options: "Patient" - }, - { - fieldname: "service_unit", - label: __("Healthcare Service Unit"), - fieldtype: "Link", - options: "Healthcare Service Unit", - get_query: () => { - var company = frappe.query_report.get_filter_value('company'); - return { - filters: { - 'company': company, - 'is_group': 0 - } - } - } - }, - { - fieldname: "show_completed_orders", - label: __("Show Completed Orders"), - fieldtype: "Check", - default: 1 - } - ] -}; diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json deleted file mode 100644 index 9217fa1891..0000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "add_total_row": 0, - "columns": [], - "creation": "2020-11-23 17:25:58.802949", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "json": "{}", - "modified": "2020-11-23 19:40:20.227591", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Inpatient Medication Orders", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Inpatient Medication Order", - "report_name": "Inpatient Medication Orders", - "report_type": "Script Report", - "roles": [ - { - "role": "System Manager" - }, - { - "role": "Healthcare Administrator" - }, - { - "role": "Nursing User" - }, - { - "role": "Physician" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py deleted file mode 100644 index 2e809fb66b..0000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/inpatient_medication_orders.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe - -from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( - get_current_healthcare_service_unit, -) - - -def execute(filters=None): - columns = get_columns() - data = get_data(filters) - chart = get_chart_data(data) - - return columns, data, None, chart - -def get_columns(): - return [ - { - "fieldname": "patient", - "fieldtype": "Link", - "label": "Patient", - "options": "Patient", - "width": 200 - }, - { - "fieldname": "healthcare_service_unit", - "fieldtype": "Link", - "label": "Healthcare Service Unit", - "options": "Healthcare Service Unit", - "width": 150 - }, - { - "fieldname": "drug", - "fieldtype": "Link", - "label": "Drug Code", - "options": "Item", - "width": 150 - }, - { - "fieldname": "drug_name", - "fieldtype": "Data", - "label": "Drug Name", - "width": 150 - }, - { - "fieldname": "dosage", - "fieldtype": "Link", - "label": "Dosage", - "options": "Prescription Dosage", - "width": 80 - }, - { - "fieldname": "dosage_form", - "fieldtype": "Link", - "label": "Dosage Form", - "options": "Dosage Form", - "width": 100 - }, - { - "fieldname": "date", - "fieldtype": "Date", - "label": "Date", - "width": 100 - }, - { - "fieldname": "time", - "fieldtype": "Time", - "label": "Time", - "width": 100 - }, - { - "fieldname": "is_completed", - "fieldtype": "Check", - "label": "Is Order Completed", - "width": 100 - }, - { - "fieldname": "healthcare_practitioner", - "fieldtype": "Link", - "label": "Healthcare Practitioner", - "options": "Healthcare Practitioner", - "width": 200 - }, - { - "fieldname": "inpatient_medication_entry", - "fieldtype": "Link", - "label": "Inpatient Medication Entry", - "options": "Inpatient Medication Entry", - "width": 200 - }, - { - "fieldname": "inpatient_record", - "fieldtype": "Link", - "label": "Inpatient Record", - "options": "Inpatient Record", - "width": 200 - } - ] - -def get_data(filters): - conditions, values = get_conditions(filters) - - data = frappe.db.sql(""" - SELECT - parent.patient, parent.inpatient_record, parent.practitioner, - child.drug, child.drug_name, child.dosage, child.dosage_form, - child.date, child.time, child.is_completed, child.name - FROM `tabInpatient Medication Order` parent - INNER JOIN `tabInpatient Medication Order Entry` child - ON child.parent = parent.name - WHERE - parent.docstatus = 1 - {conditions} - ORDER BY date, time - """.format(conditions=conditions), values, as_dict=1) - - data = get_inpatient_details(data, filters.get("service_unit")) - - return data - -def get_conditions(filters): - conditions = "" - values = dict() - - if filters.get("company"): - conditions += " AND parent.company = %(company)s" - values["company"] = filters.get("company") - - if filters.get("from_date") and filters.get("to_date"): - conditions += " AND child.date BETWEEN %(from_date)s and %(to_date)s" - values["from_date"] = filters.get("from_date") - values["to_date"] = filters.get("to_date") - - if filters.get("patient"): - conditions += " AND parent.patient = %(patient)s" - values["patient"] = filters.get("patient") - - if not filters.get("show_completed_orders"): - conditions += " AND child.is_completed = 0" - - return conditions, values - - -def get_inpatient_details(data, service_unit): - service_unit_filtered_data = [] - - for entry in data: - entry["healthcare_service_unit"] = get_current_healthcare_service_unit(entry.inpatient_record) - if entry.is_completed: - entry["inpatient_medication_entry"] = get_inpatient_medication_entry(entry.name) - - if service_unit and entry.healthcare_service_unit and service_unit != entry.healthcare_service_unit: - service_unit_filtered_data.append(entry) - - entry.pop("name", None) - - for entry in service_unit_filtered_data: - data.remove(entry) - - return data - -def get_inpatient_medication_entry(order_entry): - return frappe.db.get_value("Inpatient Medication Entry Detail", {"against_imoe": order_entry}, "parent") - -def get_chart_data(data): - if not data: - return None - - labels = ["Pending", "Completed"] - datasets = [] - - status_wise_data = { - "Pending": 0, - "Completed": 0 - } - - for d in data: - if d.is_completed: - status_wise_data["Completed"] += 1 - else: - status_wise_data["Pending"] += 1 - - datasets.append({ - "name": "Inpatient Medication Order Status", - "values": [status_wise_data.get("Pending"), status_wise_data.get("Completed")] - }) - - chart = { - "data": { - "labels": labels, - "datasets": datasets - }, - "type": "donut", - "height": 300 - } - - chart["fieldtype"] = "Data" - - return chart diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py deleted file mode 100644 index 7f7bebf514..0000000000 --- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py +++ /dev/null @@ -1,146 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -import datetime -import unittest - -import frappe -from frappe.utils import getdate, now_datetime - -from erpnext.healthcare.doctype.inpatient_medication_order.test_inpatient_medication_order import ( - create_ipme, - create_ipmo, -) -from erpnext.healthcare.doctype.inpatient_record.inpatient_record import ( - admit_patient, - discharge_patient, - schedule_discharge, -) -from erpnext.healthcare.doctype.inpatient_record.test_inpatient_record import ( - create_inpatient, - create_patient, - get_healthcare_service_unit, - mark_invoiced_inpatient_occupancy, -) -from erpnext.healthcare.report.inpatient_medication_orders.inpatient_medication_orders import ( - execute, -) - - -class TestInpatientMedicationOrders(unittest.TestCase): - @classmethod - def setUpClass(self): - frappe.db.sql("delete from `tabInpatient Medication Order` where company='_Test Company'") - frappe.db.sql("delete from `tabInpatient Medication Entry` where company='_Test Company'") - self.patient = create_patient() - self.ip_record = create_records(self.patient) - - def test_inpatient_medication_orders_report(self): - filters = { - 'company': '_Test Company', - 'from_date': getdate(), - 'to_date': getdate(), - 'patient': '_Test IPD Patient', - 'service_unit': '_Test Service Unit Ip Occupancy - _TC' - } - - report = execute(filters) - - expected_data = [ - { - 'patient': '_Test IPD Patient', - 'inpatient_record': self.ip_record.name, - 'practitioner': None, - 'drug': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': 1.0, - 'dosage_form': 'Tablet', - 'date': getdate(), - 'time': datetime.timedelta(seconds=32400), - 'is_completed': 0, - 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' - }, - { - 'patient': '_Test IPD Patient', - 'inpatient_record': self.ip_record.name, - 'practitioner': None, - 'drug': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': 1.0, - 'dosage_form': 'Tablet', - 'date': getdate(), - 'time': datetime.timedelta(seconds=50400), - 'is_completed': 0, - 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' - }, - { - 'patient': '_Test IPD Patient', - 'inpatient_record': self.ip_record.name, - 'practitioner': None, - 'drug': 'Dextromethorphan', - 'drug_name': 'Dextromethorphan', - 'dosage': 1.0, - 'dosage_form': 'Tablet', - 'date': getdate(), - 'time': datetime.timedelta(seconds=75600), - 'is_completed': 0, - 'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC' - } - ] - - self.assertEqual(expected_data, report[1]) - - filters = frappe._dict(from_date=getdate(), to_date=getdate(), from_time='', to_time='') - ipme = create_ipme(filters) - ipme.submit() - - filters = { - 'company': '_Test Company', - 'from_date': getdate(), - 'to_date': getdate(), - 'patient': '_Test IPD Patient', - 'service_unit': '_Test Service Unit Ip Occupancy - _TC', - 'show_completed_orders': 0 - } - - report = execute(filters) - self.assertEqual(len(report[1]), 0) - - def tearDown(self): - if frappe.db.get_value('Patient', self.patient, 'inpatient_record'): - # cleanup - Discharge - schedule_discharge(frappe.as_json({'patient': self.patient})) - self.ip_record.reload() - mark_invoiced_inpatient_occupancy(self.ip_record) - - self.ip_record.reload() - discharge_patient(self.ip_record) - - for entry in frappe.get_all('Inpatient Medication Entry'): - doc = frappe.get_doc('Inpatient Medication Entry', entry.name) - doc.cancel() - doc.delete() - - for entry in frappe.get_all('Inpatient Medication Order'): - doc = frappe.get_doc('Inpatient Medication Order', entry.name) - doc.cancel() - doc.delete() - - -def create_records(patient): - frappe.db.sql("""delete from `tabInpatient Record`""") - - # Admit - ip_record = create_inpatient(patient) - ip_record.expected_length_of_stay = 0 - ip_record.save() - ip_record.reload() - service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy') - admit_patient(ip_record, service_unit, now_datetime()) - - ipmo = create_ipmo(patient) - ipmo.submit() - - return ip_record diff --git a/erpnext/healthcare/report/lab_test_report/__init__.py b/erpnext/healthcare/report/lab_test_report/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.js b/erpnext/healthcare/report/lab_test_report/lab_test_report.js deleted file mode 100644 index 7754e2e196..0000000000 --- a/erpnext/healthcare/report/lab_test_report/lab_test_report.js +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2016, ESS -// License: See license.txt - -frappe.query_reports["Lab Test Report"] = { - "filters": [ - { - "fieldname": "from_date", - "label": __("From Date"), - "fieldtype": "Date", - "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), - "reqd": 1 - }, - { - "fieldname": "to_date", - "label": __("To Date"), - "fieldtype": "Date", - "default": frappe.datetime.now_date(), - "reqd": 1 - }, - { - "fieldname": "company", - "label": __("Company"), - "fieldtype": "Link", - "default": frappe.defaults.get_default("Company"), - "options": "Company" - }, - { - "fieldname": "template", - "label": __("Lab Test Template"), - "fieldtype": "Link", - "options": "Lab Test Template" - }, - { - "fieldname": "patient", - "label": __("Patient"), - "fieldtype": "Link", - "options": "Patient" - }, - { - "fieldname": "department", - "label": __("Medical Department"), - "fieldtype": "Link", - "options": "Medical Department" - }, - { - "fieldname": "status", - "label": __("Status"), - "fieldtype": "Select", - "options": "\nCompleted\nApproved\nRejected" - }, - { - "fieldname": "invoiced", - "label": __("Invoiced"), - "fieldtype": "Check" - } - ] -}; diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.json b/erpnext/healthcare/report/lab_test_report/lab_test_report.json deleted file mode 100644 index aeb42897b8..0000000000 --- a/erpnext/healthcare/report/lab_test_report/lab_test_report.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "add_total_row": 0, - "creation": "2013-04-23 18:15:29", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 1, - "is_standard": "Yes", - "modified": "2020-07-30 18:53:20.102873", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Lab Test Report", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Lab Test", - "report_name": "Lab Test Report", - "report_type": "Script Report", - "roles": [ - { - "role": "Laboratory User" - }, - { - "role": "Nursing User" - }, - { - "role": "LabTest Approver" - }, - { - "role": "Healthcare Administrator" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.py b/erpnext/healthcare/report/lab_test_report/lab_test_report.py deleted file mode 100644 index e2a53bb1e4..0000000000 --- a/erpnext/healthcare/report/lab_test_report/lab_test_report.py +++ /dev/null @@ -1,213 +0,0 @@ -# Copyright (c) 2016, ESS -# License: See license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _, msgprint - - -def execute(filters=None): - if not filters: filters = {} - - data, columns = [], [] - - columns = get_columns() - lab_test_list = get_lab_tests(filters) - - if not lab_test_list: - msgprint(_('No records found')) - return columns, lab_test_list - - data = [] - for lab_test in lab_test_list: - row = frappe._dict({ - 'test': lab_test.name, - 'template': lab_test.template, - 'company': lab_test.company, - 'patient': lab_test.patient, - 'patient_name': lab_test.patient_name, - 'practitioner': lab_test.practitioner, - 'employee': lab_test.employee, - 'status': lab_test.status, - 'invoiced': lab_test.invoiced, - 'result_date': lab_test.result_date, - 'department': lab_test.department - }) - data.append(row) - - chart = get_chart_data(data) - report_summary = get_report_summary(data) - return columns, data, None, chart, report_summary - - -def get_columns(): - return [ - { - 'fieldname': 'test', - 'label': _('Lab Test'), - 'fieldtype': 'Link', - 'options': 'Lab Test', - 'width': '120' - }, - { - 'fieldname': 'template', - 'label': _('Lab Test Template'), - 'fieldtype': 'Link', - 'options': 'Lab Test Template', - 'width': '120' - }, - { - 'fieldname': 'company', - 'label': _('Company'), - 'fieldtype': 'Link', - 'options': 'Company', - 'width': '120' - }, - { - 'fieldname': 'patient', - 'label': _('Patient'), - 'fieldtype': 'Link', - 'options': 'Patient', - 'width': '120' - }, - { - 'fieldname': 'patient_name', - 'label': _('Patient Name'), - 'fieldtype': 'Data', - 'width': '120' - }, - { - 'fieldname': 'employee', - 'label': _('Lab Technician'), - 'fieldtype': 'Link', - 'options': 'Employee', - 'width': '120' - }, - { - 'fieldname': 'status', - 'label': _('Status'), - 'fieldtype': 'Data', - 'width': '100' - }, - { - 'fieldname': 'invoiced', - 'label': _('Invoiced'), - 'fieldtype': 'Check', - 'width': '100' - }, - { - 'fieldname': 'result_date', - 'label': _('Result Date'), - 'fieldtype': 'Date', - 'width': '100' - }, - { - 'fieldname': 'practitioner', - 'label': _('Requesting Practitioner'), - 'fieldtype': 'Link', - 'options': 'Healthcare Practitioner', - 'width': '120' - }, - { - 'fieldname': 'department', - 'label': _('Medical Department'), - 'fieldtype': 'Link', - 'options': 'Medical Department', - 'width': '100' - } - ] - -def get_lab_tests(filters): - conditions = get_conditions(filters) - data = frappe.get_all( - doctype='Lab Test', - fields=['name', 'template', 'company', 'patient', 'patient_name', 'practitioner', 'employee', 'status', 'invoiced', 'result_date', 'department'], - filters=conditions, - order_by='submitted_date desc' - ) - return data - -def get_conditions(filters): - conditions = { - 'docstatus': ('=', 1) - } - - if filters.get('from_date') and filters.get('to_date'): - conditions['result_date'] = ('between', (filters.get('from_date'), filters.get('to_date'))) - filters.pop('from_date') - filters.pop('to_date') - - for key, value in filters.items(): - if filters.get(key): - conditions[key] = value - - return conditions - -def get_chart_data(data): - if not data: - return None - - labels = ['Completed', 'Approved', 'Rejected'] - - status_wise_data = { - 'Completed': 0, - 'Approved': 0, - 'Rejected': 0 - } - - datasets = [] - - for entry in data: - status_wise_data[entry.status] += 1 - - datasets.append({ - 'name': 'Lab Test Status', - 'values': [status_wise_data.get('Completed'), status_wise_data.get('Approved'), status_wise_data.get('Rejected')] - }) - - chart = { - 'data': { - 'labels': labels, - 'datasets': datasets - }, - 'type': 'bar', - 'height': 300, - } - - return chart - - -def get_report_summary(data): - if not data: - return None - - total_lab_tests = len(data) - invoiced_lab_tests, unbilled_lab_tests = 0, 0 - - for entry in data: - if entry.invoiced: - invoiced_lab_tests += 1 - else: - unbilled_lab_tests += 1 - - return [ - { - 'value': total_lab_tests, - 'indicator': 'Blue', - 'label': 'Total Lab Tests', - 'datatype': 'Int', - }, - { - 'value': invoiced_lab_tests, - 'indicator': 'Green', - 'label': 'Invoiced Lab Tests', - 'datatype': 'Int', - }, - { - 'value': unbilled_lab_tests, - 'indicator': 'Red', - 'label': 'Unbilled Lab Tests', - 'datatype': 'Int', - } - ] diff --git a/erpnext/healthcare/report/patient_appointment_analytics/__init__.py b/erpnext/healthcare/report/patient_appointment_analytics/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js deleted file mode 100644 index 18d252ede1..0000000000 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.js +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports['Patient Appointment Analytics'] = { - "filters": [ - { - fieldname: 'tree_type', - label: __('Tree Type'), - fieldtype: 'Select', - options: ['Healthcare Practitioner', 'Medical Department'], - default: 'Healthcare Practitioner', - reqd: 1 - }, - { - fieldname: 'status', - label: __('Appointment Status'), - fieldtype: 'Select', - options:[ - {label: __('Scheduled'), value: 'Scheduled'}, - {label: __('Open'), value: 'Open'}, - {label: __('Closed'), value: 'Closed'}, - {label: __('Expired'), value: 'Expired'}, - {label: __('Cancelled'), value: 'Cancelled'} - ] - }, - { - fieldname: 'appointment_type', - label: __('Appointment Type'), - fieldtype: 'Link', - options: 'Appointment Type' - }, - { - fieldname: 'practitioner', - label: __('Healthcare Practitioner'), - fieldtype: 'Link', - options: 'Healthcare Practitioner' - }, - { - fieldname: 'department', - label: __('Medical Department'), - fieldtype: 'Link', - options: 'Medical Department' - }, - { - fieldname: 'from_date', - label: __('From Date'), - fieldtype: 'Date', - default: frappe.defaults.get_user_default('year_start_date'), - reqd: 1 - }, - { - fieldname: 'to_date', - label: __('To Date'), - fieldtype: 'Date', - default: frappe.defaults.get_user_default('year_end_date'), - reqd: 1 - }, - { - fieldname: 'range', - label: __('Range'), - fieldtype: 'Select', - options:[ - {label: __('Weekly'), value: 'Weekly'}, - {label: __('Monthly'), value: 'Monthly'}, - {label: __('Quarterly'), value: 'Quarterly'}, - {label: __('Yearly'), value: 'Yearly'} - ], - default: 'Monthly', - reqd: 1 - } - ], - after_datatable_render: function(datatable_obj) { - $(datatable_obj.wrapper).find(".dt-row-0").find('input[type=checkbox]').click(); - }, - get_datatable_options(options) { - return Object.assign(options, { - checkboxColumn: true, - events: { - onCheckRow: function(data) { - row_name = data[2].content; - length = data.length; - - row_values = data.slice(3,length-1).map(function (column) { - return column.content; - }) - - entry = { - 'name': row_name, - 'values': row_values - } - - let raw_data = frappe.query_report.chart.data; - let new_datasets = raw_data.datasets; - - let found = false; - for (let i=0; i < new_datasets.length;i++) { - if (new_datasets[i].name == row_name) { - found = true; - new_datasets.splice(i,1); - break; - } - } - - if (!found) { - new_datasets.push(entry); - } - - let new_data = { - labels: raw_data.labels, - datasets: new_datasets - } - - setTimeout(() => { - frappe.query_report.chart.update(new_data) - }, 500) - - - setTimeout(() => { - frappe.query_report.chart.draw(true); - }, 1000) - - frappe.query_report.raw_chart_data = new_data; - }, - } - }) - }, -}; diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json deleted file mode 100644 index 64750c012f..0000000000 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "add_total_row": 1, - "creation": "2020-03-02 15:13:16.273493", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 0, - "is_standard": "Yes", - "modified": "2020-03-02 15:13:16.273493", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Patient Appointment Analytics", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Patient Appointment", - "report_name": "Patient Appointment Analytics", - "report_type": "Script Report", - "roles": [ - { - "role": "Healthcare Administrator" - }, - { - "role": "LabTest Approver" - }, - { - "role": "Physician" - }, - { - "role": "Nursing User" - }, - { - "role": "Laboratory User" - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py b/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py deleted file mode 100644 index 1afb5da1fb..0000000000 --- a/erpnext/healthcare/report/patient_appointment_analytics/patient_appointment_analytics.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _, scrub -from frappe.utils import add_days, add_to_date, flt, getdate -from six import iteritems - -from erpnext.accounts.utils import get_fiscal_year - - -def execute(filters=None): - return Analytics(filters).run() - -class Analytics(object): - def __init__(self, filters=None): - """Patient Appointment Analytics Report.""" - self.filters = frappe._dict(filters or {}) - self.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] - self.get_period_date_ranges() - - def run(self): - self.get_columns() - self.get_data() - self.get_chart_data() - - return self.columns, self.data, None, self.chart - - def get_period_date_ranges(self): - from dateutil.relativedelta import MO, relativedelta - from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date) - - increment = { - 'Monthly': 1, - 'Quarterly': 3, - 'Half-Yearly': 6, - 'Yearly': 12 - }.get(self.filters.range, 1) - - if self.filters.range in ['Monthly', 'Quarterly']: - from_date = from_date.replace(day=1) - elif self.filters.range == 'Yearly': - from_date = get_fiscal_year(from_date)[1] - else: - from_date = from_date + relativedelta(from_date, weekday=MO(-1)) - - self.periodic_daterange = [] - for dummy in range(1, 53): - if self.filters.range == 'Weekly': - period_end_date = add_days(from_date, 6) - else: - period_end_date = add_to_date(from_date, months=increment, days=-1) - - if period_end_date > to_date: - period_end_date = to_date - - self.periodic_daterange.append(period_end_date) - - from_date = add_days(period_end_date, 1) - if period_end_date == to_date: - break - - def get_columns(self): - self.columns = [] - - if self.filters.tree_type == 'Healthcare Practitioner': - self.columns.append({ - 'label': _('Healthcare Practitioner'), - 'options': 'Healthcare Practitioner', - 'fieldname': 'practitioner', - 'fieldtype': 'Link', - 'width': 200 - }) - - elif self.filters.tree_type == 'Medical Department': - self.columns.append({ - 'label': _('Medical Department'), - 'fieldname': 'department', - 'fieldtype': 'Link', - 'options': 'Medical Department', - 'width': 150 - }) - - for end_date in self.periodic_daterange: - period = self.get_period(end_date) - self.columns.append({ - 'label': _(period), - 'fieldname': scrub(period), - 'fieldtype': 'Int', - 'width': 120 - }) - - self.columns.append({ - 'label': _('Total'), - 'fieldname': 'total', - 'fieldtype': 'Int', - 'width': 120 - }) - - def get_data(self): - if self.filters.tree_type == 'Healthcare Practitioner': - self.get_appointments_based_on_healthcare_practitioner() - self.get_rows() - - elif self.filters.tree_type == 'Medical Department': - self.get_appointments_based_on_medical_department() - self.get_rows() - - def get_period(self, appointment_date): - if self.filters.range == 'Weekly': - period = 'Week ' + str(appointment_date.isocalendar()[1]) - elif self.filters.range == 'Monthly': - period = str(self.months[appointment_date.month - 1]) - elif self.filters.range == 'Quarterly': - period = 'Quarter ' + str(((appointment_date.month - 1) // 3) + 1) - else: - year = get_fiscal_year(appointment_date, company=self.filters.company) - period = str(year[0]) - - if getdate(self.filters.from_date).year != getdate(self.filters.to_date).year: - period += ' ' + str(appointment_date.year) - - return period - - def get_appointments_based_on_healthcare_practitioner(self): - filters = self.get_common_filters() - - self.entries = frappe.db.get_all('Patient Appointment', - fields=['appointment_date', 'name', 'patient', 'practitioner'], - filters=filters - ) - - def get_appointments_based_on_medical_department(self): - filters = self.get_common_filters() - if not filters.get('department'): - filters['department'] = ('!=', '') - - self.entries = frappe.db.get_all('Patient Appointment', - fields=['appointment_date', 'name', 'patient', 'practitioner', 'department'], - filters=filters - ) - - def get_common_filters(self): - filters = {} - filters['appointment_date'] = ('between', [self.filters.from_date, self.filters.to_date]) - for entry in ['appointment_type', 'practitioner', 'department', 'status']: - if self.filters.get(entry): - filters[entry] = self.filters.get(entry) - - return filters - - def get_rows(self): - self.data = [] - self.get_periodic_data() - - for entity, period_data in iteritems(self.appointment_periodic_data): - if self.filters.tree_type == 'Healthcare Practitioner': - row = {'practitioner': entity} - elif self.filters.tree_type == 'Medical Department': - row = {'department': entity} - - total = 0 - for end_date in self.periodic_daterange: - period = self.get_period(end_date) - amount = flt(period_data.get(period, 0.0)) - row[scrub(period)] = amount - total += amount - - row['total'] = total - - self.data.append(row) - - def get_periodic_data(self): - self.appointment_periodic_data = frappe._dict() - - for d in self.entries: - period = self.get_period(d.get('appointment_date')) - if self.filters.tree_type == 'Healthcare Practitioner': - self.appointment_periodic_data.setdefault(d.practitioner, frappe._dict()).setdefault(period, 0.0) - self.appointment_periodic_data[d.practitioner][period] += 1 - - elif self.filters.tree_type == 'Medical Department': - self.appointment_periodic_data.setdefault(d.department, frappe._dict()).setdefault(period, 0.0) - self.appointment_periodic_data[d.department][period] += 1 - - def get_chart_data(self): - length = len(self.columns) - labels = [d.get("label") for d in self.columns[1:length - 1]] - self.chart = { - "data": { - 'labels': labels, - 'datasets': [] - }, - "type": "line" - } diff --git a/erpnext/healthcare/setup.py b/erpnext/healthcare/setup.py deleted file mode 100644 index 891272ddf8..0000000000 --- a/erpnext/healthcare/setup.py +++ /dev/null @@ -1,295 +0,0 @@ -from __future__ import unicode_literals -import frappe - -from frappe import _ -from erpnext.setup.utils import insert_record - -def setup_healthcare(): - if frappe.db.exists('Medical Department', 'Cardiology'): - # already setup - return - create_medical_departments() - create_antibiotics() - create_lab_test_uom() - create_duration() - create_dosage() - create_healthcare_item_groups() - create_sensitivity() - add_healthcare_service_unit_tree_root() - setup_patient_history_settings() - -def create_medical_departments(): - departments = [ - "Accident And Emergency Care" ,"Anaesthetics", "Biochemistry", "Cardiology", "Dermatology", - "Diagnostic Imaging", "ENT", "Gastroenterology", "General Surgery", "Gynaecology", - "Haematology", "Maternity", "Microbiology", "Nephrology", "Neurology", "Oncology", - "Orthopaedics", "Pathology", "Physiotherapy", "Rheumatology", "Serology", "Urology" - ] - for department in departments: - mediacal_department = frappe.new_doc("Medical Department") - mediacal_department.department = _(department) - try: - mediacal_department.save() - except frappe.DuplicateEntryError: - pass - -def create_antibiotics(): - abt = [ - "Amoxicillin", "Ampicillin", "Bacampicillin", "Carbenicillin", "Cloxacillin", "Dicloxacillin", - "Flucloxacillin", "Mezlocillin", "Nafcillin", "Oxacillin", "Penicillin G", "Penicillin V", - "Piperacillin", "Pivampicillin", "Pivmecillinam", "Ticarcillin", "Cefacetrile (cephacetrile)", - "Cefadroxil (cefadroxyl)", "Cefalexin (cephalexin)", "Cefaloglycin (cephaloglycin)", - "Cefalonium (cephalonium)", "Cefaloridine (cephaloradine)", "Cefalotin (cephalothin)", - "Cefapirin (cephapirin)", "Cefatrizine", "Cefazaflur", "Cefazedone", "Cefazolin (cephazolin)", - "Cefradine (cephradine)", "Cefroxadine", "Ceftezole", "Cefaclor", "Cefamandole", "Cefmetazole", - "Cefonicid", "Cefotetan", "Cefoxitin", "Cefprozil (cefproxil)", "Cefuroxime", "Cefuzonam", - "Cefcapene", "Cefdaloxime", "Cefdinir", "Cefditoren", "Cefetamet", "Cefixime", "Cefmenoxime", - "Cefodizime", "Cefotaxime", "Cefpimizole", "Cefpodoxime", "Cefteram", "Ceftibuten", "Ceftiofur", - "Ceftiolene", "Ceftizoxime", "Ceftriaxone", "Cefoperazone", "Ceftazidime", "Cefclidine", "Cefepime", - "Cefluprenam", "Cefoselis", "Cefozopran", "Cefpirome", "Cefquinome", "Ceftobiprole", "Ceftaroline", - "Cefaclomezine","Cefaloram", "Cefaparole", "Cefcanel", "Cefedrolor", "Cefempidone", "Cefetrizole", - "Cefivitril", "Cefmatilen", "Cefmepidium", "Cefovecin", "Cefoxazole", "Cefrotil", "Cefsumide", - "Cefuracetime", "Ceftioxide", "Ceftazidime/Avibactam", "Ceftolozane/Tazobactam", "Aztreonam", - "Imipenem", "Imipenem/cilastatin", "Doripenem", "Meropenem", "Ertapenem", "Azithromycin", - "Erythromycin", "Clarithromycin", "Dirithromycin", "Roxithromycin", "Telithromycin", "Clindamycin", - "Lincomycin", "Pristinamycin", "Quinupristin/dalfopristin", "Amikacin", "Gentamicin", "Kanamycin", - "Neomycin", "Netilmicin", "Paromomycin", "Streptomycin", "Tobramycin", "Flumequine", "Nalidixic acid", - "Oxolinic acid", "Piromidic acid", "Pipemidic acid", "Rosoxacin", "Ciprofloxacin", "Enoxacin", - "Lomefloxacin", "Nadifloxacin", "Norfloxacin", "Ofloxacin", "Pefloxacin", "Rufloxacin", "Balofloxacin", - "Gatifloxacin", "Grepafloxacin", "Levofloxacin", "Moxifloxacin", "Pazufloxacin", "Sparfloxacin", - "Temafloxacin", "Tosufloxacin", "Besifloxacin", "Clinafloxacin", "Gemifloxacin", - "Sitafloxacin", "Trovafloxacin", "Prulifloxacin", "Sulfamethizole", "Sulfamethoxazole", - "Sulfisoxazole", "Trimethoprim-Sulfamethoxazole", "Demeclocycline", "Doxycycline", "Minocycline", - "Oxytetracycline", "Tetracycline", "Tigecycline", "Chloramphenicol", "Metronidazole", - "Tinidazole", "Nitrofurantoin", "Vancomycin", "Teicoplanin", "Telavancin", "Linezolid", - "Cycloserine 2", "Rifampin", "Rifabutin", "Rifapentine", "Rifalazil", "Bacitracin", "Polymyxin B", - "Viomycin", "Capreomycin" - ] - - for a in abt: - antibiotic = frappe.new_doc("Antibiotic") - antibiotic.antibiotic_name = a - try: - antibiotic.save() - except frappe.DuplicateEntryError: - pass - -def create_lab_test_uom(): - records = [ - {"doctype": "Lab Test UOM", "name": "umol/L", "lab_test_uom": "umol/L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mg/L", "lab_test_uom": "mg/L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mg / dl", "lab_test_uom": "mg / dl", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "pg / ml", "lab_test_uom": "pg / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "U/ml", "lab_test_uom": "U/ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "/HPF", "lab_test_uom": "/HPF", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Million Cells / cumm", "lab_test_uom": "Million Cells / cumm", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Lakhs Cells / cumm", "lab_test_uom": "Lakhs Cells / cumm", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "U / L", "lab_test_uom": "U / L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "g / L", "lab_test_uom": "g / L", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "IU / ml", "lab_test_uom": "IU / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "gm %", "lab_test_uom": "gm %", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Microgram", "lab_test_uom": "Microgram", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Micron", "lab_test_uom": "Micron", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "Cells / cumm", "lab_test_uom": "Cells / cumm", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "%", "lab_test_uom": "%", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mm / dl", "lab_test_uom": "mm / dl", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "mm / hr", "lab_test_uom": "mm / hr", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ulU / ml", "lab_test_uom": "ulU / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ng / ml", "lab_test_uom": "ng / ml", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ng / dl", "lab_test_uom": "ng / dl", "uom_description": None }, - {"doctype": "Lab Test UOM", "name": "ug / dl", "lab_test_uom": "ug / dl", "uom_description": None } - ] - - insert_record(records) - -def create_duration(): - records = [ - {"doctype": "Prescription Duration", "name": "3 Month", "number": "3", "period": "Month" }, - {"doctype": "Prescription Duration", "name": "2 Month", "number": "2", "period": "Month" }, - {"doctype": "Prescription Duration", "name": "1 Month", "number": "1", "period": "Month" }, - {"doctype": "Prescription Duration", "name": "12 Hour", "number": "12", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "11 Hour", "number": "11", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "10 Hour", "number": "10", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "9 Hour", "number": "9", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "8 Hour", "number": "8", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "7 Hour", "number": "7", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "6 Hour", "number": "6", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "5 Hour", "number": "5", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "4 Hour", "number": "4", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "3 Hour", "number": "3", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "2 Hour", "number": "2", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "1 Hour", "number": "1", "period": "Hour" }, - {"doctype": "Prescription Duration", "name": "5 Week", "number": "5", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "4 Week", "number": "4", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "3 Week", "number": "3", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "2 Week", "number": "2", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "1 Week", "number": "1", "period": "Week" }, - {"doctype": "Prescription Duration", "name": "6 Day", "number": "6", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "5 Day", "number": "5", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "4 Day", "number": "4", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "3 Day", "number": "3", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "2 Day", "number": "2", "period": "Day" }, - {"doctype": "Prescription Duration", "name": "1 Day", "number": "1", "period": "Day" } - ] - insert_record(records) - -def create_dosage(): - records = [ - {"doctype": "Prescription Dosage", "name": "1-1-1-1", "dosage": "1-1-1-1","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "13:00:00"},{"strength": "1.0","strength_time": "17:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "0-0-1", "dosage": "0-0-1","dosage_strength": - [{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "1-0-0", "dosage": "1-0-0","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "0-1-0", "dosage": "0-1-0","dosage_strength": - [{"strength": "1.0","strength_time": "14:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "1-1-1", "dosage": "1-1-1","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "14:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "1-0-1", "dosage": "1-0-1","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "Once Bedtime", "dosage": "Once Bedtime","dosage_strength": - [{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "5 times a day", "dosage": "5 times a day","dosage_strength": - [{"strength": "1.0","strength_time": "5:00:00"}, {"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "13:00:00"},{"strength": "1.0","strength_time": "17:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "QID", "dosage": "QID","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "13:00:00"},{"strength": "1.0","strength_time": "17:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "TID", "dosage": "TID","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "14:00:00"},{"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "BID", "dosage": "BID","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}, {"strength": "1.0","strength_time": "21:00:00"}] - }, - {"doctype": "Prescription Dosage", "name": "Once Daily", "dosage": "Once Daily","dosage_strength": - [{"strength": "1.0","strength_time": "9:00:00"}] - } - ] - insert_record(records) - -def create_healthcare_item_groups(): - records = [ - {'doctype': 'Item Group', 'item_group_name': _('Laboratory'), - 'is_group': 0, 'parent_item_group': _('All Item Groups') }, - {'doctype': 'Item Group', 'item_group_name': _('Drug'), - 'is_group': 0, 'parent_item_group': _('All Item Groups') } - ] - insert_record(records) - -def create_sensitivity(): - records = [ - {"doctype": "Sensitivity", "sensitivity": _("Low Sensitivity")}, - {"doctype": "Sensitivity", "sensitivity": _("High Sensitivity")}, - {"doctype": "Sensitivity", "sensitivity": _("Moderate Sensitivity")}, - {"doctype": "Sensitivity", "sensitivity": _("Susceptible")}, - {"doctype": "Sensitivity", "sensitivity": _("Resistant")}, - {"doctype": "Sensitivity", "sensitivity": _("Intermediate")} - ] - insert_record(records) - -def add_healthcare_service_unit_tree_root(): - record = [ - { - "doctype": "Healthcare Service Unit", - "healthcare_service_unit_name": "All Healthcare Service Units", - "is_group": 1, - "company": get_company() - } - ] - insert_record(record) - -def get_company(): - company = frappe.defaults.get_defaults().company - if company: - return company - else: - company = frappe.get_list("Company", limit=1) - if company: - return company[0].name - return None - -def setup_patient_history_settings(): - import json - - settings = frappe.get_single('Patient History Settings') - configuration = get_patient_history_config() - for dt, config in configuration.items(): - settings.append("standard_doctypes", { - "document_type": dt, - "date_fieldname": config[0], - "selected_fields": json.dumps(config[1]) - }) - settings.save() - -def get_patient_history_config(): - return { - "Patient Encounter": ("encounter_date", [ - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Symptoms", "fieldname": "symptoms", "fieldtype": "Table Multiselect"}, - {"label": "Diagnosis", "fieldname": "diagnosis", "fieldtype": "Table Multiselect"}, - {"label": "Drug Prescription", "fieldname": "drug_prescription", "fieldtype": "Table"}, - {"label": "Lab Tests", "fieldname": "lab_test_prescription", "fieldtype": "Table"}, - {"label": "Clinical Procedures", "fieldname": "procedure_prescription", "fieldtype": "Table"}, - {"label": "Therapies", "fieldname": "therapies", "fieldtype": "Table"}, - {"label": "Review Details", "fieldname": "encounter_comment", "fieldtype": "Small Text"} - ]), - "Clinical Procedure": ("start_date", [ - {"label": "Procedure Template", "fieldname": "procedure_template", "fieldtype": "Link"}, - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Notes", "fieldname": "notes", "fieldtype": "Small Text"}, - {"label": "Service Unit", "fieldname": "service_unit", "fieldtype": "Healthcare Service Unit"}, - {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, - {"label": "Sample", "fieldname": "sample", "fieldtype": "Link"} - ]), - "Lab Test": ("result_date", [ - {"label": "Test Template", "fieldname": "template", "fieldtype": "Link"}, - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Test Name", "fieldname": "lab_test_name", "fieldtype": "Data"}, - {"label": "Lab Technician Name", "fieldname": "employee_name", "fieldtype": "Data"}, - {"label": "Sample ID", "fieldname": "sample", "fieldtype": "Link"}, - {"label": "Normal Test Result", "fieldname": "normal_test_items", "fieldtype": "Table"}, - {"label": "Descriptive Test Result", "fieldname": "descriptive_test_items", "fieldtype": "Table"}, - {"label": "Organism Test Result", "fieldname": "organism_test_items", "fieldtype": "Table"}, - {"label": "Sensitivity Test Result", "fieldname": "sensitivity_test_items", "fieldtype": "Table"}, - {"label": "Comments", "fieldname": "lab_test_comment", "fieldtype": "Table"} - ]), - "Therapy Session": ("start_date", [ - {"label": "Therapy Type", "fieldname": "therapy_type", "fieldtype": "Link"}, - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Therapy Plan", "fieldname": "therapy_plan", "fieldtype": "Link"}, - {"label": "Duration", "fieldname": "duration", "fieldtype": "Int"}, - {"label": "Location", "fieldname": "location", "fieldtype": "Link"}, - {"label": "Healthcare Service Unit", "fieldname": "service_unit", "fieldtype": "Link"}, - {"label": "Start Time", "fieldname": "start_time", "fieldtype": "Time"}, - {"label": "Exercises", "fieldname": "exercises", "fieldtype": "Table"}, - {"label": "Total Counts Targeted", "fieldname": "total_counts_targeted", "fieldtype": "Int"}, - {"label": "Total Counts Completed", "fieldname": "total_counts_completed", "fieldtype": "Int"} - ]), - "Vital Signs": ("signs_date", [ - {"label": "Body Temperature", "fieldname": "temperature", "fieldtype": "Data"}, - {"label": "Heart Rate / Pulse", "fieldname": "pulse", "fieldtype": "Data"}, - {"label": "Respiratory rate", "fieldname": "respiratory_rate", "fieldtype": "Data"}, - {"label": "Tongue", "fieldname": "tongue", "fieldtype": "Select"}, - {"label": "Abdomen", "fieldname": "abdomen", "fieldtype": "Select"}, - {"label": "Reflexes", "fieldname": "reflexes", "fieldtype": "Select"}, - {"label": "Blood Pressure", "fieldname": "bp", "fieldtype": "Data"}, - {"label": "Notes", "fieldname": "vital_signs_note", "fieldtype": "Small Text"}, - {"label": "Height (In Meter)", "fieldname": "height", "fieldtype": "Float"}, - {"label": "Weight (In Kilogram)", "fieldname": "weight", "fieldtype": "Float"}, - {"label": "BMI", "fieldname": "bmi", "fieldtype": "Float"} - ]), - "Inpatient Medication Order": ("start_date", [ - {"label": "Healthcare Practitioner", "fieldname": "practitioner", "fieldtype": "Link"}, - {"label": "Start Date", "fieldname": "start_date", "fieldtype": "Date"}, - {"label": "End Date", "fieldname": "end_date", "fieldtype": "Date"}, - {"label": "Medication Orders", "fieldname": "medication_orders", "fieldtype": "Table"}, - {"label": "Total Orders", "fieldname": "total_orders", "fieldtype": "Float"} - ]) - } diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py deleted file mode 100644 index cae3008ca8..0000000000 --- a/erpnext/healthcare/utils.py +++ /dev/null @@ -1,792 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, earthians and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json -import math - -import frappe -from frappe import _ -from frappe.utils import cstr, rounded, time_diff_in_hours -from frappe.utils.formatters import format_value - -from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity -from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account -from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple - - -@frappe.whitelist() -def get_healthcare_services_to_invoice(patient, company): - patient = frappe.get_doc('Patient', patient) - items_to_invoice = [] - if patient: - validate_customer_created(patient) - # Customer validated, build a list of billable services - items_to_invoice += get_appointments_to_invoice(patient, company) - items_to_invoice += get_encounters_to_invoice(patient, company) - items_to_invoice += get_lab_tests_to_invoice(patient, company) - items_to_invoice += get_clinical_procedures_to_invoice(patient, company) - items_to_invoice += get_inpatient_services_to_invoice(patient, company) - items_to_invoice += get_therapy_plans_to_invoice(patient, company) - items_to_invoice += get_therapy_sessions_to_invoice(patient, company) - - return items_to_invoice - - -def validate_customer_created(patient): - if not frappe.db.get_value('Patient', patient.name, 'customer'): - msg = _("Please set a Customer linked to the Patient") - msg += " {0}".format(patient.name) - frappe.throw(msg, title=_('Customer Not Found')) - - -def get_appointments_to_invoice(patient, company): - appointments_to_invoice = [] - patient_appointments = frappe.get_list( - 'Patient Appointment', - fields = '*', - filters = {'patient': patient.name, 'company': company, 'invoiced': 0, 'status': ['not in', 'Cancelled']}, - order_by = 'appointment_date' - ) - - for appointment in patient_appointments: - # Procedure Appointments - if appointment.procedure_template: - if frappe.db.get_value('Clinical Procedure Template', appointment.procedure_template, 'is_billable'): - appointments_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': appointment.procedure_template - }) - # Consultation Appointments, should check fee validity - else: - if frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups') and \ - frappe.db.exists('Fee Validity Reference', {'appointment': appointment.name}): - continue # Skip invoicing, fee validty present - practitioner_charge = 0 - income_account = None - service_item = None - if appointment.practitioner: - details = get_service_item_and_practitioner_charge(appointment) - service_item = details.get('service_item') - practitioner_charge = details.get('practitioner_charge') - income_account = get_income_account(appointment.practitioner, appointment.company) - appointments_to_invoice.append({ - 'reference_type': 'Patient Appointment', - 'reference_name': appointment.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) - - return appointments_to_invoice - - -def get_encounters_to_invoice(patient, company): - if not isinstance(patient, str): - patient = patient.name - encounters_to_invoice = [] - encounters = frappe.get_list( - 'Patient Encounter', - fields=['*'], - filters={'patient': patient, 'company': company, 'invoiced': False, 'docstatus': 1} - ) - if encounters: - for encounter in encounters: - if not encounter.appointment: - practitioner_charge = 0 - income_account = None - service_item = None - if encounter.practitioner: - if encounter.inpatient_record and \ - frappe.db.get_single_value('Healthcare Settings', 'do_not_bill_inpatient_encounters'): - continue - - details = get_service_item_and_practitioner_charge(encounter) - service_item = details.get('service_item') - practitioner_charge = details.get('practitioner_charge') - income_account = get_income_account(encounter.practitioner, encounter.company) - - encounters_to_invoice.append({ - 'reference_type': 'Patient Encounter', - 'reference_name': encounter.name, - 'service': service_item, - 'rate': practitioner_charge, - 'income_account': income_account - }) - - return encounters_to_invoice - - -def get_lab_tests_to_invoice(patient, company): - lab_tests_to_invoice = [] - lab_tests = frappe.get_list( - 'Lab Test', - fields=['name', 'template'], - filters={'patient': patient.name, 'company': company, 'invoiced': False, 'docstatus': 1} - ) - for lab_test in lab_tests: - item, is_billable = frappe.get_cached_value('Lab Test Template', lab_test.template, ['item', 'is_billable']) - if is_billable: - lab_tests_to_invoice.append({ - 'reference_type': 'Lab Test', - 'reference_name': lab_test.name, - 'service': item - }) - - lab_prescriptions = frappe.db.sql( - ''' - SELECT - lp.name, lp.lab_test_code - FROM - `tabPatient Encounter` et, `tabLab Prescription` lp - WHERE - et.patient=%s - and lp.parent=et.name - and lp.lab_test_created=0 - and lp.invoiced=0 - ''', (patient.name), as_dict=1) - - for prescription in lab_prescriptions: - item, is_billable = frappe.get_cached_value('Lab Test Template', prescription.lab_test_code, ['item', 'is_billable']) - if prescription.lab_test_code and is_billable: - lab_tests_to_invoice.append({ - 'reference_type': 'Lab Prescription', - 'reference_name': prescription.name, - 'service': item - }) - - return lab_tests_to_invoice - - -def get_clinical_procedures_to_invoice(patient, company): - clinical_procedures_to_invoice = [] - procedures = frappe.get_list( - 'Clinical Procedure', - fields='*', - filters={'patient': patient.name, 'company': company, 'invoiced': False} - ) - for procedure in procedures: - if not procedure.appointment: - item, is_billable = frappe.get_cached_value('Clinical Procedure Template', procedure.procedure_template, ['item', 'is_billable']) - if procedure.procedure_template and is_billable: - clinical_procedures_to_invoice.append({ - 'reference_type': 'Clinical Procedure', - 'reference_name': procedure.name, - 'service': item - }) - - # consumables - if procedure.invoice_separately_as_consumables and procedure.consume_stock \ - and procedure.status == 'Completed' and not procedure.consumption_invoiced: - - service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') - if not service_item: - frappe.throw(_('Please configure Clinical Procedure Consumable Item in {0}').format( - frappe.utils.get_link_to_form('Healthcare Settings', 'Healthcare Settings')), - title=_('Missing Configuration')) - - clinical_procedures_to_invoice.append({ - 'reference_type': 'Clinical Procedure', - 'reference_name': procedure.name, - 'service': service_item, - 'rate': procedure.consumable_total_amount, - 'description': procedure.consumption_details - }) - - procedure_prescriptions = frappe.db.sql( - ''' - SELECT - pp.name, pp.procedure - FROM - `tabPatient Encounter` et, `tabProcedure Prescription` pp - WHERE - et.patient=%s - and pp.parent=et.name - and pp.procedure_created=0 - and pp.invoiced=0 - and pp.appointment_booked=0 - ''', (patient.name), as_dict=1) - - for prescription in procedure_prescriptions: - item, is_billable = frappe.get_cached_value('Clinical Procedure Template', prescription.procedure, ['item', 'is_billable']) - if is_billable: - clinical_procedures_to_invoice.append({ - 'reference_type': 'Procedure Prescription', - 'reference_name': prescription.name, - 'service': item - }) - - return clinical_procedures_to_invoice - - -def get_inpatient_services_to_invoice(patient, company): - services_to_invoice = [] - inpatient_services = frappe.db.sql( - ''' - SELECT - io.* - FROM - `tabInpatient Record` ip, `tabInpatient Occupancy` io - WHERE - ip.patient=%s - and ip.company=%s - and io.parent=ip.name - and io.left=1 - and io.invoiced=0 - ''', (patient.name, company), as_dict=1) - - for inpatient_occupancy in inpatient_services: - service_unit_type = frappe.db.get_value('Healthcare Service Unit', inpatient_occupancy.service_unit, 'service_unit_type') - service_unit_type = frappe.get_cached_doc('Healthcare Service Unit Type', service_unit_type) - if service_unit_type and service_unit_type.is_billable: - hours_occupied = time_diff_in_hours(inpatient_occupancy.check_out, inpatient_occupancy.check_in) - qty = 0.5 - if hours_occupied > 0: - actual_qty = hours_occupied / service_unit_type.no_of_hours - floor = math.floor(actual_qty) - decimal_part = actual_qty - floor - if decimal_part > 0.5: - qty = rounded(floor + 1, 1) - elif decimal_part < 0.5 and decimal_part > 0: - qty = rounded(floor + 0.5, 1) - if qty <= 0: - qty = 0.5 - services_to_invoice.append({ - 'reference_type': 'Inpatient Occupancy', - 'reference_name': inpatient_occupancy.name, - 'service': service_unit_type.item, 'qty': qty - }) - - return services_to_invoice - - -def get_therapy_plans_to_invoice(patient, company): - therapy_plans_to_invoice = [] - therapy_plans = frappe.get_list( - 'Therapy Plan', - fields=['therapy_plan_template', 'name'], - filters={ - 'patient': patient.name, - 'invoiced': 0, - 'company': company, - 'therapy_plan_template': ('!=', '') - } - ) - for plan in therapy_plans: - therapy_plans_to_invoice.append({ - 'reference_type': 'Therapy Plan', - 'reference_name': plan.name, - 'service': frappe.db.get_value('Therapy Plan Template', plan.therapy_plan_template, 'linked_item') - }) - - return therapy_plans_to_invoice - - -def get_therapy_sessions_to_invoice(patient, company): - therapy_sessions_to_invoice = [] - therapy_plans = frappe.db.get_all('Therapy Plan', {'therapy_plan_template': ('!=', '')}) - therapy_plans_created_from_template = [] - for entry in therapy_plans: - therapy_plans_created_from_template.append(entry.name) - - therapy_sessions = frappe.get_list( - 'Therapy Session', - fields='*', - filters={ - 'patient': patient.name, - 'invoiced': 0, - 'company': company, - 'therapy_plan': ('not in', therapy_plans_created_from_template) - } - ) - 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 - -@frappe.whitelist() -def get_service_item_and_practitioner_charge(doc): - if isinstance(doc, str): - doc = json.loads(doc) - doc = frappe.get_doc(doc) - - service_item = None - practitioner_charge = None - department = doc.medical_department if doc.doctype == 'Patient Encounter' else doc.department - - is_inpatient = doc.inpatient_record - - if doc.get('appointment_type'): - service_item, practitioner_charge = get_appointment_type_service_item(doc.appointment_type, department, is_inpatient) - - if not service_item and not practitioner_charge: - service_item, practitioner_charge = get_practitioner_service_item(doc.practitioner, is_inpatient) - if not service_item: - service_item = get_healthcare_service_item(is_inpatient) - - if not service_item: - throw_config_service_item(is_inpatient) - - if not practitioner_charge: - throw_config_practitioner_charge(is_inpatient, doc.practitioner) - - return {'service_item': service_item, 'practitioner_charge': practitioner_charge} - - -def get_appointment_type_service_item(appointment_type, department, is_inpatient): - from erpnext.healthcare.doctype.appointment_type.appointment_type import ( - get_service_item_based_on_department, - ) - - item_list = get_service_item_based_on_department(appointment_type, department) - service_item = None - practitioner_charge = None - - if item_list: - if is_inpatient: - service_item = item_list.get('inpatient_visit_charge_item') - practitioner_charge = item_list.get('inpatient_visit_charge') - else: - service_item = item_list.get('op_consulting_charge_item') - practitioner_charge = item_list.get('op_consulting_charge') - - return service_item, practitioner_charge - - -def throw_config_service_item(is_inpatient): - service_item_label = _('Out Patient Consulting Charge Item') - if is_inpatient: - service_item_label = _('Inpatient Visit Charge Item') - - msg = _(('Please Configure {0} in ').format(service_item_label) \ - + '''Healthcare Settings''') - frappe.throw(msg, title=_('Missing Configuration')) - - -def throw_config_practitioner_charge(is_inpatient, practitioner): - charge_name = _('OP Consulting Charge') - if is_inpatient: - charge_name = _('Inpatient Visit Charge') - - msg = _(('Please Configure {0} for Healthcare Practitioner').format(charge_name) \ - + ''' {0}'''.format(practitioner)) - frappe.throw(msg, title=_('Missing Configuration')) - - -def get_practitioner_service_item(practitioner, is_inpatient): - service_item = None - practitioner_charge = None - - if is_inpatient: - service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['inpatient_visit_charge_item', 'inpatient_visit_charge']) - else: - service_item, practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, ['op_consulting_charge_item', 'op_consulting_charge']) - - return service_item, practitioner_charge - - -def get_healthcare_service_item(is_inpatient): - service_item = None - - if is_inpatient: - service_item = frappe.db.get_single_value('Healthcare Settings', 'inpatient_visit_charge_item') - else: - service_item = frappe.db.get_single_value('Healthcare Settings', 'op_consulting_charge_item') - - return service_item - - -def get_practitioner_charge(practitioner, is_inpatient): - if is_inpatient: - practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, 'inpatient_visit_charge') - else: - practitioner_charge = frappe.db.get_value('Healthcare Practitioner', practitioner, 'op_consulting_charge') - if practitioner_charge: - return practitioner_charge - return False - - -def manage_invoice_submit_cancel(doc, method): - if doc.items: - for item in doc.items: - if item.get('reference_dt') and item.get('reference_dn'): - if frappe.get_meta(item.reference_dt).has_field('invoiced'): - set_invoiced(item, method, doc.name) - - if method=='on_submit' and frappe.db.get_single_value('Healthcare Settings', 'create_lab_test_on_si_submit'): - create_multiple('Sales Invoice', doc.name) - - -def set_invoiced(item, method, ref_invoice=None): - invoiced = False - if method=='on_submit': - validate_invoiced_on_submit(item) - invoiced = True - - if item.reference_dt == 'Clinical Procedure': - service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') - if service_item == item.item_code: - frappe.db.set_value(item.reference_dt, item.reference_dn, 'consumption_invoiced', invoiced) - else: - frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) - else: - frappe.db.set_value(item.reference_dt, item.reference_dn, 'invoiced', invoiced) - - if item.reference_dt == 'Patient Appointment': - if frappe.db.get_value('Patient Appointment', item.reference_dn, 'procedure_template'): - dt_from_appointment = 'Clinical Procedure' - else: - dt_from_appointment = 'Patient Encounter' - manage_doc_for_appointment(dt_from_appointment, item.reference_dn, invoiced) - - elif item.reference_dt == 'Lab Prescription': - manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, 'Lab Test', 'lab_test_created') - - elif item.reference_dt == 'Procedure Prescription': - manage_prescriptions(invoiced, item.reference_dt, item.reference_dn, 'Clinical Procedure', 'procedure_created') - - -def validate_invoiced_on_submit(item): - if item.reference_dt == 'Clinical Procedure' and \ - frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item') == item.item_code: - is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'consumption_invoiced') - else: - is_invoiced = frappe.db.get_value(item.reference_dt, item.reference_dn, 'invoiced') - if is_invoiced: - frappe.throw(_('The item referenced by {0} - {1} is already invoiced').format( - item.reference_dt, item.reference_dn)) - - -def manage_prescriptions(invoiced, ref_dt, ref_dn, dt, created_check_field): - created = frappe.db.get_value(ref_dt, ref_dn, created_check_field) - if created: - # Fetch the doc created for the prescription - doc_created = frappe.db.get_value(dt, {'prescription': ref_dn}) - frappe.db.set_value(dt, doc_created, 'invoiced', invoiced) - - -def check_fee_validity(appointment): - if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'): - return - - validity = frappe.db.exists('Fee Validity', { - 'practitioner': appointment.practitioner, - 'patient': appointment.patient, - 'valid_till': ('>=', appointment.appointment_date) - }) - if not validity: - return - - validity = frappe.get_doc('Fee Validity', validity) - return validity - - -def manage_fee_validity(appointment): - fee_validity = check_fee_validity(appointment) - - if fee_validity: - if appointment.status == 'Cancelled' and fee_validity.visited > 0: - fee_validity.visited -= 1 - frappe.db.delete('Fee Validity Reference', {'appointment': appointment.name}) - elif fee_validity.status == 'Completed': - return - else: - fee_validity.visited += 1 - fee_validity.append('ref_appointments', { - 'appointment': appointment.name - }) - fee_validity.save(ignore_permissions=True) - else: - fee_validity = create_fee_validity(appointment) - return fee_validity - - -def manage_doc_for_appointment(dt_from_appointment, appointment, invoiced): - dn_from_appointment = frappe.db.get_value( - dt_from_appointment, - filters={'appointment': appointment} - ) - if dn_from_appointment: - frappe.db.set_value(dt_from_appointment, dn_from_appointment, 'invoiced', invoiced) - - -@frappe.whitelist() -def get_drugs_to_invoice(encounter): - encounter = frappe.get_doc('Patient Encounter', encounter) - if encounter: - patient = frappe.get_doc('Patient', encounter.patient) - if patient: - if patient.customer: - items_to_invoice = [] - for drug_line in encounter.drug_prescription: - if drug_line.drug_code: - qty = 1 - if frappe.db.get_value('Item', drug_line.drug_code, 'stock_uom') == 'Nos': - qty = drug_line.get_quantity() - - description = '' - if drug_line.dosage and drug_line.period: - description = _('{0} for {1}').format(drug_line.dosage, drug_line.period) - - items_to_invoice.append({ - 'drug_code': drug_line.drug_code, - 'quantity': qty, - 'description': description - }) - return items_to_invoice - else: - validate_customer_created(patient) - - -@frappe.whitelist() -def get_children(doctype, parent=None, company=None, is_root=False): - parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_') - fields = [ - 'name as value', - 'is_group as expandable', - 'lft', - 'rgt' - ] - - filters = [["ifnull(`{0}`,'')".format(parent_fieldname), - '=', '' if is_root else parent]] - - if is_root: - fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else [] - filters.append(['company', '=', company]) - else: - fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy', - 'occupancy_status'] if doctype == 'Healthcare Service Unit' else [] - fields += [parent_fieldname + ' as parent'] - - service_units = frappe.get_list(doctype, fields=fields, filters=filters) - for each in service_units: - if each['expandable'] == 1: # group node - available_count = frappe.db.count('Healthcare Service Unit', filters={ - 'parent_healthcare_service_unit': each['value'], - 'inpatient_occupancy': 1}) - - if available_count > 0: - occupied_count = frappe.db.count('Healthcare Service Unit', { - 'parent_healthcare_service_unit': each['value'], - 'inpatient_occupancy': 1, - 'occupancy_status': 'Occupied'}) - # set occupancy status of group node - each['occupied_of_available'] = str( - occupied_count) + ' Occupied of ' + str(available_count) - - return service_units - - -@frappe.whitelist() -def get_patient_vitals(patient, from_date=None, to_date=None): - if not patient: return - - vitals = frappe.db.get_all('Vital Signs', filters={ - 'docstatus': 1, - 'patient': patient - }, order_by='signs_date, signs_time', fields=['*']) - - if len(vitals): - return vitals - return False - - -@frappe.whitelist() -def render_docs_as_html(docs): - # docs key value pair {doctype: docname} - docs_html = "
" - for doc in docs: - docs_html += render_doc_as_html(doc['doctype'], doc['docname'])['html'] + '
' - return {'html': docs_html} - - -@frappe.whitelist() -def render_doc_as_html(doctype, docname, exclude_fields = []): - """ - Render document as HTML - """ - - doc = frappe.get_doc(doctype, docname) - meta = frappe.get_meta(doctype) - doc_html = section_html = section_label = html = "" - sec_on = has_data = False - col_on = 0 - - for df in meta.fields: - # on section break append previous section and html to doc html - if df.fieldtype == "Section Break": - if has_data and col_on and sec_on: - doc_html += section_html + html + "
" - - elif has_data and not col_on and sec_on: - doc_html += """ -
-
-
- {0} -
-
-
-
- {1} {2} -
-
- """.format(section_label, section_html, html) - - # close divs for columns - while col_on: - doc_html += "
" - col_on -= 1 - - sec_on = True - has_data = False - col_on = 0 - section_html = html = "" - - if df.label: - section_label = df.label - continue - - # on column break append html to section html or doc html - if df.fieldtype == "Column Break": - if sec_on and not col_on and has_data: - section_html += """ -
-
-
- {0} -
-
-
-
- {1} -
- """.format(section_label, html) - elif col_on == 1 and has_data: - section_html += "
" + html + "
" - elif col_on > 1 and has_data: - doc_html += "
" + html + "
" - else: - doc_html += """ -
-
- {0} -
-
- """.format(html) - - html = "" - col_on += 1 - - if df.label: - html += "
" + df.label - continue - - # on table iterate through items and create table - # based on the in_list_view property - # append to section html or doc html - if df.fieldtype == "Table": - items = doc.get(df.fieldname) - if not items: - continue - child_meta = frappe.get_meta(df.options) - - if not has_data: - has_data = True - table_head = table_row = "" - create_head = True - - for item in items: - table_row += "" - for cdf in child_meta.fields: - if cdf.in_list_view: - if create_head: - table_head += "" + cdf.label + "" - if item.get(cdf.fieldname): - table_row += "" + cstr(item.get(cdf.fieldname)) + "" - else: - table_row += "" - - create_head = False - table_row += "" - - if sec_on: - section_html += """ - - {0} {1} -
- """.format(table_head, table_row) - else: - html += """ - - {0} {1} -
- """.format(table_head, table_row) - continue - - # on any other field type add label and value to html - if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields: - formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc) - html += "
{0} : {1}".format(df.label or df.fieldname, formatted_value) - - if not has_data : has_data = True - - if sec_on and col_on and has_data: - doc_html += section_html + html + "
" - elif sec_on and not col_on and has_data: - doc_html += """ -
-
- {0} {1} -
-
- """.format(section_html, html) - - return {"html": doc_html} - - -def update_address_links(address, method): - ''' - Hook validate Address - If Patient is linked in Address, also link the associated Customer - ''' - if 'Healthcare' not in frappe.get_active_domains(): - return - - patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links)) - - for link in patient_links: - customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer') - if customer and not address.has_link('Customer', customer): - address.append('links', dict(link_doctype = 'Customer', link_name = customer)) - - -def update_patient_email_and_phone_numbers(contact, method): - ''' - Hook validate Contact - Update linked Patients' primary mobile and phone numbers - ''' - if 'Healthcare' not in frappe.get_active_domains(): - return - - if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone): - patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links)) - - for link in patient_links: - contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1) - if contact.email_id and contact.email_id != contact_details.get('email'): - frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id) - if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'): - frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no) - if contact.phone and contact.phone != contact_details.get('phone'): - frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone) diff --git a/erpnext/healthcare/web_form/__init__.py b/erpnext/healthcare/web_form/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/web_form/lab_test/__init__.py b/erpnext/healthcare/web_form/lab_test/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.js b/erpnext/healthcare/web_form/lab_test/lab_test.js deleted file mode 100644 index efcd8abc89..0000000000 --- a/erpnext/healthcare/web_form/lab_test/lab_test.js +++ /dev/null @@ -1,34 +0,0 @@ -frappe.ready(function() { - // bind events here - var normal_test_items = $('div[data-fieldname = "normal_test_items"]'); - var normal_test_items_add_btn = $('button[data-fieldname = "normal_test_items"]'); - var special_test_items = $('div[data-fieldname = "special_test_items"]'); - var special_test_items_add_btn = $('button[data-fieldname = "special_test_items"]'); - var sensitivity_test_items = $('div[data-fieldname = "sensitivity_test_items"]'); - var sensitivity_test_items_add_btn = $('button[data-fieldname = "sensitivity_test_items"]'); - var sensitivity_toggle = $('input[name = "sensitivity_toggle"]'); - var special_toggle = $('input[name = "special_toggle"]'); - var normal_toggle = $('input[name = "normal_toggle"]'); - if(normal_toggle.val() == 1){ - // normal_test_items[0].style.display = "none"; - // normal_test_items[0].setAttribute("hidden", true); - // normal_test_items_add_btn[0].style.visibility = "hidden"; - special_test_items[0].style.display = "none"; - special_test_items_add_btn[0].style.display = "none"; - sensitivity_test_items[0].style.display = "none"; - sensitivity_test_items_add_btn[0].style.display = "none"; - normal_test_items_add_btn[0].style.display = "none"; - }else if(sensitivity_toggle.val() == 1){ - special_test_items[0].style.display = "none"; - special_test_items_add_btn[0].style.display = "none"; - normal_test_items[0].style.display = "none"; - normal_test_items_add_btn[0].style.display = "none"; - sensitivity_test_items_add_btn[0].style.display = "none"; - }else if(special_toggle.val() == 1){ - normal_test_items[0].style.display = "none"; - normal_test_items_add_btn[0].style.display = "none"; - sensitivity_test_items[0].style.display = "none"; - sensitivity_test_items_add_btn[0].style.display = "none"; - special_test_items_add_btn[0].style.display = "none"; - } -}); diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.json b/erpnext/healthcare/web_form/lab_test/lab_test.json deleted file mode 100644 index 35099174e8..0000000000 --- a/erpnext/healthcare/web_form/lab_test/lab_test.json +++ /dev/null @@ -1,460 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 1, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-06 16:12:33.052258", - "currency": "INR", - "doc_type": "Lab Test", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Lab Test", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2020-06-22 12:59:49.126398", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "lab-test", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "print_format": "Lab Test Print", - "published": 1, - "route": "lab-test", - "route_to_success_link": 0, - "show_attachments": 0, - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/lab-test", - "title": "Lab Test", - "web_form_fields": [ - { - "allow_read_on_all_link_options": 0, - "fieldname": "lab_test_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Test Name", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "column_break_26", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "label": "Company", - "max_length": 0, - "max_value": 0, - "options": "Company", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "label": "Status", - "max_length": 0, - "max_value": 0, - "options": "Draft\nCompleted\nApproved\nRejected\nCancelled", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "submitted_date", - "fieldtype": "Datetime", - "hidden": 0, - "label": "Submitted Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_first", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "label": "Patient", - "max_length": 0, - "max_value": 0, - "options": "Patient", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Patient Name", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient_age", - "fieldtype": "Data", - "hidden": 0, - "label": "Age", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "patient_sex", - "fieldtype": "Link", - "hidden": 0, - "label": "Gender", - "max_length": 0, - "max_value": 0, - "options": "Gender", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "inpatient_record", - "fieldtype": "Link", - "hidden": 0, - "label": "Inpatient Record", - "max_length": 0, - "max_value": 0, - "options": "Inpatient Record", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "report_preference", - "fieldtype": "Data", - "hidden": 0, - "label": "Report Preference", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "email", - "fieldtype": "Data", - "hidden": 1, - "label": "Email", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 1, - "label": "Mobile", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "c_b", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Requesting Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "practitioner_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Requesting Practitioner", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "requesting_department", - "fieldtype": "Link", - "hidden": 0, - "label": "Requesting Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "label": "Employee (Lab Technician)", - "max_length": 0, - "max_value": 0, - "options": "Employee", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "employee_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Lab Technician Name", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "employee_designation", - "fieldtype": "Data", - "hidden": 0, - "label": "Lab Technician Designation", - "max_length": 0, - "max_value": 0, - "read_only": 1, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_normal", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "lab_test_html", - "fieldtype": "HTML", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "normal_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Normal Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_descriptive", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "descriptive_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Descriptive Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "depends_on": "special_toggle", - "fieldname": "organisms_section", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "organisms", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Organism Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_sensitivity", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sensitivity_test_items", - "fieldtype": "Table", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "options": "Sensitivity Test Result", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_comments", - "fieldtype": "Section Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "lab_test_comment", - "fieldtype": "Text", - "hidden": 0, - "label": "Comments", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sb_customresult", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Custom Result", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "custom_result", - "fieldtype": "Text Editor", - "hidden": 0, - "label": "Custom Result", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/lab_test/lab_test.py b/erpnext/healthcare/web_form/lab_test/lab_test.py deleted file mode 100644 index ec08985b19..0000000000 --- a/erpnext/healthcare/web_form/lab_test/lab_test.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def get_context(context): - context.read_only = 1 - -def get_list_context(context): - context.row_template = "erpnext/templates/includes/healthcare/lab_test_row_template.html" - context.get_list = get_lab_test_list - -def get_lab_test_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by='modified desc'): - patient = get_patient() - lab_tests = frappe.db.sql("""select * from `tabLab Test` - where patient = %s order by result_date""", patient, as_dict = True) - return lab_tests - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.patient == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/web_form/patient_appointments/__init__.py b/erpnext/healthcare/web_form/patient_appointments/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.js b/erpnext/healthcare/web_form/patient_appointments/patient_appointments.js deleted file mode 100644 index f09e540919..0000000000 --- a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.json b/erpnext/healthcare/web_form/patient_appointments/patient_appointments.json deleted file mode 100644 index e9cf7a8c97..0000000000 --- a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-07 15:30:44.984832", - "currency": "INR", - "doc_type": "Patient Appointment", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Patient Appointments", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-07-16 13:11:08.626316", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "patient-appointments", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "patient-appointments", - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/patient-appointments", - "title": "Patient Appointments", - "web_form_fields": [ - { - "fieldname": "patient", - "fieldtype": "Link", - "hidden": 0, - "label": "Patient", - "max_length": 0, - "max_value": 0, - "options": "Patient", - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Healthcare Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "appointment_date", - "fieldtype": "Date", - "hidden": 0, - "label": "Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "appointment_time", - "fieldtype": "Data", - "hidden": 0, - "label": "Time", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, - { - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 0, - "reqd": 0 - }, - { - "fieldname": "appointment_type", - "fieldtype": "Link", - "hidden": 0, - "label": "Type", - "max_length": 0, - "max_value": 0, - "options": "Appointment Type", - "read_only": 0, - "reqd": 0 - }, - { - "default": "Scheduled", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "label": "Status", - "max_length": 0, - "max_value": 0, - "options": "\nScheduled\nOpen\nClosed\nPending\nCancelled", - "read_only": 1, - "reqd": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.py b/erpnext/healthcare/web_form/patient_appointments/patient_appointments.py deleted file mode 100644 index 80c12fd214..0000000000 --- a/erpnext/healthcare/web_form/patient_appointments/patient_appointments.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def get_context(context): - context.read_only = 1 - -def get_list_context(context): - context.row_template = "erpnext/templates/includes/healthcare/appointment_row_template.html" - context.get_list = get_appointment_list - -def get_appointment_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by='modified desc'): - patient = get_patient() - lab_tests = frappe.db.sql("""select * from `tabPatient Appointment` - where patient = %s and (status = 'Open' or status = 'Scheduled') order by appointment_date""", patient, as_dict = True) - return lab_tests - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.patient == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/web_form/patient_registration/__init__.py b/erpnext/healthcare/web_form/patient_registration/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.js b/erpnext/healthcare/web_form/patient_registration/patient_registration.js deleted file mode 100644 index f09e540919..0000000000 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.json b/erpnext/healthcare/web_form/patient_registration/patient_registration.json deleted file mode 100644 index 9ed92de16f..0000000000 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.json +++ /dev/null @@ -1,397 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 0, - "allow_print": 0, - "amount": 0.0, - "amount_based_on_field": 0, - "button_label": "Register", - "creation": "2020-03-03 01:01:16.250607", - "currency": "INR", - "doc_type": "Patient", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "", - "is_standard": 1, - "login_required": 0, - "max_attachment_size": 0, - "modified": "2020-03-26 17:25:15.361918", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "patient-registration", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "patient-registration", - "route_to_success_link": 0, - "show_attachments": 0, - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_message": "Registration Successfully. Thank You!", - "success_url": "/patient-registration", - "title": "Patient Registration", - "web_form_fields": [ - { - "allow_read_on_all_link_options": 0, - "fieldname": "basic_info", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Patient Demographics", - "max_length": 0, - "max_value": 0, - "options": "fa fa-user", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "first_name", - "fieldtype": "Data", - "hidden": 0, - "label": "First Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "middle_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Middle Name (optional)", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "last_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Last Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "sex", - "fieldtype": "Link", - "hidden": 0, - "label": "Gender", - "max_length": 0, - "max_value": 0, - "options": "Gender", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "blood_group", - "fieldtype": "Select", - "hidden": 0, - "label": "Blood Group", - "max_length": 0, - "max_value": 0, - "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "label": "Date of birth", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "label": "Mobile", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "label": "Email", - "max_length": 0, - "max_value": 0, - "options": "Email", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "phone", - "fieldtype": "Data", - "hidden": 0, - "label": "Phone", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Personal Details", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "occupation", - "fieldtype": "Data", - "hidden": 0, - "label": "Occupation", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "marital_status", - "fieldtype": "Select", - "hidden": 0, - "label": "Marital Status", - "max_length": 0, - "max_value": 0, - "options": "\nSingle\nMarried\nDivorced\nWidow", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "allergy_medical_and_surgical_history", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Allergies, Medical and Surgical History", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "allergies", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Allergies", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "medication", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Medication", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "medical_history", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Medical History", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "surgical_history", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Surgical History", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "risk_factors", - "fieldtype": "Section Break", - "hidden": 0, - "label": "Risk Factors", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "tobacco_past_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you have a history of Tobacco Consumption", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "tobacco_current_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you consume Tobacco", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "alcohol_past_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you have a history of Alcohol Consumption", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "default": "0", - "fieldname": "alcohol_current_use", - "fieldtype": "Check", - "hidden": 0, - "label": "Check if you consume Alcohol", - "max_length": 0, - "max_value": 0, - "options": "", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "column_break_32", - "fieldtype": "Column Break", - "hidden": 0, - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "surrounding_factors", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Occupational Hazards and Environmental Factors", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "allow_read_on_all_link_options": 0, - "fieldname": "other_risk_factors", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Other Risk Factors", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/patient_registration/patient_registration.py b/erpnext/healthcare/web_form/patient_registration/patient_registration.py deleted file mode 100644 index f57de916dd..0000000000 --- a/erpnext/healthcare/web_form/patient_registration/patient_registration.py +++ /dev/null @@ -1,6 +0,0 @@ -from __future__ import unicode_literals - - -def get_context(context): - # do your magic here - pass diff --git a/erpnext/healthcare/web_form/personal_details/__init__.py b/erpnext/healthcare/web_form/personal_details/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/web_form/personal_details/personal_details.js b/erpnext/healthcare/web_form/personal_details/personal_details.js deleted file mode 100644 index f09e540919..0000000000 --- a/erpnext/healthcare/web_form/personal_details/personal_details.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/personal_details/personal_details.json b/erpnext/healthcare/web_form/personal_details/personal_details.json deleted file mode 100644 index aad987aeb9..0000000000 --- a/erpnext/healthcare/web_form/personal_details/personal_details.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 0, - "allow_print": 0, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2018-07-03 19:33:23.332661", - "currency": "INR", - "doc_type": "Patient", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-07-04 17:22:28.936442", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "personal-details", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "published": 1, - "route": "personal-details", - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/personal-details", - "title": "Personal Details", - "web_form_fields": [ - { - "fieldname": "patient_name", - "fieldtype": "Data", - "hidden": 0, - "label": "Full Name", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "sex", - "fieldtype": "Select", - "hidden": 0, - "label": "Gender", - "max_length": 0, - "max_value": 0, - "options": "\nMale\nFemale\nOther", - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "dob", - "fieldtype": "Date", - "hidden": 0, - "label": "Date of birth", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1 - }, - { - "fieldname": "mobile", - "fieldtype": "Data", - "hidden": 0, - "label": "Mobile", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0 - }, - { - "fieldname": "email", - "fieldtype": "Data", - "hidden": 0, - "label": "Email", - "max_length": 0, - "max_value": 0, - "options": "Email", - "read_only": 1, - "reqd": 0 - } - ] -} diff --git a/erpnext/healthcare/web_form/personal_details/personal_details.py b/erpnext/healthcare/web_form/personal_details/personal_details.py deleted file mode 100644 index fe46d7b22d..0000000000 --- a/erpnext/healthcare/web_form/personal_details/personal_details.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe import _ - -no_cache = 1 - -def get_context(context): - if frappe.session.user=='Guest': - frappe.throw(_("You need to be logged in to access this page"), frappe.PermissionError) - - context.show_sidebar=True - - if frappe.db.exists("Patient", {'email': frappe.session.user}): - patient = frappe.get_doc("Patient", {'email': frappe.session.user}) - context.doc = patient - frappe.form_dict.new = 0 - frappe.form_dict.name = patient.name - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.name == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/web_form/prescription/__init__.py b/erpnext/healthcare/web_form/prescription/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/healthcare/web_form/prescription/prescription.js b/erpnext/healthcare/web_form/prescription/prescription.js deleted file mode 100644 index f09e540919..0000000000 --- a/erpnext/healthcare/web_form/prescription/prescription.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.ready(function() { - // bind events here -}); diff --git a/erpnext/healthcare/web_form/prescription/prescription.json b/erpnext/healthcare/web_form/prescription/prescription.json deleted file mode 100644 index 8e19e325ed..0000000000 --- a/erpnext/healthcare/web_form/prescription/prescription.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "accept_payment": 0, - "allow_comments": 0, - "allow_delete": 0, - "allow_edit": 1, - "allow_incomplete": 0, - "allow_multiple": 1, - "allow_print": 1, - "amount": 0.0, - "amount_based_on_field": 0, - "creation": "2017-06-06 17:13:19.101374", - "currency": "INR", - "doc_type": "Patient Encounter", - "docstatus": 0, - "doctype": "Web Form", - "idx": 0, - "introduction_text": "Patient Prescriptions", - "is_standard": 1, - "login_required": 1, - "max_attachment_size": 0, - "modified": "2018-09-04 11:53:40.954517", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "prescription", - "owner": "Administrator", - "payment_button_label": "Buy Now", - "print_format": "Encounter Print", - "published": 1, - "route": "prescription", - "show_in_grid": 0, - "show_sidebar": 1, - "sidebar_items": [], - "success_url": "/prescription", - "title": "Prescription", - "web_form_fields": [ - { - "fieldname": "practitioner", - "fieldtype": "Link", - "hidden": 0, - "label": "Healthcare Practitioner", - "max_length": 0, - "max_value": 0, - "options": "Healthcare Practitioner", - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "fieldname": "visit_department", - "fieldtype": "Link", - "hidden": 0, - "label": "Department", - "max_length": 0, - "max_value": 0, - "options": "Medical Department", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "default": "Today", - "fieldname": "encounter_date", - "fieldtype": "Date", - "hidden": 0, - "label": "Encounter Date", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "default": "", - "fieldname": "encounter_time", - "fieldtype": "Data", - "hidden": 0, - "label": "Encounter Time", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 1, - "show_in_filter": 0 - }, - { - "fieldname": "drug_prescription", - "fieldtype": "Table", - "hidden": 0, - "label": "Drug Prescription", - "max_length": 0, - "max_value": 0, - "options": "Drug Prescription", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "fieldname": "lab_test_prescription", - "fieldtype": "Table", - "hidden": 0, - "label": "Investigations", - "max_length": 0, - "max_value": 0, - "options": "Lab Prescription", - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - }, - { - "fieldname": "encounter_comment", - "fieldtype": "Small Text", - "hidden": 0, - "label": "Review Details", - "max_length": 0, - "max_value": 0, - "read_only": 0, - "reqd": 0, - "show_in_filter": 0 - } - ] -} \ No newline at end of file diff --git a/erpnext/healthcare/web_form/prescription/prescription.py b/erpnext/healthcare/web_form/prescription/prescription.py deleted file mode 100644 index f6f273ad58..0000000000 --- a/erpnext/healthcare/web_form/prescription/prescription.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def get_context(context): - context.read_only = 1 - -def get_list_context(context): - context.row_template = "erpnext/templates/includes/healthcare/prescription_row_template.html" - context.get_list = get_encounter_list - -def get_encounter_list(doctype, txt, filters, limit_start, limit_page_length = 20, order_by='modified desc'): - patient = get_patient() - encounters = frappe.db.sql("""select * from `tabPatient Encounter` - where patient = %s order by creation desc""", patient, as_dict = True) - return encounters - -def get_patient(): - return frappe.get_value("Patient",{"email": frappe.session.user}, "name") - -def has_website_permission(doc, ptype, user, verbose=False): - if doc.patient == get_patient(): - return True - else: - return False diff --git a/erpnext/healthcare/workspace/healthcare/healthcare.json b/erpnext/healthcare/workspace/healthcare/healthcare.json deleted file mode 100644 index f69604c099..0000000000 --- a/erpnext/healthcare/workspace/healthcare/healthcare.json +++ /dev/null @@ -1,626 +0,0 @@ -{ - "category": "", - "charts": [ - { - "chart_name": "Patient Appointments", - "label": "Patient Appointments" - } - ], - "charts_label": "", - "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Healthcare\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Patient Appointments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient Appointment\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Service Unit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Practitioner\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient History\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Facility Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Inpatient\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Rehabilitation and Physiotherapy\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Records and History\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", - "creation": "2020-03-02 17:23:17.919682", - "developer_mode_only": 0, - "disable_user_customization": 0, - "docstatus": 0, - "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, - "for_user": "", - "hide_custom": 0, - "icon": "healthcare", - "idx": 0, - "is_default": 0, - "is_standard": 0, - "label": "Healthcare", - "links": [ - { - "hidden": 0, - "is_query_report": 0, - "label": "Masters", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient", - "link_count": 0, - "link_to": "Patient", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Practitioner", - "link_count": 0, - "link_to": "Healthcare Practitioner", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Practitioner Schedule", - "link_count": 0, - "link_to": "Practitioner Schedule", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Department", - "link_count": 0, - "link_to": "Medical Department", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Consultation Setup", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Appointment Type", - "link_to": "Appointment Type", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Clinical Procedure Template", - "link_to": "Clinical Procedure Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Prescription Dosage", - "link_to": "Prescription Dosage", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Prescription Duration", - "link_to": "Prescription Duration", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Antibiotic", - "link_to": "Antibiotic", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Complaint", - "link_to": "Complaint", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Diagnosis", - "link_to": "Diagnosis", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Consultation", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Appointment", - "link_to": "Patient Appointment", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Clinical Procedure", - "link_to": "Clinical Procedure", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Encounter", - "link_to": "Patient Encounter", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Vital Signs", - "link_to": "Vital Signs", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Fee Validity", - "link_to": "Fee Validity", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Facility Management", - "link_type": "DocType", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Service Unit Type", - "link_to": "Healthcare Service Unit Type", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Service Unit", - "link_to": "Healthcare Service Unit", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Medical Coding", - "link_type": "DocType", - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Code Standard", - "link_to": "Medical Code Standard", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Medical Code", - "link_to": "Medical Code", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Settings", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Healthcare Settings", - "link_count": 0, - "link_to": "Healthcare Settings", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Laboratory Setup", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test Template", - "link_count": 0, - "link_to": "Lab Test Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test Sample", - "link_count": 0, - "link_to": "Lab Test Sample", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test UOM", - "link_count": 0, - "link_to": "Lab Test UOM", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Sensitivity", - "link_count": 0, - "link_to": "Sensitivity", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Dosage Form", - "link_to": "Dosage Form", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Laboratory", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Lab Test", - "link_count": 0, - "link_to": "Lab Test", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Sample Collection", - "link_count": 0, - "link_to": "Sample Collection", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Inpatient", - "link_type": "DocType", - "onboard": 0, - "type": "Card Break" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Inpatient Medication Order", - "link_to": "Inpatient Medication Order", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Inpatient Record", - "link_to": "Inpatient Record", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Inpatient Medication Entry", - "link_to": "Inpatient Medication Entry", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Rehabilitation and Physiotherapy", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Exercise Type", - "link_count": 0, - "link_to": "Exercise Type", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Therapy Type", - "link_count": 0, - "link_to": "Therapy Type", - "link_type": "DocType", - "onboard": 1, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Therapy Plan", - "link_count": 0, - "link_to": "Therapy Plan", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Therapy Session", - "link_count": 0, - "link_to": "Therapy Session", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Assessment Template", - "link_count": 0, - "link_to": "Patient Assessment Template", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Assessment", - "link_count": 0, - "link_to": "Patient Assessment", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Records and History", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient History", - "link_count": 0, - "link_to": "patient_history", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Progress", - "link_count": 0, - "link_to": "patient-progress", - "link_type": "Page", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Patient Medical Record", - "link_count": 0, - "link_to": "Patient Medical Record", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 0, - "label": "Inpatient Record", - "link_count": 0, - "link_to": "Inpatient Record", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Reports", - "link_count": 0, - "onboard": 0, - "type": "Card Break" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 1, - "label": "Patient Appointment Analytics", - "link_count": 0, - "link_to": "Patient Appointment Analytics", - "link_type": "Report", - "onboard": 0, - "type": "Link" - }, - { - "dependencies": "", - "hidden": 0, - "is_query_report": 1, - "label": "Lab Test Report", - "link_count": 0, - "link_to": "Lab Test Report", - "link_type": "Report", - "onboard": 0, - "type": "Link" - } - ], - "modified": "2021-08-30 17:37:45.316999", - "modified_by": "Administrator", - "module": "Healthcare", - "name": "Healthcare", - "onboarding": "Healthcare", - "owner": "Administrator", - "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, - "public": 1, - "restrict_to_domain": "Healthcare", - "roles": [], - "sequence_id": 13, - "shortcuts": [ - { - "color": "Orange", - "format": "{} Open", - "label": "Patient Appointment", - "link_to": "Patient Appointment", - "stats_filter": "{\n \"status\": \"Open\",\n \"company\": [\"like\", '%' + frappe.defaults.get_global_default(\"company\") + '%']\n}", - "type": "DocType" - }, - { - "color": "Orange", - "format": "{} Active", - "label": "Patient", - "link_to": "Patient", - "stats_filter": "{\n \"status\": \"Active\"\n}", - "type": "DocType" - }, - { - "color": "Green", - "format": "{} Vacant", - "label": "Healthcare Service Unit", - "link_to": "Healthcare Service Unit", - "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0,\n \"company\": [\"like\", \"%\" + frappe.defaults.get_global_default(\"company\") + \"%\"]\n}", - "type": "DocType" - }, - { - "label": "Healthcare Practitioner", - "link_to": "Healthcare Practitioner", - "type": "DocType" - }, - { - "label": "Patient History", - "link_to": "patient_history", - "type": "Page" - }, - { - "label": "Dashboard", - "link_to": "Healthcare", - "type": "Dashboard" - } - ], - "title": "Healthcare" -} \ No newline at end of file From 5d4133ecacdcc3ac69c4208c12a81bac303ad394 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Thu, 2 Sep 2021 18:25:36 +0530 Subject: [PATCH 007/416] chore: Removed healthcare demo, patch files --- erpnext/demo/setup/healthcare.py | 171 ------------------ erpnext/domains/healthcare.py | 71 -------- .../add_healthcare_service_unit_tree_root.py | 22 --- .../redesign_healthcare_billing_work_flow.py | 69 ------- .../patches/v11_0/rename_health_insurance.py | 11 -- .../rename_healthcare_doctype_and_fields.py | 67 ------- .../patches/v11_0/rename_healthcare_fields.py | 54 ------ ...te_appointment_reminder_scheduler_entry.py | 8 - ...are_custom_fields_in_stock_entry_detail.py | 12 -- ..._history_settings_for_standard_doctypes.py | 19 -- 10 files changed, 504 deletions(-) delete mode 100644 erpnext/demo/setup/healthcare.py delete mode 100644 erpnext/domains/healthcare.py delete mode 100644 erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py delete mode 100644 erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py delete mode 100644 erpnext/patches/v11_0/rename_health_insurance.py delete mode 100644 erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py delete mode 100644 erpnext/patches/v11_0/rename_healthcare_fields.py delete mode 100644 erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py delete mode 100644 erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py delete mode 100644 erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py diff --git a/erpnext/demo/setup/healthcare.py b/erpnext/demo/setup/healthcare.py deleted file mode 100644 index 5d5707f647..0000000000 --- a/erpnext/demo/setup/healthcare.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -import datetime -import json - -import frappe -from frappe.utils import getdate -from frappe.utils.make_random import get_random - -from erpnext.demo.setup.setup_data import import_json -from erpnext.healthcare.doctype.lab_test.lab_test import create_test_from_template - - -def setup_data(): - frappe.flags.mute_emails = True - make_masters() - make_patient() - make_lab_test() - make_consulation() - make_appointment() - consulation_on_appointment() - lab_test_on_encounter() - frappe.db.commit() - frappe.clear_cache() - -def make_masters(): - import_json("Healthcare Practitioner") - import_drug() - frappe.db.commit() - -def make_patient(): - file_path = get_json_path("Patient") - with open(file_path, "r") as open_file: - patient_data = json.loads(open_file.read()) - count = 1 - - for d in enumerate(patient_data): - patient = frappe.new_doc("Patient") - patient.patient_name = d[1]['patient_name'].title() - patient.sex = d[1]['gender'] - patient.blood_group = "A Positive" - patient.date_of_birth = datetime.datetime(1990, 3, 25) - patient.email_id = d[1]['patient_name'] + "_" + patient.date_of_birth.strftime('%m/%d/%Y') + "@example.com" - if count <5: - patient.insert() - frappe.db.commit() - count+=1 - -def make_appointment(): - i = 1 - while i <= 4: - practitioner = get_random("Healthcare Practitioner") - department = frappe.get_value("Healthcare Practitioner", practitioner, "department") - patient = get_random("Patient") - patient_sex = frappe.get_value("Patient", patient, "sex") - appointment = frappe.new_doc("Patient Appointment") - startDate = datetime.datetime.now() - for x in random_date(startDate,0): - appointment_datetime = x - appointment.appointment_datetime = appointment_datetime - appointment.appointment_time = appointment_datetime - appointment.appointment_date = appointment_datetime - appointment.patient = patient - appointment.patient_sex = patient_sex - appointment.practitioner = practitioner - appointment.department = department - appointment.save(ignore_permissions = True) - i += 1 - -def make_consulation(): - for i in range(3): - practitioner = get_random("Healthcare Practitioner") - department = frappe.get_value("Healthcare Practitioner", practitioner, "department") - patient = get_random("Patient") - patient_sex = frappe.get_value("Patient", patient, "sex") - encounter = set_encounter(patient, patient_sex, practitioner, department, getdate(), i) - encounter.save(ignore_permissions=True) - -def consulation_on_appointment(): - for i in range(3): - appointment = get_random("Patient Appointment") - appointment = frappe.get_doc("Patient Appointment",appointment) - encounter = set_encounter(appointment.patient, appointment.patient_sex, appointment.practitioner, appointment.department, appointment.appointment_date, i) - encounter.appointment = appointment.name - encounter.save(ignore_permissions=True) - -def set_encounter(patient, patient_sex, practitioner, department, encounter_date, i): - encounter = frappe.new_doc("Patient Encounter") - encounter.patient = patient - encounter.patient_sex = patient_sex - encounter.practitioner = practitioner - encounter.visit_department = department - encounter.encounter_date = encounter_date - if i > 2 and patient_sex=='Female': - encounter.symptoms = "Having chest pains for the last week." - encounter.diagnosis = """This patient's description of dull, aching, - exertion related substernal chest pain is suggestive of ischemic - cardiac origin. Her findings of a FH of early ASCVD, hypertension, - and early surgical menopause are pertinent risk factors for development - of coronary artery disease. """ - else: - encounter = append_drug_rx(encounter) - encounter = append_test_rx(encounter) - return encounter - -def make_lab_test(): - practitioner = get_random("Healthcare Practitioner") - patient = get_random("Patient") - patient_sex = frappe.get_value("Patient", patient, "sex") - template = get_random("Lab Test Template") - set_lab_test(patient, patient_sex, practitioner, template) - -def lab_test_on_encounter(): - i = 1 - while i <= 2: - test_rx = get_random("Lab Prescription", filters={'test_created': 0}) - test_rx = frappe.get_doc("Lab Prescription", test_rx) - encounter = frappe.get_doc("Patient Encounter", test_rx.parent) - set_lab_test(encounter.patient, encounter.patient_sex, encounter.practitioner, test_rx.test_code, test_rx.name) - i += 1 - -def set_lab_test(patient, patient_sex, practitioner, template, rx=None): - lab_test = frappe.new_doc("Lab Test") - lab_test.practitioner = practitioner - lab_test.patient = patient - lab_test.patient_sex = patient_sex - lab_test.template = template - lab_test.prescription = rx - create_test_from_template(lab_test) - -def append_test_rx(encounter): - i = 1 - while i <= 2: - test_rx = encounter.append("test_prescription") - test_rx.test_code = get_random("Lab Test Template") - i += 1 - return encounter - -def append_drug_rx(encounter): - i = 1 - while i <= 3: - drug = get_random("Item", filters={"item_group":"Drug"}) - drug = frappe.get_doc("Item", drug) - drug_rx = encounter.append("drug_prescription") - drug_rx.drug_code = drug.item_code - drug_rx.drug_name = drug.item_name - drug_rx.dosage = get_random("Prescription Dosage") - drug_rx.period = get_random("Prescription Duration") - i += 1 - return encounter - -def random_date(start,l): - current = start - while l >= 0: - curr = current + datetime.timedelta(minutes=60) - yield curr - l-=1 - -def import_drug(): - frappe.flags.in_import = True - data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'drug_list.json')).read()) - for d in data: - doc = frappe.new_doc("Item") - doc.update(d) - doc.insert() - frappe.flags.in_import = False - -def get_json_path(doctype): - return frappe.get_app_path('erpnext', 'demo', 'data', frappe.scrub(doctype) + '.json') diff --git a/erpnext/domains/healthcare.py b/erpnext/domains/healthcare.py deleted file mode 100644 index bbeb2c66bc..0000000000 --- a/erpnext/domains/healthcare.py +++ /dev/null @@ -1,71 +0,0 @@ -from __future__ import unicode_literals - -data = { - 'desktop_icons': [ - 'Patient', - 'Patient Appointment', - 'Patient Encounter', - 'Lab Test', - 'Healthcare', - 'Vital Signs', - 'Clinical Procedure', - 'Inpatient Record', - 'Accounts', - 'Buying', - 'Stock', - 'HR', - 'ToDo' - ], - 'default_portal_role': 'Patient', - 'restricted_roles': [ - 'Healthcare Administrator', - 'LabTest Approver', - 'Laboratory User', - 'Nursing User', - 'Physician', - 'Patient' - ], - 'custom_fields': { - 'Sales Invoice': [ - { - 'fieldname': 'patient', 'label': 'Patient', 'fieldtype': 'Link', 'options': 'Patient', - 'insert_after': 'naming_series' - }, - { - 'fieldname': 'patient_name', 'label': 'Patient Name', 'fieldtype': 'Data', 'fetch_from': 'patient.patient_name', - 'insert_after': 'patient', 'read_only': True - }, - { - 'fieldname': 'ref_practitioner', 'label': 'Referring Practitioner', 'fieldtype': 'Link', 'options': 'Healthcare Practitioner', - 'insert_after': 'customer' - } - ], - 'Sales Invoice Item': [ - { - 'fieldname': 'reference_dt', 'label': 'Reference DocType', 'fieldtype': 'Link', 'options': 'DocType', - 'insert_after': 'edit_references' - }, - { - 'fieldname': 'reference_dn', 'label': 'Reference Name', 'fieldtype': 'Dynamic Link', 'options': 'reference_dt', - 'insert_after': 'reference_dt' - } - ], - 'Stock Entry': [ - { - 'fieldname': 'inpatient_medication_entry', 'label': 'Inpatient Medication Entry', 'fieldtype': 'Link', 'options': 'Inpatient Medication Entry', - 'insert_after': 'credit_note', 'read_only': True - } - ], - 'Stock Entry Detail': [ - { - 'fieldname': 'patient', 'label': 'Patient', 'fieldtype': 'Link', 'options': 'Patient', - 'insert_after': 'po_detail', 'read_only': True - }, - { - 'fieldname': 'inpatient_medication_entry_child', 'label': 'Inpatient Medication Entry Child', 'fieldtype': 'Data', - 'insert_after': 'patient', 'read_only': True - } - ] - }, - 'on_setup': 'erpnext.healthcare.setup.setup_healthcare' -} diff --git a/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py b/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py deleted file mode 100644 index 9bb91dc14c..0000000000 --- a/erpnext/patches/v11_0/add_healthcare_service_unit_tree_root.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe import _ - - -def execute(): - """ assign lft and rgt appropriately """ - if "Healthcare" not in frappe.get_active_domains(): - return - - frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit") - frappe.reload_doc("healthcare", "doctype", "healthcare_service_unit_type") - company = frappe.get_value("Company", {"domain": "Healthcare"}, "name") - - if company: - frappe.get_doc({ - 'doctype': 'Healthcare Service Unit', - 'healthcare_service_unit_name': _('All Healthcare Service Units'), - 'is_group': 1, - 'company': company - }).insert(ignore_permissions=True) diff --git a/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py b/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py deleted file mode 100644 index b1ed0f598c..0000000000 --- a/erpnext/patches/v11_0/redesign_healthcare_billing_work_flow.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from frappe.modules import get_doctype_module, scrub - -from erpnext.domains.healthcare import data - -sales_invoice_referenced_doc = { - "Patient Appointment": "sales_invoice", - "Patient Encounter": "invoice", - "Lab Test": "invoice", - "Lab Prescription": "invoice", - "Sample Collection": "invoice" -} - -def execute(): - frappe.reload_doc('accounts', 'doctype', 'loyalty_program') - frappe.reload_doc('accounts', 'doctype', 'sales_invoice_item') - - if "Healthcare" not in frappe.get_active_domains(): - return - - healthcare_custom_field_in_sales_invoice() - for si_ref_doc in sales_invoice_referenced_doc: - if frappe.db.exists('DocType', si_ref_doc): - frappe.reload_doc(get_doctype_module(si_ref_doc), 'doctype', scrub(si_ref_doc)) - - if frappe.db.has_column(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc]) \ - and frappe.db.has_column(si_ref_doc, 'invoiced'): - # Set Reference DocType and Reference Docname - doc_list = frappe.db.sql(""" - select name from `tab{0}` - where {1} is not null - """.format(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc])) - if doc_list: - frappe.reload_doc(get_doctype_module("Sales Invoice"), 'doctype', 'sales_invoice') - for doc_id in doc_list: - invoice_id = frappe.db.get_value(si_ref_doc, doc_id[0], sales_invoice_referenced_doc[si_ref_doc]) - if frappe.db.exists("Sales Invoice", invoice_id): - if si_ref_doc == "Lab Test": - template = frappe.db.get_value("Lab Test", doc_id[0], "template") - if template: - item = frappe.db.get_value("Lab Test Template", template, "item") - if item: - frappe.db.sql("""update `tabSales Invoice Item` set reference_dt = '{0}', - reference_dn = '{1}' where parent = '{2}' and item_code='{3}'""".format\ - (si_ref_doc, doc_id[0], invoice_id, item)) - else: - invoice = frappe.get_doc("Sales Invoice", invoice_id) - for item_line in invoice.items: - if not item_line.reference_dn: - item_line.db_set({"reference_dt":si_ref_doc, "reference_dn": doc_id[0]}) - break - # Documents mark invoiced for submitted sales invoice - frappe.db.sql("""update `tab{0}` doc, `tabSales Invoice` si - set doc.invoiced = 1 where si.docstatus = 1 and doc.{1} = si.name - """.format(si_ref_doc, sales_invoice_referenced_doc[si_ref_doc])) - -def healthcare_custom_field_in_sales_invoice(): - frappe.reload_doc('healthcare', 'doctype', 'patient') - frappe.reload_doc('healthcare', 'doctype', 'healthcare_practitioner') - if data['custom_fields']: - create_custom_fields(data['custom_fields']) - - frappe.db.sql(""" - delete from `tabCustom Field` - where fieldname = 'appointment' and options = 'Patient Appointment' - """) diff --git a/erpnext/patches/v11_0/rename_health_insurance.py b/erpnext/patches/v11_0/rename_health_insurance.py deleted file mode 100644 index a4f53b078e..0000000000 --- a/erpnext/patches/v11_0/rename_health_insurance.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (c) 2018, Frappe and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -import frappe - - -def execute(): - frappe.rename_doc('DocType', 'Health Insurance', 'Employee Health Insurance', force=True) - frappe.reload_doc('hr', 'doctype', 'employee_health_insurance') diff --git a/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py b/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py deleted file mode 100644 index 7a8c52f102..0000000000 --- a/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import get_doctype_module, scrub - -field_rename_map = { - "Patient Encounter": [ - ["consultation_time", "encounter_time"], - ["consultation_date", "encounter_date"], - ["consultation_comment", "encounter_comment"], - ["physician", "practitioner"] - ], - "Fee Validity": [ - ["physician", "practitioner"] - ], - "Lab Test": [ - ["physician", "practitioner"] - ], - "Patient Appointment": [ - ["physician", "practitioner"], - ["referring_physician", "referring_practitioner"] - ], - "Procedure Prescription": [ - ["physician", "practitioner"] - ] -} - -doc_rename_map = { - "Physician Schedule Time Slot": "Healthcare Schedule Time Slot", - "Physician Schedule": "Practitioner Schedule", - "Physician Service Unit Schedule": "Practitioner Service Unit Schedule", - "Consultation": "Patient Encounter", - "Physician": "Healthcare Practitioner" -} - -def execute(): - for dt in doc_rename_map: - if frappe.db.exists('DocType', dt): - frappe.rename_doc('DocType', dt, doc_rename_map[dt], force=True) - - for dn in field_rename_map: - if frappe.db.exists('DocType', dn): - frappe.reload_doc(get_doctype_module(dn), "doctype", scrub(dn)) - - for dt, field_list in field_rename_map.items(): - if frappe.db.exists('DocType', dt): - for field in field_list: - if frappe.db.has_column(dt, field[0]): - rename_field(dt, field[0], field[1]) - - if frappe.db.exists('DocType', 'Practitioner Service Unit Schedule'): - if frappe.db.has_column('Practitioner Service Unit Schedule', 'parentfield'): - frappe.db.sql(""" - update `tabPractitioner Service Unit Schedule` set parentfield = 'practitioner_schedules' - where parentfield = 'physician_schedules' and parenttype = 'Healthcare Practitioner' - """) - - if frappe.db.exists("DocType", "Healthcare Practitioner"): - frappe.reload_doc("healthcare", "doctype", "healthcare_practitioner") - frappe.reload_doc("healthcare", "doctype", "practitioner_service_unit_schedule") - if frappe.db.has_column('Healthcare Practitioner', 'physician_schedule'): - for doc in frappe.get_all('Healthcare Practitioner'): - _doc = frappe.get_doc('Healthcare Practitioner', doc.name) - if _doc.physician_schedule: - _doc.append('practitioner_schedules', {'schedule': _doc.physician_schedule}) - _doc.save() diff --git a/erpnext/patches/v11_0/rename_healthcare_fields.py b/erpnext/patches/v11_0/rename_healthcare_fields.py deleted file mode 100644 index 5c96367a73..0000000000 --- a/erpnext/patches/v11_0/rename_healthcare_fields.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import unicode_literals - -import frappe -from frappe.model.utils.rename_field import rename_field -from frappe.modules import get_doctype_module, scrub - -lab_test_name = ["test_name", "lab_test_name"] -lab_test_code = ["test_code", "lab_test_code"] -lab_test_comment = ["test_comment", "lab_test_comment"] -lab_test_created = ["test_created", "lab_test_created"] -lab_test_template = ["test_template", "lab_test_template"] -lab_test_rate = ["test_rate", "lab_test_rate"] -lab_test_description = ["test_description", "lab_test_description"] -lab_test_group = ["test_group", "lab_test_group"] -lab_test_template_type = ["test_template_type", "lab_test_template_type"] -lab_test_uom = ["test_uom", "lab_test_uom"] -lab_test_normal_range = ["test_normal_range", "lab_test_normal_range"] -lab_test_event = ["test_event", "lab_test_event"] -lab_test_particulars = ["test_particulars", "lab_test_particulars"] - -field_rename_map = { - "Lab Test Template": [lab_test_name, lab_test_code, lab_test_rate, lab_test_description, - lab_test_group, lab_test_template_type, lab_test_uom, lab_test_normal_range], - "Normal Test Items": [lab_test_name, lab_test_comment, lab_test_uom, lab_test_event], - "Lab Test": [lab_test_name, lab_test_comment, lab_test_group], - "Lab Prescription": [lab_test_name, lab_test_code, lab_test_comment, lab_test_created], - "Lab Test Groups": [lab_test_template, lab_test_rate, lab_test_description], - "Lab Test UOM": [lab_test_uom], - "Normal Test Template": [lab_test_uom, lab_test_event], - "Special Test Items": [lab_test_particulars] -} - - -def execute(): - for dt, field_list in field_rename_map.items(): - if frappe.db.exists('DocType', dt): - frappe.reload_doc(get_doctype_module(dt), "doctype", scrub(dt)) - for field in field_list: - if frappe.db.has_column(dt, field[0]): - rename_field(dt, field[0], field[1]) - - if frappe.db.exists('DocType', 'Lab Prescription'): - if frappe.db.has_column('Lab Prescription', 'parentfield'): - frappe.db.sql(""" - update `tabLab Prescription` set parentfield = 'lab_test_prescription' - where parentfield = 'test_prescription' - """) - - if frappe.db.exists('DocType', 'Lab Test Groups'): - if frappe.db.has_column('Lab Test Groups', 'parentfield'): - frappe.db.sql(""" - update `tabLab Test Groups` set parentfield = 'lab_test_groups' - where parentfield = 'test_groups' - """) diff --git a/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py b/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py deleted file mode 100644 index 024cb2b763..0000000000 --- a/erpnext/patches/v12_0/update_appointment_reminder_scheduler_entry.py +++ /dev/null @@ -1,8 +0,0 @@ -import frappe - - -def execute(): - job = frappe.db.exists('Scheduled Job Type', 'patient_appointment.send_appointment_reminder') - if job: - method = 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder' - frappe.db.set_value('Scheduled Job Type', job, 'method', method) diff --git a/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py b/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py deleted file mode 100644 index 543faeb74a..0000000000 --- a/erpnext/patches/v13_0/create_healthcare_custom_fields_in_stock_entry_detail.py +++ /dev/null @@ -1,12 +0,0 @@ -import frappe -from frappe.custom.doctype.custom_field.custom_field import create_custom_fields - -from erpnext.domains.healthcare import data - - -def execute(): - if 'Healthcare' not in frappe.get_active_domains(): - return - - if data['custom_fields']: - create_custom_fields(data['custom_fields']) diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py deleted file mode 100644 index 80622d4609..0000000000 --- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -import frappe - -from erpnext.healthcare.setup import setup_patient_history_settings - - -def execute(): - if "Healthcare" not in frappe.get_active_domains(): - return - - frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order") - frappe.reload_doc("healthcare", "doctype", "Therapy Session") - frappe.reload_doc("healthcare", "doctype", "Clinical Procedure") - frappe.reload_doc("healthcare", "doctype", "Patient History Settings") - frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type") - frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type") - - setup_patient_history_settings() From e913e277d9c7b8e6dad74935c867a8761ae5633d Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Thu, 2 Sep 2021 19:47:39 +0530 Subject: [PATCH 008/416] chore: Rename imports from erpnext to healthcare --- .../doctype/pricing_rule/test_pricing_rule.py | 2 +- .../doctype/sales_invoice/sales_invoice.js | 4 ++-- .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/controllers/queries.py | 2 +- erpnext/hooks.py | 24 +++++++++---------- .../non_profit/doctype/donation/donation.py | 2 +- .../doctype/membership/membership.py | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 2e2d425dab..ba06716faf 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -10,7 +10,7 @@ import frappe from frappe import MandatoryError from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from erpnext.healthcare.doctype.lab_test_template.lab_test_template import make_item_price +from healthcare.healthcare.doctype.lab_test_template.lab_test_template import make_item_price from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.get_item_details import get_item_details diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 2cb9acfa2a..ea94467808 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -1071,7 +1071,7 @@ var get_healthcare_services_to_invoice = function(frm) { var patient = dialog.fields_dict.patient.input.value; if(patient && patient!=selected_patient){ selected_patient = patient; - var method = "erpnext.healthcare.utils.get_healthcare_services_to_invoice"; + var method = "healthcare.healthcare.utils.get_healthcare_services_to_invoice"; var args = {patient: patient, company: frm.doc.company}; var columns = (["service", "reference_name", "reference_type"]); get_healthcare_items(frm, true, $results, $placeholder, method, args, columns); @@ -1239,7 +1239,7 @@ var get_drugs_to_invoice = function(frm) { var encounter = dialog.fields_dict.encounter.input.value; if(encounter && encounter!=selected_encounter){ selected_encounter = encounter; - var method = "erpnext.healthcare.utils.get_drugs_to_invoice"; + var method = "healthcare.healthcare.utils.get_drugs_to_invoice"; var args = {encounter: encounter}; var columns = (["drug_code", "quantity", "description"]); get_healthcare_items(frm, false, $results, $placeholder, method, args, columns); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ec249c2419..22aa4e5cbf 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -40,7 +40,7 @@ from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, ) from erpnext.controllers.selling_controller import SellingController -from erpnext.healthcare.utils import manage_invoice_submit_cancel +from healthcare.healthcare.utils import manage_invoice_submit_cancel from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.setup.doctype.company.company import update_company_current_month_sales from erpnext.stock.doctype.batch.batch import set_batch_nos diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index aafaf5b9e0..1582d8b383 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -697,7 +697,7 @@ def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, fil company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt))) if filters and filters.get('inpatient_record'): - from erpnext.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( + from healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( get_current_healthcare_service_unit, ) service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record')) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 53f40bc07c..bf45fed659 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -214,10 +214,10 @@ has_website_permission = { "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", "Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission", - "Lab Test": "erpnext.healthcare.web_form.lab_test.lab_test.has_website_permission", - "Patient Encounter": "erpnext.healthcare.web_form.prescription.prescription.has_website_permission", - "Patient Appointment": "erpnext.healthcare.web_form.patient_appointments.patient_appointments.has_website_permission", - "Patient": "erpnext.healthcare.web_form.personal_details.personal_details.has_website_permission" + "Lab Test": "healthcare.healthcare.web_form.lab_test.lab_test.has_website_permission", + "Patient Encounter": "healthcare.healthcare.web_form.prescription.prescription.has_website_permission", + "Patient Appointment": "healthcare.healthcare.web_form.patient_appointments.patient_appointments.has_website_permission", + "Patient": "healthcare.healthcare.web_form.personal_details.personal_details.has_website_permission" } dump_report_map = "erpnext.startup.report_data_map.data_map" @@ -226,15 +226,15 @@ before_tests = "erpnext.setup.utils.before_tests" standard_queries = { "Customer": "erpnext.selling.doctype.customer.customer.get_customer_list", - "Healthcare Practitioner": "erpnext.healthcare.doctype.healthcare_practitioner.healthcare_practitioner.get_practitioner_list" + "Healthcare Practitioner": "healthcare.healthcare.doctype.healthcare_practitioner.healthcare_practitioner.get_practitioner_list" } doc_events = { "*": { "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", - "on_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", - "on_update_after_submit": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", - "on_cancel": "erpnext.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" + "on_submit": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", + "on_update_after_submit": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", + "on_cancel": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" }, "Stock Entry": { "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", @@ -295,7 +295,7 @@ doc_events = { 'erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category', - 'erpnext.healthcare.utils.update_address_links' + 'healthcare.healthcare.utils.update_address_links' ], }, 'Supplier': { @@ -307,7 +307,7 @@ doc_events = { "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", - "validate": ["erpnext.crm.utils.update_lead_phone_numbers", "erpnext.healthcare.utils.update_patient_email_and_phone_numbers"] + "validate": ["erpnext.crm.utils.update_lead_phone_numbers", "healthcare.healthcare.utils.update_patient_email_and_phone_numbers"] }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" @@ -338,7 +338,7 @@ scheduler_events = { }, "all": [ "erpnext.projects.doctype.project.project.project_status_update_reminder", - "erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", + "healthcare.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts" ], "hourly": [ @@ -379,7 +379,7 @@ scheduler_events = { "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status", - "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", + "healthcare.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.non_profit.doctype.membership.membership.set_expired_status" diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index e4e2b4e7d6..3a6c5ee74b 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -171,7 +171,7 @@ def create_donor(payment): def get_company_for_donations(): company = frappe.db.get_single_value('Non Profit Settings', 'donation_company') if not company: - from erpnext.healthcare.setup import get_company + from healthcare.healthcare.setup import get_company company = get_company() return company diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index b65dc8e1e3..173e292220 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -355,7 +355,7 @@ def process_request_data(data): def get_company_for_memberships(): company = frappe.db.get_single_value("Non Profit Settings", "company") if not company: - from erpnext.healthcare.setup import get_company + from healthcare.healthcare.setup import get_company company = get_company() return company From 66e92d423e77adfa8ca6b0d0b86669ea2839e28b Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Thu, 2 Sep 2021 19:57:58 +0530 Subject: [PATCH 009/416] chore: Added healthcare deprecation warning patch --- .../patches/v13_0/healthcare_deprecation_warning.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 erpnext/patches/v13_0/healthcare_deprecation_warning.py diff --git a/erpnext/patches/v13_0/healthcare_deprecation_warning.py b/erpnext/patches/v13_0/healthcare_deprecation_warning.py new file mode 100644 index 0000000000..c6fba59371 --- /dev/null +++ b/erpnext/patches/v13_0/healthcare_deprecation_warning.py @@ -0,0 +1,10 @@ +import click + + +def execute(): + + click.secho( + "Healthcare Module is moved to a separate app and will be removed from ERPNext in version-14.\n" + "Please install the app to continue using the module: https://github.com/frappe/healthcare", + fg="yellow", + ) From 092d41ecda917c68db8329c824ca68ced82db195 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 3 Sep 2021 20:04:18 +0530 Subject: [PATCH 010/416] fix: Debug CI --- .../accounts/doctype/purchase_invoice/test_purchase_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 51f58b7402..a7298d5e30 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1230,6 +1230,7 @@ def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year fiscal_year = get_fiscal_year(date=date, company=company) + print(fiscal_year[0], fiscal_year[1], fiscal_year[2], "$#$#$#") if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), From 51d9572fe72902593e363a19bc52967d65f56b88 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 5 Sep 2021 17:56:12 +0530 Subject: [PATCH 011/416] fix: Hardcode fiscal year and posting date --- .../doctype/fiscal_year/fiscal_year_dashboard.py | 2 +- .../doctype/purchase_invoice/test_purchase_invoice.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py index 58480df119..92e8a426cf 100644 --- a/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py +++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year_dashboard.py @@ -13,7 +13,7 @@ def get_data(): }, { 'label': _('References'), - 'items': ['Period Closing Voucher', 'Tax Withholding Category'] + 'items': ['Period Closing Voucher'] }, { 'label': _('Target Details'), diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index a7298d5e30..2d6ab7b293 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1151,10 +1151,11 @@ class TestPurchaseInvoice(unittest.TestCase): tax_withholding_category = 'TDS - 194 - Dividends - Individual') # Update tax withholding category with current fiscal year and rate details - update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate()) + update_tax_witholding_category('_Test Company', 'TDS Payable - _TC') # Create Purchase Order with TDS applied - po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item') + po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item', + posting_date='2021-09-15') po.apply_tds = 1 po.tax_withholding_category = 'TDS - 194 - Dividends - Individual' po.save() @@ -1226,11 +1227,10 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): doc.assertEqual(expected_gle[i][2], gle.credit) doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date) -def update_tax_witholding_category(company, account, date): +def update_tax_witholding_category(company, account): from erpnext.accounts.utils import get_fiscal_year - fiscal_year = get_fiscal_year(date=date, company=company) - print(fiscal_year[0], fiscal_year[1], fiscal_year[2], "$#$#$#") + fiscal_year = get_fiscal_year(fiscal_year='_Test Fiscal Year 2021') if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), From aa7ee3ca03fef661184b46667ba7aedc7286c132 Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Mon, 6 Sep 2021 11:40:44 +0200 Subject: [PATCH 012/416] fix: link in dashboard missing on SI from DN --- .../accounts/doctype/sales_invoice/sales_invoice_dashboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index 64b35b2987..2956cdc097 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -7,7 +7,6 @@ def get_data(): return { 'fieldname': 'sales_invoice', 'non_standard_fieldnames': { - 'Delivery Note': 'against_sales_invoice', 'Journal Entry': 'reference_name', 'Payment Entry': 'reference_name', 'Payment Request': 'reference_name', @@ -15,7 +14,8 @@ def get_data(): 'Auto Repeat': 'reference_document', }, 'internal_links': { - 'Sales Order': ['items', 'sales_order'] + 'Sales Order': ['items', 'sales_order'], + 'Delivery Note': ['items', 'delivery_note'], }, 'transactions': [ { From c8565c7090d153cba83d4c08423dafa941582e10 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Thu, 2 Sep 2021 19:57:43 +0530 Subject: [PATCH 013/416] chore: Removed healthcare module code in other modules --- .../doctype/pricing_rule/test_pricing_rule.py | 11 ++++- .../doctype/sales_invoice/sales_invoice.py | 14 ------ erpnext/controllers/queries.py | 30 ------------- erpnext/demo/demo.py | 4 +- erpnext/demo/domains.py | 3 -- erpnext/demo/setup/setup_data.py | 4 -- erpnext/hooks.py | 45 +------------------ erpnext/modules.txt | 3 +- .../non_profit/doctype/donation/donation.py | 3 -- .../doctype/membership/membership.py | 3 -- erpnext/patches.txt | 12 ----- .../patches/v11_0/refactor_naming_series.py | 4 -- 12 files changed, 12 insertions(+), 124 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index ba06716faf..23ce4e4620 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -7,10 +7,8 @@ from __future__ import unicode_literals import unittest import frappe -from frappe import MandatoryError from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice -from healthcare.healthcare.doctype.lab_test_template.lab_test_template import make_item_price from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.get_item_details import get_item_details @@ -623,3 +621,12 @@ def delete_existing_pricing_rules(): "Pricing Rule Item Group", "Pricing Rule Brand"]: frappe.db.sql("delete from `tab{0}`".format(doctype)) + + +def make_item_price(item, price_list_name, item_price): + 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) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 22aa4e5cbf..af72d30a17 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -40,7 +40,6 @@ from erpnext.assets.doctype.asset.depreciation import ( post_depreciation_entries, ) from erpnext.controllers.selling_controller import SellingController -from healthcare.healthcare.utils import manage_invoice_submit_cancel from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data from erpnext.setup.doctype.company.company import update_company_current_month_sales from erpnext.stock.doctype.batch.batch import set_batch_nos @@ -263,13 +262,6 @@ class SalesInvoice(SellingController): if self.redeem_loyalty_points and not self.is_consolidated and self.loyalty_points: self.apply_loyalty_points() - # Healthcare Service Invoice. - domain_settings = frappe.get_doc('Domain Settings') - active_domains = [d.domain for d in domain_settings.active_domains] - - if "Healthcare" in active_domains: - manage_invoice_submit_cancel(self, "on_submit") - self.process_common_party_accounting() def validate_pos_return(self): @@ -352,12 +344,6 @@ class SalesInvoice(SellingController): unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) - # Healthcare Service Invoice. - domain_settings = frappe.get_doc('Domain Settings') - active_domains = [d.domain for d in domain_settings.active_domains] - - if "Healthcare" in active_domains: - manage_invoice_submit_cancel(self, "on_cancel") self.unlink_sales_invoice_from_timesheets() self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 1582d8b383..6d3ad38d99 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -684,36 +684,6 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql(query, filters) -@frappe.whitelist() -@frappe.validate_and_sanitize_search_inputs -def get_healthcare_service_units(doctype, txt, searchfield, start, page_len, filters): - query = """ - select name - from `tabHealthcare Service Unit` - where - is_group = 0 - and company = {company} - and name like {txt}""".format( - company = frappe.db.escape(filters.get('company')), txt = frappe.db.escape('%{0}%'.format(txt))) - - if filters and filters.get('inpatient_record'): - from healthcare.healthcare.doctype.inpatient_medication_entry.inpatient_medication_entry import ( - get_current_healthcare_service_unit, - ) - service_unit = get_current_healthcare_service_unit(filters.get('inpatient_record')) - - # if the patient is admitted, then appointments should be allowed against the admission service unit, - # inspite of it being an Inpatient Occupancy service unit - if service_unit: - query += " and (allow_appointments = 1 or name = {service_unit})".format(service_unit = frappe.db.escape(service_unit)) - else: - query += " and allow_appointments = 1" - else: - query += " and allow_appointments = 1" - - return frappe.db.sql(query, filters) - - @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_tax_template(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/demo/demo.py b/erpnext/demo/demo.py index 5be24206ca..bd744b2546 100644 --- a/erpnext/demo/demo.py +++ b/erpnext/demo/demo.py @@ -6,7 +6,7 @@ import frappe import frappe.utils import erpnext -from erpnext.demo.setup import education, healthcare, manufacture, retail, setup_data +from erpnext.demo.setup import education, manufacture, retail, setup_data from erpnext.demo.user import accounts from erpnext.demo.user import education as edu from erpnext.demo.user import fixed_asset, hr, manufacturing, projects, purchase, sales, stock @@ -38,8 +38,6 @@ def make(domain='Manufacturing', days=100): retail.setup_data() elif domain== 'Education': education.setup_data() - elif domain== 'Healthcare': - healthcare.setup_data() site = frappe.local.site frappe.destroy() diff --git a/erpnext/demo/domains.py b/erpnext/demo/domains.py index b1db7b57b1..7f48b92570 100644 --- a/erpnext/demo/domains.py +++ b/erpnext/demo/domains.py @@ -16,9 +16,6 @@ data = { 'Education': { 'company_name': 'Whitmore College' }, - 'Healthcare': { - 'company_name': 'ABC Hospital Ltd.' - }, 'Agriculture': { 'company_name': 'Schrute Farms' }, diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index 42f83a3b4e..af53043385 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -196,10 +196,6 @@ def setup_user_roles(domain): 'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager', 'Support Team') - if domain == "Healthcare": - user.add_roles('Physician', 'Healthcare Administrator', 'Laboratory User', - 'Nursing User', 'Patient') - if domain == "Education": user.add_roles('Academics User') diff --git a/erpnext/hooks.py b/erpnext/hooks.py index bf45fed659..dff5cc62ea 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -69,7 +69,6 @@ domains = { 'Agriculture': 'erpnext.domains.agriculture', 'Distribution': 'erpnext.domains.distribution', 'Education': 'erpnext.domains.education', - 'Healthcare': 'erpnext.domains.healthcare', 'Hospitality': 'erpnext.domains.hospitality', 'Manufacturing': 'erpnext.domains.manufacturing', 'Non Profit': 'erpnext.domains.non_profit', @@ -164,7 +163,6 @@ website_route_rules = [ ] standard_portal_menu_items = [ - {"title": _("Personal Details"), "route": "/personal-details", "reference_doctype": "Patient", "role": "Patient"}, {"title": _("Projects"), "route": "/project", "reference_doctype": "Project"}, {"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"}, {"title": _("Supplier Quotation"), "route": "/supplier-quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"}, @@ -177,9 +175,6 @@ standard_portal_menu_items = [ {"title": _("Issues"), "route": "/issues", "reference_doctype": "Issue", "role":"Customer"}, {"title": _("Addresses"), "route": "/addresses", "reference_doctype": "Address"}, {"title": _("Timesheets"), "route": "/timesheets", "reference_doctype": "Timesheet", "role":"Customer"}, - {"title": _("Lab Test"), "route": "/lab-test", "reference_doctype": "Lab Test", "role":"Patient"}, - {"title": _("Prescription"), "route": "/prescription", "reference_doctype": "Patient Encounter", "role":"Patient"}, - {"title": _("Patient Appointment"), "route": "/patient-appointments", "reference_doctype": "Patient Appointment", "role":"Patient"}, {"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"}, {"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"}, {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"}, @@ -214,10 +209,6 @@ has_website_permission = { "Delivery Note": "erpnext.controllers.website_list_for_contact.has_website_permission", "Issue": "erpnext.support.doctype.issue.issue.has_website_permission", "Timesheet": "erpnext.controllers.website_list_for_contact.has_website_permission", - "Lab Test": "healthcare.healthcare.web_form.lab_test.lab_test.has_website_permission", - "Patient Encounter": "healthcare.healthcare.web_form.prescription.prescription.has_website_permission", - "Patient Appointment": "healthcare.healthcare.web_form.patient_appointments.patient_appointments.has_website_permission", - "Patient": "healthcare.healthcare.web_form.personal_details.personal_details.has_website_permission" } dump_report_map = "erpnext.startup.report_data_map.data_map" @@ -226,15 +217,11 @@ before_tests = "erpnext.setup.utils.before_tests" standard_queries = { "Customer": "erpnext.selling.doctype.customer.customer.get_customer_list", - "Healthcare Practitioner": "healthcare.healthcare.doctype.healthcare_practitioner.healthcare_practitioner.get_practitioner_list" } doc_events = { "*": { "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply", - "on_submit": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.create_medical_record", - "on_update_after_submit": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.update_medical_record", - "on_cancel": "healthcare.healthcare.doctype.patient_history_settings.patient_history_settings.delete_medical_record" }, "Stock Entry": { "on_submit": "erpnext.stock.doctype.material_request.material_request.update_completed_and_requested_qty", @@ -295,7 +282,6 @@ doc_events = { 'erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category', - 'healthcare.healthcare.utils.update_address_links' ], }, 'Supplier': { @@ -307,7 +293,7 @@ doc_events = { "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", "after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations", - "validate": ["erpnext.crm.utils.update_lead_phone_numbers", "healthcare.healthcare.utils.update_patient_email_and_phone_numbers"] + "validate": ["erpnext.crm.utils.update_lead_phone_numbers"] }, "Email Unsubscribe": { "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" @@ -325,7 +311,6 @@ doc_events = { # if payment entry not in auto cancel exempted doctypes it will cancel payment entry. auto_cancel_exempted_doctypes= [ "Payment Entry", - "Inpatient Medication Entry" ] after_migrate = ["erpnext.setup.install.update_select_perm_after_install"] @@ -338,7 +323,6 @@ scheduler_events = { }, "all": [ "erpnext.projects.doctype.project.project.project_status_update_reminder", - "healthcare.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts" ], "hourly": [ @@ -379,7 +363,6 @@ scheduler_events = { "erpnext.crm.doctype.email_campaign.email_campaign.send_email_to_leads_or_contacts", "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status", - "healthcare.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status", "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.non_profit.doctype.membership.membership.set_expired_status" @@ -532,32 +515,6 @@ global_search_doctypes = { {"doctype": "Maintenance Visit", "index": 46}, {"doctype": "Warranty Claim", "index": 47}, ], - "Healthcare": [ - {'doctype': 'Patient', 'index': 1}, - {'doctype': 'Medical Department', 'index': 2}, - {'doctype': 'Vital Signs', 'index': 3}, - {'doctype': 'Healthcare Practitioner', 'index': 4}, - {'doctype': 'Patient Appointment', 'index': 5}, - {'doctype': 'Healthcare Service Unit', 'index': 6}, - {'doctype': 'Patient Encounter', 'index': 7}, - {'doctype': 'Antibiotic', 'index': 8}, - {'doctype': 'Diagnosis', 'index': 9}, - {'doctype': 'Lab Test', 'index': 10}, - {'doctype': 'Clinical Procedure', 'index': 11}, - {'doctype': 'Inpatient Record', 'index': 12}, - {'doctype': 'Sample Collection', 'index': 13}, - {'doctype': 'Patient Medical Record', 'index': 14}, - {'doctype': 'Appointment Type', 'index': 15}, - {'doctype': 'Fee Validity', 'index': 16}, - {'doctype': 'Practitioner Schedule', 'index': 17}, - {'doctype': 'Dosage Form', 'index': 18}, - {'doctype': 'Lab Test Sample', 'index': 19}, - {'doctype': 'Prescription Duration', 'index': 20}, - {'doctype': 'Prescription Dosage', 'index': 21}, - {'doctype': 'Sensitivity', 'index': 22}, - {'doctype': 'Complaint', 'index': 23}, - {'doctype': 'Medical Code', 'index': 24}, - ], "Education": [ {'doctype': 'Article', 'index': 1}, {'doctype': 'Video', 'index': 2}, diff --git a/erpnext/modules.txt b/erpnext/modules.txt index 62f5dce846..a9f94ce133 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -15,7 +15,6 @@ Portal Maintenance Education Regional -Healthcare Restaurant Agriculture ERPNext Integrations @@ -26,4 +25,4 @@ Quality Management Communication Loan Management Payroll -Telephony \ No newline at end of file +Telephony diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 3a6c5ee74b..880a983df6 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -170,9 +170,6 @@ def create_donor(payment): def get_company_for_donations(): company = frappe.db.get_single_value('Non Profit Settings', 'donation_company') - if not company: - from healthcare.healthcare.setup import get_company - company = get_company() return company diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 173e292220..b64daa57fe 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -354,9 +354,6 @@ def process_request_data(data): def get_company_for_memberships(): company = frappe.db.get_single_value("Non Profit Settings", "company") - if not company: - from healthcare.healthcare.setup import get_company - company = get_company() return company diff --git a/erpnext/patches.txt b/erpnext/patches.txt index f7f3ddf7fd..a48daa50e1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -30,15 +30,12 @@ erpnext.patches.v11_0.add_default_email_template_for_leave erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018 erpnext.patches.v11_0.uom_conversion_data #30-06-2018 erpnext.patches.v11_0.update_account_type_in_party_type -erpnext.patches.v11_0.rename_healthcare_doctype_and_fields erpnext.patches.v11_0.rename_supplier_type_to_supplier_group erpnext.patches.v10_1.transfer_subscription_to_auto_repeat erpnext.patches.v11_0.update_brand_in_item_price erpnext.patches.v11_0.create_default_success_action -erpnext.patches.v11_0.add_healthcare_service_unit_tree_root erpnext.patches.v11_0.rename_field_max_days_allowed erpnext.patches.v11_0.create_salary_structure_assignments -erpnext.patches.v11_0.rename_health_insurance erpnext.patches.v11_0.rebuild_tree_for_company erpnext.patches.v11_0.create_department_records_for_each_company erpnext.patches.v11_0.make_location_from_warehouse @@ -65,9 +62,7 @@ execute:frappe.delete_doc("Page", "hub") erpnext.patches.v11_0.reset_publish_in_hub_for_all_items erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03 erpnext.patches.v11_0.make_job_card -erpnext.patches.v11_0.redesign_healthcare_billing_work_flow erpnext.patches.v10_0.delete_hub_documents # 12-08-2018 -erpnext.patches.v11_0.rename_healthcare_fields erpnext.patches.v11_0.add_default_dispatch_notification_template erpnext.patches.v11_0.add_market_segments erpnext.patches.v11_0.add_sales_stages @@ -169,7 +164,6 @@ erpnext.patches.v12_0.add_permission_in_lower_deduction erpnext.patches.v12_0.set_received_qty_in_material_request_as_per_stock_uom erpnext.patches.v12_0.rename_account_type_doctype erpnext.patches.v12_0.recalculate_requested_qty_in_bin -erpnext.patches.v12_0.update_healthcare_refactored_changes erpnext.patches.v12_0.set_total_batch_quantity erpnext.patches.v12_0.rename_mws_settings_fields erpnext.patches.v12_0.set_updated_purpose_in_pick_list @@ -178,7 +172,6 @@ erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse erpnext.patches.v12_0.update_end_date_and_status_in_email_campaign erpnext.patches.v13_0.move_tax_slabs_from_payroll_period_to_income_tax_slab #123 erpnext.patches.v12_0.fix_quotation_expired_status -erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry erpnext.patches.v12_0.rename_pos_closing_doctype erpnext.patches.v13_0.replace_pos_payment_mode_table #2020-12-29 erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22 @@ -196,7 +189,6 @@ execute:frappe.reload_doctype('Dashboard') execute:frappe.reload_doc('desk', 'doctype', 'number_card_link') execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts') erpnext.patches.v13_0.update_actual_start_and_end_date_in_wo -erpnext.patches.v13_0.set_company_field_in_healthcare_doctypes #2021-04-16 erpnext.patches.v12_0.update_bom_in_so_mr execute:frappe.delete_doc("Report", "Department Analytics") execute:frappe.rename_doc("Desk Page", "Loan Management", "Loan", force=True) @@ -222,7 +214,6 @@ erpnext.patches.v12_0.fix_percent_complete_for_projects erpnext.patches.v13_0.delete_report_requested_items_to_order erpnext.patches.v12_0.update_item_tax_template_company erpnext.patches.v13_0.move_branch_code_to_bank_account -erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24 erpnext.patches.v13_0.stock_entry_enhancements erpnext.patches.v12_0.update_state_code_for_daman_and_diu @@ -236,7 +227,6 @@ erpnext.patches.v13_0.set_youtube_video_id erpnext.patches.v13_0.set_app_name erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account -erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.update_reason_for_resignation_in_employee execute:frappe.delete_doc("Report", "Quoted Item Comparison") @@ -251,7 +241,6 @@ erpnext.patches.v13_0.create_uae_pos_invoice_fields erpnext.patches.v13_0.update_project_template_tasks erpnext.patches.v13_0.set_company_in_leave_ledger_entry erpnext.patches.v13_0.convert_qi_parameter_to_link_field -erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021 erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh @@ -263,7 +252,6 @@ erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') -erpnext.patches.v13_0.rename_discharge_date_in_ip_record erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status diff --git a/erpnext/patches/v11_0/refactor_naming_series.py b/erpnext/patches/v11_0/refactor_naming_series.py index 9f231edea7..a124ff835b 100644 --- a/erpnext/patches/v11_0/refactor_naming_series.py +++ b/erpnext/patches/v11_0/refactor_naming_series.py @@ -27,7 +27,6 @@ doctype_series_map = { 'Fee Schedule': 'EDU-FSH-.YYYY.-', 'Fee Structure': 'EDU-FST-.YYYY.-', 'Fees': 'EDU-FEE-.YYYY.-', - 'Inpatient Record': 'HLC-INP-.YYYY.-', 'Installation Note': 'MAT-INS-.YYYY.-', 'Instructor': 'EDU-INS-.YYYY.-', 'Issue': 'ISS-.YYYY.-', @@ -43,9 +42,6 @@ doctype_series_map = { 'Member': 'NPO-MEM-.YYYY.-', 'Opportunity': 'CRM-OPP-.YYYY.-', 'Packing Slip': 'MAT-PAC-.YYYY.-', - 'Patient': 'HLC-PAT-.YYYY.-', - 'Patient Encounter': 'HLC-ENC-.YYYY.-', - 'Patient Medical Record': 'HLC-PMR-.YYYY.-', 'Payment Entry': 'ACC-PAY-.YYYY.-', 'Payment Request': 'ACC-PRQ-.YYYY.-', 'Production Plan': 'MFG-PP-.YYYY.-', From cb0c2d1477795566ca8bf5b03ed355791511b654 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Thu, 26 Aug 2021 07:35:59 +0500 Subject: [PATCH 014/416] feat(Regional): KSA VAT Report --- .../workspace/accounting/accounting.json | 21 +++ .../ksa_vat_purchase_account/__init__.py | 0 .../ksa_vat_purchase_account.json | 49 +++++ .../ksa_vat_purchase_account.py | 8 + .../doctype/ksa_vat_sales_account/__init__.py | 0 .../ksa_vat_sales_account.js | 8 + .../ksa_vat_sales_account.json | 49 +++++ .../ksa_vat_sales_account.py | 8 + .../test_ksa_vat_sales_account.py | 8 + .../doctype/ksa_vat_setting/__init__.py | 0 .../ksa_vat_setting/ksa_vat_setting.js | 8 + .../ksa_vat_setting/ksa_vat_setting.json | 49 +++++ .../ksa_vat_setting/ksa_vat_setting.py | 8 + .../ksa_vat_setting/ksa_vat_setting_list.js | 5 + .../ksa_vat_setting/test_ksa_vat_setting.py | 8 + erpnext/regional/report/ksa_vat/__init__.py | 0 erpnext/regional/report/ksa_vat/ksa_vat.js | 60 ++++++ erpnext/regional/report/ksa_vat/ksa_vat.json | 32 ++++ erpnext/regional/report/ksa_vat/ksa_vat.py | 177 ++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++ .../regional/saudi_arabia/wizard/__init__.py | 0 .../saudi_arabia/wizard/data/__init__.py | 0 .../wizard/data/ksa_vat_settings.json | 47 +++++ .../wizard/operations/__init__.py | 0 .../operations/setup_ksa_vat_setting.py | 45 +++++ 25 files changed, 607 insertions(+) create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/__init__.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py create mode 100644 erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js create mode 100644 erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py create mode 100644 erpnext/regional/report/ksa_vat/__init__.py create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.js create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.json create mode 100644 erpnext/regional/report/ksa_vat/ksa_vat.py create mode 100644 erpnext/regional/saudi_arabia/wizard/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/__init__.py create mode 100644 erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 2b26ac5090..e7210f19ff 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -533,6 +533,17 @@ "only_for": "United Arab Emirates", "type": "Link" }, + { + "dependencies": "GL Entry", + "hidden": 0, + "is_query_report": 1, + "label": "KSA VAT Report", + "link_to": "KSA VAT", + "link_type": "Report", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, @@ -1153,6 +1164,16 @@ "onboard": 0, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 0, + "label": "KSA VAT Setting", + "link_to": "KSA VAT Setting", + "link_type": "DocType", + "onboard": 0, + "only_for": "Saudi Arabia", + "type": "Link" + }, { "hidden": 0, "is_query_report": 0, diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py b/erpnext/regional/doctype/ksa_vat_purchase_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json new file mode 100644 index 0000000000..89ba3e977a --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 09:17:09.862163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:38.205597", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Purchase Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py new file mode 100644 index 0000000000..1302e314c3 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATPurchaseAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py b/erpnext/regional/doctype/ksa_vat_sales_account/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js new file mode 100644 index 0000000000..72613f4064 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Sales Account', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json new file mode 100644 index 0000000000..df2747891d --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "creation": "2021-07-13 08:46:33.820968", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "item_tax_template", + "account" + ], + "fields": [ + { + "fieldname": "account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Account", + "options": "Account", + "reqd": 1 + }, + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "item_tax_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Tax Template", + "options": "Item Tax Template", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-08-04 06:42:00.081407", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Sales Account", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py new file mode 100644 index 0000000000..8e437f832b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSalesAccount(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py new file mode 100644 index 0000000000..d7f911343b --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSalesAccount(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/__init__.py b/erpnext/regional/doctype/ksa_vat_setting/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js new file mode 100644 index 0000000000..0238c7b306 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Havenir Solutions and contributors +// For license information, please see license.txt + +frappe.ui.form.on('KSA VAT Setting', { + onload: function(frm) { + frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); + } +}); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json new file mode 100644 index 0000000000..33619467ed --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.json @@ -0,0 +1,49 @@ +{ + "actions": [], + "autoname": "field:company", + "creation": "2021-07-13 08:49:01.100356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "ksa_vat_sales_accounts", + "ksa_vat_purchase_accounts" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "ksa_vat_sales_accounts", + "fieldtype": "Table", + "label": "KSA VAT Sales Accounts", + "options": "KSA VAT Sales Account", + "reqd": 1 + }, + { + "fieldname": "ksa_vat_purchase_accounts", + "fieldtype": "Table", + "label": "KSA VAT Purchase Accounts", + "options": "KSA VAT Purchase Account", + "reqd": 1 + } + ], + "links": [], + "modified": "2021-08-26 04:29:06.499378", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT Setting", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "company", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py new file mode 100644 index 0000000000..6db6f9cc59 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + +class KSAVATSetting(Document): + pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js new file mode 100644 index 0000000000..23d28b9e68 --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -0,0 +1,5 @@ +frappe.listview_settings['KSA VAT Setting'] = { + onload(list) { + frappe.breadcrumbs.add('Accounts'); + } +} \ No newline at end of file diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py new file mode 100644 index 0000000000..ae2defc61d --- /dev/null +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Havenir Solutions and Contributors +# See license.txt + +# import frappe +import unittest + +class TestKSAVATSetting(unittest.TestCase): + pass diff --git a/erpnext/regional/report/ksa_vat/__init__.py b/erpnext/regional/report/ksa_vat/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js new file mode 100644 index 0000000000..5dd9c92bf7 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -0,0 +1,60 @@ +// Copyright (c) 2016, Havenir Solutions and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["KSA VAT"] = { + onload() { + frappe.breadcrumbs.add('Accounts'); + }, + "filters": [ + { + "fieldname": "company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname": "from_date", + "label": __("From Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldname": "to_date", + "label": __("To Date"), + "fieldtype": "Date", + "reqd": 1, + "default": frappe.datetime.get_today() + } + ], + "formatter": function(value, row, column, data, default_formatter) { + if (data + && (data.title=='VAT on Sales' || data.title=='VAT on Purchases') + && data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else if (data.title=='Grand Total'){ + if (data.title==value) { + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + return value + }else{ + value = default_formatter(value, row, column, data); + value = $(`${value}`); + var $value = $(value).css("font-weight", "bold"); + value = $value.wrap("

").parent().html(); + console.log($value) + return value + } + }else{ + value = default_formatter(value, row, column, data); + return value; + } + }, +}; diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.json b/erpnext/regional/report/ksa_vat/ksa_vat.json new file mode 100644 index 0000000000..036e260310 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-13 08:54:38.000949", + "disable_prepared_report": 1, + "disabled": 1, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-08-26 04:14:37.202594", + "modified_by": "Administrator", + "module": "Regional", + "name": "KSA VAT", + "owner": "Administrator", + "prepared_report": 1, + "ref_doctype": "GL Entry", + "report_name": "KSA VAT", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py new file mode 100644 index 0000000000..6828007203 --- /dev/null +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -0,0 +1,177 @@ +# Copyright (c) 2013, Havenir Solutions and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import get_url_to_list +from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data, get_rounded_tax_amount +import json + +def execute(filters=None): + columns = columns = get_columns() + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "fieldname": "title", + "label": _("Title"), + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "amount", + "label": _("Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "adjustment_amount", + "label": _("Adjustment (SAR)"), + "fieldtype": "Currency", + "width": 150, + }, + { + "fieldname": "vat_amount", + "label": _("VAT Amount (SAR)"), + "fieldtype": "Currency", + "width": 150, + } + ] + +def get_data(filters): + data = [] + + # Validate if vat settings exist + company = filters.get('company') + if frappe.db.exists('KSA VAT Setting', company) is None: + url = get_url_to_list('KSA VAT Setting') + frappe.msgprint(f'Create KSA VAT Setting for this company') + return data + + ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) + + # Sales Heading + append_data(data, 'VAT on Sales', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Sales Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + # Blank Line + append_data(data, '', '', '', '') + + # Purchase Heading + append_data(data, 'VAT on Purchases', '', '', '') + + grand_total_taxable_amount = 0 + grand_total_taxable_adjustment_amount = 0 + grand_total_tax = 0 + + for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: + total_taxable_amount, total_taxable_adjustment_amount, \ + total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') + + # Adding results to data + append_data(data, vat_setting.title, total_taxable_amount, + total_taxable_adjustment_amount, total_tax) + + grand_total_taxable_amount += total_taxable_amount + grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount + grand_total_tax += total_tax + + # Purchase Grand Total + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax ) + + return data + +def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): + ''' + (KSA, {filters}, 'Sales Invoice') => 500, 153, 10 \n + calculates and returns \n + total_taxable_amount, total_taxable_adjustment_amount, total_tax''' + from_date = filters.get('from_date') + to_date = filters.get('to_date') + + # Initiate variables + total_taxable_amount = 0 + total_taxable_adjustment_amount = 0 + total_tax = 0 + # Fetch All Invoices + invoices = frappe.get_list(doctype, + filters ={ + 'docstatus': 1, + 'posting_date': ['between', [from_date, to_date]] + }, + fields =['name', 'is_return']) + + for invoice in invoices: + invoice_items = frappe.get_list(f'{doctype} Item', + filters ={ + 'docstatus': 1, + 'parent': invoice.name, + 'item_tax_template': vat_setting.item_tax_template + }, + fields =['item_code', 'net_amount']) + + + for item in invoice_items: + # Summing up total taxable amount + if invoice.is_return == 0: + total_taxable_amount += item.net_amount + + if invoice.is_return == 1: + total_taxable_adjustment_amount += item.net_amount + + # Summing up total tax + total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) + + return total_taxable_amount, total_taxable_adjustment_amount, total_tax + + + +def append_data(data, title, amount, adjustment_amount, vat_amount): + """Returns data with appended value.""" + data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + +def get_tax_amount(item_code, account_head, doctype, parent): + if doctype == 'Sales Invoice': + tax_doctype = 'Sales Taxes and Charges' + + elif doctype == 'Purchase Invoice': + tax_doctype = 'Purchase Taxes and Charges' + + item_wise_tax_detail = frappe.get_value(tax_doctype, { + 'docstatus': 1, + 'parent': parent, + 'account_head': account_head + }, 'item_wise_tax_detail') + + tax_amount = 0 + if item_wise_tax_detail and len(item_wise_tax_detail) > 0: + item_wise_tax_detail = json.loads(item_wise_tax_detail) + for key, value in item_wise_tax_detail.items(): + if key == item_code: + tax_amount = value[1] + break + + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 9b3677d2c6..053496729f 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -3,9 +3,26 @@ from __future__ import unicode_literals +import frappe +from frappe.permissions import add_permission, update_permission_property from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting def setup(company=None, patch=True): make_custom_fields() add_print_formats() + add_permissions() + create_ksa_vat_setting(company) + + +def add_permissions(): + """Add Permissions for KSA VAT Setting.""" + add_permission('KSA VAT Setting', 'All', 0) + for role in ('Accounts Manager', 'Accounts User', 'System Manager'): + add_permission('KSA VAT Setting', role, 0) + update_permission_property('KSA VAT Setting', role, 0, 'write', 1) + update_permission_property('KSA VAT Setting', role, 0, 'create', 1) + + """Enable KSA VAT Report""" + frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) diff --git a/erpnext/regional/saudi_arabia/wizard/__init__.py b/erpnext/regional/saudi_arabia/wizard/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/saudi_arabia/wizard/data/__init__.py b/erpnext/regional/saudi_arabia/wizard/data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json new file mode 100644 index 0000000000..709d65be04 --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/data/ksa_vat_settings.json @@ -0,0 +1,47 @@ +[ + { + "type": "Sales Account", + "accounts": [ + { + "title": "Standard rated Sales", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Zero rated domestic sales", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted sales", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + }, + { + "type": "Purchase Account", + "accounts": [ + { + "title": "Standard rated domestic purchases", + "item_tax_template": "KSA VAT 5%", + "account": "VAT 5%" + }, + { + "title": "Imports subject to VAT paid at customs", + "item_tax_template": "KSA Excise 50%", + "account": "Excise 50%" + }, + { + "title": "Zero rated purchases", + "item_tax_template": "KSA VAT Zero", + "account": "VAT Zero" + }, + { + "title": "Exempted purchases", + "item_tax_template": "KSA VAT Exempted", + "account": "VAT Zero" + } + ] + } +] \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/wizard/operations/__init__.py b/erpnext/regional/saudi_arabia/wizard/operations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py new file mode 100644 index 0000000000..4bccdc35bf --- /dev/null +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -0,0 +1,45 @@ +import frappe +import os +import json +from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + +def create_ksa_vat_setting(company): + """ + On creation of first company. Creates KSA VAT Setting""" + + company = frappe.get_doc('Company', company) + setup_taxes_and_charges(company.name, company.country) + + file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') + with open(file_path, 'r') as json_file: + account_data = json.load(json_file) + + + # Creating KSA VAT Setting + ksa_vat_setting = frappe.get_doc({ + 'doctype': 'KSA VAT Setting', + 'company': company.name + }) + + for data in account_data: + if data['type'] == 'Sales Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_sales_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + elif data['type'] == 'Purchase Account': + for row in data['accounts']: + item_tax_template = row['item_tax_template'] + account = row['account'] + ksa_vat_setting.append('ksa_vat_purchase_accounts', { + 'title': row['title'], + 'item_tax_template': f'{item_tax_template} - {company.abbr}', + 'account': f'{account} - {company.abbr}' + }) + + ksa_vat_setting.save() \ No newline at end of file From 87380d021d589a9717a26ee74db19e3902948c36 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Mon, 6 Sep 2021 23:36:55 +0500 Subject: [PATCH 015/416] feat(regional): QR Code generation for Saudi Arabia Sales Invoices --- erpnext/hooks.py | 6 ++- erpnext/regional/__init__.py | 72 ++++++++++++++++++++++++++ erpnext/regional/saudi_arabia/setup.py | 17 ++++-- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5b6e1eeafc..050ce2e11d 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,6 +262,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { + "after_insert": "erpnext.regional.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", @@ -271,7 +272,10 @@ doc_events = { "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.erpnext_integrations.taxjar_integration.delete_transaction" ], - "on_trash": "erpnext.regional.check_deletion_permission", + "on_trash": [ + "erpnext.regional.check_deletion_permission", + "erpnext.regional.delete_qr_code_file" + ], "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 45a689efa8..3591be07b8 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -7,6 +7,9 @@ import frappe from frappe import _ from erpnext import get_region +from pyqrcode import create as qr_create +import io +import os def check_deletion_permission(doc, method): @@ -31,3 +34,72 @@ def create_transaction_log(doc, method): "document_name": doc.name, "data": data }).insert(ignore_permissions=True) + + +def create_qr_code(doc, method): + """Create QR Code after inserting Sales Inv + """ + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, do nothing + if not hasattr(doc, 'qr_code'): + return + + # Don't create QR Code if it already exists + qr_code = doc.get("qr_code") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + fields = frappe.get_meta('Sales Invoice').fields + + for field in fields: + if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + # Creating public url to print format + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + + # System Language + language = frappe.get_system_settings('language') + + # creating qr code for the url + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + qr_image = io.BytesIO() + url = qr_create(url, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + # making file + filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue() + }) + + _file.save() + + # assigning to document + doc.db_set('qr_code', _file.file_url) + doc.notify_update() + + break + + else: + pass + +def delete_qr_code_file(doc, method): + """Delete QR Code on deleted sales invoice""" + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'qr_code'): + if doc.get('qr_code'): + file_doc = frappe.get_list('File', { + 'file_url': doc.qr_code + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 053496729f..f55900023d 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -2,18 +2,18 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import frappe from frappe.permissions import add_permission, update_permission_property -from erpnext.regional.united_arab_emirates.setup import make_custom_fields, add_print_formats +from erpnext.regional.united_arab_emirates.setup import make_custom_fields as uae_custom_fields, add_print_formats from erpnext.regional.saudi_arabia.wizard.operations.setup_ksa_vat_setting import create_ksa_vat_setting - +from frappe.custom.doctype.custom_field.custom_field import create_custom_field def setup(company=None, patch=True): - make_custom_fields() + uae_custom_fields() add_print_formats() add_permissions() create_ksa_vat_setting(company) + make_custom_fields() def add_permissions(): @@ -26,3 +26,12 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) + +def make_custom_fields(): + qr_code_field = dict( + fieldname='qr_code', + label='QR Code', + fieldtype='Attach Image', + read_only=1, no_copy=1, hidden=1) + + create_custom_field('Sales Invoice', qr_code_field) From 88c9fe35bd152cde0d350ba859e2692e954f141a Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 7 Sep 2021 00:15:58 +0530 Subject: [PATCH 016/416] fix: employee remider settings (#27365) --- erpnext/hr/doctype/employee/employee_reminders.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index fc018664b4..ba086dc060 100644 --- a/erpnext/hr/doctype/employee/employee_reminders.py +++ b/erpnext/hr/doctype/employee/employee_reminders.py @@ -13,7 +13,7 @@ from erpnext.hr.utils import get_holidays_for_employee # HOLIDAY REMINDERS # ----------------- def send_reminders_in_advance_weekly(): - to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1) + to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) frequency = frappe.db.get_single_value("HR Settings", "frequency") if not (to_send_in_advance and frequency == "Weekly"): return @@ -21,7 +21,7 @@ def send_reminders_in_advance_weekly(): send_advance_holiday_reminders("Weekly") def send_reminders_in_advance_monthly(): - to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1) + to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders")) frequency = frappe.db.get_single_value("HR Settings", "frequency") if not (to_send_in_advance and frequency == "Monthly"): return @@ -79,7 +79,7 @@ def send_holidays_reminder_in_advance(employee, holidays): # ------------------ def send_birthday_reminders(): """Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set.""" - to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders") or 1) + to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders")) if not to_send: return From d795e5569470f3c35612bd59754bae0501929972 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:49:34 +0530 Subject: [PATCH 017/416] feat(CRM): Sales Pipeline Analytics Report and Opportunity Summary by Sales Stage Report (#26639) * feat: Sales Pipeline Analytics Report * fix: sider Issues and added tests * fix: Semgrep Issue * feat: Opportunity Summary by Sales Stage Report * fix: add some checks and tests * fix: sider issues and test * fix: additional checks for error handling and minor changes * fix: remove unused conditions * fix: Changes mentioned on PR * fix: currency conversions and other changes * fix: remove unused imports * fix: correction for failing test case * fix: recorrected failing test case * fix: sider issues and resolve test case errors * fix: rewrite query using query builder * fix: test case changes * fix: sider fixes and other changes * fix: clear data before running test * fix: test case fixed * refactor: code formatting - smaller functions - variable and function naming * refactor: improve code formatting * fix: linter issues * fix: linter issues * fix: change indentation to tabs * fix: linter issues * fix: naming, code formatting * fix: quarterly values not showing up in Sales Pipeline Analytics * fix: typo in tests Co-authored-by: Rucha Mahabal --- .../__init__.py | 0 .../opportunity_summary_by_sales_stage.js | 65 ++++ .../opportunity_summary_by_sales_stage.json | 29 ++ .../opportunity_summary_by_sales_stage.py | 254 +++++++++++++ ...test_opportunity_summary_by_sales_stage.py | 94 +++++ .../sales_pipeline_analytics/__init__.py | 0 .../sales_pipeline_analytics.js | 70 ++++ .../sales_pipeline_analytics.json | 29 ++ .../sales_pipeline_analytics.py | 333 ++++++++++++++++++ .../test_sales_pipeline_analytics.py | 238 +++++++++++++ erpnext/crm/workspace/crm/crm.json | 20 +- 11 files changed, 1131 insertions(+), 1 deletion(-) create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py create mode 100644 erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py create mode 100644 erpnext/crm/report/sales_pipeline_analytics/__init__.py create mode 100644 erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js create mode 100644 erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json create mode 100644 erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py create mode 100644 erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js new file mode 100644 index 0000000000..116db2f5a2 --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -0,0 +1,65 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Opportunity Summary by Sales Stage"] = { + "filters": [ + { + fieldname: "based_on", + label: __("Based On"), + fieldtype: "Select", + options: "Opportunity Owner\nSource\nOpportunity Type", + default: "Opportunity Owner" + }, + { + fieldname: "data_based_on", + label: __("Data Based On"), + fieldtype: "Select", + options: "Number\nAmount", + default: "Number" + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + }, + { + fieldname: "status", + label: __("Status"), + fieldtype: "MultiSelectList", + get_data: function() { + return [ + {value: "Open", description: "Status"}, + {value: "Converted", description: "Status"}, + {value: "Quotation", description: "Status"}, + {value: "Replied", description: "Status"} + ] + } + }, + { + fieldname: "opportunity_source", + label: __("Oppoturnity Source"), + fieldtype: "Link", + options: "Lead Source", + }, + { + fieldname: "opportunity_type", + label: __("Opportunity Type"), + fieldtype: "Link", + options: "Opportunity Type", + }, + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company") + } + ] +}; \ No newline at end of file diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json new file mode 100644 index 0000000000..3605aecacd --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-28 12:18:24.028737", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-28 12:18:24.028737", + "modified_by": "Administrator", + "module": "CRM", + "name": "Opportunity Summary by Sales Stage", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Opportunity", + "report_name": "Opportunity Summary by Sales Stage ", + "report_type": "Script Report", + "roles": [ + { + "role": "Sales User" + }, + { + "role": "Sales Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py new file mode 100644 index 0000000000..4cff13f232 --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py @@ -0,0 +1,254 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt +import json + +import frappe +import pandas +from frappe import _ +from frappe.utils import flt +from six import iteritems + +from erpnext.setup.utils import get_exchange_rate + + +def execute(filters=None): + return OpportunitySummaryBySalesStage(filters).run() + +class OpportunitySummaryBySalesStage(object): + def __init__(self,filters=None): + self.filters = frappe._dict(filters or {}) + + def run(self): + self.get_columns() + self.get_data() + self.get_chart_data() + return self.columns, self.data, None, self.chart + + def get_columns(self): + self.columns = [] + + if self.filters.get('based_on') == 'Opportunity Owner': + self.columns.append({ + 'label': _('Opportunity Owner'), + 'fieldname': 'opportunity_owner', + 'width': 200 + }) + + if self.filters.get('based_on') == 'Source': + self.columns.append({ + 'label': _('Source'), + 'fieldname': 'source', + 'fieldtype': 'Link', + 'options': 'Lead Source', + 'width': 200 + }) + + if self.filters.get('based_on') == 'Opportunity Type': + self.columns.append({ + 'label': _('Opportunity Type'), + 'fieldname': 'opportunity_type', + 'width': 200 + }) + + self.set_sales_stage_columns() + + def set_sales_stage_columns(self): + self.sales_stage_list = frappe.db.get_list('Sales Stage', pluck='name') + + for sales_stage in self.sales_stage_list: + if self.filters.get('data_based_on') == 'Number': + self.columns.append({ + 'label': _(sales_stage), + 'fieldname': sales_stage, + 'fieldtype': 'Int', + 'width': 150 + }) + + elif self.filters.get('data_based_on') == 'Amount': + self.columns.append({ + 'label': _(sales_stage), + 'fieldname': sales_stage, + 'fieldtype': 'Currency', + 'width': 150 + }) + + def get_data(self): + self.data = [] + + based_on = { + 'Opportunity Owner': '_assign', + 'Source': 'source', + 'Opportunity Type': 'opportunity_type' + }[self.filters.get('based_on')] + + data_based_on = { + 'Number': 'count(name) as count', + 'Amount': 'opportunity_amount as amount', + }[self.filters.get('data_based_on')] + + self.get_data_query(based_on, data_based_on) + + self.get_rows() + + def get_data_query(self, based_on, data_based_on): + if self.filters.get('data_based_on') == 'Number': + group_by = '{},{}'.format('sales_stage', based_on) + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=['sales_stage', data_based_on, based_on], + group_by=group_by + ) + + elif self.filters.get('data_based_on') == 'Amount': + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=['sales_stage', based_on, data_based_on, 'currency'] + ) + + self.convert_to_base_currency() + + dataframe = pandas.DataFrame.from_records(self.query_result) + dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True) + result = dataframe.groupby(['sales_stage', based_on], as_index=False)['amount'].sum() + + self.grouped_data = [] + + for i in range(len(result['amount'])): + self.grouped_data.append({ + 'sales_stage': result['sales_stage'][i], + based_on : result[based_on][i], + 'amount': result['amount'][i] + }) + + self.query_result = self.grouped_data + + def get_rows(self): + self.data = [] + self.get_formatted_data() + + for based_on,data in iteritems(self.formatted_data): + row_based_on={ + 'Opportunity Owner': 'opportunity_owner', + 'Source': 'source', + 'Opportunity Type': 'opportunity_type' + }[self.filters.get('based_on')] + + row = {row_based_on: based_on} + + for d in self.query_result: + sales_stage = d.get('sales_stage') + row[sales_stage] = data.get(sales_stage) + + self.data.append(row) + + def get_formatted_data(self): + self.formatted_data = frappe._dict() + + for d in self.query_result: + data_based_on ={ + 'Number': 'count', + 'Amount': 'amount' + }[self.filters.get('data_based_on')] + + based_on ={ + 'Opportunity Owner': '_assign', + 'Source': 'source', + 'Opportunity Type': 'opportunity_type' + }[self.filters.get('based_on')] + + if self.filters.get('based_on') == 'Opportunity Owner': + if d.get(based_on) == '[]' or d.get(based_on) is None or d.get(based_on) == 'Not Assigned': + assignments = ['Not Assigned'] + else: + assignments = json.loads(d.get(based_on)) + + sales_stage = d.get('sales_stage') + count = d.get(data_based_on) + + if assignments: + if len(assignments) > 1: + for assigned_to in assignments: + self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count) + else: + assigned_to = assignments[0] + self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count) + else: + value = d.get(based_on) + sales_stage = d.get('sales_stage') + count = d.get(data_based_on) + self.set_formatted_data_based_on_sales_stage(value, sales_stage, count) + + def set_formatted_data_based_on_sales_stage(self, based_on, sales_stage, count): + self.formatted_data.setdefault(based_on, frappe._dict()).setdefault(sales_stage, 0) + self.formatted_data[based_on][sales_stage] += count + + def get_conditions(self): + filters = [] + + if self.filters.get('company'): + filters.append({'company': self.filters.get('company')}) + + if self.filters.get('opportunity_type'): + filters.append({'opportunity_type': self.filters.get('opportunity_type')}) + + if self.filters.get('opportunity_source'): + filters.append({'source': self.filters.get('opportunity_source')}) + + if self.filters.get('status'): + filters.append({'status': ('in',self.filters.get('status'))}) + + if self.filters.get('from_date') and self.filters.get('to_date'): + filters.append(['transaction_date', 'between', [self.filters.get('from_date'), self.filters.get('to_date')]]) + + return filters + + def get_chart_data(self): + labels = [] + datasets = [] + values = [0] * 8 + + for sales_stage in self.sales_stage_list: + labels.append(sales_stage) + + options = { + 'Number': 'count', + 'Amount': 'amount' + }[self.filters.get('data_based_on')] + + for data in self.query_result: + for count in range(len(values)): + if data['sales_stage'] == labels[count]: + values[count] = values[count] + data[options] + + datasets.append({'name':options, 'values':values}) + + self.chart = { + 'data':{ + 'labels': labels, + 'datasets': datasets + }, + 'type':'line' + } + + def currency_conversion(self,from_currency,to_currency): + cacheobj = frappe.cache() + + if cacheobj.get(from_currency): + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + else: + value = get_exchange_rate(from_currency,to_currency) + cacheobj.set(from_currency,value) + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + def get_default_currency(self): + company = self.filters.get('company') + return frappe.db.get_value('Company', company, 'default_currency') + + def convert_to_base_currency(self): + default_currency = self.get_default_currency() + for data in self.query_result: + if data.get('currency') != default_currency: + opportunity_currency = data.get('currency') + value = self.currency_conversion(opportunity_currency,default_currency) + data['amount'] = data['amount'] * value \ No newline at end of file diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py new file mode 100644 index 0000000000..13859d9e0b --- /dev/null +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py @@ -0,0 +1,94 @@ +import unittest + +import frappe + +from erpnext.crm.report.opportunity_summary_by_sales_stage.opportunity_summary_by_sales_stage import ( + execute, +) +from erpnext.crm.report.sales_pipeline_analytics.test_sales_pipeline_analytics import ( + create_company, + create_customer, + create_opportunity, +) + + +class TestOpportunitySummaryBySalesStage(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.delete("Opportunity") + create_company() + create_customer() + create_opportunity() + + def test_opportunity_summary_by_sales_stage(self): + self.check_for_opportunity_owner() + self.check_for_source() + self.check_for_opportunity_type() + self.check_all_filters() + + def check_for_opportunity_owner(self): + filters = { + 'based_on': "Opportunity Owner", + 'data_based_on': "Number", + 'company': "Best Test" + } + + report = execute(filters) + + expected_data = [{ + 'opportunity_owner': "Not Assigned", + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) + + def check_for_source(self): + filters = { + 'based_on': "Source", + 'data_based_on': "Number", + 'company': "Best Test" + } + + report = execute(filters) + + expected_data = [{ + 'source': 'Cold Calling', + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) + + def check_for_opportunity_type(self): + filters = { + 'based_on': "Opportunity Type", + 'data_based_on': "Number", + 'company': "Best Test" + } + + report = execute(filters) + + expected_data = [{ + 'opportunity_type': 'Sales', + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) + + def check_all_filters(self): + filters = { + 'based_on': "Opportunity Type", + 'data_based_on': "Number", + 'company': "Best Test", + 'opportunity_source': "Cold Calling", + 'opportunity_type': "Sales", + 'status': ["Open"] + } + + report = execute(filters) + + expected_data = [{ + 'opportunity_type': 'Sales', + 'Prospecting': 1 + }] + + self.assertEqual(expected_data, report[1]) \ No newline at end of file diff --git a/erpnext/crm/report/sales_pipeline_analytics/__init__.py b/erpnext/crm/report/sales_pipeline_analytics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js new file mode 100644 index 0000000000..1426f4b6fd --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js @@ -0,0 +1,70 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Sales Pipeline Analytics"] = { + "filters": [ + { + fieldname: "pipeline_by", + label: __("Pipeline By"), + fieldtype: "Select", + options: "Owner\nSales Stage", + default: "Owner" + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date" + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date" + }, + { + fieldname: "range", + label: __("Range"), + fieldtype: "Select", + options: "Monthly\nQuarterly", + default: "Monthly" + }, + { + fieldname: "assigned_to", + label: __("Assigned To"), + fieldtype: "Link", + options: "User" + }, + { + fieldname: "status", + label: __("Status"), + fieldtype: "Select", + options: "Open\nQuotation\nConverted\nReplied" + }, + { + fieldname: "based_on", + label: __("Based On"), + fieldtype: "Select", + options: "Number\nAmount", + default: "Number" + }, + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company") + }, + { + fieldname: "opportunity_source", + label: __("Opportunity Source"), + fieldtype: "Link", + options: "Lead Source" + }, + { + fieldname: "opportunity_type", + label: __("Opportunity Type"), + fieldtype: "Link", + options: "Opportunity Type" + }, + ] +}; diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json new file mode 100644 index 0000000000..cffdddfd23 --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json @@ -0,0 +1,29 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-07-01 17:29:09.530787", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2021-07-01 17:45:17.612861", + "modified_by": "Administrator", + "module": "CRM", + "name": "Sales Pipeline Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Opportunity", + "report_name": "Sales Pipeline Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "Sales User" + }, + { + "role": "Sales Manager" + } + ] +} \ No newline at end of file diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py new file mode 100644 index 0000000000..7466982d92 --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py @@ -0,0 +1,333 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import json +from datetime import date + +import frappe +import pandas +from dateutil.relativedelta import relativedelta +from frappe import _ +from frappe.utils import cint, flt +from six import iteritems + +from erpnext.setup.utils import get_exchange_rate + + +def execute(filters=None): + return SalesPipelineAnalytics(filters).run() + +class SalesPipelineAnalytics(object): + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + + def run(self): + self.get_columns() + self.get_data() + self.get_chart_data() + + return self.columns, self.data, None, self.chart + + def get_columns(self): + self.columns = [] + + self.set_range_columns() + self.set_pipeline_based_on_column() + + def set_range_columns(self): + based_on = { + 'Number': 'Int', + 'Amount': 'Currency' + }[self.filters.get('based_on')] + + if self.filters.get('range') == 'Monthly': + month_list = self.get_month_list() + + for month in month_list: + self.columns.append({ + 'fieldname': month, + 'fieldtype': based_on, + 'label': month, + 'width': 200 + }) + + elif self.filters.get('range') == 'Quarterly': + for quarter in range(1, 5): + self.columns.append({ + 'fieldname': f'Q{quarter}', + 'fieldtype': based_on, + 'label': f'Q{quarter}', + 'width': 200 + }) + + def set_pipeline_based_on_column(self): + if self.filters.get('pipeline_by') == 'Owner': + self.columns.insert(0, { + 'fieldname': 'opportunity_owner', + 'label': _('Opportunity Owner'), + 'width': 200 + }) + + elif self.filters.get('pipeline_by') == 'Sales Stage': + self.columns.insert(0, { + 'fieldname': 'sales_stage', + 'label': _('Sales Stage'), + 'width': 200 + }) + + def get_fields(self): + self.based_on ={ + 'Owner': '_assign as opportunity_owner', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + self.data_based_on ={ + 'Number': 'count(name) as count', + 'Amount': 'opportunity_amount as amount' + }[self.filters.get('based_on')] + + self.group_by_based_on = { + 'Owner': '_assign', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + self.group_by_period = { + 'Monthly': 'month(expected_closing)', + 'Quarterly': 'QUARTER(expected_closing)' + }[self.filters.get('range')] + + self.pipeline_by = { + 'Owner': 'opportunity_owner', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + self.duration = { + 'Monthly': 'monthname(expected_closing) as month', + 'Quarterly': 'QUARTER(expected_closing) as quarter' + }[self.filters.get('range')] + + self.period_by = { + 'Monthly': 'month', + 'Quarterly': 'quarter' + }[self.filters.get('range')] + + def get_data(self): + self.get_fields() + + if self.filters.get('based_on') == 'Number': + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=[self.based_on, self.data_based_on, self.duration], + group_by='{},{}'.format(self.group_by_based_on, self.group_by_period), + order_by=self.group_by_period + ) + + if self.filters.get('based_on') == 'Amount': + self.query_result = frappe.db.get_list('Opportunity', + filters=self.get_conditions(), + fields=[self.based_on, self.data_based_on, self.duration, 'currency'] + ) + + self.convert_to_base_currency() + + dataframe = pandas.DataFrame.from_records(self.query_result) + dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True) + result = dataframe.groupby([self.pipeline_by, self.period_by], as_index=False)['amount'].sum() + + self.grouped_data = [] + + for i in range(len(result['amount'])): + self.grouped_data.append({ + self.pipeline_by : result[self.pipeline_by][i], + self.period_by : result[self.period_by][i], + 'amount': result['amount'][i] + }) + + self.query_result = self.grouped_data + + self.get_periodic_data() + self.append_data(self.pipeline_by, self.period_by) + + def get_conditions(self): + conditions = [] + + if self.filters.get('opportunity_source'): + conditions.append({'source': self.filters.get('opportunity_source')}) + + if self.filters.get('opportunity_type'): + conditions.append({'opportunity_type': self.filters.get('opportunity_type')}) + + if self.filters.get('status'): + conditions.append({'status': self.filters.get('status')}) + + if self.filters.get('company'): + conditions.append({'company': self.filters.get('company')}) + + if self.filters.get('from_date') and self.filters.get('to_date'): + conditions.append(['expected_closing', 'between', + [self.filters.get('from_date'), self.filters.get('to_date')]]) + + return conditions + + def get_chart_data(self): + labels = [] + datasets = [] + + self.append_to_dataset(datasets) + + for column in self.columns: + if column['fieldname'] != 'opportunity_owner' and column['fieldname'] != 'sales_stage': + labels.append(column['fieldname']) + + self.chart = { + 'data':{ + 'labels': labels, + 'datasets': datasets + }, + 'type':'line' + } + + return self.chart + + def get_periodic_data(self): + self.periodic_data = frappe._dict() + + based_on = { + 'Number': 'count', + 'Amount': 'amount' + }[self.filters.get('based_on')] + + pipeline_by = { + 'Owner': 'opportunity_owner', + 'Sales Stage': 'sales_stage' + }[self.filters.get('pipeline_by')] + + frequency = { + 'Monthly': 'month', + 'Quarterly': 'quarter' + }[self.filters.get('range')] + + for info in self.query_result: + if self.filters.get('range') == 'Monthly': + period = info.get(frequency) + if self.filters.get('range') == 'Quarterly': + period = f'Q{cint(info.get("quarter"))}' + + value = info.get(pipeline_by) + count_or_amount = info.get(based_on) + + if self.filters.get('pipeline_by') == 'Owner': + if value == 'Not Assigned' or value == '[]' or value is None: + assigned_to = ['Not Assigned'] + else: + assigned_to = json.loads(value) + self.check_for_assigned_to(period, value, count_or_amount, assigned_to, info) + + else: + self.set_formatted_data(period, value, count_or_amount, None) + + def set_formatted_data(self, period, value, count_or_amount, assigned_to): + if assigned_to: + if len(assigned_to) > 1: + if self.filters.get('assigned_to'): + for user in assigned_to: + if self.filters.get('assigned_to') == user: + value = user + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + else: + for user in assigned_to: + value = user + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + else: + value = assigned_to[0] + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + + else: + self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0) + self.periodic_data[value][period] += count_or_amount + + def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info): + if self.filters.get('assigned_to'): + for data in json.loads(info.get('opportunity_owner')): + if data == self.filters.get('assigned_to'): + self.set_formatted_data(period, data, count_or_amount, assigned_to) + else: + self.set_formatted_data(period, value, count_or_amount, assigned_to) + + def get_month_list(self): + month_list= [] + current_date = date.today() + month_number = date.today().month + + for month in range(month_number,13): + month_list.append(current_date.strftime('%B')) + current_date = current_date + relativedelta(months=1) + + return month_list + + def append_to_dataset(self, datasets): + range_by = { + 'Monthly': 'month', + 'Quarterly': 'quarter' + }[self.filters.get('range')] + + based_on = { + 'Amount': 'amount', + 'Number': 'count' + }[self.filters.get('based_on')] + + if self.filters.get('range') == 'Quarterly': + frequency_list = [1,2,3,4] + count = [0] * 4 + + if self.filters.get('range') == 'Monthly': + frequency_list = self.get_month_list() + count = [0] * 12 + + for info in self.query_result: + for i in range(len(frequency_list)): + if info[range_by] == frequency_list[i]: + count[i] = count[i] + info[based_on] + datasets.append({'name': based_on, 'values': count}) + + def append_data(self, pipeline_by, period_by): + self.data = [] + for pipeline,period_data in iteritems(self.periodic_data): + row = {pipeline_by : pipeline} + for info in self.query_result: + if self.filters.get('range') == 'Monthly': + period = info.get(period_by) + + if self.filters.get('range') == 'Quarterly': + period = f'Q{cint(info.get(period_by))}' + + count = period_data.get(period,0.0) + row[period] = count + + self.data.append(row) + + def get_default_currency(self): + company = self.filters.get('company') + return frappe.db.get_value('Company',company,['default_currency']) + + def get_currency_rate(self, from_currency, to_currency): + cacheobj = frappe.cache() + + if cacheobj.get(from_currency): + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + else: + value = get_exchange_rate(from_currency, to_currency) + cacheobj.set(from_currency, value) + return flt(str(cacheobj.get(from_currency),'UTF-8')) + + def convert_to_base_currency(self): + default_currency = self.get_default_currency() + for data in self.query_result: + if data.get('currency') != default_currency: + opportunity_currency = data.get('currency') + value = self.get_currency_rate(opportunity_currency,default_currency) + data['amount'] = data['amount'] * value \ No newline at end of file diff --git a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py new file mode 100644 index 0000000000..24c3839d2d --- /dev/null +++ b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py @@ -0,0 +1,238 @@ +import unittest + +import frappe + +from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute + + +class TestSalesPipelineAnalytics(unittest.TestCase): + @classmethod + def setUpClass(self): + frappe.db.delete("Opportunity") + create_company() + create_customer() + create_opportunity() + + def test_sales_pipeline_analytics(self): + self.check_for_monthly_and_number() + self.check_for_monthly_and_amount() + self.check_for_quarterly_and_number() + self.check_for_quarterly_and_amount() + self.check_for_all_filters() + + def check_for_monthly_and_number(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Monthly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'August':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Monthly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'August':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_monthly_and_amount(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Monthly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'August':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Monthly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'August':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_quarterly_and_number(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Quarterly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'Q3':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Quarterly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'Q3':1 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_quarterly_and_amount(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Quarterly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'Q3':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + filters = { + 'pipeline_by':"Sales Stage", + 'range':"Quarterly", + 'based_on':"Amount", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test" + } + + report = execute(filters) + + expected_data = [ + { + 'sales_stage':'Prospecting', + 'Q3':150000 + } + ] + + self.assertEqual(expected_data,report[1]) + + def check_for_all_filters(self): + filters = { + 'pipeline_by':"Owner", + 'range':"Monthly", + 'based_on':"Number", + 'status':"Open", + 'opportunity_type':"Sales", + 'company':"Best Test", + 'opportunity_source':'Cold Calling', + 'from_date': '2021-08-01', + 'to_date':'2021-08-31' + } + + report = execute(filters) + + expected_data = [ + { + 'opportunity_owner':'Not Assigned', + 'August': 1 + } + ] + + self.assertEqual(expected_data,report[1]) + +def create_company(): + doc = frappe.db.exists('Company','Best Test') + if not doc: + doc = frappe.new_doc('Company') + doc.company_name = 'Best Test' + doc.default_currency = "INR" + doc.insert() + +def create_customer(): + doc = frappe.db.exists("Customer","_Test NC") + if not doc: + doc = frappe.new_doc("Customer") + doc.customer_name = '_Test NC' + doc.insert() + +def create_opportunity(): + doc = frappe.db.exists({"doctype":"Opportunity","party_name":"_Test NC"}) + if not doc: + doc = frappe.new_doc("Opportunity") + doc.opportunity_from = "Customer" + customer_name = frappe.db.get_value("Customer",{"customer_name":'_Test NC'},['customer_name']) + doc.party_name = customer_name + doc.opportunity_amount = 150000 + doc.source = "Cold Calling" + doc.currency = "INR" + doc.expected_closing = "2021-08-31" + doc.company = 'Best Test' + doc.insert() \ No newline at end of file diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json index c363395452..a661b62379 100644 --- a/erpnext/crm/workspace/crm/crm.json +++ b/erpnext/crm/workspace/crm/crm.json @@ -147,6 +147,24 @@ "onboard": 1, "type": "Link" }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Sales Pipeline Analytics", + "link_to": "Sales Pipeline Analytics", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 1, + "label": "Opportunity Summary by Sales Stage", + "link_to": "Opportunity Summary by Sales Stage", + "link_type": "Report", + "onboard": 0, + "type": "Link" + }, { "dependencies": "", "hidden": 0, @@ -403,7 +421,7 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:56.913091", + "modified": "2021-08-19 19:08:08.728876", "modified_by": "Administrator", "module": "CRM", "name": "CRM", From e1f55df4d76de7a2125f7608fe1c7a40823efa51 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 7 Sep 2021 11:07:22 +0530 Subject: [PATCH 018/416] fix: GSTR-1 Reports not showing any data --- erpnext/regional/report/gstr_1/gstr_1.py | 64 +++++++++++++----------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index ca0defa648..16346f0706 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -50,6 +50,7 @@ class Gstr1Report(object): self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1) self.get_invoice_data() + print(self.invoices, "$#$#$#$#$#") if self.invoices: self.get_invoice_items() self.get_items_based_on_tax_rate() @@ -96,35 +97,36 @@ class Gstr1Report(object): def get_b2c_data(self): b2cs_output = {} - for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): - invoice_details = self.invoices.get(inv) - for rate, items in items_based_on_rate.items(): - place_of_supply = invoice_details.get("place_of_supply") - ecommerce_gstin = invoice_details.get("ecommerce_gstin") + if self.invoices: + for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): + invoice_details = self.invoices.get(inv) + for rate, items in items_based_on_rate.items(): + place_of_supply = invoice_details.get("place_of_supply") + ecommerce_gstin = invoice_details.get("ecommerce_gstin") - b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{ - "place_of_supply": "", - "ecommerce_gstin": "", - "rate": "", - "taxable_value": 0, - "cess_amount": 0, - "type": "", - "invoice_number": invoice_details.get("invoice_number"), - "posting_date": invoice_details.get("posting_date"), - "invoice_value": invoice_details.get("base_grand_total"), - }) + b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin), { + "place_of_supply": "", + "ecommerce_gstin": "", + "rate": "", + "taxable_value": 0, + "cess_amount": 0, + "type": "", + "invoice_number": invoice_details.get("invoice_number"), + "posting_date": invoice_details.get("posting_date"), + "invoice_value": invoice_details.get("base_grand_total"), + }) - row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) - row["place_of_supply"] = place_of_supply - row["ecommerce_gstin"] = ecommerce_gstin - row["rate"] = rate - row["taxable_value"] += sum([abs(net_amount) - for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) - row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) - row["type"] = "E" if ecommerce_gstin else "OE" + row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin)) + row["place_of_supply"] = place_of_supply + row["ecommerce_gstin"] = ecommerce_gstin + row["rate"] = rate + row["taxable_value"] += sum([abs(net_amount) + for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items]) + row["cess_amount"] += flt(self.invoice_cess.get(inv), 2) + row["type"] = "E" if ecommerce_gstin else "OE" - for key, value in iteritems(b2cs_output): - self.data.append(value) + for key, value in iteritems(b2cs_output): + self.data.append(value) def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] @@ -173,9 +175,10 @@ class Gstr1Report(object): company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) - self.filters.update({ - 'company_gstins': company_gstins - }) + if company_gstins: + self.filters.update({ + 'company_gstins': company_gstins + }) invoice_data = frappe.db.sql(""" select @@ -185,7 +188,7 @@ class Gstr1Report(object): and is_opening = 'No' order by posting_date desc """.format(select_columns=self.select_columns, doctype=self.doctype, - where_conditions=conditions), self.filters, as_dict=1) + where_conditions=conditions), self.filters, as_dict=1, debug=1) for d in invoice_data: self.invoices.setdefault(d.invoice_number, d) @@ -1050,6 +1053,7 @@ def get_company_gstin_number(company, address=None, all_gstins=False): ["Dynamic Link", "link_doctype", "=", "Company"], ["Dynamic Link", "link_name", "=", company], ["Dynamic Link", "parenttype", "=", "Address"], + ["gstin", "!=", ''] ] gstin = frappe.get_all("Address", filters=filters, pluck="gstin", order_by="is_primary_address desc") if gstin and not all_gstins: From 6dfcc1e2ae408ffb6a468be36cd1e7f2f957d95a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 7 Sep 2021 11:08:30 +0530 Subject: [PATCH 019/416] fix: Remove print and debug --- erpnext/regional/report/gstr_1/gstr_1.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 16346f0706..cf4850e278 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -50,7 +50,6 @@ class Gstr1Report(object): self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1) self.get_invoice_data() - print(self.invoices, "$#$#$#$#$#") if self.invoices: self.get_invoice_items() self.get_items_based_on_tax_rate() @@ -188,7 +187,7 @@ class Gstr1Report(object): and is_opening = 'No' order by posting_date desc """.format(select_columns=self.select_columns, doctype=self.doctype, - where_conditions=conditions), self.filters, as_dict=1, debug=1) + where_conditions=conditions), self.filters, as_dict=1) for d in invoice_data: self.invoices.setdefault(d.invoice_number, d) From 058d98342adcef0438a6fdad061af19dfe1fe702 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 7 Sep 2021 12:14:40 +0530 Subject: [PATCH 020/416] fix: missed to add voucher_type, voucher_no to get GL Entries (#27368) * fix: missed to add voucher_type, voucher_no to get gl entries * test: get voucherwise details utilities Co-authored-by: Ankush Menat --- erpnext/accounts/test/test_utils.py | 71 +++++++++++++++++------------ erpnext/accounts/utils.py | 5 +- 2 files changed, 45 insertions(+), 31 deletions(-) diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py index d7b60daa37..c3f6d27443 100644 --- a/erpnext/accounts/test/test_utils.py +++ b/erpnext/accounts/test/test_utils.py @@ -5,21 +5,48 @@ import unittest from frappe.test_runner import make_test_objects from erpnext.accounts.party import get_party_shipping_address +from erpnext.accounts.utils import get_future_stock_vouchers, get_voucherwise_gl_entries +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestUtils(unittest.TestCase): @classmethod def setUpClass(cls): super(TestUtils, cls).setUpClass() - make_test_objects('Address', ADDRESS_RECORDS) + make_test_objects("Address", ADDRESS_RECORDS) def test_get_party_shipping_address(self): - address = get_party_shipping_address('Customer', '_Test Customer 1') - self.assertEqual(address, '_Test Billing Address 2 Title-Billing') + address = get_party_shipping_address("Customer", "_Test Customer 1") + self.assertEqual(address, "_Test Billing Address 2 Title-Billing") def test_get_party_shipping_address2(self): - address = get_party_shipping_address('Customer', '_Test Customer 2') - self.assertEqual(address, '_Test Shipping Address 2 Title-Shipping') + address = get_party_shipping_address("Customer", "_Test Customer 2") + self.assertEqual(address, "_Test Shipping Address 2 Title-Shipping") + + def test_get_voucher_wise_gl_entry(self): + + pr = make_purchase_receipt( + item_code="_Test Item", + posting_date="2021-02-01", + rate=100, + qty=1, + warehouse="Stores - TCP1", + company="_Test Company with perpetual inventory", + ) + + future_vouchers = get_future_stock_vouchers("2021-01-01", "00:00:00", for_items=["_Test Item"]) + + voucher_type_and_no = ("Purchase Receipt", pr.name) + self.assertTrue( + voucher_type_and_no in future_vouchers, + msg="get_future_stock_vouchers not returning correct value", + ) + + posting_date = "2021-01-01" + gl_entries = get_voucherwise_gl_entries(future_vouchers, posting_date) + self.assertTrue( + voucher_type_and_no in gl_entries, msg="get_voucherwise_gl_entries not returning expected GLes", + ) ADDRESS_RECORDS = [ @@ -31,12 +58,8 @@ ADDRESS_RECORDS = [ "city": "Lagos", "country": "Nigeria", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 2", - "doctype": "Dynamic Link" - } - ] + {"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"} + ], }, { "doctype": "Address", @@ -46,12 +69,8 @@ ADDRESS_RECORDS = [ "city": "Lagos", "country": "Nigeria", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 2", - "doctype": "Dynamic Link" - } - ] + {"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"} + ], }, { "doctype": "Address", @@ -62,12 +81,8 @@ ADDRESS_RECORDS = [ "country": "Nigeria", "is_shipping_address": "1", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 2", - "doctype": "Dynamic Link" - } - ] + {"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"} + ], }, { "doctype": "Address", @@ -78,11 +93,7 @@ ADDRESS_RECORDS = [ "country": "Nigeria", "is_shipping_address": "1", "links": [ - { - "link_doctype": "Customer", - "link_name": "_Test Customer 1", - "doctype": "Dynamic Link" - } - ] - } + {"link_doctype": "Customer", "link_name": "_Test Customer 1", "doctype": "Dynamic Link"} + ], + }, ] diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 4692869343..fbad171b78 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -963,6 +963,9 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): Only fetches GLE fields required for comparing with new GLE. Check compare_existing_and_expected_gle function below. + + returns: + Dict[Tuple[voucher_type, voucher_no], List[GL Entries]] """ gl_entries = {} if not future_stock_vouchers: @@ -971,7 +974,7 @@ def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): voucher_nos = [d[1] for d in future_stock_vouchers] gles = frappe.db.sql(""" - select name, account, credit, debit, cost_center, project + select name, account, credit, debit, cost_center, project, voucher_type, voucher_no from `tabGL Entry` where posting_date >= %s and voucher_no in (%s)""" % From 61a1356109ae35fcdec438e09df4f784f76d24b8 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 7 Sep 2021 14:42:17 +0530 Subject: [PATCH 021/416] fix: Check if Item is serialised before trying to fetch SN and set SO in it (#27358) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 41ca8300a8..4ccfa62b5a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1503,7 +1503,8 @@ class StockEntry(StockController): qty_to_reserve -= reserved_qty[0][0] if qty_to_reserve > 0: for item in self.items: - if item.item_code == item_code: + has_serial_no = frappe.get_cached_value("Item", item.item_code, "has_serial_no") + if item.item_code == item_code and has_serial_no: serial_nos = (item.serial_no).split("\n") for serial_no in serial_nos: if qty_to_reserve > 0: From 5596988c94527509a847e9df055d1cfc996181c4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 7 Sep 2021 14:53:30 +0530 Subject: [PATCH 022/416] ci: fix docs checker for wiki based docs (#27380) --- .github/helper/documentation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py index 91983d3eae..378983e95f 100644 --- a/.github/helper/documentation.py +++ b/.github/helper/documentation.py @@ -24,6 +24,8 @@ def docs_link_exists(body): parts = parsed_url.path.split('/') if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos: return True + elif parsed_url.netloc == "docs.erpnext.com": + return True if __name__ == "__main__": From df3e4ce1c09675f941f7622333b555eee16e0981 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 8 Sep 2021 16:04:13 +0530 Subject: [PATCH 023/416] fix: scan barcode fields input length (#27389) * fix: undo unintentional changes * fix: scan barcode fields input length --- .../doctype/sales_invoice/sales_invoice.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b5620ae6a9..e476439c7a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -247,7 +247,7 @@ "depends_on": "customer", "fetch_from": "customer.customer_name", "fieldname": "customer_name", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "in_global_search": 1, @@ -1061,6 +1061,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Apply Additional Discount On", + "length": 15, "options": "\nGrand Total\nNet Total", "print_hide": 1 }, @@ -1147,7 +1148,7 @@ { "description": "In Words will be visible once you save the Sales Invoice.", "fieldname": "base_in_words", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "label": "In Words (Company Currency)", @@ -1207,7 +1208,7 @@ }, { "fieldname": "in_words", - "fieldtype": "Data", + "fieldtype": "Small Text", "hide_days": 1, "hide_seconds": 1, "label": "In Words", @@ -1560,6 +1561,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Print Language", + "length": 6, "print_hide": 1, "read_only": 1 }, @@ -1647,6 +1649,7 @@ "hide_seconds": 1, "in_standard_filter": 1, "label": "Status", + "length": 30, "no_copy": 1, "options": "\nDraft\nReturn\nCredit Note Issued\nSubmitted\nPaid\nUnpaid\nUnpaid and Discounted\nOverdue and Discounted\nOverdue\nCancelled\nInternal Transfer", "print_hide": 1, @@ -1706,6 +1709,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "Is Opening Entry", + "length": 4, "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", @@ -1717,6 +1721,7 @@ "hide_days": 1, "hide_seconds": 1, "label": "C-Form Applicable", + "length": 4, "no_copy": 1, "options": "No\nYes", "print_hide": 1 @@ -2017,7 +2022,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-08-27 20:13:40.456462", + "modified": "2021-09-08 15:24:25.486499", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From fa819f2fb0523276f64ed80c885088ac0596933d Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 8 Sep 2021 16:28:05 +0530 Subject: [PATCH 024/416] fix: General Ledger translation issues (#27298) * fix: remove translations from GL report options Options need not be translated, their display label gets translated client side. * fix: make group by options translatable * ci: semgrep rule for translated options in report Co-authored-by: Ankush Menat --- .github/helper/semgrep_rules/report.yml | 13 +++++++++++ .../report/general_ledger/general_ledger.js | 23 ++++++++++++++++--- .../report/general_ledger/general_ledger.py | 20 ++++++++-------- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/.github/helper/semgrep_rules/report.yml b/.github/helper/semgrep_rules/report.yml index 7f3dd011dc..f2a9b16739 100644 --- a/.github/helper/semgrep_rules/report.yml +++ b/.github/helper/semgrep_rules/report.yml @@ -19,3 +19,16 @@ rules: languages: [python] severity: ERROR +- id: frappe-translated-values-in-business-logic + paths: + include: + - "**/report" + patterns: + - pattern-inside: | + {..., filters: [...], ...} + - pattern: | + {..., options: [..., __("..."), ...], ...} + message: | + Using translated values in options field will require you to translate the values while comparing in business logic. Instead of passing translated labels provide objects that contain both label and value. e.g. { label: __("Option value"), value: "Option value"} + languages: [javascript] + severity: ERROR diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 095f5eda66..b2968761c6 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -110,9 +110,26 @@ frappe.query_reports["General Ledger"] = { "fieldname":"group_by", "label": __("Group by"), "fieldtype": "Select", - "options": ["", __("Group by Voucher"), __("Group by Voucher (Consolidated)"), - __("Group by Account"), __("Group by Party")], - "default": __("Group by Voucher (Consolidated)") + "options": [ + "", + { + label: __("Group by Voucher"), + value: "Group by Voucher", + }, + { + label: __("Group by Voucher (Consolidated)"), + value: "Group by Voucher (Consolidated)", + }, + { + label: __("Group by Account"), + value: "Group by Account", + }, + { + label: __("Group by Party"), + value: "Group by Party", + }, + ], + "default": "Group by Voucher (Consolidated)" }, { "fieldname":"tax_id", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index a044518749..5bd6e583db 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -62,14 +62,14 @@ def validate_filters(filters, account_details): if not account_details.get(account): frappe.throw(_("Account {0} does not exists").format(account)) - if (filters.get("account") and filters.get("group_by") == _('Group by Account')): + if (filters.get("account") and filters.get("group_by") == 'Group by Account'): filters.account = frappe.parse_json(filters.get('account')) for account in filters.account: if account_details[account].is_group == 0: frappe.throw(_("Can not filter based on Child Account, if grouped by Account")) if (filters.get("voucher_no") - and filters.get("group_by") in [_('Group by Voucher')]): + and filters.get("group_by") in ['Group by Voucher']): frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher")) if filters.from_date > filters.to_date: @@ -153,7 +153,7 @@ def get_gl_entries(filters, accounting_dimensions): if filters.get("include_dimensions"): order_by_statement = "order by posting_date, creation" - if filters.get("group_by") == _("Group by Voucher"): + if filters.get("group_by") == "Group by Voucher": order_by_statement = "order by posting_date, voucher_type, voucher_no" if filters.get("include_default_book_entries"): @@ -312,13 +312,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension # Opening for filtered account data.append(totals.opening) - if filters.get("group_by") != _('Group by Voucher (Consolidated)'): + if filters.get("group_by") != 'Group by Voucher (Consolidated)': for acc, acc_dict in iteritems(gle_map): # acc if acc_dict.entries: # opening data.append({}) - if filters.get("group_by") != _("Group by Voucher"): + if filters.get("group_by") != "Group by Voucher": data.append(acc_dict.totals.opening) data += acc_dict.entries @@ -327,7 +327,7 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension data.append(acc_dict.totals.total) # closing - if filters.get("group_by") != _("Group by Voucher"): + if filters.get("group_by") != "Group by Voucher": data.append(acc_dict.totals.closing) data.append({}) else: @@ -357,9 +357,9 @@ def get_totals_dict(): ) def group_by_field(group_by): - if group_by == _('Group by Party'): + if group_by == 'Group by Party': return 'party' - elif group_by in [_('Group by Voucher (Consolidated)'), _('Group by Account')]: + elif group_by in ['Group by Voucher (Consolidated)', 'Group by Account']: return 'account' else: return 'voucher_no' @@ -423,9 +423,9 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): elif gle.posting_date <= to_date: update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle) update_value_in_dict(totals, 'total', gle) - if filters.get("group_by") != _('Group by Voucher (Consolidated)'): + if filters.get("group_by") != 'Group by Voucher (Consolidated)': gle_map[gle.get(group_by)].entries.append(gle) - elif filters.get("group_by") == _('Group by Voucher (Consolidated)'): + elif filters.get("group_by") == 'Group by Voucher (Consolidated)': keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")] for dim in accounting_dimensions: keylist.append(gle.get(dim)) From 9c27f9be1e0fb26166b65c96ceb3f56f82312819 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 8 Sep 2021 16:47:04 +0530 Subject: [PATCH 025/416] fix: document naming rule not working for subscription invoices (#27386) --- erpnext/accounts/doctype/subscription/subscription.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 445eb3c709..8171b3b019 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -400,6 +400,7 @@ class Subscription(Document): invoice.flags.ignore_mandatory = True + invoice.set_missing_values() invoice.save() if self.submit_invoice: From 81d3524d2747e2e3f2227053f6b0e48cb94919cc Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 8 Sep 2021 16:48:42 +0530 Subject: [PATCH 026/416] fix: auto complete sales order rows in production plan (#27352) * fix: auto complete sales order rows in production plan * fix: sider --- .../production_plan/production_plan.js | 19 +++++++++++++++++++ .../production_plan/production_plan.py | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 7b4b7c3ca3..db0f2c5dbd 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -434,6 +434,25 @@ frappe.ui.form.on("Material Request Plan Item", { } }); +frappe.ui.form.on("Production Plan Sales Order", { + sales_order(frm, cdt, cdn) { + const { sales_order } = locals[cdt][cdn]; + if (!sales_order) { + return; + } + frappe.call({ + method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details", + args: { sales_order }, + callback(r) { + const {transaction_date, customer, grand_total} = r.message; + frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date); + frappe.model.set_value(cdt, cdn, 'customer', customer); + frappe.model.set_value(cdt, cdn, 'grand_total', grand_total); + } + }); + } +}); + cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = function() { return{ filters: [ diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 91e5748964..73db29030e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -800,6 +800,12 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False): group by item_code, warehouse """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) +@frappe.whitelist() +def get_so_details(sales_order): + return frappe.db.get_value("Sales Order", sales_order, + ['transaction_date', 'customer', 'grand_total'], as_dict=1 + ) + def get_warehouse_list(warehouses): warehouse_list = [] From 295020451f54c83e711d598a67f72ecbc94fcae2 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 8 Sep 2021 19:28:30 +0530 Subject: [PATCH 027/416] fix: added delivery date filters to get sales orders in production plan (#27367) --- .../production_plan/production_plan.json | 16 ++++++- .../production_plan/production_plan.py | 45 +++++++++---------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json index b5ed28802c..56cf2b4f08 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.json +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json @@ -16,10 +16,12 @@ "customer", "warehouse", "project", + "sales_order_status", "column_break2", "from_date", "to_date", - "sales_order_status", + "from_delivery_date", + "to_delivery_date", "sales_orders_detail", "get_sales_orders", "sales_orders", @@ -358,13 +360,23 @@ "fieldname": "get_sub_assembly_items", "fieldtype": "Button", "label": "Get Sub Assembly Items" + }, + { + "fieldname": "from_delivery_date", + "fieldtype": "Date", + "label": "From Delivery Date" + }, + { + "fieldname": "to_delivery_date", + "fieldtype": "Date", + "label": "To Delivery Date" } ], "icon": "fa fa-calendar", "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-08-23 17:26:03.799876", + "modified": "2021-09-06 18:35:59.642232", "modified_by": "Administrator", "module": "Manufacturing", "name": "Production Plan", diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 73db29030e..a28fc7abf0 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -735,43 +735,42 @@ def get_material_request_items(row, sales_order, company, def get_sales_orders(self): so_filter = item_filter = "" bom_item = "bom.item = so_item.item_code" - if self.from_date: - so_filter += " and so.transaction_date >= %(from_date)s" - if self.to_date: - so_filter += " and so.transaction_date <= %(to_date)s" - if self.customer: - so_filter += " and so.customer = %(customer)s" - if self.project: - so_filter += " and so.project = %(project)s" - if self.sales_order_status: - so_filter += "and so.status = %(sales_order_status)s" + + date_field_mapper = { + 'from_date': ('>=', 'so.transaction_date'), + 'to_date': ('<=', 'so.transaction_date'), + 'from_delivery_date': ('>=', 'so_item.delivery_date'), + 'to_delivery_date': ('<=', 'so_item.delivery_date') + } + + for field, value in date_field_mapper.items(): + if self.get(field): + so_filter += f" and {value[1]} {value[0]} %({field})s" + + for field in ['customer', 'project', 'sales_order_status']: + if self.get(field): + so_field = 'status' if field == 'sales_order_status' else field + so_filter += f" and so.{so_field} = %({field})s" if self.item_code and frappe.db.exists('Item', self.item_code): bom_item = self.get_bom_item() or bom_item - item_filter += " and so_item.item_code = %(item)s" + item_filter += " and so_item.item_code = %(item_code)s" - open_so = frappe.db.sql(""" + open_so = frappe.db.sql(f""" select distinct so.name, so.transaction_date, so.customer, so.base_grand_total from `tabSales Order` so, `tabSales Order Item` so_item where so_item.parent = so.name and so.docstatus = 1 and so.status not in ("Stopped", "Closed") and so.company = %(company)s - and so_item.qty > so_item.work_order_qty {0} {1} - and (exists (select name from `tabBOM` bom where {2} + and so_item.qty > so_item.work_order_qty {so_filter} {item_filter} + and (exists (select name from `tabBOM` bom where {bom_item} and bom.is_active = 1) or exists (select name from `tabPacked Item` pi where pi.parent = so.name and pi.parent_item = so_item.item_code and exists (select name from `tabBOM` bom where bom.item=pi.item_code and bom.is_active = 1))) - """.format(so_filter, item_filter, bom_item), { - "from_date": self.from_date, - "to_date": self.to_date, - "customer": self.customer, - "project": self.project, - "item": self.item_code, - "company": self.company, - "sales_order_status": self.sales_order_status - }, as_dict=1) + """, self.as_dict(), as_dict=1) + return open_so @frappe.whitelist() From c33bbd4f399b95ab23cc1a621086a366d7be7435 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 9 Sep 2021 11:13:29 +0530 Subject: [PATCH 028/416] fix: Test Case --- .../doctype/purchase_invoice/test_purchase_invoice.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2d6ab7b293..5cbeb56232 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1230,12 +1230,14 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date): def update_tax_witholding_category(company, account): from erpnext.accounts.utils import get_fiscal_year - fiscal_year = get_fiscal_year(fiscal_year='_Test Fiscal Year 2021') + fiscal_year = get_fiscal_year(fiscal_year='2021') if not frappe.db.get_value('Tax Withholding Rate', {'parent': 'TDS - 194 - Dividends - Individual', 'from_date': ('>=', fiscal_year[1]), 'to_date': ('<=', fiscal_year[2])}): tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual') + tds_category.set('rates', []) + tds_category.append('rates', { 'from_date': fiscal_year[1], 'to_date': fiscal_year[2], From e7e2ce127116a9b0152d497ee9d11fbf9fff19f9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 9 Sep 2021 11:36:57 +0530 Subject: [PATCH 029/416] fix: Linting Issues --- .../tax_withholding_category/tax_withholding_category.py | 6 ++---- .../v13_0/update_dates_in_tax_withholding_category.py | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 33b7e475e5..fa4ea218e9 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -9,8 +9,6 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import cint, getdate -from erpnext.accounts.utils import get_fiscal_year - class TaxWithholdingCategory(Document): def validate(self): @@ -163,9 +161,9 @@ def get_tax_row_for_tds(tax_details, tax_amount): } def get_lower_deduction_certificate(tax_details, pan_no): - ldc_name = frappe.db.get_value('Lower Deduction Certificate', + ldc_name = frappe.db.get_value('Lower Deduction Certificate', { - 'pan_no': pan_no, + 'pan_no': pan_no, 'valid_from': ('>=', tax_details.from_date), 'valid_upto': ('<=', tax_details.to_date) }, 'name') diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py index 33c4942853..2af7f95412 100644 --- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -2,8 +2,10 @@ # License: GNU General Public License v3. See license.txt import frappe + from erpnext.accounts.utils import get_fiscal_year + def execute(): frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') From 3576668638c4f571e66e12b762b519abd602dde7 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 9 Sep 2021 11:57:29 +0530 Subject: [PATCH 030/416] fix: added Show Remarks checkbox in AR & AP reports (#27374) --- .../accounts_payable/accounts_payable.js | 33 +++++++----- .../accounts_receivable.js | 51 ++++++++++--------- .../accounts_receivable.py | 11 +++- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index b6c6689be0..81c60bb337 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -4,7 +4,7 @@ frappe.query_reports["Accounts Payable"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -12,19 +12,19 @@ frappe.query_reports["Accounts Payable"] = { "default": frappe.defaults.get_user_default("Company") }, { - "fieldname":"report_date", + "fieldname": "report_date", "label": __("Posting Date"), "fieldtype": "Date", "default": frappe.datetime.get_today() }, { - "fieldname":"finance_book", + "fieldname": "finance_book", "label": __("Finance Book"), "fieldtype": "Link", "options": "Finance Book" }, { - "fieldname":"cost_center", + "fieldname": "cost_center", "label": __("Cost Center"), "fieldtype": "Link", "options": "Cost Center", @@ -38,7 +38,7 @@ frappe.query_reports["Accounts Payable"] = { } }, { - "fieldname":"supplier", + "fieldname": "supplier", "label": __("Supplier"), "fieldtype": "Link", "options": "Supplier", @@ -54,48 +54,48 @@ frappe.query_reports["Accounts Payable"] = { } }, { - "fieldname":"ageing_based_on", + "fieldname": "ageing_based_on", "label": __("Ageing Based On"), "fieldtype": "Select", "options": 'Posting Date\nDue Date\nSupplier Invoice Date', "default": "Due Date" }, { - "fieldname":"range1", + "fieldname": "range1", "label": __("Ageing Range 1"), "fieldtype": "Int", "default": "30", "reqd": 1 }, { - "fieldname":"range2", + "fieldname": "range2", "label": __("Ageing Range 2"), "fieldtype": "Int", "default": "60", "reqd": 1 }, { - "fieldname":"range3", + "fieldname": "range3", "label": __("Ageing Range 3"), "fieldtype": "Int", "default": "90", "reqd": 1 }, { - "fieldname":"range4", + "fieldname": "range4", "label": __("Ageing Range 4"), "fieldtype": "Int", "default": "120", "reqd": 1 }, { - "fieldname":"payment_terms_template", + "fieldname": "payment_terms_template", "label": __("Payment Terms Template"), "fieldtype": "Link", "options": "Payment Terms Template" }, { - "fieldname":"supplier_group", + "fieldname": "supplier_group", "label": __("Supplier Group"), "fieldtype": "Link", "options": "Supplier Group" @@ -106,12 +106,17 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Check" }, { - "fieldname":"based_on_payment_terms", + "fieldname": "based_on_payment_terms", "label": __("Based On Payment Terms"), "fieldtype": "Check", }, { - "fieldname":"tax_id", + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check", + }, + { + "fieldname": "tax_id", "label": __("Tax Id"), "fieldtype": "Data", "hidden": 1 diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 1a32e2a8e0..570029851e 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -4,7 +4,7 @@ frappe.query_reports["Accounts Receivable"] = { "filters": [ { - "fieldname":"company", + "fieldname": "company", "label": __("Company"), "fieldtype": "Link", "options": "Company", @@ -12,19 +12,19 @@ frappe.query_reports["Accounts Receivable"] = { "default": frappe.defaults.get_user_default("Company") }, { - "fieldname":"report_date", + "fieldname": "report_date", "label": __("Posting Date"), "fieldtype": "Date", "default": frappe.datetime.get_today() }, { - "fieldname":"finance_book", + "fieldname": "finance_book", "label": __("Finance Book"), "fieldtype": "Link", "options": "Finance Book" }, { - "fieldname":"cost_center", + "fieldname": "cost_center", "label": __("Cost Center"), "fieldtype": "Link", "options": "Cost Center", @@ -38,7 +38,7 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname":"customer", + "fieldname": "customer", "label": __("Customer"), "fieldtype": "Link", "options": "Customer", @@ -67,66 +67,66 @@ frappe.query_reports["Accounts Receivable"] = { } }, { - "fieldname":"ageing_based_on", + "fieldname": "ageing_based_on", "label": __("Ageing Based On"), "fieldtype": "Select", "options": 'Posting Date\nDue Date', "default": "Due Date" }, { - "fieldname":"range1", + "fieldname": "range1", "label": __("Ageing Range 1"), "fieldtype": "Int", "default": "30", "reqd": 1 }, { - "fieldname":"range2", + "fieldname": "range2", "label": __("Ageing Range 2"), "fieldtype": "Int", "default": "60", "reqd": 1 }, { - "fieldname":"range3", + "fieldname": "range3", "label": __("Ageing Range 3"), "fieldtype": "Int", "default": "90", "reqd": 1 }, { - "fieldname":"range4", + "fieldname": "range4", "label": __("Ageing Range 4"), "fieldtype": "Int", "default": "120", "reqd": 1 }, { - "fieldname":"customer_group", + "fieldname": "customer_group", "label": __("Customer Group"), "fieldtype": "Link", "options": "Customer Group" }, { - "fieldname":"payment_terms_template", + "fieldname": "payment_terms_template", "label": __("Payment Terms Template"), "fieldtype": "Link", "options": "Payment Terms Template" }, { - "fieldname":"sales_partner", + "fieldname": "sales_partner", "label": __("Sales Partner"), "fieldtype": "Link", "options": "Sales Partner" }, { - "fieldname":"sales_person", + "fieldname": "sales_person", "label": __("Sales Person"), "fieldtype": "Link", "options": "Sales Person" }, { - "fieldname":"territory", + "fieldname": "territory", "label": __("Territory"), "fieldtype": "Link", "options": "Territory" @@ -137,45 +137,50 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Check" }, { - "fieldname":"based_on_payment_terms", + "fieldname": "based_on_payment_terms", "label": __("Based On Payment Terms"), "fieldtype": "Check", }, { - "fieldname":"show_future_payments", + "fieldname": "show_future_payments", "label": __("Show Future Payments"), "fieldtype": "Check", }, { - "fieldname":"show_delivery_notes", + "fieldname": "show_delivery_notes", "label": __("Show Linked Delivery Notes"), "fieldtype": "Check", }, { - "fieldname":"show_sales_person", + "fieldname": "show_sales_person", "label": __("Show Sales Person"), "fieldtype": "Check", }, { - "fieldname":"tax_id", + "fieldname": "show_remarks", + "label": __("Show Remarks"), + "fieldtype": "Check", + }, + { + "fieldname": "tax_id", "label": __("Tax Id"), "fieldtype": "Data", "hidden": 1 }, { - "fieldname":"customer_name", + "fieldname": "customer_name", "label": __("Customer Name"), "fieldtype": "Data", "hidden": 1 }, { - "fieldname":"payment_terms", + "fieldname": "payment_terms", "label": __("Payment Tems"), "fieldtype": "Data", "hidden": 1 }, { - "fieldname":"credit_limit", + "fieldname": "credit_limit", "label": __("Credit Limit"), "fieldtype": "Currency", "hidden": 1 diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index e91fdf27cd..7f8eadea16 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -106,6 +106,7 @@ class ReceivablePayableReport(object): party = gle.party, posting_date = gle.posting_date, account_currency = gle.account_currency, + remarks = gle.remarks if self.filters.get("show_remarks") else None, invoiced = 0.0, paid = 0.0, credit_note = 0.0, @@ -583,10 +584,12 @@ class ReceivablePayableReport(object): else: select_fields = "debit, credit" + remarks = ", remarks" if self.filters.get("show_remarks") else "" + self.gl_entries = frappe.db.sql(""" select name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center, - against_voucher_type, against_voucher, account_currency, {0} + against_voucher_type, against_voucher, account_currency, {0} {remarks} from `tabGL Entry` where @@ -595,7 +598,7 @@ class ReceivablePayableReport(object): and party_type=%s and (party is not null and party != '') {1} {2} {3}""" - .format(select_fields, date_condition, conditions, order_by), values, as_dict=True) + .format(select_fields, date_condition, conditions, order_by, remarks=remarks), values, as_dict=True) def get_sales_invoices_or_customers_based_on_sales_person(self): if self.filters.get("sales_person"): @@ -754,6 +757,10 @@ class ReceivablePayableReport(object): self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data') self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link', options='voucher_type', width=180) + + if self.filters.show_remarks: + self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200), + self.add_column(label='Due Date', fieldtype='Date') if self.party_type == "Supplier": From 9670490a1d4ab282fb4293f5dc19a380e11b9f0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Sep 2021 12:21:47 +0530 Subject: [PATCH 031/416] chore(deps): bump axios from 0.21.1 to 0.21.4 (#27402) Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.4. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.4) --- updated-dependencies: - dependency-name: axios dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0a49c52f0e..75b281509d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -677,11 +677,11 @@ async@^3.2.0: integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== axios@^0.21.1: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== + version "0.21.4" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" + integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: - follow-redirects "^1.10.0" + follow-redirects "^1.14.0" balanced-match@^1.0.0: version "1.0.0" @@ -1269,10 +1269,10 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -follow-redirects@^1.10.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.0.tgz#f5d260f95c5f8c105894491feee5dc8993b402fe" - integrity sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg== +follow-redirects@^1.14.0: + version "1.14.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" + integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== fs-constants@^1.0.0: version "1.0.0" From 678335f8ac290c9e519d8483bd70baa46285cb8a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 9 Sep 2021 12:24:23 +0530 Subject: [PATCH 032/416] fix: job card overlap unknown column `jc.employee` (#27403) * fix: incorrect query for job card overlap * test: employee overlap in job cards * test: simplify/refactor job card tests --- .../doctype/job_card/job_card.py | 2 +- .../doctype/job_card/test_job_card.py | 114 ++++++++++-------- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 3bf9de2708..ceae63cb94 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -91,7 +91,7 @@ class JobCard(Document): if args.get("employee"): # override capacity for employee production_capacity = 1 - validate_overlap_for = " and jc.employee = %(employee)s " + validate_overlap_for = " and jctl.employee = %(employee)s " extra_cond = '' if check_next_available_slot: diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index db0e08fcd0..80295bba63 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -8,71 +8,91 @@ import unittest import frappe from frappe.utils import random_string -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError +from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation class TestJobCard(unittest.TestCase): + + def setUp(self): + self.work_order = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + def tearDown(self): + frappe.db.rollback() + def test_job_card(self): - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) - if data: - bom, bom_item = data + job_cards = frappe.get_all('Job Card', + filters = {'work_order': self.work_order.name}, fields = ["operation_id", "name"]) - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + if job_cards: + job_card = job_cards[0] + frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id) - job_cards = frappe.get_all('Job Card', - filters = {'work_order': work_order.name}, fields = ["operation_id", "name"]) + doc = frappe.get_doc("Job Card", job_card.name) + doc.operation_id = "Test Data" + self.assertRaises(OperationMismatchError, doc.save) - if job_cards: - job_card = job_cards[0] - frappe.db.set_value("Job Card", job_card.name, "operation_row_number", job_card.operation_id) - - doc = frappe.get_doc("Job Card", job_card.name) - doc.operation_id = "Test Data" - self.assertRaises(OperationMismatchError, doc.save) - - for d in job_cards: - frappe.delete_doc("Job Card", d.name) + for d in job_cards: + frappe.delete_doc("Job Card", d.name) def test_job_card_with_different_work_station(self): - data = frappe.get_cached_value('BOM', - {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + job_cards = frappe.get_all('Job Card', + filters = {'work_order': self.work_order.name}, + fields = ["operation_id", "workstation", "name", "for_quantity"]) - if data: - bom, bom_item = data + job_card = job_cards[0] - work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + if job_card: + workstation = frappe.db.get_value("Workstation", + {"name": ("not in", [job_card.workstation])}, "name") - job_cards = frappe.get_all('Job Card', - filters = {'work_order': work_order.name}, - fields = ["operation_id", "workstation", "name", "for_quantity"]) + if not workstation or job_card.workstation == workstation: + workstation = make_workstation(workstation_name=random_string(5)).name - job_card = job_cards[0] + doc = frappe.get_doc("Job Card", job_card.name) + doc.workstation = workstation + doc.append("time_logs", { + "from_time": "2009-01-01 12:06:25", + "to_time": "2009-01-01 12:37:25", + "time_in_mins": "31.00002", + "completed_qty": job_card.for_quantity + }) + doc.submit() - if job_card: - workstation = frappe.db.get_value("Workstation", - {"name": ("not in", [job_card.workstation])}, "name") + completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty") + self.assertEqual(completed_qty, job_card.for_quantity) - if not workstation or job_card.workstation == workstation: - workstation = make_workstation(workstation_name=random_string(5)).name - - doc = frappe.get_doc("Job Card", job_card.name) - doc.workstation = workstation - doc.append("time_logs", { - "from_time": "2009-01-01 12:06:25", - "to_time": "2009-01-01 12:37:25", - "time_in_mins": "31.00002", - "completed_qty": job_card.for_quantity - }) - doc.submit() - - completed_qty = frappe.db.get_value("Work Order Operation", job_card.operation_id, "completed_qty") - self.assertEqual(completed_qty, job_card.for_quantity) - - doc.cancel() + doc.cancel() for d in job_cards: frappe.delete_doc("Job Card", d.name) + + def test_job_card_overlap(self): + wo2 = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + + jc1_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + jc2_name = frappe.db.get_value("Job Card", {'work_order': wo2.name}) + + jc1 = frappe.get_doc("Job Card", jc1_name) + jc2 = frappe.get_doc("Job Card", jc2_name) + + employee = "_T-Employee-00001" # from test records + + jc1.append("time_logs", { + "from_time": "2021-01-01 00:00:00", + "to_time": "2021-01-01 08:00:00", + "completed_qty": 1, + "employee": employee, + }) + jc1.save() + + # add a new entry in same time slice + jc2.append("time_logs", { + "from_time": "2021-01-01 00:01:00", + "to_time": "2021-01-01 06:00:00", + "completed_qty": 1, + "employee": employee, + }) + self.assertRaises(OverlapError, jc2.save) From 518f827d8721a67cf0ea00455435b03e35ca1b30 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 9 Sep 2021 12:52:45 +0530 Subject: [PATCH 033/416] chore: remove snyk from devdependencies --- package.json | 15 +- yarn.lock | 3549 -------------------------------------------------- 2 files changed, 4 insertions(+), 3560 deletions(-) diff --git a/package.json b/package.json index 5bc1e56a21..6c11e9dddc 100644 --- a/package.json +++ b/package.json @@ -11,16 +11,9 @@ "bugs": { "url": "https://github.com/frappe/erpnext/issues" }, - "devDependencies": { - "snyk": "^1.518.0" - }, + "devDependencies": {}, "dependencies": { - "onscan.js": "^1.5.2", - "html2canvas": "^1.1.4" - }, - "scripts": { - "snyk-protect": "snyk protect", - "prepare": "yarn run snyk-protect" - }, - "snyk": true + "html2canvas": "^1.1.4", + "onscan.js": "^1.5.2" + } } diff --git a/yarn.lock b/yarn.lock index 75b281509d..8e5d1bd1c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,1006 +2,11 @@ # yarn lockfile v1 -"@arcanis/slice-ansi@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@arcanis/slice-ansi/-/slice-ansi-1.0.2.tgz#35331e41a1062e3c53c01ad2ec1555c5c1959d8f" - integrity sha512-lDL63z0W/L/WTgqrwVOuNyMAsTv+pvjybd21z9SWdStmQoXT59E/iVWwat3gYjcdTNBf6oHAMoyFm8dtjpXEYw== - dependencies: - grapheme-splitter "^1.0.4" - -"@deepcode/dcignore@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@deepcode/dcignore/-/dcignore-1.0.2.tgz#39e4a3df7dde8811925330506e4bb3fbf3c288d8" - integrity sha512-DPgxtHuJwBORpqRkPXzzOT+uoPRVJmaN7LR+pmeL6DQM90kj6G6GFUH1i/YpRH8NbML8ZGEDwB9f9u4UwD2pzg== - -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== - dependencies: - "@nodelib/fs.stat" "2.0.4" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== - dependencies: - "@nodelib/fs.scandir" "2.1.4" - fastq "^1.6.0" - -"@octetstream/promisify@2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@octetstream/promisify/-/promisify-2.0.2.tgz#29ac3bd7aefba646db670227f895d812c1a19615" - integrity sha512-7XHoRB61hxsz8lBQrjC1tq/3OEIgpvGWg6DKAdwi7WRzruwkmsdwmOoUXbU4Dtd4RSOMDwed0SkP3y8UlMt1Bg== - -"@open-policy-agent/opa-wasm@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@open-policy-agent/opa-wasm/-/opa-wasm-1.2.0.tgz#481b766093f70b00efefbee1e4192f375fd34ca2" - integrity sha512-CtUBTnzvDrT0NASa8IuGQTxFGgt2vxbLnMYuTA+uDFxOcA4uK4mGFgrhHJtxUZnWHiwemOvKKSY3BMCo7qiAsQ== - dependencies: - sprintf-js "^1.1.2" - utf8 "^3.0.0" - -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@sindresorhus/is@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-2.1.1.tgz#ceff6a28a5b4867c2dd4a1ba513de278ccbe8bb1" - integrity sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg== - -"@sindresorhus/is@^4.0.0": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" - integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== - -"@snyk/cli-interface@2.11.0", "@snyk/cli-interface@^2.11.0", "@snyk/cli-interface@^2.9.1", "@snyk/cli-interface@^2.9.2": - version "2.11.0" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.11.0.tgz#9df68c8cd54de5dff69f0ab797a188541d9c8965" - integrity sha512-T3xfDqrEFKclHGdJx4/5+D5F7e76/99f33guE4RTlVITBhy7VVnjz4t/NDr3UYqcC0MgAmiC4bSVYHnlshuwJw== - dependencies: - "@types/graphlib" "^2" - -"@snyk/cli-interface@^2.0.3": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@snyk/cli-interface/-/cli-interface-2.3.1.tgz#73f2f4bd717b9f03f096ede3ff5830eb8d2f3716" - integrity sha512-JZvsmhDXSyjv1dkc12lPI3tNTNYlIaOiIQMYFg2RgqF3QmWjTyBUgRZcF7LoKyufHtS4dIudM6k1aHBpSaDrhw== - dependencies: - tslib "^1.9.3" - -"@snyk/cloud-config-parser@^1.9.2": - version "1.9.2" - resolved "https://registry.yarnpkg.com/@snyk/cloud-config-parser/-/cloud-config-parser-1.9.2.tgz#e6c8e575db8527b33cf1ba766f86e1b3414cf6e1" - integrity sha512-m8Y2+3l4fxj96QMrTfiCEaXgCpDkCkJIX/5wv0V0RHuxpUiyh+KxC2yJ8Su4wybBj6v6hB9hB7h5/L+Gy4V4PA== - dependencies: - esprima "^4.0.1" - tslib "^1.10.0" - yaml-js "^0.3.0" - -"@snyk/cocoapods-lockfile-parser@3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@snyk/cocoapods-lockfile-parser/-/cocoapods-lockfile-parser-3.6.2.tgz#803ae9466f408c48ba7c5a8ec51b9dbac6f633b3" - integrity sha512-ca2JKOnSRzYHJkhOB9gYmdRZHmd02b/uBd/S0D5W+L9nIMS7sUBV5jfhKwVgrYPIpVNIc0XCI9rxK4TfkQRpiA== - dependencies: - "@snyk/dep-graph" "^1.23.1" - "@types/js-yaml" "^3.12.1" - js-yaml "^3.13.1" - tslib "^1.10.0" - -"@snyk/code-client@3.4.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@snyk/code-client/-/code-client-3.4.1.tgz#b9d025897cd586e0aef903162ac0407d0bffc3cd" - integrity sha512-XJ7tUdX1iQyzN/BmHac7p+Oyw1SyTcqSkCNExwBJxyQdlnUAKK6QKIWLXS81tTpZ79FgCdT+0fdS0AjsyS99eA== - dependencies: - "@deepcode/dcignore" "^1.0.2" - "@snyk/fast-glob" "^3.2.6-patch" - "@types/flat-cache" "^2.0.0" - "@types/lodash.chunk" "^4.2.6" - "@types/lodash.omit" "^4.5.6" - "@types/lodash.union" "^4.6.6" - "@types/micromatch" "^4.0.1" - "@types/sarif" "^2.1.3" - "@types/uuid" "^8.3.0" - axios "^0.21.1" - ignore "^5.1.8" - lodash.chunk "^4.2.0" - lodash.omit "^4.5.0" - lodash.union "^4.6.0" - micromatch "^4.0.2" - queue "^6.0.1" - uuid "^8.3.2" - -"@snyk/composer-lockfile-parser@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@snyk/composer-lockfile-parser/-/composer-lockfile-parser-1.4.1.tgz#2f7c93ad367520322b16d9490a208fec08445e0e" - integrity sha512-wNANv235j95NFsQuODIXCiQZ9kcyg9fz92Kg1zoGvaP3kN/ma7fgCnvQL/dyml6iouQJR5aZovjhrrfEFoKtiQ== - dependencies: - lodash.findkey "^4.6.0" - lodash.get "^4.4.2" - lodash.invert "^4.3.0" - lodash.isempty "^4.4.0" - -"@snyk/dep-graph@^1.19.3", "@snyk/dep-graph@^1.21.0", "@snyk/dep-graph@^1.23.0", "@snyk/dep-graph@^1.23.1", "@snyk/dep-graph@^1.27.1", "@snyk/dep-graph@^1.28.0": - version "1.28.0" - resolved "https://registry.yarnpkg.com/@snyk/dep-graph/-/dep-graph-1.28.0.tgz#d68c0576cb3562c6e819ca8a8c7ac29ee11d9776" - integrity sha512-Oup9nAvb558jdNvbZah/vaBtOtCcizkdeS+OBQeBIqIffyer4mc4juSn4b1SFjCpu7AG7piio8Lj8k1B9ps6Tg== - dependencies: - event-loop-spinner "^2.1.0" - lodash.clone "^4.5.0" - lodash.constant "^3.0.0" - lodash.filter "^4.6.0" - lodash.foreach "^4.5.0" - lodash.isempty "^4.4.0" - lodash.isequal "^4.5.0" - lodash.isfunction "^3.0.9" - lodash.isundefined "^3.0.1" - lodash.keys "^4.2.0" - lodash.map "^4.6.0" - lodash.reduce "^4.6.0" - lodash.size "^4.2.0" - lodash.transform "^4.6.0" - lodash.union "^4.6.0" - lodash.values "^4.3.0" - object-hash "^2.0.3" - semver "^7.0.0" - tslib "^1.13.0" - -"@snyk/docker-registry-v2-client@1.13.9": - version "1.13.9" - resolved "https://registry.yarnpkg.com/@snyk/docker-registry-v2-client/-/docker-registry-v2-client-1.13.9.tgz#54c2e3071de58fc6fc12c5fef5eaeae174ecda12" - integrity sha512-DIFLEhr8m1GrAwsLGInJmpcQMacjuhf3jcbpQTR+LeMvZA9IuKq+B7kqw2O2FzMiHMZmUb5z+tV+BR7+IUHkFQ== - dependencies: - needle "^2.5.0" - parse-link-header "^1.0.1" - tslib "^1.10.0" - -"@snyk/fast-glob@^3.2.6-patch": - version "3.2.6-patch" - resolved "https://registry.yarnpkg.com/@snyk/fast-glob/-/fast-glob-3.2.6-patch.tgz#a0866bedb17f95255e4050dad08daeaff0f4caa8" - integrity sha512-E/Pfdze/WFfxwyuTFcfhQN1SwyUsc43yuCoW63RVBCaxTD6OzhVD2Pvc/Sy7BjiWUfmelzyKkIBpoow8zZX7Zg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - "@snyk/glob-parent" "^5.1.2-patch.1" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -"@snyk/fix@1.554.0": - version "1.554.0" - resolved "https://registry.yarnpkg.com/@snyk/fix/-/fix-1.554.0.tgz#7ae786882e0ffea5e7f10d0b41e3d593b65555c4" - integrity sha512-q2eRVStgspPeI2wZ2EQGLpiWZMRg7o+4tsCk6m/kHZgQGDN4Bb7L3xslFW3OgF0+ZksYSaHl2cW2HmGiLRaYcA== - dependencies: - "@snyk/dep-graph" "^1.21.0" - chalk "4.1.0" - debug "^4.3.1" - ora "5.3.0" - p-map "^4.0.0" - strip-ansi "6.0.0" - -"@snyk/gemfile@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@snyk/gemfile/-/gemfile-1.2.0.tgz#919857944973cce74c650e5428aaf11bcd5c0457" - integrity sha512-nI7ELxukf7pT4/VraL4iabtNNMz8mUo7EXlqCFld8O5z6mIMLX9llps24iPpaIZOwArkY3FWA+4t+ixyvtTSIA== - -"@snyk/glob-parent@^5.1.2-patch.1": - version "5.1.2-patch.1" - resolved "https://registry.yarnpkg.com/@snyk/glob-parent/-/glob-parent-5.1.2-patch.1.tgz#87733b4ab282043fa7915200bc94cb391df6d44f" - integrity sha512-OkUPdHgxIWKAAzceG1nraNA0kgI+eS0I9wph8tll9UL0slD2mIWSj4mAqroGovaEXm8nHedoUfuDRGEb6wnzCQ== - dependencies: - is-glob "^4.0.1" - -"@snyk/graphlib@2.1.9-patch.3", "@snyk/graphlib@^2.1.9-patch.3": - version "2.1.9-patch.3" - resolved "https://registry.yarnpkg.com/@snyk/graphlib/-/graphlib-2.1.9-patch.3.tgz#b8edb2335af1978db7f3cb1f28f5d562960acf23" - integrity sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q== - dependencies: - lodash.clone "^4.5.0" - lodash.constant "^3.0.0" - lodash.filter "^4.6.0" - lodash.foreach "^4.5.0" - lodash.has "^4.5.2" - lodash.isempty "^4.4.0" - lodash.isfunction "^3.0.9" - lodash.isundefined "^3.0.1" - lodash.keys "^4.2.0" - lodash.map "^4.6.0" - lodash.reduce "^4.6.0" - lodash.size "^4.2.0" - lodash.transform "^4.6.0" - lodash.union "^4.6.0" - lodash.values "^4.3.0" - -"@snyk/inquirer@^7.3.3-patch": - version "7.3.3-patch" - resolved "https://registry.yarnpkg.com/@snyk/inquirer/-/inquirer-7.3.3-patch.tgz#ef84d531724c53b755e8dd454e1a3c2ccdcfc0bf" - integrity sha512-aWiQSOacH2lOpJ1ard9ErABcH4tdJogdr+mg1U67iZJOPO9n2gFgAwz1TQJDyPkv4/A5mh4hT2rg03Uq+KBn2Q== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash.assign "^4.2.0" - lodash.assignin "^4.2.0" - lodash.clone "^4.5.0" - lodash.defaults "^4.2.0" - lodash.filter "^4.6.0" - lodash.find "^4.6.0" - lodash.findindex "^4.6.0" - lodash.flatten "^4.4.0" - lodash.isboolean "^3.0.3" - lodash.isfunction "^3.0.9" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.last "^3.0.0" - lodash.map "^4.6.0" - lodash.omit "^4.5.0" - lodash.set "^4.3.2" - lodash.sum "^4.0.2" - lodash.uniq "^4.5.0" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -"@snyk/java-call-graph-builder@1.19.1": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.19.1.tgz#1d579d782df3bb5f9d5171cc35180596cd90aa8b" - integrity sha512-bxjHef5Qm3pNc+BrFlxMudmSSbOjA395ZqBddc+dvsFHoHeyNbiY56Y1JSGUlTgjRM+PKNPBiCuELTSMaROeZg== - dependencies: - "@snyk/graphlib" "2.1.9-patch.3" - ci-info "^2.0.0" - debug "^4.1.1" - glob "^7.1.6" - jszip "^3.2.2" - needle "^2.3.3" - progress "^2.0.3" - snyk-config "^4.0.0-rc.2" - source-map-support "^0.5.7" - temp-dir "^2.0.0" - tmp "^0.2.1" - tslib "^1.9.3" - xml-js "^1.6.11" - -"@snyk/java-call-graph-builder@1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@snyk/java-call-graph-builder/-/java-call-graph-builder-1.20.0.tgz#ffca734cf7ce276a69277963149358190eaac3e5" - integrity sha512-NX8bpIu7oG5cuSSm6WvtxqcCuJs2gRjtKhtuSeF1p5TYXyESs3FXQ0nHjfY90LiyTTc+PW/UBq6SKbBA6bCBww== - dependencies: - "@snyk/graphlib" "2.1.9-patch.3" - ci-info "^2.0.0" - debug "^4.1.1" - glob "^7.1.6" - jszip "^3.2.2" - needle "^2.3.3" - progress "^2.0.3" - snyk-config "^4.0.0-rc.2" - source-map-support "^0.5.7" - temp-dir "^2.0.0" - tmp "^0.2.1" - tslib "^1.9.3" - xml-js "^1.6.11" - -"@snyk/mix-parser@^1.1.1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@snyk/mix-parser/-/mix-parser-1.3.2.tgz#930de1d9c3a91e20660751f78c3e6f6a88ac5b2b" - integrity sha512-0Aq9vcgmjH0d9Gk5q0k6l4ZOvSHPf6/BCQGDVOpKp0hwOkXWnpDOLLPxL+uBCktuH9zTYQFB0aTk91kQImZqmA== - dependencies: - "@snyk/dep-graph" "^1.28.0" - tslib "^2.0.0" - -"@snyk/rpm-parser@^2.0.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@snyk/rpm-parser/-/rpm-parser-2.2.1.tgz#b61ccf5478684b203576bd2be68de434ccbb0069" - integrity sha512-OAON0bPf3c5fgM/GK9DX0aZErB6SnuRyYlPH0rqI1TXGsKrYnVELhaE6ctNbEfPTQuY9r6q0vM+UYDaFM/YliA== - dependencies: - event-loop-spinner "^2.0.0" - -"@snyk/snyk-cocoapods-plugin@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@snyk/snyk-cocoapods-plugin/-/snyk-cocoapods-plugin-2.5.2.tgz#cd724fcd637cb3af76187bf7254819b6079489f6" - integrity sha512-WHhnwyoGOhjFOjBXqUfszD84SErrtjHjium/4xFbqKpEE+yuwxs8OwV/S29BtxhYiGtjpD1azv5QtH30VUMl0A== - dependencies: - "@snyk/cli-interface" "^2.11.0" - "@snyk/cocoapods-lockfile-parser" "3.6.2" - "@snyk/dep-graph" "^1.23.1" - source-map-support "^0.5.7" - tslib "^2.0.0" - -"@snyk/snyk-docker-pull@3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@snyk/snyk-docker-pull/-/snyk-docker-pull-3.2.3.tgz#9743ea624098c7abd0f95c438c76067530494f4b" - integrity sha512-hiFiSmWGLc2tOI7FfgIhVdFzO2f69im8O6p3OV4xEZ/Ss1l58vwtqudItoswsk7wj/azRlgfBW8wGu2MjoudQg== - dependencies: - "@snyk/docker-registry-v2-client" "1.13.9" - child-process "^1.0.2" - tar-stream "^2.1.2" - tmp "^0.1.0" - -"@snyk/snyk-hex-plugin@1.1.4": - version "1.1.4" - resolved "https://registry.yarnpkg.com/@snyk/snyk-hex-plugin/-/snyk-hex-plugin-1.1.4.tgz#4a5b1684cecc1a557ec1a9f5f8646683ae89f0da" - integrity sha512-kLfFGckSmyKe667UGPyWzR/H7/Trkt4fD8O/ktElOx1zWgmivpLm0Symb4RCfEmz9irWv+N6zIKRrfSNdytcPQ== - dependencies: - "@snyk/dep-graph" "^1.28.0" - "@snyk/mix-parser" "^1.1.1" - debug "^4.3.1" - tmp "^0.0.33" - tslib "^2.0.0" - upath "2.0.1" - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - -"@szmarczak/http-timer@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" - integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== - dependencies: - defer-to-connect "^2.0.0" - -"@types/braces@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" - integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw== - -"@types/cacheable-request@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" - integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "*" - "@types/node" "*" - "@types/responselike" "*" - -"@types/debug@^4.1.4": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" - integrity sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ== - -"@types/emscripten@^1.38.0": - version "1.39.4" - resolved "https://registry.yarnpkg.com/@types/emscripten/-/emscripten-1.39.4.tgz#d61990c0cee72c4e475de737a140b51fe925a2c8" - integrity sha512-k3LLVMFrdNA9UCvMDPWMbFrGPNb+GcPyw29ktJTo1RCN7RmxFG5XzPZcPKRlnLuLT/FRm8wp4ohvDwNY7GlROQ== - -"@types/flat-cache@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/flat-cache/-/flat-cache-2.0.0.tgz#64e5d3b426c392b603a208a55bdcc7d920ce6e57" - integrity sha512-fHeEsm9hvmZ+QHpw6Fkvf19KIhuqnYLU6vtWLjd5BsMd/qVi7iTkMioDZl0mQmfNRA1A6NwvhrSRNr9hGYZGww== - -"@types/graphlib@^2": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@types/graphlib/-/graphlib-2.1.7.tgz#e6a47a4f43511f5bad30058a669ce5ce93bfd823" - integrity sha512-K7T1n6U2HbTYu+SFHlBjz/RH74OA2D/zF1qlzn8uXbvB4uRg7knOM85ugS2bbXI1TXMh7rLqk4OVRwIwEBaixg== - -"@types/http-cache-semantics@*": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" - integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== - -"@types/js-yaml@^3.12.1": - version "3.12.2" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a" - integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug== - -"@types/keyv@*": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" - integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== - dependencies: - "@types/node" "*" - -"@types/lodash.chunk@^4.2.6": - version "4.2.6" - resolved "https://registry.yarnpkg.com/@types/lodash.chunk/-/lodash.chunk-4.2.6.tgz#9d35f05360b0298715d7f3d9efb34dd4f77e5d2a" - integrity sha512-SPlusB7jxXyGcTXYcUdWr7WmhArO/rmTq54VN88iKMxGUhyg79I4Q8n4riGn3kjaTjOJrVlHhxgX/d7woak5BQ== - dependencies: - "@types/lodash" "*" - -"@types/lodash.omit@^4.5.6": - version "4.5.6" - resolved "https://registry.yarnpkg.com/@types/lodash.omit/-/lodash.omit-4.5.6.tgz#f2a9518259e481a48ff7ec423420fa8fd58933e2" - integrity sha512-KXPpOSNX2h0DAG2w7ajpk7TXvWF28ZHs5nJhOJyP0BQHkehgr948RVsToItMme6oi0XJkp19CbuNXkIX8FiBlQ== - dependencies: - "@types/lodash" "*" - -"@types/lodash.union@^4.6.6": - version "4.6.6" - resolved "https://registry.yarnpkg.com/@types/lodash.union/-/lodash.union-4.6.6.tgz#2f77f2088326ed147819e9e384182b99aae8d4b0" - integrity sha512-Wu0ZEVNcyCz8eAn6TlUbYWZoGbH9E+iOHxAZbwUoCEXdUiy6qpcz5o44mMXViM4vlPLLCPlkAubEP1gokoSZaw== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.168" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" - integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== - -"@types/micromatch@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/micromatch/-/micromatch-4.0.1.tgz#9381449dd659fc3823fd2a4190ceacc985083bc7" - integrity sha512-my6fLBvpY70KattTNzYOK6KU1oR1+UCz9ug/JbcF5UrEmeCt9P7DV2t7L8+t18mMPINqGQCE4O8PLOPbI84gxw== - dependencies: - "@types/braces" "*" - -"@types/node@*": - version "13.5.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.3.tgz#37f1f539b7535b9fb4ef77d59db1847a837b7f17" - integrity sha512-ZPnWX9PW992w6DUsz3JIXHaSb5v7qmKCVzC3km6SxcDGxk7zmLfYaCJTbktIa5NeywJkkZDhGldKqDIvC5DRrA== - -"@types/node@^13.7.0": - version "13.13.50" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.50.tgz#bc8ebf70c392a98ffdba7aab9b46989ea96c1c62" - integrity sha512-y7kkh+hX/0jZNxMyBR/6asG0QMSaPSzgeVK63dhWHl4QAXCQB8lExXmzLL6SzmOgKHydtawpMnNhlDbv7DXPEA== - -"@types/responselike@*", "@types/responselike@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" - integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== - dependencies: - "@types/node" "*" - -"@types/sarif@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@types/sarif/-/sarif-2.1.3.tgz#1f9c16033f1461536ac014284920350109614c02" - integrity sha512-zf+EoIplTkQW2TV2mwtJtlI0g540Z3Rs9tX9JqRAtyjnDCqkP+eMTgWCj3PGNbQpi+WXAjvC3Ou/dvvX2sLK4w== - -"@types/semver@^7.1.0": - version "7.3.4" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb" - integrity sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ== - -"@types/treeify@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/treeify/-/treeify-1.0.0.tgz#f04743cb91fc38254e8585d692bd92503782011c" - integrity sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg== - -"@types/uuid@^8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" - integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== - -"@yarnpkg/core@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/core/-/core-2.4.0.tgz#b5d8cc7ee2ddb022816c7afa3f83c3ee3d317c80" - integrity sha512-FYjcPNTfDfMKLFafQPt49EY28jnYC82Z2S7oMwLPUh144BL8v8YXzb4aCnFyi5nFC5h2kcrJfZh7+Pm/qvCqGw== - dependencies: - "@arcanis/slice-ansi" "^1.0.2" - "@types/semver" "^7.1.0" - "@types/treeify" "^1.0.0" - "@yarnpkg/fslib" "^2.4.0" - "@yarnpkg/json-proxy" "^2.1.0" - "@yarnpkg/libzip" "^2.2.1" - "@yarnpkg/parsers" "^2.3.0" - "@yarnpkg/pnp" "^2.3.2" - "@yarnpkg/shell" "^2.4.1" - binjumper "^0.1.4" - camelcase "^5.3.1" - chalk "^3.0.0" - ci-info "^2.0.0" - clipanion "^2.6.2" - cross-spawn "7.0.3" - diff "^4.0.1" - globby "^11.0.1" - got "^11.7.0" - json-file-plus "^3.3.1" - lodash "^4.17.15" - micromatch "^4.0.2" - mkdirp "^0.5.1" - p-limit "^2.2.0" - pluralize "^7.0.0" - pretty-bytes "^5.1.0" - semver "^7.1.2" - stream-to-promise "^2.2.0" - tar-stream "^2.0.1" - treeify "^1.1.0" - tslib "^1.13.0" - tunnel "^0.0.6" - -"@yarnpkg/fslib@^2.1.0", "@yarnpkg/fslib@^2.4.0": - version "2.4.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/fslib/-/fslib-2.4.0.tgz#a265b737cd089ef293ad964e06c143f5efd411a9" - integrity sha512-CwffYY9owtl3uImNOn1K4jl5iIb/L16a9UZ9Q3lkBARk6tlUsPrNFX00eoUlFcLn49TTfd3zdN6higloGCyncw== - dependencies: - "@yarnpkg/libzip" "^2.2.1" - tslib "^1.13.0" - -"@yarnpkg/json-proxy@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/json-proxy/-/json-proxy-2.1.0.tgz#362a161678cd7dda74b47b4fc848a2f1730d16cd" - integrity sha512-rOgCg2DkyviLgr80mUMTt9vzdf5RGOujQB26yPiXjlz4WNePLBshKlTNG9rKSoKQSOYEQcw6cUmosfOKDatrCw== - dependencies: - "@yarnpkg/fslib" "^2.1.0" - tslib "^1.13.0" - -"@yarnpkg/libzip@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@yarnpkg/libzip/-/libzip-2.2.1.tgz#61c9b8b2499ee6bd9c4fcbf8248f68e07bd89948" - integrity sha512-AYDJXrkzayoDd3ZlVgFJ+LyDX+Zj/cki3vxIpcYxejtgkl3aquVWOxlC0DD9WboBWsJFIP1MjrUbchLyh++/7A== - dependencies: - "@types/emscripten" "^1.38.0" - tslib "^1.13.0" - -"@yarnpkg/lockfile@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" - integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== - -"@yarnpkg/parsers@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@yarnpkg/parsers/-/parsers-2.3.0.tgz#7b9564c6df02f4921d5cfe8287c4b648e93ea84b" - integrity sha512-qgz0QUgOvnhtF92kaluIhIIKBUHlYlHUBQxqh5v9+sxEQvUeF6G6PKiFlzo3E6O99XwvNEGpVu1xZPoSGyGscQ== - dependencies: - js-yaml "^3.10.0" - tslib "^1.13.0" - -"@yarnpkg/pnp@^2.3.2": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@yarnpkg/pnp/-/pnp-2.3.2.tgz#9a052a06bf09c9f0b7c31e0867a7e725cb6401ed" - integrity sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA== - dependencies: - "@types/node" "^13.7.0" - "@yarnpkg/fslib" "^2.4.0" - tslib "^1.13.0" - -"@yarnpkg/shell@^2.4.1": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@yarnpkg/shell/-/shell-2.4.1.tgz#abc557f8924987c9c382703e897433a82780265d" - integrity sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g== - dependencies: - "@yarnpkg/fslib" "^2.4.0" - "@yarnpkg/parsers" "^2.3.0" - clipanion "^2.6.2" - cross-spawn "7.0.3" - fast-glob "^3.2.2" - micromatch "^4.0.2" - stream-buffers "^3.0.2" - tslib "^1.13.0" - -abbrev@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ansi-align@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.0.tgz#b536b371cf687caaef236c18d3e21fe3797467cb" - integrity sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw== - dependencies: - string-width "^3.0.0" - -ansi-escapes@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" - integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansicolors@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= - -any-promise@^1.1.0, any-promise@~1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -asap@~2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" - integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= - -asn1@~0.2.0: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" - -async@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== - -axios@^0.21.1: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== - dependencies: - follow-redirects "^1.14.0" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - base64-arraybuffer@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bcrypt-pbkdf@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= - dependencies: - tweetnacl "^0.14.3" - -binjumper@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/binjumper/-/binjumper-0.1.4.tgz#4acc0566832714bd6508af6d666bd9e5e21fc7f8" - integrity sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w== - -bl@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.1.tgz#1cbb439299609e419b5a74d7fce2f8b37d8e5c6f" - integrity sha512-jrCW5ZhfQ/Vt07WX1Ngs+yn9BDqPL/gw28S7s9H6QK/gupnizNzJAss5akW20ISgOrbLTlXOOCTJeNUQqruAWQ== - dependencies: - readable-stream "^3.0.1" - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -boolean@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.3.tgz#0fee0c9813b66bef25a8a6a904bb46736d05f024" - integrity sha512-EqrTKXQX6Z3A2nRmMEIlAIfjQOgFnVO2nqZGpbcsPnYGWBwpFqzlrozU1dy+S2iqfYDLh26ef4KrgTxu9xQrxA== - -boxen@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" - integrity sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ== - dependencies: - ansi-align "^3.0.0" - camelcase "^5.3.1" - chalk "^3.0.0" - cli-boxes "^2.2.0" - string-width "^4.1.0" - term-size "^2.1.0" - type-fest "^0.8.1" - widest-line "^3.1.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserify-zlib@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" - integrity sha1-uzX4pRn2AOD6a4SFJByXnQFB+y0= - dependencies: - pako "~0.2.0" - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - -cacheable-request@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" - integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^2.0.0" - -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -chalk@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -child-process@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/child-process/-/child-process-1.0.2.tgz#98974dc7ed1ee4c6229f8e305fa7313a6885a7f2" - integrity sha1-mJdNx+0e5MYin44wX6cxOmiFp/I= - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-boxes@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" - integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinner@0.2.10: - version "0.2.10" - resolved "https://registry.yarnpkg.com/cli-spinner/-/cli-spinner-0.2.10.tgz#f7d617a36f5c47a7bc6353c697fc9338ff782a47" - integrity sha512-U0sSQ+JJvSLi1pAYuJykwiA8Dsr15uHEy85iCJ6A+0DjVxivr3d+N2Wjvodeg89uP5K6TswFkKBfAD7B3YSn/Q== - -cli-spinners@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.0.tgz#36c7dc98fb6a9a76bd6238ec3f77e2425627e939" - integrity sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q== - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -clipanion@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/clipanion/-/clipanion-2.6.2.tgz#820e7440812052442455b248f927b187ed732f71" - integrity sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw== - -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -configstore@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" - integrity sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA== - dependencies: - dot-prop "^5.2.0" - graceful-fs "^4.1.2" - make-dir "^3.0.0" - unique-string "^2.0.0" - write-file-atomic "^3.0.0" - xdg-basedir "^4.0.0" - -core-js@^3.6.5: - version "3.11.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.0.tgz#05dac6aa70c0a4ad842261f8957b961d36eb8926" - integrity sha512-bd79DPpx+1Ilh9+30aT5O1sgpQd4Ttg8oqkqi51ZzhedMM1omD2e6IOF48Z/DzDCZ2svp49tN/3vneTK6ZBkXw== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cross-spawn@7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -crypto-random-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" - integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== - css-line-break@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" @@ -1009,481 +14,6 @@ css-line-break@1.1.1: dependencies: base64-arraybuffer "^0.2.0" -debug@^3.1.0, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== - dependencies: - ms "^2.1.1" - -debug@^4.2.0, debug@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= - dependencies: - mimic-response "^1.0.0" - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= - dependencies: - clone "^1.0.2" - -defer-to-connect@^1.0.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" - integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== - -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== - -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -detect-node@^2.0.4: - version "2.0.5" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.5.tgz#9d270aa7eaa5af0b72c4c9d9b814e7f4ce738b79" - integrity sha512-qi86tE6hRcFHy8jI1m2VG+LaPUR1LhqDa5G8tVjuUXmOrpuAgqsA1pN0+ldgr3aKUH+QLI9hCY/OcRYisERejw== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -docker-modem@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-2.1.3.tgz#15432225f63db02eb5de4bb9a621b7293e5f264d" - integrity sha512-cwaRptBmYZwu/FyhGcqBm2MzXA77W2/E6eVkpOZVDk6PkI9Bjj84xPrXiHMA+OWjzNy+DFjgKh8Q+1hMR7/OHg== - dependencies: - debug "^4.1.1" - readable-stream "^3.5.0" - split-ca "^1.0.1" - ssh2 "^0.8.7" - -dockerfile-ast@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/dockerfile-ast/-/dockerfile-ast-0.2.0.tgz#13cc4a6fe3aea30a4104622b30f49a0fe3a5c038" - integrity sha512-iQyp12k1A4tF3sEfLAq2wfFPKdpoiGTJeuiu2Y1bdEqIZu0DfSSL2zm0fk7a/UHeQkngnYaRRGuON+C+2LO1Fw== - dependencies: - vscode-languageserver-types "^3.16.0" - -dot-prop@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.2.0.tgz#c34ecc29556dc45f1f4c22697b6f4904e0cc4fcb" - integrity sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A== - dependencies: - is-obj "^2.0.0" - -dotnet-deps-parser@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/dotnet-deps-parser/-/dotnet-deps-parser-5.0.0.tgz#5115c442cbefea59e4fb9f9ed8fa4863a0f3186d" - integrity sha512-1l9K4UnQQHSfKgeHeLrxnB53AidCZqPyf9dkRL4/fZl8//NPiiDD43zHtgylw8DHlO7gvM8+O5a0UPHesNYZKw== - dependencies: - lodash.isempty "^4.4.0" - lodash.set "^4.3.2" - lodash.uniq "^4.5.0" - source-map-support "^0.5.7" - tslib "^1.10.0" - xml2js "0.4.23" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - -duplexify@^3.5.0, duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -elfy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/elfy/-/elfy-1.0.0.tgz#7a1c86af7d41e0a568cbb4a3fa5b685648d9efcd" - integrity sha512-4Kp3AA94jC085IJox+qnvrZ3PudqTi4gQNvIoTZfJJ9IqkRuCoqP60vCVYlIg00c5aYusi5Wjh2bf0cHYt+6gQ== - dependencies: - endian-reader "^0.3.0" - -email-validator@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-2.0.4.tgz#b8dfaa5d0dae28f1b03c95881d904d4e40bfe7ed" - integrity sha512-gYCwo7kh5S3IDyZPLZf6hSS0MnZT8QmJFqYvbqlDZSbwdZlY6QZWxJ4i/6UhITOJ4XzyI647Bm2MXKCLqnJ4nQ== - -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -end-of-stream@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07" - integrity sha1-6TUyWLqpEIll78QcsO+K3i88+wc= - dependencies: - once "~1.3.0" - -endian-reader@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/endian-reader/-/endian-reader-0.3.0.tgz#84eca436b80aed0d0639c47291338b932efe50a0" - integrity sha1-hOykNrgK7Q0GOcRykTOLky7+UKA= - -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - -escape-goat@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" - integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -event-loop-spinner@^2.0.0, event-loop-spinner@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/event-loop-spinner/-/event-loop-spinner-2.1.0.tgz#75f501d585105c6d57f174073b39af1b6b3a1567" - integrity sha512-RJ10wL8/F9AlfBgRCvYctJIXSb9XkVmSCK3GGUvPD3dJrvTjDeDT0tmhcbEC6I2NEjNM9xD38HQJ4F/f/gb4VQ== - dependencies: - tslib "^2.1.0" - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -fast-glob@^3.1.1, fast-glob@^3.2.2: - version "3.2.5" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.5.tgz#7939af2a656de79a4f1901903ee8adcaa7cb9661" - integrity sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.0" - merge2 "^1.3.0" - micromatch "^4.0.2" - picomatch "^2.2.1" - -fastq@^1.6.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.11.0.tgz#bb9fb955a07130a918eb63c1f5161cc32a5d0858" - integrity sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g== - dependencies: - reusify "^1.0.4" - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -follow-redirects@^1.14.0: - version "1.14.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.3.tgz#6ada78118d8d24caee595595accdc0ac6abd022e" - integrity sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -get-stream@^4.0.0, get-stream@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -glob-parent@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-agent@^2.1.12: - version "2.2.0" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.2.0.tgz#566331b0646e6bf79429a16877685c4a1fbf76dc" - integrity sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg== - dependencies: - boolean "^3.0.1" - core-js "^3.6.5" - es6-error "^4.1.1" - matcher "^3.0.0" - roarr "^2.15.3" - semver "^7.3.2" - serialize-error "^7.0.1" - -global-dirs@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" - integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== - dependencies: - ini "1.3.7" - -globalthis@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.2.tgz#2a235d34f4d8036219f7e34929b5de9e18166b8b" - integrity sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ== - dependencies: - define-properties "^1.1.3" - -globby@^11.0.1: - version "11.0.3" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" - integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.1.1" - ignore "^5.1.4" - merge2 "^1.3.0" - slash "^3.0.0" - -got@11.4.0: - version "11.4.0" - resolved "https://registry.yarnpkg.com/got/-/got-11.4.0.tgz#1f0910310572af4efcc6890e1dacd7affb710b39" - integrity sha512-XysJZuZNVpaQ37Oo2LV90MIkPeYITehyy1A0QzO1JwOXm8EWuEf9eeGk2XuHePvLEGnm9AVOI37bHwD6KYyBtg== - dependencies: - "@sindresorhus/is" "^2.1.1" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.4.5" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -got@^11.7.0: - version "11.8.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" - integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.1" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - -graceful-fs@^4.1.2: - version "4.1.15" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" - integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== - -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -gunzip-maybe@^1.4.2: - version "1.4.2" - resolved "https://registry.yarnpkg.com/gunzip-maybe/-/gunzip-maybe-1.4.2.tgz#b913564ae3be0eda6f3de36464837a9cd94b98ac" - integrity sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw== - dependencies: - browserify-zlib "^0.1.4" - is-deflate "^1.0.0" - is-gzip "^1.0.0" - peek-stream "^1.1.0" - pumpify "^1.3.3" - through2 "^2.0.3" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" - integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== - -has-yarn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77" - integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw== - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -hosted-git-info@^3.0.4, hosted-git-info@^3.0.7: - version "3.0.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" - integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== - dependencies: - lru-cache "^6.0.0" - html2canvas@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.1.4.tgz#53ae91cd26e9e9e623c56533cccb2e3f57c8124c" @@ -1491,2086 +21,7 @@ html2canvas@^1.1.4: dependencies: css-line-break "1.1.1" -http-cache-semantics@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== - -http2-wrapper@^1.0.0-beta.4.5, http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - -iconv-lite@^0.4.24, iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore@^5.1.4, ignore@^5.1.8: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== - -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= - -import-lazy@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" - integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM= - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@1.3.7, ini@~1.3.0: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== - -is-callable@^1.1.5: - version "1.2.3" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" - integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-deflate@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-deflate/-/is-deflate-1.0.0.tgz#c862901c3c161fb09dac7cdc7e784f80e98f2f14" - integrity sha1-yGKQHDwWH7CdrHzcfnhPgOmPLxQ= - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-gzip@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-gzip/-/is-gzip-1.0.0.tgz#6ca8b07b99c77998025900e555ced8ed80879a83" - integrity sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM= - -is-installed-globally@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== - dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-npm@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" - integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-path-inside@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -is-yarn-global@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" - integrity sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw== - -is@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" - integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -js-yaml@^3.10.0: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^3.13.1: - version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - -json-file-plus@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/json-file-plus/-/json-file-plus-3.3.1.tgz#f4363806b82819ff8803d83d539d6a9edd2a5258" - integrity sha512-wo0q1UuiV5NsDPQDup1Km8IwEeqe+olr8tkWxeJq9Bjtcp7DZ0l+yrg28fSC3DEtrE311mhTZ54QGS6oiqnZEA== - dependencies: - is "^3.2.1" - node.extend "^2.0.0" - object.assign "^4.1.0" - promiseback "^2.0.2" - safer-buffer "^2.0.2" - -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= - -jszip@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.4.0.tgz#1a69421fa5f0bb9bc222a46bca88182fba075350" - integrity sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - set-immediate-shim "~1.0.1" - -jszip@^3.2.2: - version "3.6.0" - resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" - integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== - dependencies: - lie "~3.3.0" - pako "~1.0.2" - readable-stream "~2.3.6" - set-immediate-shim "~1.0.1" - -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - -keyv@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" - integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== - dependencies: - json-buffer "3.0.1" - -latest-version@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" - integrity sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA== - dependencies: - package-json "^6.3.0" - -lie@~3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - -lodash.assign@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" - integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= - -lodash.assignin@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - -lodash.chunk@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" - integrity sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw= - -lodash.clone@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" - integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= - -lodash.clonedeep@^4.3.0, lodash.clonedeep@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= - -lodash.constant@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash.constant/-/lodash.constant-3.0.0.tgz#bfe05cce7e515b3128925d6362138420bd624910" - integrity sha1-v+Bczn5RWzEokl1jYhOEIL1iSRA= - -lodash.defaults@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= - -lodash.endswith@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09" - integrity sha1-/tWawXOO0+I27dcGTsRWRIs3vAk= - -lodash.filter@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= - -lodash.find@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1" - integrity sha1-ywcE1Hq3F4n/oN6Ll92Sb7iLE7E= - -lodash.findindex@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.findindex/-/lodash.findindex-4.6.0.tgz#a3245dee61fb9b6e0624b535125624bb69c11106" - integrity sha1-oyRd7mH7m24GJLU1ElYku2nBEQY= - -lodash.findkey@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718" - integrity sha1-gwWOkDtRy7dZ0JzPVG3qPqOcRxg= - -lodash.flatmap@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.flatmap/-/lodash.flatmap-4.5.0.tgz#ef8cbf408f6e48268663345305c6acc0b778702e" - integrity sha1-74y/QI9uSCaGYzRTBcaswLd4cC4= - -lodash.flatten@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - -lodash.flattendeep@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" - integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= - -lodash.foreach@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" - integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= - -lodash.groupby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" - integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= - -lodash.has@^4.5.2: - version "4.5.2" - resolved "https://registry.yarnpkg.com/lodash.has/-/lodash.has-4.5.2.tgz#d19f4dc1095058cccbe2b0cdf4ee0fe4aa37c862" - integrity sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI= - -lodash.invert@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.invert/-/lodash.invert-4.3.0.tgz#8ffe20d4b616f56bea8f1aa0c6ebd80dcf742aee" - integrity sha1-j/4g1LYW9WvqjxqgxuvYDc90Ku4= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - -lodash.isempty@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" - integrity sha1-b4bL7di+TsmHvpqvM8loTbGzHn4= - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= - -lodash.isfunction@^3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" - integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - -lodash.isobject@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" - integrity sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0= - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.isundefined@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" - integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= - -lodash.keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" - integrity sha1-oIYCrBLk+4P5H8H7ejYKTZujUgU= - -lodash.last@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash.last/-/lodash.last-3.0.0.tgz#242f663112dd4c6e63728c60a3c909d1bdadbd4c" - integrity sha1-JC9mMRLdTG5jcoxgo8kJ0b2tvUw= - -lodash.map@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.omit@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" - integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= - -lodash.orderby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" - integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM= - -lodash.reduce@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= - -lodash.set@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" - integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= - -lodash.size@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.size/-/lodash.size-4.2.0.tgz#71fe75ed3eabdb2bcb73a1b0b4f51c392ee27b86" - integrity sha1-cf517T6r2yvLc6GwtPUcOS7ie4Y= - -lodash.sortby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" - integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= - -lodash.sum@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/lodash.sum/-/lodash.sum-4.0.2.tgz#ad90e397965d803d4f1ff7aa5b2d0197f3b4637b" - integrity sha1-rZDjl5ZdgD1PH/eqWy0Bl/O0Y3s= - -lodash.topairs@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.topairs/-/lodash.topairs-4.3.0.tgz#3b6deaa37d60fb116713c46c5f17ea190ec48d64" - integrity sha1-O23qo31g+xFnE8RsXxfqGQ7EjWQ= - -lodash.transform@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.transform/-/lodash.transform-4.6.0.tgz#12306422f63324aed8483d3f38332b5f670547a0" - integrity sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A= - -lodash.union@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash.upperfirst@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce" - integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= - -lodash.values@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" - integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= - -lodash@^4.17.15: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - -lru-cache@^4.0.0: - version "4.1.5" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" - integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-cache@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" - integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== - dependencies: - yallist "^3.0.2" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -macos-release@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" - integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -matcher@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" - integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== - dependencies: - escape-string-regexp "^4.0.0" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" - integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== - dependencies: - braces "^3.0.1" - picomatch "^2.0.5" - -micromatch@^4.0.2: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-response@^1.0.0, mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -minipass@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== - dependencies: - yallist "^4.0.0" - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp@^0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -needle@2.6.0, needle@^2.3.3, needle@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.6.0.tgz#24dbb55f2509e2324b4a99d61f413982013ccdbe" - integrity sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg== - dependencies: - debug "^3.2.6" - iconv-lite "^0.4.4" - sax "^1.2.4" - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node.extend@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-2.0.2.tgz#b4404525494acc99740f3703c496b7d5182cc6cc" - integrity sha512-pDT4Dchl94/+kkgdwyS2PauDFjZG0Hk0IcHIB+LkW27HLDtdoeMxHTxZh39DYbPP8UflWXWj9JcdDozF+YDOpQ== - dependencies: - has "^1.0.3" - is "^3.2.1" - -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -object-hash@^2.0.3: - version "2.1.1" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.1.1.tgz#9447d0279b4fcf80cff3259bf66a1dc73afabe09" - integrity sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ== - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= - dependencies: - wrappy "1" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - onscan.js@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/onscan.js/-/onscan.js-1.5.2.tgz#14ed636e5f4c3f0a78bacbf9a505dad3140ee341" integrity sha512-9oGYy2gXYRjvXO9GYqqVca0VuCTAmWhbmX3egBSBP13rXiMNb+dKPJzKFEeECGqPBpf0m40Zoo+GUQ7eCackdw== - -open@^7.0.3: - version "7.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" - integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== - dependencies: - is-docker "^2.0.0" - is-wsl "^2.1.1" - -ora@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" - integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== - dependencies: - bl "^4.0.3" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - log-symbols "^4.0.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -os-name@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-name/-/os-name-3.1.0.tgz#dec19d966296e1cd62d701a5a66ee1ddeae70801" - integrity sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg== - dependencies: - macos-release "^2.2.0" - windows-release "^3.1.0" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - -p-cancelable@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.0.tgz#4d51c3b91f483d02a0d300765321fca393d758dd" - integrity sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-map@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" - integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -package-json@^6.3.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" - integrity sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ== - dependencies: - got "^9.6.0" - registry-auth-token "^4.0.0" - registry-url "^5.0.0" - semver "^6.2.0" - -pako@~0.2.0: - version "0.2.9" - resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= - -pako@~1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" - integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== - -parse-link-header@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-link-header/-/parse-link-header-1.0.1.tgz#bedfe0d2118aeb84be75e7b025419ec8a61140a7" - integrity sha1-vt/g0hGK64S+deewJUGeyKYRQKc= - dependencies: - xtend "~4.0.1" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -peek-stream@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/peek-stream/-/peek-stream-1.1.3.tgz#3b35d84b7ccbbd262fff31dc10da56856ead6d67" - integrity sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA== - dependencies: - buffer-from "^1.0.0" - duplexify "^3.5.0" - through2 "^2.0.3" - -picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== - -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - -pretty-bytes@^5.1.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" - integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== - -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -promise-deferred@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/promise-deferred/-/promise-deferred-2.0.3.tgz#b99c9588820798501862a593d49cece51d06fd7f" - integrity sha512-n10XaoznCzLfyPFOlEE8iurezHpxrYzyjgq/1eW9Wk1gJwur/N7BdBmjJYJpqMeMcXK4wEbzo2EvZQcqjYcKUQ== - dependencies: - promise "^7.3.1" - -promise-fs@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/promise-fs/-/promise-fs-2.1.1.tgz#0b725a592c165ff16157d1f13640ba390637e557" - integrity sha512-43p7e4QzAQ3w6eyN0+gbBL7jXiZFWLWYITg9wIObqkBySu/a5K1EDcQ/S6UyB/bmiZWDA4NjTbcopKLTaKcGSw== - dependencies: - "@octetstream/promisify" "2.0.2" - -promise-queue@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/promise-queue/-/promise-queue-2.2.5.tgz#2f6f5f7c0f6d08109e967659c79b88a9ed5e93b4" - integrity sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q= - -"promise@>=3.2 <8", promise@^7.3.1: - version "7.3.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" - integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== - dependencies: - asap "~2.0.3" - -promiseback@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/promiseback/-/promiseback-2.0.3.tgz#bd468d86930e8cd44bfc3292de9a6fbafb6378e6" - integrity sha512-VZXdCwS0ppVNTIRfNsCvVwJAaP2b+pxQF7lM8DMWfmpNWyTxB6O5YNbzs+8z0ki/KIBHKHk308NTIl4kJUem3w== - dependencies: - is-callable "^1.1.5" - promise-deferred "^2.0.3" - -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -pupa@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" - integrity sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A== - dependencies: - escape-goat "^2.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -queue@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.2.tgz#b91525283e2315c7553d2efa18d83e76432fed65" - integrity sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA== - dependencies: - inherits "~2.0.3" - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^2.0.0, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.1, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -registry-auth-token@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== - dependencies: - rc "^1.2.8" - -registry-url@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-5.1.0.tgz#e98334b50d5434b81136b44ec638d9c2009c5009" - integrity sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw== - dependencies: - rc "^1.2.8" - -resolve-alpn@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.1.2.tgz#30b60cfbb0c0b8dc897940fe13fe255afcdd4d28" - integrity sha512-8OyfzhAtA32LVUsJSke3auIyINcwdh5l3cvYKdKO0nvsYSKuiLfTM5i78PJswFPT8y6cPW+L1v6/hE95chcpDA== - -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - -responselike@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" - integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== - dependencies: - lowercase-keys "^2.0.0" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^2.6.3: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - -rimraf@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -roarr@^2.15.3: - version "2.15.4" - resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" - integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== - dependencies: - boolean "^3.0.1" - detect-node "^2.0.4" - globalthis "^1.0.1" - json-stringify-safe "^5.0.1" - semver-compare "^1.0.0" - sprintf-js "^1.1.2" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^6.6.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sax@>=0.6.0, sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - -semver-diff@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" - integrity sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg== - dependencies: - semver "^6.3.0" - -semver@^5.5.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -semver@^5.5.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.0.0, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -serialize-error@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" - integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== - dependencies: - type-fest "^0.13.1" - -set-immediate-shim@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -snyk-config@4.0.0, snyk-config@^4.0.0-rc.2: - version "4.0.0" - resolved "https://registry.yarnpkg.com/snyk-config/-/snyk-config-4.0.0.tgz#21d459f19087991246cc07a7ffb4501dce6f4159" - integrity sha512-E6jNe0oUjjzVASWBOAc/mA23DhbzABDF9MI6UZvl0gylh2NSXSXw2/LjlqMNOKL2c1qkbSkzLOdIX5XACoLCAQ== - dependencies: - async "^3.2.0" - debug "^4.1.1" - lodash.merge "^4.6.2" - minimist "^1.2.5" - -snyk-cpp-plugin@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/snyk-cpp-plugin/-/snyk-cpp-plugin-2.2.1.tgz#55891511a43a6448e5a7c836a94f66f70fa705eb" - integrity sha512-NFwVLMCqKTocY66gcim0ukF6e31VRDJqDapg5sy3vCHqlD1OCNUXSK/aI4VQEEndDrsnFmQepsL5KpEU0dDRIQ== - dependencies: - "@snyk/dep-graph" "^1.19.3" - chalk "^4.1.0" - debug "^4.1.1" - hosted-git-info "^3.0.7" - tslib "^2.0.0" - -snyk-docker-plugin@4.19.3: - version "4.19.3" - resolved "https://registry.yarnpkg.com/snyk-docker-plugin/-/snyk-docker-plugin-4.19.3.tgz#14569f25c52a3fc71a20f80f5beac4ccdc326c11" - integrity sha512-5WkXyT7uY5NrTOvEqxeMqb6dDcskT3c/gbHUTOyPuvE6tMut+OOYK8RRXbwZFeLzpS8asq4e1R7U7syYG3VXwg== - dependencies: - "@snyk/dep-graph" "^1.21.0" - "@snyk/rpm-parser" "^2.0.0" - "@snyk/snyk-docker-pull" "3.2.3" - chalk "^2.4.2" - debug "^4.1.1" - docker-modem "2.1.3" - dockerfile-ast "0.2.0" - elfy "^1.0.0" - event-loop-spinner "^2.0.0" - gunzip-maybe "^1.4.2" - mkdirp "^1.0.4" - semver "^7.3.4" - snyk-nodejs-lockfile-parser "1.30.2" - tar-stream "^2.1.0" - tmp "^0.2.1" - tslib "^1" - uuid "^8.2.0" - -snyk-go-parser@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/snyk-go-parser/-/snyk-go-parser-1.4.1.tgz#df16a5fbd7a517ee757268ef081abc33506c8857" - integrity sha512-StU3uHB85VMEkcgXta63M0Fgd+9cs5sMCjQXTBoYTdE4dxarPn7U67yCuwkRRdZdny1ZXtzfY8LKns9i0+dy9w== - dependencies: - toml "^3.0.0" - tslib "^1.10.0" - -snyk-go-plugin@1.17.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/snyk-go-plugin/-/snyk-go-plugin-1.17.0.tgz#56d0c92d7def29ba4c3c2030c5830093e3b0dd26" - integrity sha512-1jAYPRgMapO2BYL+HWsUq5gsAiDGmI0Pn7omc0lk24tcUOMhUB+1hb0u9WBMNzHvXBjevBkjOctjpnt2hMKN6Q== - dependencies: - "@snyk/dep-graph" "^1.23.1" - "@snyk/graphlib" "2.1.9-patch.3" - debug "^4.1.1" - snyk-go-parser "1.4.1" - tmp "0.2.1" - tslib "^1.10.0" - -snyk-gradle-plugin@3.14.2: - version "3.14.2" - resolved "https://registry.yarnpkg.com/snyk-gradle-plugin/-/snyk-gradle-plugin-3.14.2.tgz#898b051f679e681b6d859f0ca84a500ac028af7d" - integrity sha512-l/nivKDZz7e2wymrwP6g2WQD8qgaYeE22SnbZrfIpwGolif81U28A9FsRedwkxKyB/shrM0vGEoD3c3zI8NLBw== - dependencies: - "@snyk/cli-interface" "2.11.0" - "@snyk/dep-graph" "^1.28.0" - "@snyk/java-call-graph-builder" "1.20.0" - "@types/debug" "^4.1.4" - chalk "^3.0.0" - debug "^4.1.1" - tmp "0.2.1" - tslib "^2.0.0" - -snyk-module@3.1.0, snyk-module@^3.0.0, snyk-module@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/snyk-module/-/snyk-module-3.1.0.tgz#3e088ff473ddf0d4e253a46ea6749d76d8e6e7ba" - integrity sha512-HHuOYEAACpUpkFgU8HT57mmxmonaJ4O3YADoSkVhnhkmJ+AowqZyJOau703dYHNrq2DvQ7qYw81H7yyxS1Nfjw== - dependencies: - debug "^4.1.1" - hosted-git-info "^3.0.4" - -snyk-mvn-plugin@2.25.3: - version "2.25.3" - resolved "https://registry.yarnpkg.com/snyk-mvn-plugin/-/snyk-mvn-plugin-2.25.3.tgz#fb7f6fa1d565b9f07c032e8b34e6308c310b2a27" - integrity sha512-JAxOThX51JDbgMMjp3gQDVi07G9VgTYSF06QC7f5LNA0zoXNr743e2rm78RGw5bqE3JRjZxEghiLHPPuvS5DDg== - dependencies: - "@snyk/cli-interface" "2.11.0" - "@snyk/dep-graph" "^1.23.1" - "@snyk/java-call-graph-builder" "1.19.1" - debug "^4.1.1" - glob "^7.1.6" - needle "^2.5.0" - tmp "^0.1.0" - tslib "1.11.1" - -snyk-nodejs-lockfile-parser@1.30.2: - version "1.30.2" - resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.30.2.tgz#8dbb64c42382aeaf4488c36e48c1e48eb75a1584" - integrity sha512-wI3VXVYO/ok0uaQm5i+Koo4rKBNilYC/QRIQFlyGbZXf+WBdRcTBKVDfTy8uNfUhMRSGzd84lNclMnetU9Y+vw== - dependencies: - "@snyk/graphlib" "2.1.9-patch.3" - "@yarnpkg/lockfile" "^1.1.0" - event-loop-spinner "^2.0.0" - got "11.4.0" - lodash.clonedeep "^4.5.0" - lodash.flatmap "^4.5.0" - lodash.isempty "^4.4.0" - lodash.set "^4.3.2" - lodash.topairs "^4.3.0" - p-map "2.1.0" - snyk-config "^4.0.0-rc.2" - tslib "^1.9.3" - uuid "^8.3.0" - yaml "^1.9.2" - -snyk-nodejs-lockfile-parser@1.32.0: - version "1.32.0" - resolved "https://registry.yarnpkg.com/snyk-nodejs-lockfile-parser/-/snyk-nodejs-lockfile-parser-1.32.0.tgz#2e25ea8622ef03ae7457a93ae70e156d6c46c2ef" - integrity sha512-FdYa/7NibnJPqBfobyw5jgI1/rd0LpMZf2W4WYYLRc2Hz7LZjKAByPjIX6qoA+lB9SC7yk5HYwWj2n4Fbg/DDw== - dependencies: - "@snyk/graphlib" "2.1.9-patch.3" - "@yarnpkg/core" "^2.4.0" - "@yarnpkg/lockfile" "^1.1.0" - event-loop-spinner "^2.0.0" - got "11.4.0" - lodash.clonedeep "^4.5.0" - lodash.flatmap "^4.5.0" - lodash.isempty "^4.4.0" - lodash.set "^4.3.2" - lodash.topairs "^4.3.0" - p-map "2.1.0" - snyk-config "^4.0.0-rc.2" - tslib "^1.9.3" - uuid "^8.3.0" - yaml "^1.9.2" - -snyk-nuget-plugin@1.21.1: - version "1.21.1" - resolved "https://registry.yarnpkg.com/snyk-nuget-plugin/-/snyk-nuget-plugin-1.21.1.tgz#a79bbc65456823a1148119873226afb0e4907ec8" - integrity sha512-nRtedIvrow5ODqOKkQWolKrxn8ZoNL3iNJGuW0jNhwv+/9K0XE1UORM5F1ENAsd+nzCSO/kiYAXCc5CNK8HWEw== - dependencies: - debug "^4.1.1" - dotnet-deps-parser "5.0.0" - jszip "3.4.0" - snyk-paket-parser "1.6.0" - tslib "^1.11.2" - xml2js "^0.4.17" - -snyk-paket-parser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/snyk-paket-parser/-/snyk-paket-parser-1.6.0.tgz#f70c423b33d31484c8c4cae74bb7f5deb9bbc382" - integrity sha512-6htFynjBe/nakclEHUZ1A3j5Eu32/0pNve5Qm4MFn3YQmJgj7UcAO8hdyK3QfzEY29/kAv/rkJQg+SKshn+N9Q== - dependencies: - tslib "^1.9.3" - -snyk-php-plugin@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/snyk-php-plugin/-/snyk-php-plugin-1.9.2.tgz#282ef733060aab49da23e1fb2d2dd1af8f71f7cd" - integrity sha512-IQcdsQBqqXVRY5DatlI7ASy4flbhtU2V7cr4P2rK9rkFnVHO6LHcitwKXVZa9ocdOmpZDzk7U6iwHJkVFcR6OA== - dependencies: - "@snyk/cli-interface" "^2.9.1" - "@snyk/composer-lockfile-parser" "^1.4.1" - tslib "1.11.1" - -snyk-poetry-lockfile-parser@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/snyk-poetry-lockfile-parser/-/snyk-poetry-lockfile-parser-1.1.6.tgz#bab5a279c103cbcca8eb86ab87717b115592881e" - integrity sha512-MoekbWOZPj9umfukjk2bd2o3eRj0OyO+58sxq9crMtHmTlze4h0/Uj4+fb0JFPBOtBO3c2zwbA+dvFQmpKoOTA== - dependencies: - "@snyk/cli-interface" "^2.9.2" - "@snyk/dep-graph" "^1.23.0" - debug "^4.2.0" - toml "^3.0.0" - tslib "^2.0.0" - -snyk-policy@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/snyk-policy/-/snyk-policy-1.19.0.tgz#0cbc442d9503970fb3afea938f57d57993a914ad" - integrity sha512-XYjhOTRPFA7NfDUsH6uH1fbML2OgSFsqdUPbud7x01urNP9CHXgUgAD4NhKMi3dVQK+7IdYadWt0wrFWw4y+qg== - dependencies: - debug "^4.1.1" - email-validator "^2.0.4" - js-yaml "^3.13.1" - lodash.clonedeep "^4.5.0" - promise-fs "^2.1.1" - semver "^6.0.0" - snyk-module "^3.0.0" - snyk-resolve "^1.1.0" - snyk-try-require "^2.0.0" - -snyk-python-plugin@1.19.8: - version "1.19.8" - resolved "https://registry.yarnpkg.com/snyk-python-plugin/-/snyk-python-plugin-1.19.8.tgz#9e4dfa8ed7e16ef2752f934b786d2e033de62ce0" - integrity sha512-LMKVnv0J4X/qHMoKB17hMND0abWtm9wdgI4xVzrOcf2Vtzs3J87trRhwLxQA2lMoBW3gcjtTeBUvNKaxikSVeQ== - dependencies: - "@snyk/cli-interface" "^2.0.3" - snyk-poetry-lockfile-parser "^1.1.6" - tmp "0.0.33" - -snyk-resolve-deps@4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/snyk-resolve-deps/-/snyk-resolve-deps-4.7.2.tgz#11e7051110dadd8756819594bb30e6b88777a8b4" - integrity sha512-Bmtr7QdRL2b3Js+mPDmvXbkprOpzO8aUFXqR0nJKAOlUVQqZ84yiuT0n/mssEiJJ0vP+k0kZvTeiTwgio4KZRg== - dependencies: - ansicolors "^0.3.2" - debug "^4.1.1" - lodash.assign "^4.2.0" - lodash.assignin "^4.2.0" - lodash.clone "^4.5.0" - lodash.flatten "^4.4.0" - lodash.get "^4.4.2" - lodash.set "^4.3.2" - lru-cache "^4.0.0" - semver "^5.5.1" - snyk-module "^3.1.0" - snyk-resolve "^1.0.0" - snyk-tree "^1.0.0" - snyk-try-require "^1.1.1" - then-fs "^2.0.0" - -snyk-resolve@1.1.0, snyk-resolve@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.1.0.tgz#52740cb01ba477851086855f9857b3a44296ee0e" - integrity sha512-OZMF8I8TOu0S58Z/OS9mr8jkEzGAPByCsAkrWlcmZgPaE0RsxVKVIFPhbMNy/JlYswgGDYYIEsNw+e0j1FnTrw== - dependencies: - debug "^4.1.1" - promise-fs "^2.1.1" - -snyk-resolve@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/snyk-resolve/-/snyk-resolve-1.0.1.tgz#eaa4a275cf7e2b579f18da5b188fe601b8eed9ab" - integrity sha512-7+i+LLhtBo1Pkth01xv+RYJU8a67zmJ8WFFPvSxyCjdlKIcsps4hPQFebhz+0gC5rMemlaeIV6cqwqUf9PEDpw== - dependencies: - debug "^3.1.0" - then-fs "^2.0.0" - -snyk-sbt-plugin@2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/snyk-sbt-plugin/-/snyk-sbt-plugin-2.11.0.tgz#f5469dcf5589e34575fc901e2064475582cc3e48" - integrity sha512-wUqHLAa3MzV6sVO+05MnV+lwc+T6o87FZZaY+43tQPytBI2Wq23O3j4POREM4fa2iFfiQJoEYD6c7xmhiEUsSA== - dependencies: - debug "^4.1.1" - semver "^6.1.2" - tmp "^0.1.0" - tree-kill "^1.2.2" - tslib "^1.10.0" - -snyk-tree@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/snyk-tree/-/snyk-tree-1.0.0.tgz#0fb73176dbf32e782f19100294160448f9111cc8" - integrity sha1-D7cxdtvzLngvGRAClBYESPkRHMg= - dependencies: - archy "^1.0.0" - -snyk-try-require@1.3.1, snyk-try-require@^1.1.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-1.3.1.tgz#6e026f92e64af7fcccea1ee53d524841e418a212" - integrity sha1-bgJvkuZK9/zM6h7lPVJIQeQYohI= - dependencies: - debug "^3.1.0" - lodash.clonedeep "^4.3.0" - lru-cache "^4.0.0" - then-fs "^2.0.0" - -snyk-try-require@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/snyk-try-require/-/snyk-try-require-2.0.1.tgz#076ae9bc505d64d28389452ce19fcac28f26655a" - integrity sha512-VCOfFIvqLMXgCXEdooQgu3A40XYIFBnj0X8Y01RJ5iAbu08b4WKGN/uAKaRVF30dABS4EcjsalmCO+YlKUPEIA== - dependencies: - debug "^4.1.1" - lodash.clonedeep "^4.3.0" - lru-cache "^5.1.1" - -snyk@^1.518.0: - version "1.564.0" - resolved "https://registry.yarnpkg.com/snyk/-/snyk-1.564.0.tgz#c8c511128351f8b8fe239b010b6799f40bb659c5" - integrity sha512-Fh4YusvJ9XdQfyz8JH9J8mBbipfgGLiF60MW9DYhQgP6h8z5uckAfd+S/uFMwPOVOIoe00fFo7aCLxFUuPcVJQ== - dependencies: - "@open-policy-agent/opa-wasm" "^1.2.0" - "@snyk/cli-interface" "2.11.0" - "@snyk/cloud-config-parser" "^1.9.2" - "@snyk/code-client" "3.4.1" - "@snyk/dep-graph" "^1.27.1" - "@snyk/fix" "1.554.0" - "@snyk/gemfile" "1.2.0" - "@snyk/graphlib" "^2.1.9-patch.3" - "@snyk/inquirer" "^7.3.3-patch" - "@snyk/snyk-cocoapods-plugin" "2.5.2" - "@snyk/snyk-hex-plugin" "1.1.4" - abbrev "^1.1.1" - ansi-escapes "3.2.0" - chalk "^2.4.2" - cli-spinner "0.2.10" - configstore "^5.0.1" - debug "^4.1.1" - diff "^4.0.1" - global-agent "^2.1.12" - lodash.assign "^4.2.0" - lodash.camelcase "^4.3.0" - lodash.clonedeep "^4.5.0" - lodash.endswith "^4.2.1" - lodash.flatten "^4.4.0" - lodash.flattendeep "^4.4.0" - lodash.get "^4.4.2" - lodash.groupby "^4.6.0" - lodash.isempty "^4.4.0" - lodash.isobject "^3.0.2" - lodash.map "^4.6.0" - lodash.omit "^4.5.0" - lodash.orderby "^4.6.0" - lodash.sortby "^4.7.0" - lodash.uniq "^4.5.0" - lodash.upperfirst "^4.3.1" - lodash.values "^4.3.0" - micromatch "4.0.2" - needle "2.6.0" - open "^7.0.3" - ora "5.3.0" - os-name "^3.0.0" - promise-queue "^2.2.5" - proxy-from-env "^1.0.0" - rimraf "^2.6.3" - semver "^6.0.0" - snyk-config "4.0.0" - snyk-cpp-plugin "2.2.1" - snyk-docker-plugin "4.19.3" - snyk-go-plugin "1.17.0" - snyk-gradle-plugin "3.14.2" - snyk-module "3.1.0" - snyk-mvn-plugin "2.25.3" - snyk-nodejs-lockfile-parser "1.32.0" - snyk-nuget-plugin "1.21.1" - snyk-php-plugin "1.9.2" - snyk-policy "1.19.0" - snyk-python-plugin "1.19.8" - snyk-resolve "1.1.0" - snyk-resolve-deps "4.7.2" - snyk-sbt-plugin "2.11.0" - snyk-tree "^1.0.0" - snyk-try-require "1.3.1" - source-map-support "^0.5.11" - strip-ansi "^5.2.0" - tar "^6.1.0" - tempfile "^2.0.0" - update-notifier "^4.1.0" - uuid "^3.3.2" - wrap-ansi "^5.1.0" - -source-map-support@^0.5.11, source-map-support@^0.5.7: - version "0.5.16" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" - integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-ca@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" - integrity sha1-bIOv82kvphJW4M0ZfgXp3hV2kaY= - -sprintf-js@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -ssh2-streams@~0.4.10: - version "0.4.10" - resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34" - integrity sha512-8pnlMjvnIZJvmTzUIIA5nT4jr2ZWNNVHwyXfMGdRJbug9TpI3kd99ffglgfSWqujVv/0gxwMsDn9j9RVst8yhQ== - dependencies: - asn1 "~0.2.0" - bcrypt-pbkdf "^1.0.2" - streamsearch "~0.1.2" - -ssh2@^0.8.7: - version "0.8.9" - resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.9.tgz#54da3a6c4ba3daf0d8477a538a481326091815f3" - integrity sha512-GmoNPxWDMkVpMFa9LVVzQZHF6EW3WKmBwL+4/GeILf2hFmix5Isxm7Amamo8o7bHiU0tC+wXsGcUXOxp8ChPaw== - dependencies: - ssh2-streams "~0.4.10" - -stream-buffers@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-3.0.2.tgz#5249005a8d5c2d00b3a32e6e0a6ea209dc4f3521" - integrity sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ== - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -stream-to-array@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" - integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M= - dependencies: - any-promise "^1.1.0" - -stream-to-promise@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-2.2.0.tgz#b1edb2e1c8cb11289d1b503c08d3f2aef51e650f" - integrity sha1-se2y4cjLESidG1A8CNPyrvUeZQ8= - dependencies: - any-promise "~1.3.0" - end-of-stream "~1.1.0" - stream-to-array "~2.3.0" - -streamsearch@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= - -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - -string-width@^4.0.0, string-width@^4.1.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5" - integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@6.0.0, strip-ansi@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" - integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== - dependencies: - ansi-regex "^5.0.0" - -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -tar-stream@^2.0.1, tar-stream@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar-stream@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" - integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== - dependencies: - bl "^3.0.0" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@^6.1.0: - version "6.1.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" - integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -temp-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" - integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0= - -temp-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" - integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== - -tempfile@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tempfile/-/tempfile-2.0.0.tgz#6b0446856a9b1114d1856ffcbe509cccb0977265" - integrity sha1-awRGhWqbERTRhW/8vlCczLCXcmU= - dependencies: - temp-dir "^1.0.0" - uuid "^3.0.1" - -term-size@^2.1.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" - integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== - -then-fs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/then-fs/-/then-fs-2.0.0.tgz#72f792dd9d31705a91ae19ebfcf8b3f968c81da2" - integrity sha1-cveS3Z0xcFqRrhnr/Piz+WjIHaI= - dependencies: - promise ">=3.2 <8" - -through2@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -tmp@0.0.33, tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmp@0.2.1, tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -tmp@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" - integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== - dependencies: - rimraf "^2.6.3" - -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toml@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" - integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== - -tree-kill@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - -treeify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8" - integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A== - -tslib@1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== - -tslib@^1, tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: - version "1.10.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - -tslib@^1.11.2, tslib@^1.13.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.0.0, tslib@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" - integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== - -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - -tweetnacl@^0.14.3: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= - -type-fest@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" - integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -unique-string@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" - integrity sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg== - dependencies: - crypto-random-string "^2.0.0" - -upath@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" - integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== - -update-notifier@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" - integrity sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A== - dependencies: - boxen "^4.2.0" - chalk "^3.0.0" - configstore "^5.0.1" - has-yarn "^2.1.0" - import-lazy "^2.1.0" - is-ci "^2.0.0" - is-installed-globally "^0.3.1" - is-npm "^4.0.0" - is-yarn-global "^0.3.0" - latest-version "^5.0.0" - pupa "^2.0.1" - semver-diff "^3.1.1" - xdg-basedir "^4.0.0" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - -utf8@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" - integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -uuid@^3.0.1, uuid@^3.3.2: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - -uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= - dependencies: - defaults "^1.0.3" - -which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -widest-line@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" - integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== - dependencies: - string-width "^4.0.0" - -windows-release@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/windows-release/-/windows-release-3.2.0.tgz#8122dad5afc303d833422380680a79cdfa91785f" - integrity sha512-QTlz2hKLrdqukrsapKsINzqMgOUpQW268eJ0OaOpJN32h272waxR9fkB9VoWRtK7uKHG5EHJcTXQBD8XZVJkFA== - dependencies: - execa "^1.0.0" - -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -xdg-basedir@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" - integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== - -xml-js@^1.6.11: - version "1.6.11" - resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" - integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== - dependencies: - sax "^1.2.4" - -xml2js@0.4.23, xml2js@^0.4.17: - version "0.4.23" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= - -yallist@^3.0.2: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" - integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml-js@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/yaml-js/-/yaml-js-0.3.0.tgz#ad0893d9de881a93fd6bf936e8d89cdde309e848" - integrity sha512-JbTUdsPiCkOyz+JOSqAVc19omTnUBnBQglhuclYov5HpWbEOz8y+ftqWjiMa9Pe/eF/dmCUeNgVs/VWg53GlgQ== - -yaml@^1.9.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From a40709a673a383004db4b82bf6132bd6e6bc6978 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 9 Sep 2021 13:55:56 +0530 Subject: [PATCH 034/416] chore: remove obsolete file snyk has been removed, this file is not required anymore. --- .snyk | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .snyk diff --git a/.snyk b/.snyk deleted file mode 100644 index 140f3edd84..0000000000 --- a/.snyk +++ /dev/null @@ -1,8 +0,0 @@ -# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. -version: v1.14.0 -ignore: {} -# patches apply the minimum changes required to fix a vulnerability -patch: - SNYK-JS-LODASH-450202: - - cypress > getos > async > lodash: - patched: '2020-01-31T01:35:12.802Z' From acdb10377f66a3c980d822a1f74e954fa10544b4 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 9 Sep 2021 18:54:00 +0530 Subject: [PATCH 035/416] refactor: .doc missing and empty row on new doc (#27408) * refactor: .doc missing and empty row on new doc * fix: let -> const --- .../doctype/maintenance_visit/maintenance_visit.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js index 6b3f18484a..7f983541f6 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js @@ -31,8 +31,8 @@ frappe.ui.form.on('Maintenance Visit', { }, onload: function (frm, cdt, cdn) { let item = locals[cdt][cdn]; - if (frm.maintenance_type == 'Scheduled') { - let schedule_id = item.purposes[0].prevdoc_detail_docname; + if (frm.doc.maintenance_type === "Scheduled") { + const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail; frappe.call({ method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos", args: { @@ -43,6 +43,9 @@ frappe.ui.form.on('Maintenance Visit', { } }); } + else { + frm.clear_table("purposes"); + } if (!frm.doc.status) { frm.set_value({ status: 'Draft' }); From 7edac5a5d7acbea0b1a627bde3a20bfbdb44876d Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 9 Sep 2021 19:17:01 +0530 Subject: [PATCH 036/416] fix: removing duplicate currency() from sales_invoice.js (#27410) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 2cb9acfa2a..34ac343bed 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -445,15 +445,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e this.frm.refresh_field("base_paid_amount"); } - currency() { - this._super(); - $.each(cur_frm.doc.timesheets, function(i, d) { - let row = frappe.get_doc(d.doctype, d.name) - set_timesheet_detail_rate(row.doctype, row.name, cur_frm.doc.currency, row.timesheet_detail) - }); - calculate_total_billing_amount(cur_frm) - } - currency() { var me = this; super.currency(); From 8b4c57ee09166c689dfca7d67a65d5376d27b501 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:33:34 +0530 Subject: [PATCH 037/416] fix: pos payment mode selection issue (#27409) --- .../selling/page/point_of_sale/pos_payment.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js index 8e69851213..7ddbf45fdb 100644 --- a/erpnext/selling/page/point_of_sale/pos_payment.js +++ b/erpnext/selling/page/point_of_sale/pos_payment.js @@ -297,6 +297,7 @@ erpnext.PointOfSale.Payment = class { this.render_payment_mode_dom(); this.make_invoice_fields_control(); this.update_totals_section(); + this.focus_on_default_mop(); } edit_cart() { @@ -378,17 +379,24 @@ erpnext.PointOfSale.Payment = class { }); this[`${mode}_control`].toggle_label(false); this[`${mode}_control`].set_value(p.amount); + }); + this.render_loyalty_points_payment_mode(); + + this.attach_cash_shortcuts(doc); + } + + focus_on_default_mop() { + const doc = this.events.get_frm().doc; + const payments = doc.payments; + payments.forEach(p => { + const mode = p.mode_of_payment.replace(/ +/g, "_").toLowerCase(); if (p.default) { setTimeout(() => { this.$payment_modes.find(`.${mode}.mode-of-payment-control`).parent().click(); }, 500); } }); - - this.render_loyalty_points_payment_mode(); - - this.attach_cash_shortcuts(doc); } attach_cash_shortcuts(doc) { From a58e309297964a2509b2e372b5440db85502e7d7 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 9 Sep 2021 19:37:52 +0530 Subject: [PATCH 038/416] fix: fix to fetch customers and billing email in PSOA (#27363) --- .../process_statement_of_accounts.json | 4 ++- .../process_statement_of_accounts.py | 30 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json index 295a3b86a9..a26267ba5e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json @@ -219,6 +219,7 @@ }, { "default": "1", + "description": "A customer must have primary contact email.", "fieldname": "primary_mandatory", "fieldtype": "Check", "label": "Send To Primary Contact" @@ -286,10 +287,11 @@ } ], "links": [], - "modified": "2021-05-21 11:14:22.426672", + "modified": "2021-09-06 21:00:45.732505", "modified_by": "Administrator", "module": "Accounts", "name": "Process Statement Of Accounts", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index 73f3038512..503fd0d6f8 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -196,7 +196,10 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): primary_email = customer.get('email_id') or '' billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False) - if billing_email == '' or (primary_email == '' and int(primary_mandatory)): + if int(primary_mandatory): + if (primary_email == ''): + continue + elif (billing_email == '') and (primary_email == ''): continue customer_list.append({ @@ -208,10 +211,29 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory): @frappe.whitelist() def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True): + """ Returns first email from Contact Email table as a Billing email + when Is Billing Contact checked + and Primary email- email with Is Primary checked """ + billing_email = frappe.db.sql(""" - SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent - WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1 - order by c.creation desc""", customer_name) + SELECT + email.email_id + FROM + `tabContact Email` AS email + JOIN + `tabDynamic Link` AS link + ON + email.parent=link.parent + JOIN + `tabContact` AS contact + ON + contact.name=link.parent + WHERE + link.link_doctype='Customer' + and link.link_name=%s + and contact.is_billing_contact=1 + ORDER BY + contact.creation desc""", customer_name) if len(billing_email) == 0 or (billing_email[0][0] is None): if billing_and_primary: From 1fc8fcc2587000749eb2647563aa9cbac839a54a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 Sep 2021 08:08:00 +0530 Subject: [PATCH 039/416] ci: Upload coverage report to codecov --- .github/workflows/server-tests.yml | 38 ++++++------------------------ 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 71e9c2cd13..4f84b860af 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -99,34 +99,10 @@ jobs: CI_BUILD_ID: ${{ github.run_id }} ORCHESTRATOR_URL: http://test-orchestrator.frappe.io - - name: Upload Coverage Data - run: | - cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE} - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }} - COVERALLS_FLAG_NAME: run-${{ matrix.container }} - COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }} - COVERALLS_PARALLEL: true - - coveralls: - name: Coverage Wrap Up - needs: test - container: python:3-slim - runs-on: ubuntu-latest - steps: - - name: Clone - uses: actions/checkout@v2 - - - name: Coveralls Finished - run: | - cd ${GITHUB_WORKSPACE} - pip3 install coverage==5.5 - pip3 install coveralls==3.0.1 - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload coverage data + uses: codecov/codecov-action@v2 + with: + name: MariaDB + fail_ci_if_error: true + files: /home/runner/frappe-bench/sites/coverage.xml + verbose: true From 86afece6ad774e2d6a444030830a56c79109f854 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 Sep 2021 08:14:43 +0530 Subject: [PATCH 040/416] ci: Add codecov configuration file --- codecov.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..217b52c21f --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +codecov: + require_ci_to_pass: yes + +coverage: + status: + project: + default: + target: auto + threshold: 0.5% + +comment: + layout: "diff, files" + require_changes: true From 629f5e1fdf6191a47622afe081e20c94f1eb4f58 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 10 Sep 2021 08:21:01 +0530 Subject: [PATCH 041/416] chore: Add codecov badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c6fc251244..847904d1dd 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![CI](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext) -[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop) +[![codecov](https://codecov.io/gh/frappe/erpnext/branch/develop/graph/badge.svg?token=0TwvyUg3I5)](https://codecov.io/gh/frappe/erpnext) [https://erpnext.com](https://erpnext.com) From 333b83b583f81b9b8555cc32034fd92b697b42f1 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 10 Sep 2021 09:58:48 +0530 Subject: [PATCH 042/416] ci: Ignore demo folder Coverage for the demo folder is not relevant... can be ignored --- codecov.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codecov.yml b/codecov.yml index 217b52c21f..7af239924c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,3 +11,6 @@ coverage: comment: layout: "diff, files" require_changes: true + + ignore: + - "erpnext/demo" From 3da34382b5792902b5dbc4bea8635a7eb92ab7ad Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 10 Sep 2021 10:18:41 +0530 Subject: [PATCH 043/416] ci: Remove extra space MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤦🏻‍♂️ --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 7af239924c..a8da340d08 100644 --- a/codecov.yml +++ b/codecov.yml @@ -12,5 +12,5 @@ comment: layout: "diff, files" require_changes: true - ignore: +ignore: - "erpnext/demo" From 62fc5442801e09dfa8e93b7855a0de3e487ba458 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 10 Sep 2021 12:46:35 +0530 Subject: [PATCH 044/416] test: basic tests for controllers/queries (#27422) --- erpnext/controllers/queries.py | 2 +- erpnext/controllers/tests/test_queries.py | 87 +++++++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 erpnext/controllers/tests/test_queries.py diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index aafaf5b9e0..ccd417be04 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -307,7 +307,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters): @frappe.validate_and_sanitize_search_inputs def get_project_name(doctype, txt, searchfield, start, page_len, filters): cond = '' - if filters.get('customer'): + if filters and filters.get('customer'): cond = """(`tabProject`.customer = %s or ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer"))) diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py new file mode 100644 index 0000000000..05541d1688 --- /dev/null +++ b/erpnext/controllers/tests/test_queries.py @@ -0,0 +1,87 @@ +import unittest +from functools import partial + +from erpnext.controllers import queries + + +def add_default_params(func, doctype): + return partial( + func, doctype=doctype, txt="", searchfield="name", start=0, page_len=20, filters=None + ) + + +class TestQueries(unittest.TestCase): + + # All tests are based on doctype/test_records.json + + def assert_nested_in(self, item, container): + self.assertIn(item, [vals for tuples in container for vals in tuples]) + + def test_employee_query(self): + query = add_default_params(queries.employee_query, "Employee") + + self.assertGreaterEqual(len(query(txt="_Test Employee")), 3) + self.assertGreaterEqual(len(query(txt="_Test Employee 1")), 1) + + def test_lead_query(self): + query = add_default_params(queries.lead_query, "Lead") + + self.assertGreaterEqual(len(query(txt="_Test Lead")), 4) + self.assertEqual(len(query(txt="_Test Lead 4")), 1) + + def test_customer_query(self): + query = add_default_params(queries.customer_query, "Customer") + + self.assertGreaterEqual(len(query(txt="_Test Customer")), 7) + self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1) + + def test_supplier_query(self): + query = add_default_params(queries.supplier_query, "Supplier") + + self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7) + self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1) + + def test_item_query(self): + query = add_default_params(queries.item_query, "Item") + + self.assertGreaterEqual(len(query(txt="_Test Item")), 7) + self.assertEqual(len(query(txt="_Test Item Home Desktop 100 3")), 1) + + fg_item = "_Test FG Item" + stock_items = query(txt=fg_item, filters={"is_stock_item": 1}) + self.assert_nested_in("_Test FG Item", stock_items) + + bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1}) + self.assertEqual(len(bundled_stock_items), 0) + + def test_bom_qury(self): + query = add_default_params(queries.bom, "BOM") + + self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1) + + def test_project_query(self): + query = add_default_params(queries.get_project_name, "BOM") + + self.assertGreaterEqual(len(query(txt="_Test Project")), 1) + + def test_account_query(self): + query = add_default_params(queries.get_account_list, "Account") + + debtor_accounts = query(txt="Debtors", filters={"company": "_Test Company"}) + self.assert_nested_in("Debtors - _TC", debtor_accounts) + + def test_income_account_query(self): + query = add_default_params(queries.get_income_account, "Account") + + self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1) + + def test_expense_account_query(self): + query = add_default_params(queries.get_expense_account, "Account") + + self.assertGreaterEqual(len(query(filters={"company": "_Test Company"})), 1) + + def test_warehouse_query(self): + query = add_default_params(queries.warehouse_query, "Account") + + wh = query(filters=[["Bin", "item_code", "=", "_Test Item"]]) + self.assertGreaterEqual(len(wh), 1) From dae0a1c1d672f44aa50967c23cf60bc03cf438f1 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Sat, 11 Sep 2021 17:54:21 +0530 Subject: [PATCH 045/416] fix: Template Error due to use of single quote (#27433) --- erpnext/public/js/bank_reconciliation_tool/dialog_manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index 239fbb92b1..ca73393c54 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -227,7 +227,7 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { { fieldtype: "HTML", fieldname: "no_matching_vouchers", - options: "
No Matching Vouchers Found
" + options: "
No Matching Vouchers Found
" }, { fieldtype: "Section Break", From a52d4c25fffbfbb2465b955fddafe8a47f531523 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 11 Sep 2021 17:54:55 +0530 Subject: [PATCH 046/416] fix: fail migration due to None type during v13_0.update_returned_qty_in_pr_dn (#27430) (#27436) * fix: fail migration due to None type * fix: incorrect key: value pair in filter. Co-authored-by: Ankush Menat (cherry picked from commit becf471a3a238cfaa20a8940e0106c7e0e0386c2) Co-authored-by: Devin Slauenwhite --- erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py index efb3a5961f..dd64e05ec1 100644 --- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py +++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py @@ -13,7 +13,7 @@ def execute(): frappe.reload_doc('stock', 'doctype', 'stock_settings') def update_from_return_docs(doctype): - for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}): + for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1, 'return_against': ('!=', '')}): # Update original receipt/delivery document from return return_doc = frappe.get_cached_doc(doctype, return_doc.name) try: From 6ef879fca28b5e7239f025f64b7e7c248284a1a7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Sep 2021 16:36:51 +0530 Subject: [PATCH 047/416] fix(ux): clean invalid fields from variant setting (#27412) --- .../item_variant_settings.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js index e8fb34732f..488920aadb 100644 --- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js +++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js @@ -2,19 +2,32 @@ // For license information, please see license.txt frappe.ui.form.on('Item Variant Settings', { - setup: function(frm) { + refresh: function(frm) { const allow_fields = []; - const exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website", - "show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]; + + const existing_fields = frm.doc.fields.map(row => row.field_name); + const exclude_fields = [...existing_fields, "naming_series", "item_code", "item_name", + "show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image", + "variant_of", "valuation_rate", "barcodes", "website_image", "thumbnail", + "website_specifiations", "web_long_description", "has_variants", "attributes"]; + + const exclude_field_types = ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only']; frappe.model.with_doctype('Item', () => { frappe.get_meta('Item').fields.forEach(d => { - if(!in_list(['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'], d.fieldtype) + if (!in_list(exclude_field_types, d.fieldtype) && !d.no_copy && !in_list(exclude_fields, d.fieldname)) { allow_fields.push(d.fieldname); } }); + if (allow_fields.length == 0) { + allow_fields.push({ + label: __("No additional fields available"), + value: "", + }); + } + frm.fields_dict.fields.grid.update_docfield_property( 'field_name', 'options', allow_fields ); From d743c41b54c6ff72eb5dd6f0386bfc1f37e4d288 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Sep 2021 16:38:18 +0530 Subject: [PATCH 048/416] fix(ux): apply proper filtering in stock reports (#27411) * fix(ux): apply proper filtering in stock reports Stock Balance: apply company filter to warehouse field Stock Ageing: apply company filter to warehouse field * fix: unnecessary parens Co-authored-by: Alan <2.alan.tom@gmail.com> --- erpnext/stock/report/stock_ageing/stock_ageing.js | 10 +++++++++- .../stock/report/stock_balance/stock_balance.js | 15 ++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.js b/erpnext/stock/report/stock_ageing/stock_ageing.js index b22788f7a2..db463b7ca0 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.js +++ b/erpnext/stock/report/stock_ageing/stock_ageing.js @@ -22,7 +22,15 @@ frappe.query_reports["Stock Ageing"] = { "fieldname":"warehouse", "label": __("Warehouse"), "fieldtype": "Link", - "options": "Warehouse" + "options": "Warehouse", + get_query: () => { + const company = frappe.query_report.get_filter_value("company"); + return { + filters: { + ...company && {company}, + } + }; + } }, { "fieldname":"item_code", diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index 7d22823eb8..ce6ffa0b91 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -53,13 +53,14 @@ frappe.query_reports["Stock Balance"] = { "width": "80", "options": "Warehouse", get_query: () => { - var warehouse_type = frappe.query_report.get_filter_value('warehouse_type'); - if(warehouse_type){ - return { - filters: { - 'warehouse_type': warehouse_type - } - }; + let warehouse_type = frappe.query_report.get_filter_value("warehouse_type"); + let company = frappe.query_report.get_filter_value("company"); + + return { + filters: { + ...warehouse_type && {warehouse_type}, + ...company && {company} + } } } }, From 7292f5476d7f0c3a9a5d78da7ad664dcc1f7d51a Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 13 Sep 2021 12:13:43 +0530 Subject: [PATCH 049/416] feat: (get_items_from) filter material request item in purchase order (#27449) --- .../buying/doctype/purchase_order/purchase_order.js | 5 ++++- erpnext/public/js/utils.js | 5 ++++- .../doctype/material_request/material_request.py | 13 +++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 521432d296..2005dac37d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -425,7 +425,10 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e status: ["!=", "Stopped"], per_ordered: ["<", 100], company: me.frm.doc.company - } + }, + allow_child_item_selection: true, + child_fielname: "items", + child_columns: ["item_code", "qty"] }) }, __("Get Items From")); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index e0610eb8c0..2a74d6015f 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -709,6 +709,9 @@ erpnext.utils.map_current_doc = function(opts) { setters: opts.setters, get_query: opts.get_query, add_filters_group: 1, + allow_child_item_selection: opts.allow_child_item_selection, + child_fieldname: opts.child_fielname, + child_columns: opts.child_columns, action: function(selections, args) { let values = selections; if(values.length === 0){ @@ -716,7 +719,7 @@ erpnext.utils.map_current_doc = function(opts) { return; } opts.source_name = values; - opts.setters = args; + opts.args = args; d.dialog.hide(); _map(); }, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 9eb4721626..2569c04251 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -6,10 +6,13 @@ from __future__ import unicode_literals +import json + import frappe from frappe import _, msgprint from frappe.model.mapper import get_mapped_doc from frappe.utils import cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate +from six import string_types from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items from erpnext.controllers.buying_controller import BuyingController @@ -269,7 +272,10 @@ def update_status(name, status): material_request.update_status(status) @frappe.whitelist() -def make_purchase_order(source_name, target_doc=None): +def make_purchase_order(source_name, target_doc=None, args={}): + + if isinstance(args, string_types): + args = json.loads(args) def postprocess(source, target_doc): if frappe.flags.args and frappe.flags.args.default_supplier: @@ -284,7 +290,10 @@ def make_purchase_order(source_name, target_doc=None): set_missing_values(source, target_doc) def select_item(d): - return d.ordered_qty < d.stock_qty + filtered_items = args.get('filtered_children', []) + child_filter = d.name in filtered_items if filtered_items else True + + return d.ordered_qty < d.stock_qty and child_filter doclist = get_mapped_doc("Material Request", source_name, { "Material Request": { From bab644a249de4355d6700f53a7bfbf0114ebb30c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 13 Sep 2021 13:24:27 +0530 Subject: [PATCH 050/416] fix(Payroll): incorrect component amount calculation if dependent on another payment days based component (#27349) * fix(Payroll): incorrect component amount calculation if dependent on another payment days based component * fix: set component amount precision at the end * fix: consider default amount during taxt calculations * test: component amount dependent on another payment days based component * fix: test --- .../doctype/salary_slip/salary_slip.py | 33 ++-- .../doctype/salary_slip/test_salary_slip.py | 148 ++++++++++++++++++ 2 files changed, 167 insertions(+), 14 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 8c48345d8f..d113e7e569 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -487,7 +487,7 @@ class SalarySlip(TransactionBase): self.calculate_component_amounts("deductions") self.set_loan_repayment() - self.set_component_amounts_based_on_payment_days() + self.set_precision_for_component_amounts() self.set_net_pay() def set_net_pay(self): @@ -709,6 +709,17 @@ class SalarySlip(TransactionBase): component_row.amount = amount + self.update_component_amount_based_on_payment_days(component_row) + + def update_component_amount_based_on_payment_days(self, component_row): + joining_date, relieving_date = self.get_joining_and_relieving_dates() + component_row.amount = self.get_amount_based_on_payment_days(component_row, joining_date, relieving_date)[0] + + def set_precision_for_component_amounts(self): + for component_type in ("earnings", "deductions"): + for component_row in self.get(component_type): + component_row.amount = flt(component_row.amount, component_row.precision("amount")) + def calculate_variable_based_on_taxable_salary(self, tax_component, payroll_period): if not payroll_period: frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.") @@ -866,14 +877,7 @@ class SalarySlip(TransactionBase): return total_tax_paid def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0): - joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, - ["date_of_joining", "relieving_date"]) - - if not relieving_date: - relieving_date = getdate(self.end_date) - - if not joining_date: - frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) + joining_date, relieving_date = self.get_joining_and_relieving_dates() taxable_earnings = 0 additional_income = 0 @@ -884,7 +888,10 @@ class SalarySlip(TransactionBase): if based_on_payment_days: amount, additional_amount = self.get_amount_based_on_payment_days(earning, joining_date, relieving_date) else: - amount, additional_amount = earning.amount, earning.additional_amount + if earning.additional_amount: + amount, additional_amount = earning.amount, earning.additional_amount + else: + amount, additional_amount = earning.default_amount, earning.additional_amount if earning.is_tax_applicable: if additional_amount: @@ -1055,7 +1062,7 @@ class SalarySlip(TransactionBase): total += amount return total - def set_component_amounts_based_on_payment_days(self): + def get_joining_and_relieving_dates(self): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -1065,9 +1072,7 @@ class SalarySlip(TransactionBase): if not joining_date: frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) - for component_type in ("earnings", "deductions"): - for d in self.get(component_type): - d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount")) + return joining_date, relieving_date def set_loan_repayment(self): self.total_loan_repayment = 0 diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 480daa2595..bff36a4149 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -17,6 +17,7 @@ from frappe.utils import ( getdate, nowdate, ) +from frappe.utils.make_random import get_random import erpnext from erpnext.accounts.utils import get_fiscal_year @@ -134,6 +135,65 @@ class TestSalarySlip(unittest.TestCase): frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_component_amount_dependent_on_another_payment_days_based_component(self): + from erpnext.hr.doctype.attendance.attendance import mark_attendance + from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( + create_salary_structure_assignment, + ) + + no_of_days = self.get_no_of_days() + # Payroll based on attendance + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance") + + salary_structure = make_salary_structure_for_payment_days_based_component_dependency() + employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company") + + # base = 50000 + create_salary_structure_assignment(employee, salary_structure.name, company="_Test Company", currency="INR") + + # mark employee absent for a day since this case works fine if payment days are equal to working days + month_start_date = get_first_day(nowdate()) + month_end_date = get_last_day(nowdate()) + + first_sunday = frappe.db.sql(""" + select holiday_date from `tabHoliday` + where parent = 'Salary Slip Test Holiday List' + and holiday_date between %s and %s + order by holiday_date + """, (month_start_date, month_end_date))[0][0] + + mark_attendance(employee, add_days(first_sunday, 1), 'Absent', ignore_validate=True) # counted as absent + + # make salary slip and assert payment days + ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name) + self.assertEqual(ss.absent_days, 1) + + days_in_month = no_of_days[0] + no_of_holidays = no_of_days[1] + + self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 1) + + ss.reload() + payment_days_based_comp_amount = 0 + for component in ss.earnings: + if component.salary_component == "HRA - Payment Days": + payment_days_based_comp_amount = flt(component.amount, component.precision("amount")) + break + + # check if the dependent component is calculated using the amount updated after payment days + actual_amount = 0 + precision = 0 + for component in ss.deductions: + if component.salary_component == "P - Employee Provident Fund": + precision = component.precision("amount") + actual_amount = flt(component.amount, precision) + break + + expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision) + + self.assertEqual(actual_amount, expected_amount) + frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") + def test_salary_slip_with_holidays_included(self): no_of_days = self.get_no_of_days() frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1) @@ -864,3 +924,91 @@ def make_holiday_list(): holiday_list = holiday_list.name return holiday_list + +def make_salary_structure_for_payment_days_based_component_dependency(): + earnings = [ + { + "salary_component": "Basic Salary - Payment Days", + "abbr": "P_BS", + "type": "Earning", + "formula": "base", + "amount_based_on_formula": 1 + }, + { + "salary_component": "HRA - Payment Days", + "abbr": "P_HRA", + "type": "Earning", + "depends_on_payment_days": 1, + "amount_based_on_formula": 1, + "formula": "base * 0.20" + } + ] + + make_salary_component(earnings, False, company_list=["_Test Company"]) + + deductions = [ + { + "salary_component": "P - Professional Tax", + "abbr": "P_PT", + "type": "Deduction", + "depends_on_payment_days": 1, + "amount": 200.00 + }, + { + "salary_component": "P - Employee Provident Fund", + "abbr": "P_EPF", + "type": "Deduction", + "exempted_from_income_tax": 1, + "amount_based_on_formula": 1, + "depends_on_payment_days": 0, + "formula": "(gross_pay - P_HRA) * 0.12" + } + ] + + make_salary_component(deductions, False, company_list=["_Test Company"]) + + salary_structure = "Salary Structure with PF" + if frappe.db.exists("Salary Structure", salary_structure): + frappe.db.delete("Salary Structure", salary_structure) + + details = { + "doctype": "Salary Structure", + "name": salary_structure, + "company": "_Test Company", + "payroll_frequency": "Monthly", + "payment_account": get_random("Account", filters={"account_currency": "INR"}), + "currency": "INR" + } + + salary_structure_doc = frappe.get_doc(details) + + for entry in earnings: + salary_structure_doc.append("earnings", entry) + + for entry in deductions: + salary_structure_doc.append("deductions", entry) + + salary_structure_doc.insert() + salary_structure_doc.submit() + + return salary_structure_doc + +def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure): + employee = frappe.db.get_value("Employee", { + "user_id": employee + }, + ["name", "company", "employee_name"], + as_dict=True) + + salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})}) + + if not salary_slip_name: + salary_slip = make_salary_slip(salary_structure, employee=employee.name) + salary_slip.employee_name = employee.employee_name + salary_slip.payroll_frequency = "Monthly" + salary_slip.posting_date = nowdate() + salary_slip.insert() + else: + salary_slip = frappe.get_doc("Salary Slip", salary_slip_name) + + return salary_slip \ No newline at end of file From a5baf909b7b9defd046b23e11d2c0c3a32737e2c Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 13 Sep 2021 15:50:20 +0530 Subject: [PATCH 051/416] fix: editable price list rate field in sales transactions (#27455) --- erpnext/selling/sales_common.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 955ef5e939..6a09109bfd 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -247,7 +247,12 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate")); if(df && editable_price_list_rate) { - df.read_only = 0; + const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item"); + if (!this.frm.fields_dict[parent_field]) return; + + this.frm.fields_dict[parent_field].grid.update_docfield_property( + 'price_list_rate', 'read_only', 0 + ); } } From 44139fbac5ed73fa2be82fb8ae029c3bdb6023ad Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 13 Sep 2021 16:50:11 +0530 Subject: [PATCH 052/416] fix: cancelled sales invoices are considered in billed quantity calculation --- .../selling/report/sales_order_analysis/sales_order_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 805c3d804f..5c4d8b601f 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -73,7 +73,7 @@ def get_data(conditions, filters): `tabSales Order` so, `tabSales Order Item` soi LEFT JOIN `tabSales Invoice Item` sii - ON sii.so_detail = soi.name + ON sii.so_detail = soi.name and sii.docstatus = 1 WHERE soi.parent = so.name and so.status not in ('Stopped', 'Closed', 'On Hold') From b98740b44a73b5fda7e7e11f7f5f85f0fb5d0bf9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 14 Sep 2021 11:20:15 +0530 Subject: [PATCH 053/416] fix: employee advance return through multiple additional salaries (#27438) * fix: employee advance return through multiple additional salaries * test: test repay unclaimed amount from salary * fix: sorting in imports --- .../employee_advance/employee_advance.js | 2 +- .../employee_advance/employee_advance.json | 5 +- .../employee_advance/employee_advance.py | 5 +- .../employee_advance/test_employee_advance.py | 49 ++++++++++++++++++- .../additional_salary/additional_salary.py | 16 ++++-- 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index fa4b06aad3..7d1c7cbf4a 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -73,7 +73,7 @@ frappe.ui.form.on('Employee Advance', { frm.trigger('make_return_entry'); }, __('Create')); } else if (frm.doc.repay_unclaimed_amount_from_salary == 1 && frappe.model.can_create("Additional Salary")) { - frm.add_custom_button(__("Deduction from salary"), function() { + frm.add_custom_button(__("Deduction from Salary"), function() { frm.events.make_deduction_via_additional_salary(frm); }, __('Create')); } diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index ea25aa720a..04754530c3 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -170,7 +170,7 @@ "default": "0", "fieldname": "repay_unclaimed_amount_from_salary", "fieldtype": "Check", - "label": "Repay unclaimed amount from salary" + "label": "Repay Unclaimed Amount from Salary" }, { "depends_on": "eval:cur_frm.doc.employee", @@ -200,10 +200,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-31 22:31:53.746659", + "modified": "2021-09-11 18:38:38.617478", "modified_by": "Administrator", "module": "HR", "name": "Employee Advance", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 87d42d34e3..8d90bccd2d 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -172,7 +172,10 @@ def get_paying_amount_paying_exchange_rate(payment_account, doc): @frappe.whitelist() def create_return_through_additional_salary(doc): import json - doc = frappe._dict(json.loads(doc)) + + if isinstance(doc, str): + doc = frappe._dict(json.loads(doc)) + additional_salary = frappe.new_doc('Additional Salary') additional_salary.employee = doc.employee additional_salary.currency = doc.currency diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.py b/erpnext/hr/doctype/employee_advance/test_employee_advance.py index f8e5f535cb..c439d45b55 100644 --- a/erpnext/hr/doctype/employee_advance/test_employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/test_employee_advance.py @@ -12,8 +12,11 @@ import erpnext from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee_advance.employee_advance import ( EmployeeAdvanceOverPayment, + create_return_through_additional_salary, make_bank_entry, ) +from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component +from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure class TestEmployeeAdvance(unittest.TestCase): @@ -33,6 +36,46 @@ class TestEmployeeAdvance(unittest.TestCase): journal_entry1 = make_payment_entry(advance) self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit) + def test_repay_unclaimed_amount_from_salary(self): + employee_name = make_employee("_T@employe.advance") + advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) + + args = {"type": "Deduction"} + create_salary_component("Advance Salary - Deduction", **args) + make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name) + + # additional salary for 700 first + advance.reload() + additional_salary = create_return_through_additional_salary(advance) + additional_salary.salary_component = "Advance Salary - Deduction" + additional_salary.payroll_date = nowdate() + additional_salary.amount = 700 + additional_salary.insert() + additional_salary.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 700) + + # additional salary for remaining 300 + additional_salary = create_return_through_additional_salary(advance) + additional_salary.salary_component = "Advance Salary - Deduction" + additional_salary.payroll_date = nowdate() + additional_salary.amount = 300 + additional_salary.insert() + additional_salary.submit() + + advance.reload() + self.assertEqual(advance.return_amount, 1000) + + # update advance return amount on additional salary cancellation + additional_salary.cancel() + advance.reload() + self.assertEqual(advance.return_amount, 700) + + def tearDown(self): + frappe.db.rollback() + + def make_payment_entry(advance): journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name)) journal_entry.cheque_no = "123123" @@ -41,7 +84,7 @@ def make_payment_entry(advance): return journal_entry -def make_employee_advance(employee_name): +def make_employee_advance(employee_name, args=None): doc = frappe.new_doc("Employee Advance") doc.employee = employee_name doc.company = "_Test company" @@ -51,6 +94,10 @@ def make_employee_advance(employee_name): doc.advance_amount = 1000 doc.posting_date = nowdate() doc.advance_account = "_Test Employee Advance - _TC" + + if args: + doc.update(args) + doc.insert() doc.submit() diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index ed10f2bc67..7c0a8eac99 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -14,12 +14,11 @@ from erpnext.hr.utils import validate_active_employee class AdditionalSalary(Document): def on_submit(self): - if self.ref_doctype == "Employee Advance" and self.ref_docname: - frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount) - + self.update_return_amount_in_employee_advance() self.update_employee_referral() def on_cancel(self): + self.update_return_amount_in_employee_advance() self.update_employee_referral(cancel=True) def validate(self): @@ -98,6 +97,17 @@ class AdditionalSalary(Document): frappe.throw(_("Additional Salary for referral bonus can only be created against Employee Referral with status {0}").format( frappe.bold("Accepted"))) + def update_return_amount_in_employee_advance(self): + if self.ref_doctype == "Employee Advance" and self.ref_docname: + return_amount = frappe.db.get_value("Employee Advance", self.ref_docname, "return_amount") + + if self.docstatus == 2: + return_amount -= self.amount + else: + return_amount += self.amount + + frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", return_amount) + def update_employee_referral(self, cancel=False): if self.ref_doctype == "Employee Referral": status = "Unpaid" if cancel else "Paid" From 1c1b476d67dd7d84ed99578f8159cd6a1cd92c27 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 14 Sep 2021 11:41:19 +0530 Subject: [PATCH 054/416] perf: Optimize get_attribute_filters (#26729) * perf: Optimize get_attribute_filters * fix: handle when filter attributes are undefined * chore: unused imports Co-authored-by: Ankush Menat --- .../setup/doctype/item_group/item_group.py | 2 +- erpnext/shopping_cart/filters.py | 53 ++++++++----------- erpnext/www/all-products/index.html | 8 +-- erpnext/www/all-products/index.py | 2 +- 4 files changed, 29 insertions(+), 36 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index ddf3e662e0..b26c6a49b4 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -99,7 +99,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): filter_engine = ProductFiltersBuilder(self.name) context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_fitlers() + context.attribute_filters = filter_engine.get_attribute_filters() context.update({ "parents": get_parent_item_groups(self.parent_item_group), diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py index aaeff0fe07..4787ae534c 100644 --- a/erpnext/shopping_cart/filters.py +++ b/erpnext/shopping_cart/filters.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe -from frappe import _dict class ProductFiltersBuilder: @@ -57,37 +56,31 @@ class ProductFiltersBuilder: return filter_data - def get_attribute_fitlers(self): + def get_attribute_filters(self): attributes = [row.attribute for row in self.doc.filter_attributes] - attribute_docs = [ - frappe.get_doc('Item Attribute', attribute) for attribute in attributes - ] - valid_attributes = [] + if not attributes: + return [] - for attr_doc in attribute_docs: - selected_attributes = [] - for attr in attr_doc.item_attribute_values: - or_filters = [] - filters= [ - ["Item Variant Attribute", "attribute", "=", attr.parent], - ["Item Variant Attribute", "attribute_value", "=", attr.attribute_value] - ] - if self.item_group: - or_filters.extend([ - ["item_group", "=", self.item_group], - ["Website Item Group", "item_group", "=", self.item_group] - ]) + result = frappe.db.sql( + """ + select + distinct attribute, attribute_value + from + `tabItem Variant Attribute` + where + attribute in %(attributes)s + and attribute_value is not null + """, + {"attributes": attributes}, + as_dict=1, + ) - if frappe.db.get_all("Item", filters, or_filters=or_filters, limit=1): - selected_attributes.append(attr) + attribute_value_map = {} + for d in result: + attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value) - if selected_attributes: - valid_attributes.append( - _dict( - item_attribute_values=selected_attributes, - name=attr_doc.name - ) - ) - - return valid_attributes + out = [] + for name, values in attribute_value_map.items(): + out.append(frappe._dict(name=name, item_attribute_values=values)) + return out diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html index 7c18ecc41f..a7838eebc1 100644 --- a/erpnext/www/all-products/index.html +++ b/erpnext/www/all-products/index.html @@ -98,14 +98,14 @@
{% for attr_value in attribute.item_attribute_values %}
-
{% endfor %} diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py index 335c10443a..df5258b238 100644 --- a/erpnext/www/all-products/index.py +++ b/erpnext/www/all-products/index.py @@ -27,7 +27,7 @@ def get_context(context): filter_engine = ProductFiltersBuilder() context.field_filters = filter_engine.get_field_filters() - context.attribute_filters = filter_engine.get_attribute_fitlers() + context.attribute_filters = filter_engine.get_attribute_filters() context.product_settings = product_settings context.body_class = "product-page" From 95460d98186ea62873487faf3c04af19f1d2fb11 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Tue, 14 Sep 2021 12:00:34 +0530 Subject: [PATCH 055/416] fix(HR): Ignore invalid fields when updating employee details (#27456) --- erpnext/hr/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index deec644209..b6f4cadcc9 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -32,7 +32,10 @@ def set_employee_name(doc): def update_employee(employee, details, date=None, cancel=False): internal_work_history = {} for item in details: - fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype + field = frappe.get_meta("Employee").get_field(item.fieldname) + if not field: + continue + fieldtype = field.fieldtype new_data = item.new if not cancel else item.current if fieldtype == "Date" and new_data: new_data = getdate(new_data) From 9520215e2b093f287ffba83762e2a90c9757d9d1 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 14 Sep 2021 12:33:21 +0530 Subject: [PATCH 056/416] feat: Handle Excess/Multiple Item Transfer against Job Card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hide MR/Material Transfer buttons in JC if cancelled - Show MR/Material transfer buttons if pending to transfer or excess transfer allowed - Renamed ‘Transferred Qty’ to ‘FG Qty from Transferred Raw Materials’ in JC - Set status to Completed in JC in case of excess transfer too - During excess transfer against JC, avoid negative ‘For Quantity’. Set to 0 instead - Job card section and excess transfer allowance checkbox in Manufacturing Settings - Renamed ’Allow Multiple Material Consumption’ to ‘Allow Continuous Material Consumption’ (fiedname is same) - Secured denominator variables in `get_transfered_raw_materials` to avoid ZeroDivisionError --- .../doctype/job_card/job_card.js | 13 ++++++--- .../doctype/job_card/job_card.json | 5 ++-- .../doctype/job_card/job_card.py | 16 +++++++++-- .../manufacturing_settings.json | 27 ++++++++++++++++--- .../stock/doctype/stock_entry/stock_entry.py | 4 +-- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 91eb4a0fa9..9b8f81bc24 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -27,14 +27,21 @@ frappe.ui.form.on('Job Card', { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; - if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) { - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length && frm.doc.docstatus < 2) { + let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; + let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; + + if (to_request || excess_transfer_allowed) { frm.add_custom_button(__("Material Request"), () => { frm.trigger("make_material_request"); }); } - if (frm.doc.for_quantity != frm.doc.transferred_qty) { + // check if any row has untransferred materials + // in case of multiple items in JC + let to_transfer = frm.doc.items.some((row) => row.transferred_qty < row.required_qty); + + if (to_transfer || excess_transfer_allowed) { frm.add_custom_button(__("Material Transfer"), () => { frm.trigger("make_stock_entry"); }).addClass("btn-primary"); diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 046e2fd182..f5bbac33b8 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -185,7 +185,7 @@ "default": "0", "fieldname": "transferred_qty", "fieldtype": "Float", - "label": "Transferred Qty", + "label": "FG Qty from Transferred Raw Materials", "read_only": 1 }, { @@ -396,10 +396,11 @@ ], "is_submittable": 1, "links": [], - "modified": "2021-03-16 15:59:32.766484", + "modified": "2021-09-13 21:34:15.177928", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ceae63cb94..1906bf6a9d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -37,6 +37,10 @@ class OperationSequenceError(frappe.ValidationError): pass class JobCardCancelError(frappe.ValidationError): pass class JobCard(Document): + def onload(self): + excess_transfer = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer") + self.set_onload("job_card_excess_transfer", excess_transfer) + def validate(self): self.validate_time_logs() self.set_status() @@ -449,6 +453,7 @@ class JobCard(Document): frappe.db.set_value('Job Card Item', row.job_card_item, 'transferred_qty', flt(qty)) def set_transferred_qty(self, update_status=False): + "Set total FG Qty for which RM was transferred." if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 @@ -457,6 +462,7 @@ class JobCard(Document): return if self.items: + # sum of 'For Quantity' of Stock Entries against JC self.transferred_qty = frappe.db.get_value('Stock Entry', { 'job_card': self.name, 'work_order': self.work_order, @@ -500,7 +506,9 @@ class JobCard(Document): self.status = 'Work In Progress' if (self.docstatus == 1 and - (self.for_quantity == self.transferred_qty or not self.items)): + (self.for_quantity <= self.transferred_qty or not self.items)): + # consider excess transfer + # completed qty is checked via separate validation self.status = 'Completed' if self.status != 'Completed': @@ -618,7 +626,11 @@ def make_stock_entry(source_name, target_doc=None): def set_missing_values(source, target): target.purpose = "Material Transfer for Manufacture" target.from_bom = 1 - target.fg_completed_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + + # avoid negative 'For Quantity' + pending_fg_qty = source.get('for_quantity', 0) - source.get('transferred_qty', 0) + target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0 + target.set_transfer_qty() target.calculate_rate_and_amount() target.set_missing_values() diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 024f784725..01647d56c9 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -25,9 +25,12 @@ "overproduction_percentage_for_sales_order", "column_break_16", "overproduction_percentage_for_work_order", + "job_card_section", + "add_corrective_operation_cost_in_finished_good_valuation", + "column_break_24", + "job_card_excess_transfer", "other_settings_section", "update_bom_costs_automatically", - "add_corrective_operation_cost_in_finished_good_valuation", "column_break_23", "make_serial_no_batch_from_work_order" ], @@ -96,10 +99,10 @@ }, { "default": "0", - "description": "Allow multiple material consumptions against a Work Order", + "description": "Allow material consumptions without immediately manufacturing finished goods against a Work Order", "fieldname": "material_consumption", "fieldtype": "Check", - "label": "Allow Multiple Material Consumption" + "label": "Allow Continuous Material Consumption" }, { "default": "0", @@ -175,13 +178,29 @@ "fieldname": "add_corrective_operation_cost_in_finished_good_valuation", "fieldtype": "Check", "label": "Add Corrective Operation Cost in Finished Good Valuation" + }, + { + "fieldname": "job_card_section", + "fieldtype": "Section Break", + "label": "Job Card" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Allow transferring raw materials even after the Required Quantity is fulfilled", + "fieldname": "job_card_excess_transfer", + "fieldtype": "Check", + "label": "Allow Excess Material Transfer" } ], "icon": "icon-wrench", "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-03-16 15:54:38.967341", + "modified": "2021-09-13 22:09:09.401559", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing Settings", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4ccfa62b5a..0459489185 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1264,9 +1264,9 @@ class StockEntry(StockController): po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from `tabWork Order` where name=%s""", self.work_order, as_dict=1)[0] - manufacturing_qty = flt(po_qty.qty) + manufacturing_qty = flt(po_qty.qty) or 1 produced_qty = flt(po_qty.produced_qty) - trans_qty = flt(po_qty.material_transferred_for_manufacturing) + trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1 for item in transferred_materials: qty= item.qty From 9aa6f52edd1ceecedd7bcd890d99572e2fb1cec9 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Tue, 14 Sep 2021 12:49:08 +0530 Subject: [PATCH 057/416] fix: Webform Permission for custom doctype (#26232) * fix: Webform Permission for custom doctype * fix: sider fix * fix: app check condition for getting correct list_context * chore: Better naming convention * test: Added test case to check permission for webform of custom doctype * chore: linting issue * chore: linting issue * fix: sider fix * test: minor changes * chore: linter and better naming method * chore: linter fix Co-authored-by: Nabin Hait --- .../controllers/website_list_for_contact.py | 36 ++++- erpnext/hooks.py | 1 + erpnext/tests/test_webform.py | 138 ++++++++++++++++++ 3 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 erpnext/tests/test_webform.py diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index ff2ed45bd2..8e5952c4a3 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _ +from frappe.modules.utils import get_module_app from frappe.utils import flt, has_common from frappe.utils.user import is_website_user @@ -21,8 +22,32 @@ def get_list_context(context=None): "get_list": get_transaction_list } +def get_webform_list_context(module): + if get_module_app(module) != 'erpnext': + return + return { + "get_list": get_webform_transaction_list + } -def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): +def get_webform_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): + """ Get List of transactions for custom doctypes """ + from frappe.www.list import get_list + + if not filters: + filters = [] + + meta = frappe.get_meta(doctype) + + for d in meta.fields: + if d.fieldtype == 'Link' and d.fieldname != 'amended_from': + allowed_docs = [d.name for d in get_transaction_list(doctype=d.options, custom=True)] + allowed_docs.append('') + filters.append((d.fieldname, 'in', allowed_docs)) + + return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=False, + fields=None, order_by="modified") + +def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified", custom=False): user = frappe.session.user ignore_permissions = False @@ -46,7 +71,7 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p filters.append(('customer', 'in', customers)) elif suppliers: filters.append(('supplier', 'in', suppliers)) - else: + elif not custom: return [] if doctype == 'Request for Quotation': @@ -56,9 +81,16 @@ def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_p # Since customers and supplier do not have direct access to internal doctypes ignore_permissions = True + if not customers and not suppliers and custom: + ignore_permissions = False + filters = [] + transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length, fields='name', ignore_permissions=ignore_permissions, order_by='modified desc') + if custom: + return transactions + return post_process(doctype, transactions) def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20, diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5b6e1eeafc..be05e35113 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -62,6 +62,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou # website update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"] my_account_context = "erpnext.shopping_cart.utils.update_my_account_context" +webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context" calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"] diff --git a/erpnext/tests/test_webform.py b/erpnext/tests/test_webform.py new file mode 100644 index 0000000000..19255db33c --- /dev/null +++ b/erpnext/tests/test_webform.py @@ -0,0 +1,138 @@ +import unittest + +import frappe + +from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + + +class TestWebsite(unittest.TestCase): + def test_permission_for_custom_doctype(self): + create_user('Supplier 1', 'supplier1@gmail.com') + create_user('Supplier 2', 'supplier2@gmail.com') + create_supplier_with_contact('Supplier1', 'All Supplier Groups', 'Supplier 1', 'supplier1@gmail.com') + create_supplier_with_contact('Supplier2', 'All Supplier Groups', 'Supplier 2', 'supplier2@gmail.com') + po1 = create_purchase_order(supplier='Supplier1') + po2 = create_purchase_order(supplier='Supplier2') + + create_custom_doctype() + create_webform() + create_order_assignment(supplier='Supplier1', po = po1.name) + create_order_assignment(supplier='Supplier2', po = po2.name) + + frappe.set_user("Administrator") + # checking if data consist of all order assignment of Supplier1 and Supplier2 + self.assertTrue('Supplier1' and 'Supplier2' in [data.supplier for data in get_data()]) + + frappe.set_user("supplier1@gmail.com") + # checking if data only consist of order assignment of Supplier1 + self.assertTrue('Supplier1' in [data.supplier for data in get_data()]) + self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier1']) + + frappe.set_user("supplier2@gmail.com") + # checking if data only consist of order assignment of Supplier2 + self.assertTrue('Supplier2' in [data.supplier for data in get_data()]) + self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier2']) + + frappe.set_user("Administrator") + +def get_data(): + webform_list_contexts = frappe.get_hooks('webform_list_context') + if webform_list_contexts: + context = frappe._dict(frappe.get_attr(webform_list_contexts[0])('Buying') or {}) + kwargs = dict(doctype='Order Assignment', order_by = 'modified desc') + return context.get_list(**kwargs) + +def create_user(name, email): + frappe.get_doc({ + 'doctype': 'User', + 'send_welcome_email': 0, + 'user_type': 'Website User', + 'first_name': name, + 'email': email, + 'roles': [{"doctype": "Has Role", "role": "Supplier"}] + }).insert(ignore_if_duplicate = True) + +def create_supplier_with_contact(name, group, contact_name, contact_email): + supplier = frappe.get_doc({ + 'doctype': 'Supplier', + 'supplier_name': name, + 'supplier_group': group + }).insert(ignore_if_duplicate = True) + + if not frappe.db.exists('Contact', contact_name+'-1-'+name): + new_contact = frappe.new_doc("Contact") + new_contact.first_name = contact_name + new_contact.is_primary_contact = True, + new_contact.append('links', { + "link_doctype": "Supplier", + "link_name": supplier.name + }) + new_contact.append('email_ids', { + "email_id": contact_email, + "is_primary": 1 + }) + + new_contact.insert(ignore_mandatory=True) + +def create_custom_doctype(): + frappe.get_doc({ + 'doctype': 'DocType', + 'name': 'Order Assignment', + 'module': 'Buying', + 'custom': 1, + 'autoname': 'field:po', + 'fields': [ + {'label': 'PO', 'fieldname': 'po', 'fieldtype': 'Link', 'options': 'Purchase Order'}, + {'label': 'Supplier', 'fieldname': 'supplier', 'fieldtype': 'Data', "fetch_from": "po.supplier"} + ], + 'permissions': [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Supplier" + } + ] + }).insert(ignore_if_duplicate = True) + +def create_webform(): + frappe.get_doc({ + 'doctype': 'Web Form', + 'module': 'Buying', + 'title': 'SO Schedule', + 'route': 'so-schedule', + 'doc_type': 'Order Assignment', + 'web_form_fields': [ + { + 'doctype': 'Web Form Field', + 'fieldname': 'po', + 'fieldtype': 'Link', + 'options': 'Purchase Order', + 'label': 'PO' + }, + { + 'doctype': 'Web Form Field', + 'fieldname': 'supplier', + 'fieldtype': 'Data', + 'label': 'Supplier' + } + ] + + }).insert(ignore_if_duplicate = True) + +def create_order_assignment(supplier, po): + frappe.get_doc({ + 'doctype': 'Order Assignment', + 'po': po, + 'supplier': supplier, + }).insert(ignore_if_duplicate = True) \ No newline at end of file From aa82624f31059dc03a17472c8aa2c88c075ae34a Mon Sep 17 00:00:00 2001 From: DeeMysterio Date: Tue, 14 Sep 2021 13:58:18 +0530 Subject: [PATCH 058/416] Merge pull request #27281 from DeeMysterio/party-specific-items feat: link items to supplier / customer --- erpnext/buying/doctype/supplier/supplier.json | 8 +- .../supplier_item_group.json | 77 ------------------- .../supplier_item_group.py | 20 ----- .../test_supplier_item_group.py | 11 --- erpnext/controllers/queries.py | 30 +++++--- erpnext/patches.txt | 1 + ...ier_item_group_with_party_specific_item.py | 17 ++++ .../selling/doctype/customer/customer.json | 22 +++--- .../doctype/party_specific_item}/__init__.py | 0 .../party_specific_item.js} | 2 +- .../party_specific_item.json | 77 +++++++++++++++++++ .../party_specific_item.py | 19 +++++ .../test_party_specific_item.py | 38 +++++++++ erpnext/selling/sales_common.js | 2 +- 14 files changed, 189 insertions(+), 135 deletions(-) delete mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.json delete mode 100644 erpnext/buying/doctype/supplier_item_group/supplier_item_group.py delete mode 100644 erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py create mode 100644 erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py rename erpnext/{buying/doctype/supplier_item_group => selling/doctype/party_specific_item}/__init__.py (100%) rename erpnext/{buying/doctype/supplier_item_group/supplier_item_group.js => selling/doctype/party_specific_item/party_specific_item.js} (79%) create mode 100644 erpnext/selling/doctype/party_specific_item/party_specific_item.json create mode 100644 erpnext/selling/doctype/party_specific_item/party_specific_item.py create mode 100644 erpnext/selling/doctype/party_specific_item/test_party_specific_item.py diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index c7a5db5994..12a09cdd0e 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -433,12 +433,12 @@ "image_field": "image", "links": [ { - "group": "Item Group", - "link_doctype": "Supplier Item Group", - "link_fieldname": "supplier" + "group": "Allowed Items", + "link_doctype": "Party Specific Item", + "link_fieldname": "party" } ], - "modified": "2021-08-27 18:02:44.314077", + "modified": "2021-09-06 17:37:56.522233", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json deleted file mode 100644 index 1971458f61..0000000000 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "actions": [], - "creation": "2021-05-07 18:16:40.621421", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "supplier", - "item_group" - ], - "fields": [ - { - "fieldname": "supplier", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Supplier", - "options": "Supplier", - "reqd": 1 - }, - { - "fieldname": "item_group", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Group", - "options": "Item Group", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-05-19 13:48:16.742303", - "modified_by": "Administrator", - "module": "Buying", - "name": "Supplier Item Group", - "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": "Purchase User", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py deleted file mode 100644 index 6d71f7d516..0000000000 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe -from frappe import _ -from frappe.model.document import Document - - -class SupplierItemGroup(Document): - def validate(self): - exists = frappe.db.exists({ - 'doctype': 'Supplier Item Group', - 'supplier': self.supplier, - 'item_group': self.item_group - }) - if exists: - frappe.throw(_("Item Group has already been linked to this supplier.")) diff --git a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py b/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py deleted file mode 100644 index 55ba85ef2d..0000000000 --- a/erpnext/buying/doctype/supplier_item_group/test_supplier_item_group.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - - -class TestSupplierItemGroup(unittest.TestCase): - pass diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index ccd417be04..9f28646a0b 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -7,6 +7,7 @@ import json from collections import defaultdict import frappe +from frappe import scrub from frappe.desk.reportview import get_filters_cond, get_match_cond from frappe.utils import nowdate, unique @@ -223,18 +224,29 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals if not field in searchfields] searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) - if filters and isinstance(filters, dict) and filters.get('supplier'): - item_group_list = frappe.get_all('Supplier Item Group', - filters = {'supplier': filters.get('supplier')}, fields = ['item_group']) + if filters and isinstance(filters, dict): + if filters.get('customer') or filters.get('supplier'): + party = filters.get('customer') or filters.get('supplier') + item_rules_list = frappe.get_all('Party Specific Item', + filters = {'party': party}, fields = ['restrict_based_on', 'based_on_value']) - item_groups = [] - for i in item_group_list: - item_groups.append(i.item_group) + filters_dict = {} + for rule in item_rules_list: + if rule['restrict_based_on'] == 'Item': + rule['restrict_based_on'] = 'name' + filters_dict[rule.restrict_based_on] = [] - del filters['supplier'] + for rule in item_rules_list: + filters_dict[rule.restrict_based_on].append(rule.based_on_value) + + for filter in filters_dict: + filters[scrub(filter)] = ['in', filters_dict[filter]] + + if filters.get('customer'): + del filters['customer'] + else: + del filters['supplier'] - if item_groups: - filters['item_group'] = ['in', item_groups] description_cond = '' if frappe.db.count('Item', cache=True) < 50000: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 15b196fd78..3bc40a17e0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -304,5 +304,6 @@ erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.create_gst_payment_entry_fields erpnext.patches.v14_0.delete_shopify_doctypes +erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields diff --git a/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py new file mode 100644 index 0000000000..ba96fdd226 --- /dev/null +++ b/erpnext/patches/v13_0/replace_supplier_item_group_with_party_specific_item.py @@ -0,0 +1,17 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + if frappe.db.table_exists('Supplier Item Group'): + frappe.reload_doc("selling", "doctype", "party_specific_item") + sig = frappe.db.get_all("Supplier Item Group", fields=["name", "supplier", "item_group"]) + for item in sig: + psi = frappe.new_doc("Party Specific Item") + psi.party_type = "Supplier" + psi.party = item.supplier + psi.restrict_based_on = "Item Group" + psi.based_on_value = item.item_group + psi.insert() diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 5913b849eb..e811435e66 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -20,7 +20,6 @@ "tax_withholding_category", "default_bank_account", "lead_name", - "prospect", "opportunity_name", "image", "column_break0", @@ -214,7 +213,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company" + "options": "Company", + "unique": 1 }, { "depends_on": "represents_company", @@ -497,14 +497,6 @@ "label": "Tax Withholding Category", "options": "Tax Withholding Category" }, - { - "fieldname": "prospect", - "fieldtype": "Link", - "label": "Prospect", - "no_copy": 1, - "options": "Prospect", - "print_hide": 1 - }, { "fieldname": "opportunity_name", "fieldtype": "Link", @@ -518,8 +510,14 @@ "idx": 363, "image_field": "image", "index_web_pages_for_search": 1, - "links": [], - "modified": "2021-08-25 18:56:09.929905", + "links": [ + { + "group": "Allowed Items", + "link_doctype": "Party Specific Item", + "link_fieldname": "party" + } + ], + "modified": "2021-09-06 17:38:54.196663", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/buying/doctype/supplier_item_group/__init__.py b/erpnext/selling/doctype/party_specific_item/__init__.py similarity index 100% rename from erpnext/buying/doctype/supplier_item_group/__init__.py rename to erpnext/selling/doctype/party_specific_item/__init__.py diff --git a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js b/erpnext/selling/doctype/party_specific_item/party_specific_item.js similarity index 79% rename from erpnext/buying/doctype/supplier_item_group/supplier_item_group.js rename to erpnext/selling/doctype/party_specific_item/party_specific_item.js index f7da90d98d..077b93631e 100644 --- a/erpnext/buying/doctype/supplier_item_group/supplier_item_group.js +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.js @@ -1,7 +1,7 @@ // Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Supplier Item Group', { +frappe.ui.form.on('Party Specific Item', { // refresh: function(frm) { // } diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.json b/erpnext/selling/doctype/party_specific_item/party_specific_item.json new file mode 100644 index 0000000000..32b5d478bb --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.json @@ -0,0 +1,77 @@ +{ + "actions": [], + "creation": "2021-08-27 19:28:07.559978", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "party_type", + "party", + "column_break_3", + "restrict_based_on", + "based_on_value" + ], + "fields": [ + { + "fieldname": "party_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Party Type", + "options": "Customer\nSupplier", + "reqd": 1 + }, + { + "fieldname": "party", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Party Name", + "options": "party_type", + "reqd": 1 + }, + { + "fieldname": "restrict_based_on", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Restrict Items Based On", + "options": "Item\nItem Group\nBrand", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "based_on_value", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Based On Value", + "options": "restrict_based_on", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-14 13:27:58.612334", + "modified_by": "Administrator", + "module": "Selling", + "name": "Party Specific Item", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "party", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/party_specific_item/party_specific_item.py b/erpnext/selling/doctype/party_specific_item/party_specific_item.py new file mode 100644 index 0000000000..a408af5642 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/party_specific_item.py @@ -0,0 +1,19 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class PartySpecificItem(Document): + def validate(self): + exists = frappe.db.exists({ + 'doctype': 'Party Specific Item', + 'party_type': self.party_type, + 'party': self.party, + 'restrict_based_on': self.restrict_based_on, + 'based_on': self.based_on_value, + }) + if exists: + frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type)) diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py new file mode 100644 index 0000000000..874a364592 --- /dev/null +++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py @@ -0,0 +1,38 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import unittest + +import frappe + +from erpnext.controllers.queries import item_query + +test_dependencies = ['Item', 'Customer', 'Supplier'] + +def create_party_specific_item(**args): + psi = frappe.new_doc("Party Specific Item") + psi.party_type = args.get('party_type') + psi.party = args.get('party') + psi.restrict_based_on = args.get('restrict_based_on') + psi.based_on_value = args.get('based_on_value') + psi.insert() + +class TestPartySpecificItem(unittest.TestCase): + def setUp(self): + self.customer = frappe.get_last_doc("Customer") + self.supplier = frappe.get_last_doc("Supplier") + self.item = frappe.get_last_doc("Item") + + def test_item_query_for_customer(self): + create_party_specific_item(party_type='Customer', party=self.customer.name, restrict_based_on='Item', based_on_value=self.item.name) + filters = {'is_sales_item': 1, 'customer': self.customer.name} + items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False) + for item in items: + self.assertEqual(item[0], self.item.name) + + def test_item_query_for_supplier(self): + create_party_specific_item(party_type='Supplier', party=self.supplier.name, restrict_based_on='Item Group', based_on_value=self.item.item_group) + filters = {'supplier': self.supplier.name, 'is_purchase_item': 1} + items = item_query(doctype= 'Item', txt= '', searchfield= 'name', start= 0, page_len= 20,filters=filters, as_dict= False) + for item in items: + self.assertEqual(item[2], self.item.item_group) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index 6a09109bfd..ddd4c4e6a5 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -63,7 +63,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran this.frm.set_query("item_code", "items", function() { return { query: "erpnext.controllers.queries.item_query", - filters: {'is_sales_item': 1} + filters: {'is_sales_item': 1, 'customer': cur_frm.doc.customer} } }); } From b5b61b3a3d8d46ef9d15af3d631ad407e18e9270 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:26:44 +0500 Subject: [PATCH 059/416] Update erpnext/regional/report/ksa_vat/ksa_vat.py String made translatable Co-authored-by: Saqib --- erpnext/regional/report/ksa_vat/ksa_vat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 6828007203..1175fbf11f 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -48,7 +48,7 @@ def get_data(filters): company = filters.get('company') if frappe.db.exists('KSA VAT Setting', company) is None: url = get_url_to_list('KSA VAT Setting') - frappe.msgprint(f'Create KSA VAT Setting for this company') + frappe.msgprint(_('Create KSA VAT Setting for this company').format(url)) return data ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) From 6985ca210e6170c55f15d3b3322fa7117a32c2e1 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:28:57 +0500 Subject: [PATCH 060/416] Refactor erpnext/regional/__init__.py Co-authored-by: Saqib --- erpnext/regional/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 3591be07b8..6b68bc032b 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -86,8 +86,6 @@ def create_qr_code(doc, method): break - else: - pass def delete_qr_code_file(doc, method): """Delete QR Code on deleted sales invoice""" From 0950713e4c01a63ae442abb443f9b0441834648b Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:29:33 +0500 Subject: [PATCH 061/416] Update erpnext/regional/saudi_arabia/setup.py Method name updated Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index f55900023d..22a050650c 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -13,7 +13,7 @@ def setup(company=None, patch=True): add_print_formats() add_permissions() create_ksa_vat_setting(company) - make_custom_fields() + make_qrcode_field() def add_permissions(): From de01066ef5258e351c61106518dc8c05aa86aa08 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Tue, 14 Sep 2021 14:29:45 +0500 Subject: [PATCH 062/416] Update erpnext/regional/saudi_arabia/setup.py Method name updated Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 22a050650c..82f2f4fab0 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -27,7 +27,7 @@ def add_permissions(): """Enable KSA VAT Report""" frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) -def make_custom_fields(): +def make_qrcode_field(): qr_code_field = dict( fieldname='qr_code', label='QR Code', From 8a42570c962af645a49c8631704fb183bb08b79a Mon Sep 17 00:00:00 2001 From: Ahmad Date: Tue, 14 Sep 2021 14:45:23 +0500 Subject: [PATCH 063/416] refactor(regional): moved methos to region specific utils for KSA --- erpnext/hooks.py | 4 +- erpnext/regional/__init__.py | 69 ------------------------ erpnext/regional/saudi_arabia/utils.py | 73 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 erpnext/regional/saudi_arabia/utils.py diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 050ce2e11d..c41a1081de 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -262,7 +262,7 @@ doc_events = { "validate": "erpnext.regional.india.utils.validate_tax_category" }, "Sales Invoice": { - "after_insert": "erpnext.regional.create_qr_code", + "after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code", "on_submit": [ "erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit", @@ -274,7 +274,7 @@ doc_events = { ], "on_trash": [ "erpnext.regional.check_deletion_permission", - "erpnext.regional.delete_qr_code_file" + "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" ], "validate": [ "erpnext.regional.india.utils.validate_document_name", diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index 6b68bc032b..d7dcbf4fe1 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -7,9 +7,6 @@ import frappe from frappe import _ from erpnext import get_region -from pyqrcode import create as qr_create -import io -import os def check_deletion_permission(doc, method): @@ -35,69 +32,3 @@ def create_transaction_log(doc, method): "data": data }).insert(ignore_permissions=True) - -def create_qr_code(doc, method): - """Create QR Code after inserting Sales Inv - """ - - region = get_region(doc.company) - if region not in ['Saudi Arabia']: - return - - # if QR Code field not present, do nothing - if not hasattr(doc, 'qr_code'): - return - - # Don't create QR Code if it already exists - qr_code = doc.get("qr_code") - if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): - return - - fields = frappe.get_meta('Sales Invoice').fields - - for field in fields: - if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': - # Creating public url to print format - default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") - - # System Language - language = frappe.get_system_settings('language') - - # creating qr code for the url - url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" - qr_image = io.BytesIO() - url = qr_create(url, error='L') - url.png(qr_image, scale=2, quiet_zone=1) - - # making file - filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") - _file = frappe.get_doc({ - "doctype": "File", - "file_name": filename, - "is_private": 0, - "content": qr_image.getvalue() - }) - - _file.save() - - # assigning to document - doc.db_set('qr_code', _file.file_url) - doc.notify_update() - - break - - -def delete_qr_code_file(doc, method): - """Delete QR Code on deleted sales invoice""" - - region = get_region(doc.company) - if region not in ['Saudi Arabia']: - return - - if hasattr(doc, 'qr_code'): - if doc.get('qr_code'): - file_doc = frappe.get_list('File', { - 'file_url': doc.qr_code - }) - if len(file_doc): - frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py new file mode 100644 index 0000000000..4431ba065e --- /dev/null +++ b/erpnext/regional/saudi_arabia/utils.py @@ -0,0 +1,73 @@ +import frappe +from frappe import _ +from erpnext import get_region +from pyqrcode import create as qr_create +import io +import os + + +def create_qr_code(doc, method): + """Create QR Code after inserting Sales Inv + """ + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + # if QR Code field not present, do nothing + if not hasattr(doc, 'qr_code'): + return + + # Don't create QR Code if it already exists + qr_code = doc.get("qr_code") + if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): + return + + fields = frappe.get_meta('Sales Invoice').fields + + for field in fields: + if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + # Creating public url to print format + default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") + + # System Language + language = frappe.get_system_settings('language') + + # creating qr code for the url + url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" + qr_image = io.BytesIO() + url = qr_create(url, error='L') + url.png(qr_image, scale=2, quiet_zone=1) + + # making file + filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") + _file = frappe.get_doc({ + "doctype": "File", + "file_name": filename, + "is_private": 0, + "content": qr_image.getvalue() + }) + + _file.save() + + # assigning to document + doc.db_set('qr_code', _file.file_url) + doc.notify_update() + + break + + +def delete_qr_code_file(doc, method): + """Delete QR Code on deleted sales invoice""" + + region = get_region(doc.company) + if region not in ['Saudi Arabia']: + return + + if hasattr(doc, 'qr_code'): + if doc.get('qr_code'): + file_doc = frappe.get_list('File', { + 'file_url': doc.qr_code + }) + if len(file_doc): + frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file From 469bb8d977f135532a4d4afe4352ece58b2de4b4 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 14 Sep 2021 16:27:39 +0530 Subject: [PATCH 064/416] test: Work Order Material Transfer against Job Card - Tests for multiple items transfer and excess transfer against JC - Remove unused __future__ imports - Changed Copyright year - Sider: (js) Added space before if --- .../doctype/job_card/job_card.js | 3 +- .../doctype/job_card/job_card.py | 5 +- .../doctype/job_card/test_job_card.py | 107 +++++++++++++++++- .../doctype/work_order/test_work_order.py | 7 +- 4 files changed, 107 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 9b8f81bc24..35be38813e 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -26,8 +26,9 @@ frappe.ui.form.on('Job Card', { refresh: function(frm) { frappe.flags.pause_job = 0; frappe.flags.resume_job = 0; + let has_items = frm.doc.items && frm.doc.items.length; - if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length && frm.doc.docstatus < 2) { + if (!frm.doc.__islocal && has_items && frm.doc.docstatus < 2) { let to_request = frm.doc.for_quantity > frm.doc.transferred_qty; let excess_transfer_allowed = frm.doc.__onload.job_card_excess_transfer; diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 1906bf6a9d..3209546a12 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -1,9 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt - -from __future__ import unicode_literals - import datetime import json diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 80295bba63..57336e1b33 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -1,22 +1,38 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals - import unittest import frappe from frappe.utils import random_string -from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError +from erpnext.manufacturing.doctype.job_card.job_card import ( + make_stock_entry as make_stock_entry_from_jc, + OperationMismatchError, + OverlapError +) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry class TestJobCard(unittest.TestCase): def setUp(self): - self.work_order = make_wo_order_test_record(item="_Test FG Item 2", qty=2) + transfer_material_against, source_warehouse = None, None + tests_that_transfer_against_jc = ("test_job_card_multiple_materials_transfer", + "test_job_card_excess_material_transfer") + + if self._testMethodName in tests_that_transfer_against_jc: + transfer_material_against = "Job Card" + source_warehouse = "Stores - _TC" + + self.work_order = make_wo_order_test_record( + item="_Test FG Item 2", + qty=2, + transfer_material_against=transfer_material_against, + source_warehouse=source_warehouse + ) def tearDown(self): frappe.db.rollback() @@ -96,3 +112,84 @@ class TestJobCard(unittest.TestCase): "employee": employee, }) self.assertRaises(OverlapError, jc2.save) + + def test_job_card_multiple_materials_transfer(self): + "Test transferring RMs separately against Job Card with multiple RMs." + make_stock_entry( + item_code="_Test Item", + target="Stores - _TC", + qty=10, + basic_rate=100 + ) + make_stock_entry( + item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", + qty=6, + basic_rate=100 + ) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + del transfer_entry_1.items[1] # transfer only 1 of 2 RMs + transfer_entry_1.insert() + transfer_entry_1.submit() + + job_card.reload() + + self.assertEqual(transfer_entry_1.fg_completed_qty, 2) + self.assertEqual(job_card.transferred_qty, 2) + + # transfer second RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + del transfer_entry_2.items[0] + transfer_entry_2.insert() + transfer_entry_2.submit() + + # 'For Quantity' here will be 0 since + # transfer was made for 2 fg qty in first transfer Stock Entry + self.assertEqual(transfer_entry_2.fg_completed_qty, 0) + + def test_job_card_excess_material_transfer(self): + "Test transferring more than required RM against Job Card." + make_stock_entry(item_code="_Test Item", target="Stores - _TC", + qty=25, basic_rate=100) + make_stock_entry(item_code="_Test Item Home Desktop Manufactured", + target="Stores - _TC", qty=15, basic_rate=100) + + job_card_name = frappe.db.get_value("Job Card", {'work_order': self.work_order.name}) + job_card = frappe.get_doc("Job Card", job_card_name) + + # fully transfer both RMs + transfer_entry_1 = make_stock_entry_from_jc(job_card_name) + transfer_entry_1.insert() + transfer_entry_1.submit() + + # transfer extra qty of both RM due to previously damaged RM + transfer_entry_2 = make_stock_entry_from_jc(job_card_name) + # deliberately change 'For Quantity' + transfer_entry_2.fg_completed_qty = 1 + transfer_entry_2.items[0].qty = 5 + transfer_entry_2.items[1].qty = 3 + transfer_entry_2.insert() + transfer_entry_2.submit() + + job_card.reload() + self.assertGreater(job_card.transferred_qty, job_card.for_quantity) + + # Check if 'For Quantity' is negative + # as 'transferred_qty' > Qty to Manufacture + transfer_entry_3 = make_stock_entry_from_jc(job_card_name) + self.assertEqual(transfer_entry_3.fg_completed_qty, 0) + + job_card.append("time_logs", { + "from_time": "2021-01-01 00:01:00", + "to_time": "2021-01-01 06:00:00", + "completed_qty": 2 + }) + job_card.save() + job_card.submit() + + # JC is Completed with excess transfer + self.assertEqual(job_card.status, "Completed") \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index bb43149863..d87b5ec654 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1,9 +1,5 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - - -from __future__ import unicode_literals - import unittest import frappe @@ -814,6 +810,7 @@ def make_wo_order_test_record(**args): wo_order.get_items_and_operations_from_bom() wo_order.sales_order = args.sales_order or None wo_order.planned_start_date = args.planned_start_date or now() + wo_order.transfer_material_against = args.transfer_material_against or "Work Order" if args.source_warehouse: for item in wo_order.get("required_items"): From fea51f40825fa281e1a331a42d77648abaf909b1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Sep 2021 19:04:32 +0530 Subject: [PATCH 065/416] chore: isort [skip ci] --- erpnext/manufacturing/doctype/job_card/test_job_card.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py index 57336e1b33..ea5d364a9c 100644 --- a/erpnext/manufacturing/doctype/job_card/test_job_card.py +++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py @@ -6,10 +6,9 @@ import unittest import frappe from frappe.utils import random_string +from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError from erpnext.manufacturing.doctype.job_card.job_card import ( make_stock_entry as make_stock_entry_from_jc, - OperationMismatchError, - OverlapError ) from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation From c53b78e712428f9b83ecd1825891e7287eb325e4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 14 Sep 2021 20:28:48 +0530 Subject: [PATCH 066/416] fix: Patch for updating tax withholding category dates (#27489) --- ...pdate_dates_in_tax_withholding_category.py | 14 +- erpnext/regional/india/setup.py | 123 +++++++++--------- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py index 2af7f95412..90fb50fb42 100644 --- a/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py +++ b/erpnext/patches/v13_0/update_dates_in_tax_withholding_category.py @@ -3,8 +3,6 @@ import frappe -from erpnext.accounts.utils import get_fiscal_year - def execute(): frappe.reload_doc('accounts', 'doctype', 'Tax Withholding Rate') @@ -13,12 +11,14 @@ def execute(): tds_category_rates = frappe.get_all('Tax Withholding Rate', fields=['name', 'fiscal_year']) fiscal_year_map = {} - for rate in tds_category_rates: - if not fiscal_year_map.get(rate.fiscal_year): - fiscal_year_map[rate.fiscal_year] = get_fiscal_year(fiscal_year=rate.fiscal_year) + fiscal_year_details = frappe.get_all('Fiscal Year', fields=['name', 'year_start_date', 'year_end_date']) - from_date = fiscal_year_map.get(rate.fiscal_year)[1] - to_date = fiscal_year_map.get(rate.fiscal_year)[2] + for d in fiscal_year_details: + fiscal_year_map.setdefault(d.name, d) + + for rate in tds_category_rates: + from_date = fiscal_year_map.get(rate.fiscal_year).get('year_start_date') + to_date = fiscal_year_map.get(rate.fiscal_year).get('year_end_date') frappe.db.set_value('Tax Withholding Rate', rate.name, { 'from_date': from_date, diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 963c4075cd..79dc4b8fc9 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -758,11 +758,11 @@ def set_tax_withholding_category(company): accounts = [dict(company=company, account=tds_account)] try: - fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0] + fiscal_year_details = get_fiscal_year(today(), verbose=0, company=company) except FiscalYearError: pass - docs = get_tds_details(accounts, fiscal_year) + docs = get_tds_details(accounts, fiscal_year_details) for d in docs: if not frappe.db.exists("Tax Withholding Category", d.get("name")): @@ -777,9 +777,10 @@ def set_tax_withholding_category(company): if accounts: doc.append("accounts", accounts[0]) - if fiscal_year: + if fiscal_year_details: # if fiscal year don't match with any of the already entered data, append rate row - fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] + fy_exist = [k for k in doc.get('rates') if k.get('from_date') <= fiscal_year_details[1] \ + and k.get('to_date') >= fiscal_year_details[2]] if not fy_exist: doc.append("rates", d.get('rates')[0]) @@ -802,149 +803,149 @@ def set_tds_account(docs, company): } ]) -def get_tds_details(accounts, fiscal_year): +def get_tds_details(accounts, fiscal_year_details): # bootstrap default tax withholding sections return [ dict(name="TDS - 194C - Company", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194C - Individual", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194C - No PAN / Invalid PAN", category_name="Payment to Contractors (Single / Aggregate)", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 30000, "cumulative_threshold": 100000}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 100000}]), dict(name="TDS - 194D - Company", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - Company Assessee", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - Individual", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194D - No PAN / Invalid PAN", category_name="Insurance Commission", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - Company", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - Individual", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 1, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 1, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194DA - No PAN / Invalid PAN", category_name="Non-exempt payments made under a life insurance policy", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 100000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 100000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - Company", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - Individual", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 5, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 5, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194H - No PAN / Invalid PAN", category_name="Commission / Brokerage", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 15000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 15000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - Company", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - Individual", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent - No PAN / Invalid PAN", category_name="Rent", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - Company", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - Individual", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 2, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 2, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194I - Rent/Machinery - No PAN / Invalid PAN", category_name="Rent-Plant / Machinery", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 180000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 180000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - Company", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - Individual", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Professional Fees - No PAN / Invalid PAN", category_name="Professional Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 30000, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 30000, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - Company", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - Individual", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194J - Director Fees - No PAN / Invalid PAN", category_name="Director Fees", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 0, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 0, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - Company", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 2500, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - Individual", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 10, - "single_threshold": 2500, "cumulative_threshold": 0}]), + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 10, "single_threshold": 2500, "cumulative_threshold": 0}]), dict(name="TDS - 194 - Dividends - No PAN / Invalid PAN", category_name="Dividends", doctype="Tax Withholding Category", accounts=accounts, - rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, - "single_threshold": 2500, "cumulative_threshold": 0}]) + rates=[{"from_date": fiscal_year_details[1], "to_date": fiscal_year_details[2], + "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) ] def create_gratuity_rule(): From b01fe1c3e2db9f1cd39a8a96c3539dbf50d59bf6 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 14 Sep 2021 20:42:47 +0530 Subject: [PATCH 067/416] fix: TaxJar update - nexus, selective api call --- .../doctype/taxjar_nexus_list/__init__.py | 0 .../taxjar_nexus_list/taxjar_nexus_list.json | 51 +++++++++++++++++++ .../taxjar_nexus_list/taxjar_nexus_list.py | 9 ++++ .../taxjar_settings/taxjar_settings.js | 14 ++++- .../taxjar_settings/taxjar_settings.json | 24 ++++++++- .../taxjar_settings/taxjar_settings.py | 16 +++++- .../taxjar_integration.py | 5 ++ 7 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_nexus_list/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json create mode 100644 erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/__init__.py b/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json b/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json new file mode 100644 index 0000000000..d543403ef9 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-09-11 05:09:53.773838", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "region", + "region_code", + "country", + "country_code" + ], + "fields": [ + { + "fieldname": "region", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Region" + }, + { + "fieldname": "region_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Region Code" + }, + { + "fieldname": "country", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Country" + }, + { + "fieldname": "country_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Country Code" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-14 05:33:06.444710", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "TaxJar Nexus List", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py b/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py new file mode 100644 index 0000000000..b93be5190f --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TaxJarNexusList(Document): + pass diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js index 62d5709f51..b6a05c42ef 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js @@ -5,5 +5,17 @@ frappe.ui.form.on('TaxJar Settings', { is_sandbox: (frm) => { frm.toggle_reqd("api_key", !frm.doc.is_sandbox); frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox); - } + }, + + refresh: (frm) => { + + frm.add_custom_button(__('Update Nexus List'), function(){ + frm.call({ + doc: frm.doc, + method: 'update_nexus_list' + }); + }); + }, + + }); diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index c0d60f7a31..da8e77f783 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -16,7 +16,10 @@ "configuration", "tax_account_head", "configuration_cb", - "shipping_account_head" + "shipping_account_head", + "section_break_12", + "nexus_address", + "nexus" ], "fields": [ { @@ -82,11 +85,28 @@ { "fieldname": "cb_keys", "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_12", + "fieldtype": "Section Break", + "label": "Nexus List" + }, + { + "fieldname": "nexus_address", + "fieldtype": "HTML", + "label": "Nexus Address" + }, + { + "fieldname": "nexus", + "fieldtype": "Table", + "label": "Nexus", + "options": "TaxJar Nexus List", + "read_only": 1 } ], "issingle": 1, "links": [], - "modified": "2020-04-30 04:38:03.311089", + "modified": "2021-09-14 01:41:55.871028", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index 9dd481747e..767e8f24f3 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -4,9 +4,21 @@ from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document +from erpnext.erpnext_integrations.taxjar_integration import get_client + class TaxJarSettings(Document): - pass + + @frappe.whitelist() + def update_nexus_list(self): + client = get_client() + nexus = client.nexus_regions() + + new_nexus_list = [frappe._dict(address) for address in nexus] + + self.set('nexus',[]) + self.set('nexus',new_nexus_list) + self.save() \ No newline at end of file diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 870a4ef54c..eb46d245f8 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -164,6 +164,11 @@ def set_sales_tax(doc, method): setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD]) return + # check if delivering within a nexus + nexus_list = frappe.get_doc('TaxJar Settings').get("nexus") + if tax_dict["to_state"] not in [nex.region_code for nex in nexus_list]: + return + tax_data = validate_tax_request(tax_dict) if tax_data is not None: if not tax_data.amount_to_collect: From 3bb60a439a54350765d82c017bfa061cf41004e0 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 14 Sep 2021 22:04:57 +0530 Subject: [PATCH 068/416] fix: sales_tax attribute in api call before submit --- erpnext/erpnext_integrations/taxjar_integration.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index eb46d245f8..29fd81cab7 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -103,7 +103,7 @@ def get_tax_data(doc): shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD]) - line_items = [get_line_item_dict(item) for item in doc.items] + line_items = [get_line_item_dict(item,doc.docstatus) for item in doc.items] if from_shipping_state not in SUPPORTED_STATE_CODES: from_shipping_state = get_state_code(from_address, 'Company') @@ -139,14 +139,21 @@ def get_state_code(address, location): return state_code -def get_line_item_dict(item): - return dict( +def get_line_item_dict(item, docstatus): + tax_dict = dict( id = item.get('idx'), quantity = item.get('qty'), unit_price = item.get('rate'), product_tax_code = item.get('product_tax_category') ) + if docstatus == 1: + tax_dict.update({ + 'sales_tax':item.get('tax_collectable') + }) + + return tax_dict + def set_sales_tax(doc, method): if not TAXJAR_CALCULATE_TAX: return From 2e2985e4f14ddc063a9f481a4ac5c40b18e1f633 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 00:37:41 +0530 Subject: [PATCH 069/416] fix: calculate operating cost based on BOM Quantity (#27464) * fix: calculate operating cost based on BOM Quantity * fix: added test cases --- erpnext/manufacturing/doctype/bom/bom.py | 12 +++- erpnext/manufacturing/doctype/bom/test_bom.py | 18 ++++++ .../doctype/bom_operation/bom_operation.json | 64 +++++++++++++++++-- 3 files changed, 87 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 28a84b2506..232e3a0b0f 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -510,8 +510,14 @@ class BOM(WebsiteGenerator): if d.workstation: self.update_rate_and_time(d, update_hour_rate) - self.operating_cost += flt(d.operating_cost) - self.base_operating_cost += flt(d.base_operating_cost) + operating_cost = d.operating_cost + base_operating_cost = d.base_operating_cost + if d.set_cost_based_on_bom_qty: + operating_cost = flt(d.cost_per_unit) * flt(self.quantity) + base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity) + + self.operating_cost += flt(operating_cost) + self.base_operating_cost += flt(base_operating_cost) def update_rate_and_time(self, row, update_hour_rate = False): if not row.hour_rate or update_hour_rate: @@ -535,6 +541,8 @@ class BOM(WebsiteGenerator): row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate) row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0 row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate) + row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0) + row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0) if update_hour_rate: row.db_update() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 7950dd9d97..706ea268c6 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -108,6 +108,24 @@ class TestBOM(unittest.TestCase): self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + def test_bom_cost_with_batch_size(self): + bom = frappe.copy_doc(test_records[2]) + bom.docstatus = 0 + op_cost = 0.0 + for op_row in bom.operations: + op_row.docstatus = 0 + op_row.batch_size = 2 + op_row.set_cost_based_on_bom_qty = 1 + op_cost += op_row.operating_cost + + bom.save() + + for op_row in bom.operations: + self.assertAlmostEqual(op_row.cost_per_unit, op_row.operating_cost / 2) + + self.assertAlmostEqual(bom.operating_cost, op_cost/2) + bom.delete() + def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self): frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1) for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json index 4458e6db23..ec617f3aaa 100644 --- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json +++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json @@ -8,15 +8,23 @@ "field_order": [ "sequence_id", "operation", - "workstation", - "description", "col_break1", - "hour_rate", + "workstation", "time_in_mins", - "operating_cost", + "costing_section", + "hour_rate", "base_hour_rate", + "column_break_9", + "operating_cost", "base_operating_cost", + "column_break_11", "batch_size", + "set_cost_based_on_bom_qty", + "cost_per_unit", + "base_cost_per_unit", + "more_information_section", + "description", + "column_break_18", "image" ], "fields": [ @@ -117,13 +125,59 @@ "fieldname": "sequence_id", "fieldtype": "Int", "label": "Sequence ID" + }, + { + "depends_on": "eval:doc.batch_size > 0 && doc.set_cost_based_on_bom_qty", + "fieldname": "cost_per_unit", + "fieldtype": "Float", + "label": "Cost Per Unit", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_cost_per_unit", + "fieldtype": "Float", + "hidden": 1, + "label": "Base Cost Per Unit", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "costing_section", + "fieldtype": "Section Break", + "label": "Costing" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "more_information_section", + "fieldtype": "Section Break", + "label": "More Information" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "set_cost_based_on_bom_qty", + "fieldtype": "Check", + "label": "Set Operating Cost Based On BOM Quantity" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-12 14:48:09.596843", + "modified": "2021-09-13 16:45:01.092868", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Operation", From 625626b973b399ccc963370edb940e2e9f84d948 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:10:54 +0530 Subject: [PATCH 070/416] fix: Values with same account and different account number in consolidated balance sheet report (#27493) --- .../consolidated_financial_statement.py | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e419727c2d..b0cfbac9cb 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -260,7 +260,12 @@ def get_company_currency(filters=None): def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters): for entries in gl_entries_by_account.values(): for entry in entries: - d = accounts_by_name.get(entry.account_name) + if entry.account_number: + account_name = entry.account_number + ' - ' + entry.account_name + else: + account_name = entry.account_name + + d = accounts_by_name.get(account_name) if d: for company in companies: # check if posting date is within the period @@ -307,7 +312,14 @@ def update_parent_account_names(accounts): of account_number and suffix of company abbr. This function adds key called `parent_account_name` which does not have such prefix/suffix. """ - name_to_account_map = { d.name : d.account_name for d in accounts } + name_to_account_map = {} + + for d in accounts: + if d.account_number: + account_name = d.account_number + ' - ' + d.account_name + else: + account_name = d.account_name + name_to_account_map[d.name] = account_name for account in accounts: if account.parent_account: @@ -420,7 +432,11 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g convert_to_presentation_currency(gl_entries, currency_info, filters.get('company')) for entry in gl_entries: - account_name = entry.account_name + if entry.account_number: + account_name = entry.account_number + ' - ' + entry.account_name + else: + account_name = entry.account_name + validate_entries(account_name, entry, accounts_by_name, accounts) gl_entries_by_account.setdefault(account_name, []).append(entry) @@ -491,7 +507,12 @@ def filter_accounts(accounts, depth=10): parent_children_map = {} accounts_by_name = {} for d in accounts: - accounts_by_name[d.account_name] = d + if d.account_number: + account_name = d.account_number + ' - ' + d.account_name + else: + account_name = d.account_name + accounts_by_name[account_name] = d + parent_children_map.setdefault(d.parent_account or None, []).append(d) filtered_accounts = [] From 759f2b7920f6bf017df555bfaba9d06fccc66d40 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:24:26 +0530 Subject: [PATCH 071/416] fix: Autoname for customer and supplier (#27398) --- .../buying/doctype/buying_settings/buying_settings.json | 4 ++-- erpnext/buying/doctype/supplier/supplier.py | 6 ++++-- erpnext/selling/doctype/customer/customer.py | 6 ++++-- .../doctype/selling_settings/selling_settings.json | 8 ++++---- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index b9c77d59b1..b828a43d3c 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -28,7 +28,7 @@ "fieldname": "supp_master_name", "fieldtype": "Select", "label": "Supplier Naming By", - "options": "Supplier Name\nNaming Series" + "options": "Supplier Name\nNaming Series\nAuto Name" }, { "fieldname": "supplier_group", @@ -123,7 +123,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-06-24 10:38:28.934525", + "modified": "2021-09-08 19:26:23.548837", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 2a9f784ec6..0ab01712e3 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -10,7 +10,7 @@ from frappe.contacts.address_and_contact import ( delete_contact_and_address, load_address_and_contact, ) -from frappe.model.naming import set_name_by_naming_series +from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from erpnext.accounts.party import get_dashboard_info, validate_party_accounts from erpnext.utilities.transaction_base import TransactionBase @@ -40,8 +40,10 @@ class Supplier(TransactionBase): supp_master_name = frappe.defaults.get_global_default('supp_master_name') if supp_master_name == 'Supplier Name': self.name = self.supplier_name - else: + elif supp_master_name == 'Naming Series': set_name_by_naming_series(self) + else: + self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def on_update(self): if not self.naming_series: diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 4be8139d57..7adf2cd909 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -14,7 +14,7 @@ from frappe.contacts.address_and_contact import ( ) from frappe.desk.reportview import build_match_conditions, get_filters_cond from frappe.model.mapper import get_mapped_doc -from frappe.model.naming import set_name_by_naming_series +from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from frappe.model.rename_doc import update_linked_doctypes from frappe.utils import cint, cstr, flt, get_formatted_email, today from frappe.utils.user import get_users_with_role @@ -40,8 +40,10 @@ class Customer(TransactionBase): cust_master_name = frappe.defaults.get_global_default('cust_master_name') if cust_master_name == 'Customer Name': self.name = self.get_customer_name() - else: + elif cust_master_name == 'Naming Series': set_name_by_naming_series(self) + else: + self.name = set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def get_customer_name(self): diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 59fcb98281..c27f1ea81a 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -41,14 +41,14 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Customer Naming By", - "options": "Customer Name\nNaming Series" + "options": "Customer Name\nNaming Series\nAuto Name" }, { "fieldname": "campaign_naming_by", "fieldtype": "Select", "in_list_view": 1, "label": "Campaign Naming By", - "options": "Campaign Name\nNaming Series" + "options": "Campaign Name\nNaming Series\nAuto Name" }, { "fieldname": "customer_group", @@ -204,7 +204,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-09-01 22:55:33.803624", + "modified": "2021-09-08 19:38:10.175989", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -223,4 +223,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file From 6e7945fbb7bafa560277841c912c3b1a46d1c416 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Sep 2021 11:10:11 +0530 Subject: [PATCH 072/416] fix: Tags getting fetched correctly in Get Supplier in RFQ (Request For Quotation) (#27499) (#27506) * fix: Tags getting fetched correctly in Get Supplier in RFQ( Request For Quotation ) #26343 * fix: Linting issues * fix: remove unnecessary caching [skip ci] Co-authored-by: Vama Mehta Co-authored-by: Ankush Menat (cherry picked from commit 50fe23308acad58ab2db2056ea6dd163913725ef) --- .../request_for_quotation/request_for_quotation.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index af1a9a907a..5aa2d1374e 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -394,12 +394,10 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc = @frappe.whitelist() def get_supplier_tag(): - if not frappe.cache().hget("Supplier", "Tags"): - filters = {"document_type": "Supplier"} - tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag)) - frappe.cache().hset("Supplier", "Tags", tags) + filters = {"document_type": "Supplier"} + tags = list(set(tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag)) - return frappe.cache().hget("Supplier", "Tags") + return tags @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs From c5a77f60ed362ece3dd7ecd4568c82809f15bf28 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 16:45:57 +0530 Subject: [PATCH 073/416] feat: provision to add scrap item in job card (#27483) --- .../doctype/job_card/job_card.json | 17 +++- .../doctype/job_card_scrap_item/__init__.py | 0 .../job_card_scrap_item.json | 82 ++++++++++++++++++ .../job_card_scrap_item.py | 8 ++ .../production_plan/test_production_plan.py | 1 + .../doctype/work_order/test_work_order.py | 56 ++++++++++++- .../stock/doctype/stock_entry/stock_entry.py | 84 ++++++++++++++++++- 7 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py create mode 100644 erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json create mode 100644 erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index f5bbac33b8..7dd38f4673 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -38,6 +38,8 @@ "total_time_in_mins", "section_break_8", "items", + "scrap_items_section", + "scrap_items", "corrective_operation_section", "for_job_card", "is_corrective_job_card", @@ -392,11 +394,24 @@ "fieldtype": "Link", "label": "Batch No", "options": "Batch" + }, + { + "fieldname": "scrap_items_section", + "fieldtype": "Section Break", + "label": "Scrap Items" + }, + { + "fieldname": "scrap_items", + "fieldtype": "Table", + "label": "Scrap Items", + "no_copy": 1, + "options": "Job Card Scrap Item", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2021-09-13 21:34:15.177928", + "modified": "2021-09-14 00:38:46.873105", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py b/erpnext/manufacturing/doctype/job_card_scrap_item/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json new file mode 100644 index 0000000000..9e9f1c4c89 --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json @@ -0,0 +1,82 @@ +{ + "actions": [], + "creation": "2021-09-14 00:30:28.533884", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "column_break_3", + "description", + "quantity_and_rate", + "stock_qty", + "column_break_6", + "stock_uom" + ], + "fields": [ + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Scrap Item Code", + "options": "Item", + "reqd": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Scrap Item Name" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.description", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "read_only": 1 + }, + { + "fieldname": "quantity_and_rate", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "reqd": 1 + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fetch_from": "item_code.stock_uom", + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-14 01:20:48.588052", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Scrap Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py new file mode 100644 index 0000000000..372df1b0fa --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.py @@ -0,0 +1,8 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from frappe.model.document import Document + + +class JobCardScrapItem(Document): + pass diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index 6a942d5433..707b3f62d4 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -404,6 +404,7 @@ def make_bom(**args): 'uom': item_doc.stock_uom, 'stock_uom': item_doc.stock_uom, 'rate': item_doc.valuation_rate or args.rate, + 'source_warehouse': args.source_warehouse }) if not args.do_not_save: diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index d87b5ec654..85b5bfb9bf 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -16,7 +16,7 @@ from erpnext.manufacturing.doctype.work_order.work_order import ( stop_unstop, ) from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.item.test_item import create_item, make_item from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.utils import get_bin @@ -768,6 +768,60 @@ class TestWorkOrder(unittest.TestCase): total_pl_qty ) + def test_job_card_scrap_item(self): + items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test', + 'Test RM Item 2 for Scrap Item Test'] + + company = '_Test Company with perpetual inventory' + for item_code in items: + create_item(item_code = item_code, is_stock_item = 1, + is_purchase_item=1, opening_stock=100, valuation_rate=10, company=company, warehouse='Stores - TCP1') + + item = 'Test FG Item for Scrap Item Test' + raw_materials = ['Test RM Item 1 for Scrap Item Test', 'Test RM Item 2 for Scrap Item Test'] + if not frappe.db.get_value('BOM', {'item': item}): + bom = make_bom(item=item, source_warehouse='Stores - TCP1', raw_materials=raw_materials, do_not_save=True) + bom.with_operations = 1 + bom.append('operations', { + 'operation': '_Test Operation 1', + 'workstation': '_Test Workstation 1', + 'hour_rate': 20, + 'time_in_mins': 60 + }) + + bom.submit() + + wo_order = make_wo_order_test_record(item=item, company=company, planned_start_date=now(), qty=20, skip_transfer=1) + job_card = frappe.db.get_value('Job Card', {'work_order': wo_order.name}, 'name') + update_job_card(job_card) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + for row in stock_entry.items: + if row.is_scrap_item: + self.assertEqual(row.qty, 1) + +def update_job_card(job_card): + job_card_doc = frappe.get_doc('Job Card', job_card) + job_card_doc.set('scrap_items', [ + { + 'item_code': 'Test RM Item 1 for Scrap Item Test', + 'stock_qty': 2 + }, + { + 'item_code': 'Test RM Item 2 for Scrap Item Test', + 'stock_qty': 2 + }, + ]) + + job_card_doc.append('time_logs', { + 'from_time': now(), + 'time_in_mins': 60, + 'completed_qty': job_card_doc.for_quantity + }) + + job_card_doc.submit() + + def get_scrap_item_details(bom_no): scrap_items = {} for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 0459489185..1c9b9614f6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import json +from collections import defaultdict import frappe from frappe import _ @@ -684,7 +685,7 @@ class StockEntry(StockController): def validate_bom(self): for d in self.get('items'): - if d.bom_no and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse): + if d.bom_no and d.is_finished_item: item_code = d.original_item or d.item_code validate_bom_no(item_code, d.bom_no) @@ -1191,13 +1192,88 @@ class StockEntry(StockController): # item dict = { item_code: {qty, description, stock_uom} } item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty, - fetch_exploded = 0, fetch_scrap_items = 1) + fetch_exploded = 0, fetch_scrap_items = 1) or {} for item in itervalues(item_dict): item.from_warehouse = "" item.is_scrap_item = 1 + + for row in self.get_scrap_items_from_job_card(): + if row.stock_qty <= 0: + continue + + item_row = item_dict.get(row.item_code) + if not item_row: + item_row = frappe._dict({}) + + item_row.update({ + 'uom': row.stock_uom, + 'from_warehouse': '', + 'qty': row.stock_qty + flt(item_row.stock_qty), + 'converison_factor': 1, + 'is_scrap_item': 1, + 'item_name': row.item_name, + 'description': row.description, + 'allow_zero_valuation_rate': 1 + }) + + item_dict[row.item_code] = item_row + return item_dict + def get_scrap_items_from_job_card(self): + if not self.pro_doc: + self.set_work_order_details() + + scrap_items = frappe.db.sql(''' + SELECT + JCSI.item_code, JCSI.item_name, SUM(JCSI.stock_qty) as stock_qty, JCSI.stock_uom, JCSI.description + FROM + `tabJob Card` JC, `tabJob Card Scrap Item` JCSI + WHERE + JCSI.parent = JC.name AND JC.docstatus = 1 + AND JCSI.item_code IS NOT NULL AND JC.work_order = %s + GROUP BY + JCSI.item_code + ''', self.work_order, as_dict=1) + + pending_qty = flt(self.pro_doc.qty) - flt(self.pro_doc.produced_qty) + if pending_qty <=0: + return [] + + used_scrap_items = self.get_used_scrap_items() + for row in scrap_items: + row.stock_qty -= flt(used_scrap_items.get(row.item_code)) + row.stock_qty = (row.stock_qty) * flt(self.fg_completed_qty) / flt(pending_qty) + + if used_scrap_items.get(row.item_code): + used_scrap_items[row.item_code] -= row.stock_qty + + if cint(frappe.get_cached_value('UOM', row.stock_uom, 'must_be_whole_number')): + row.stock_qty = frappe.utils.ceil(row.stock_qty) + + return scrap_items + + def get_used_scrap_items(self): + used_scrap_items = defaultdict(float) + data = frappe.get_all( + 'Stock Entry', + fields = [ + '`tabStock Entry Detail`.`item_code`', '`tabStock Entry Detail`.`qty`' + ], + filters = [ + ['Stock Entry', 'work_order', '=', self.work_order], + ['Stock Entry Detail', 'is_scrap_item', '=', 1], + ['Stock Entry', 'docstatus', '=', 1], + ['Stock Entry', 'purpose', 'in', ['Repack', 'Manufacture']] + ] + ) + + for row in data: + used_scrap_items[row.item_code] += row.qty + + return used_scrap_items + def get_unconsumed_raw_materials(self): wo = frappe.get_doc("Work Order", self.work_order) wo_items = frappe.get_all('Work Order Item', @@ -1417,8 +1493,8 @@ class StockEntry(StockController): se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0) se_child.is_process_loss = item_dict[d].get("is_process_loss", 0) - for field in ["idx", "po_detail", "original_item", - "expense_account", "description", "item_name", "serial_no", "batch_no"]: + for field in ["idx", "po_detail", "original_item", "expense_account", + "description", "item_name", "serial_no", "batch_no", "allow_zero_valuation_rate"]: if item_dict[d].get(field): se_child.set(field, item_dict[d].get(field)) From d4d5e2786f3e347491329d8882be5bee34fb8ee3 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Sep 2021 17:07:58 +0530 Subject: [PATCH 074/416] fix: Maintain same rate in Stock Ledger until stock become positive (#27227) (#27478) * fix: Maintain same rate in Stock Ledger until stock become positive * fix: Maintain same rate in Stock Ledger until stock become positive (cherry picked from commit 10754831c33b3459d5a45c98f875afa48a444627) Co-authored-by: Nabin Hait --- erpnext/stock/stock_ledger.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 542124204b..c33697d88a 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -681,11 +681,15 @@ class update_entries_after(object): if self.wh_data.stock_queue[-1][1]==incoming_rate: self.wh_data.stock_queue[-1][0] += actual_qty else: + # Item has a positive balance qty, add new entry if self.wh_data.stock_queue[-1][0] > 0: self.wh_data.stock_queue.append([actual_qty, incoming_rate]) - else: + else: # negative balance qty qty = self.wh_data.stock_queue[-1][0] + actual_qty - self.wh_data.stock_queue[-1] = [qty, incoming_rate] + if qty > 0: # new balance qty is positive + self.wh_data.stock_queue[-1] = [qty, incoming_rate] + else: # new balance qty is still negative, maintain same rate + self.wh_data.stock_queue[-1][0] = qty else: qty_to_pop = abs(actual_qty) while qty_to_pop: From 5e0b21582acbc3b5629b2c8c29ea3035693a3227 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 15 Sep 2021 17:37:52 +0530 Subject: [PATCH 075/416] fix: table data deleted on submitted maintenance schedule (#27513) --- .../doctype/maintenance_schedule/maintenance_schedule.js | 2 +- .../doctype/maintenance_schedule/maintenance_schedule.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js index d1a8c8de27..035290d8f1 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.js @@ -18,7 +18,7 @@ frappe.ui.form.on('Maintenance Schedule', { }, refresh: function (frm) { setTimeout(() => { - frm.toggle_display('generate_schedule', !(frm.is_new())); + frm.toggle_display('generate_schedule', !(frm.is_new() || frm.doc.docstatus)); frm.toggle_display('schedule', !(frm.is_new())); }, 10); }, diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 52e41c5863..0bf5aeae71 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -16,9 +16,9 @@ from erpnext.utilities.transaction_base import TransactionBase, delete_events class MaintenanceSchedule(TransactionBase): @frappe.whitelist() def generate_schedule(self): + if self.docstatus != 0: + return self.set('schedules', []) - frappe.db.sql("""delete from `tabMaintenance Schedule Detail` - where parent=%s""", (self.name)) count = 1 for d in self.get('items'): self.validate_maintenance_detail() From 29dab0699c5a162cedc8a207099cf9e629530314 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Wed, 15 Sep 2021 19:12:02 +0530 Subject: [PATCH 076/416] fix(minor): Remove b2c limit check from CDNR Invoices (#27516) (#27520) * fix(minor): Remove b2c limit check from CDNR Invoices * fix: Remove unnecessary format (cherry picked from commit 978028c880445362afc0cfca9118424174541cc7) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/regional/report/gstr_1/gstr_1.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index cf4850e278..23924c5fb6 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -214,7 +214,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2B": - conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1" + conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1 AND is_debit_note !=1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') @@ -223,7 +223,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2C Large": conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + AND grand_total > {0} AND is_return != 1 AND is_debit_note !=1 AND gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": conditions += """ AND ( @@ -236,8 +236,8 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "CDNR-UNREG": b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - AND ABS(grand_total) > {0} AND (is_return = 1 OR is_debit_note = 1) - AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')""".format(flt(b2c_limit)) + AND (is_return = 1 OR is_debit_note = 1) + AND IFNULL(gst_category, '') in ('Unregistered', 'Overseas')""" elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ From 70c203d19ed62fbd185809b1410ac45ff8fba8be Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 15 Sep 2021 19:24:35 +0530 Subject: [PATCH 077/416] test: automated test for running all stock reports (#27510) * test: automated test for running all stock reports These test do not assert correctness, they just check that "execute" function is working with sane filters. * test: make report execution test modular --- erpnext/stock/report/test_reports.py | 63 ++++++++++++++++++++++++++++ erpnext/tests/utils.py | 41 ++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 erpnext/stock/report/test_reports.py diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py new file mode 100644 index 0000000000..d7fb5b2bf3 --- /dev/null +++ b/erpnext/stock/report/test_reports.py @@ -0,0 +1,63 @@ +import unittest +from typing import List, Tuple + +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", +} + + +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("Stock Ledger", {"_optional": True}), + ("Stock Balance", {"_optional": True}), + ("Stock Projected Qty", {"_optional": True}), + ("Batch-Wise Balance History", {}), + ("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}), + ("COGS By Item Group", {}), + ("Stock Qty vs Serial No Count", {"warehouse": "_Test Warehouse - _TC"}), + ( + "Stock and Account Value Comparison", + { + "company": "_Test Company with perpetual inventory", + "account": "Stock In Hand - TCP1", + "as_on_date": "2021-01-01", + }, + ), + ("Product Bundle Balance", {"date": "2022-01-01", "_optional": True}), + ( + "Stock Analytics", + { + "from_date": "2021-01-01", + "to_date": "2021-12-31", + "value_quantity": "Quantity", + "_optional": True, + }, + ), + ("Warehouse wise Item Balance Age and Value", {"_optional": True}), + ("Item Variant Details", {"item": "_Test Variant Item",}), + ("Total Stock Summary", {"group_by": "warehouse",}), + ("Batch Item Expiry Status", {}), + ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), +] + +OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", +} + + +class TestReports(unittest.TestCase): + def test_execute_all_stock_reports(self): + """Test that all script report in stock modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Stock", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 2156bd51a4..a3cab4b59d 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -3,8 +3,13 @@ import copy from contextlib import contextmanager +from typing import Any, Dict, NewType, Optional import frappe +from frappe.core.doctype.report.report import get_report_module_dotted_path + +ReportFilters = Dict[str, Any] +ReportName = NewType("ReportName", str) def create_test_contact_and_address(): @@ -78,3 +83,39 @@ def change_settings(doctype, settings_dict): for key, value in previous_settings.items(): setattr(settings, key, value) settings.save() + + +def execute_script_report( + report_name: ReportName, + module: str, + filters: ReportFilters, + default_filters: Optional[ReportFilters] = None, + optional_filters: Optional[ReportFilters] = None + ): + """Util for testing execution of a report with specified filters. + + Tests the execution of report with default_filters + filters. + Tests the execution using optional_filters one at a time. + + Args: + report_name: Human readable name of report (unscrubbed) + module: module to which report belongs to + filters: specific values for filters + default_filters: default values for filters such as company name. + optional_filters: filters which should be tested one at a time in addition to default filters. + """ + + if default_filters is None: + default_filters = {} + + report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute") + report_filters = frappe._dict(default_filters).copy().update(filters) + + report_data = report_execute_fn(report_filters) + + if optional_filters: + for key, value in optional_filters.items(): + filter_with_optional_param = report_filters.copy().update({key: value}) + report_execute_fn(filter_with_optional_param) + + return report_data From e6a1ad8016b5e2aa425661978b99bc09c7ca08d1 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 15 Sep 2021 20:42:47 +0530 Subject: [PATCH 078/416] fix: not able to submit stock entry with 350 items (#27523) --- erpnext/stock/stock_ledger.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index c33697d88a..1b5b792f94 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -407,7 +407,8 @@ class update_entries_after(object): return # Get dynamic incoming/outgoing rate - self.get_dynamic_incoming_outgoing_rate(sle) + if not self.args.get("sle_id"): + self.get_dynamic_incoming_outgoing_rate(sle) if sle.serial_no: self.get_serialized_values(sle) @@ -447,7 +448,8 @@ class update_entries_after(object): sle.doctype="Stock Ledger Entry" frappe.get_doc(sle).db_update() - self.update_outgoing_rate_on_transaction(sle) + if not self.args.get("sle_id"): + self.update_outgoing_rate_on_transaction(sle) def validate_negative_stock(self, sle): """ From 866763c16a9d39e1d0382c859437e4493f798fbc Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 16 Sep 2021 00:05:16 +0530 Subject: [PATCH 079/416] fix(minor): Employee filter in Unpaid Expense Claims report (#27530) --- .../report/unpaid_expense_claim/unpaid_expense_claim.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js index 811414aaf0..f0ba78c960 100644 --- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js +++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.js @@ -4,9 +4,10 @@ frappe.query_reports["Unpaid Expense Claim"] = { "filters": [ { - "fieldname":"employee", + "fieldname": "employee", "label": __("Employee"), - "fieldtype": "Link" + "fieldtype": "Link", + "options": "Employee" } ] } From 0e527311b959fcb0d980ebebff671c75ca997f28 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:41:38 +0530 Subject: [PATCH 080/416] Update erpnext/erpnext_integrations/taxjar_integration.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/erpnext_integrations/taxjar_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 29fd81cab7..90ee8a8d9a 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -103,7 +103,7 @@ def get_tax_data(doc): shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD]) - line_items = [get_line_item_dict(item,doc.docstatus) for item in doc.items] + line_items = [get_line_item_dict(item, doc.docstatus) for item in doc.items] if from_shipping_state not in SUPPORTED_STATE_CODES: from_shipping_state = get_state_code(from_address, 'Company') From 486d7c3a39fb77f23de49d650b91c05758c7fbd7 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:41:48 +0530 Subject: [PATCH 081/416] Update erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../doctype/taxjar_settings/taxjar_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index 767e8f24f3..c6d714b097 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -19,6 +19,6 @@ class TaxJarSettings(Document): new_nexus_list = [frappe._dict(address) for address in nexus] - self.set('nexus',[]) - self.set('nexus',new_nexus_list) + self.set('nexus', []) + self.set('nexus', new_nexus_list) self.save() \ No newline at end of file From 435a5e4fa318801c7b6e0f84c37a5894ed4c2043 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:42:06 +0530 Subject: [PATCH 082/416] Update erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../doctype/taxjar_settings/taxjar_settings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js index b6a05c42ef..d49598932f 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.js @@ -8,8 +8,7 @@ frappe.ui.form.on('TaxJar Settings', { }, refresh: (frm) => { - - frm.add_custom_button(__('Update Nexus List'), function(){ + frm.add_custom_button(__('Update Nexus List'), function() { frm.call({ doc: frm.doc, method: 'update_nexus_list' From 11bd42467e5346c1d286c2fd9ec4b1d032b32d85 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:42:15 +0530 Subject: [PATCH 083/416] Update erpnext/erpnext_integrations/taxjar_integration.py Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/erpnext_integrations/taxjar_integration.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index 90ee8a8d9a..bd09557f78 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -172,8 +172,7 @@ def set_sales_tax(doc, method): return # check if delivering within a nexus - nexus_list = frappe.get_doc('TaxJar Settings').get("nexus") - if tax_dict["to_state"] not in [nex.region_code for nex in nexus_list]: + if not frappe.db.get_value('TaxJar Nexus List', {'region_code': tax_dict["to_state"]}) return tax_data = validate_tax_request(tax_dict) From 78fe92542cb3ee781ba8103f3a5f1e982f29ce10 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 16 Sep 2021 15:34:50 +0530 Subject: [PATCH 084/416] fix(ProdPlan): Get SubAssy Items does not work (#27537) * fix(ProdPlan): Get SubAssy Items does not work This button wasn't working unless the document was saved already. * fix: make form dirty when subassy item are fetched --- .../manufacturing/doctype/production_plan/production_plan.js | 2 ++ .../manufacturing/doctype/production_plan/production_plan.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index db0f2c5dbd..2bd02dabd8 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -242,6 +242,8 @@ frappe.ui.form.on('Production Plan', { }, get_sub_assembly_items: function(frm) { + frm.dirty(); + frappe.call({ method: "get_sub_assembly_items", freeze: true, diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a28fc7abf0..18284e0199 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -561,8 +561,6 @@ class ProductionPlan(Document): get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type) - self.save() - def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None): bom_data = sorted(bom_data, key = lambda i: i.bom_level) From 9b388883d39d278ecc20ae3d99ee25a752714caa Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 16 Sep 2021 15:47:17 +0530 Subject: [PATCH 085/416] fix: Validate if item exists on uploading items in stock reco (#27538) * fix: Validate if item exists on uploading items in stock reco - Uploading non existent item in stock reco and then changing warehouse or batch gave an error - Check for non existent item * chore: translation Co-authored-by: Ankush Menat --- .../doctype/stock_reconciliation/stock_reconciliation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index fa96c5a09b..f59a4e6ff8 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -619,6 +619,11 @@ def get_stock_balance_for(item_code, warehouse, item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1) + if not item_dict: + # In cases of data upload to Items table + msg = _("Item {} does not exist.").format(item_code) + frappe.throw(msg, title=_("Missing")) + serial_nos = "" with_serial_no = True if item_dict.get("has_serial_no") else False data = get_stock_balance(item_code, warehouse, posting_date, posting_time, From 557d9516ffe5308ce28df7a758bb4f59494b2412 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 16 Sep 2021 17:17:10 +0530 Subject: [PATCH 086/416] ci: comment about coverage after min. 3 builds (#27544) [skip ci] --- codecov.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index a8da340d08..67bd4454ef 100644 --- a/codecov.yml +++ b/codecov.yml @@ -11,6 +11,7 @@ coverage: comment: layout: "diff, files" require_changes: true - + after_n_builds: 3 + ignore: - "erpnext/demo" From 5eba1ccd51488a4b5d02382b94d659c71e73e36d Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 16 Sep 2021 17:31:37 +0530 Subject: [PATCH 087/416] fix: no validation on item defaults (#27393) * fix: no validation on item defaults * fix: cache value while validating * test: item default company relation * fix: reorder validations * refactor: add guard conditions on update_defaults * test: add default warehouse for item group * fix: validate item defaults for item groups Co-authored-by: Ankush Menat --- .../setup/doctype/item_group/item_group.py | 5 + .../doctype/item_group/test_records.json | 91 ++++++++++--------- erpnext/stock/doctype/item/item.py | 82 +++++++++++------ erpnext/stock/doctype/item/test_item.py | 17 ++++ 4 files changed, 123 insertions(+), 72 deletions(-) diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index b26c6a49b4..ab50a58c4f 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -39,6 +39,7 @@ class ItemGroup(NestedSet, WebsiteGenerator): self.parent_item_group = _('All Item Groups') self.make_route() + self.validate_item_group_defaults() def on_update(self): NestedSet.on_update(self) @@ -134,6 +135,10 @@ class ItemGroup(NestedSet, WebsiteGenerator): def delete_child_item_groups_key(self): frappe.cache().hdel("child_item_groups", self.name) + def validate_item_group_defaults(self): + from erpnext.stock.doctype.item.item import validate_item_default_company_links + validate_item_default_company_links(self.item_group_defaults) + @frappe.whitelist(allow_guest=True) def get_product_list_for_group(product_group=None, start=0, limit=10, search=None): if product_group: diff --git a/erpnext/setup/doctype/item_group/test_records.json b/erpnext/setup/doctype/item_group/test_records.json index 146da87bdd..ce1d718375 100644 --- a/erpnext/setup/doctype/item_group/test_records.json +++ b/erpnext/setup/doctype/item_group/test_records.json @@ -1,73 +1,74 @@ [ { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group", "parent_item_group": "All Item Groups", "item_group_defaults": [{ "company": "_Test Company", "buying_cost_center": "_Test Cost Center 2 - _TC", - "selling_cost_center": "_Test Cost Center 2 - _TC" + "selling_cost_center": "_Test Cost Center 2 - _TC", + "default_warehouse": "_Test Warehouse - _TC" }] - }, + }, { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group Desktops", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group Desktops", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group A", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group A", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 1", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 1", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group B - 2", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group B - 2", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group B - 3", + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group B - 3", "parent_item_group": "_Test Item Group B" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C", "parent_item_group": "All Item Groups" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C - 1", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C - 1", "parent_item_group": "_Test Item Group C" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group C - 2", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group C - 2", "parent_item_group": "_Test Item Group C" - }, + }, { - "doctype": "Item Group", - "is_group": 1, - "item_group_name": "_Test Item Group D", + "doctype": "Item Group", + "is_group": 1, + "item_group_name": "_Test Item Group D", "parent_item_group": "All Item Groups" }, { @@ -104,4 +105,4 @@ } ] } -] \ No newline at end of file +] diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 50fdd3845d..86737f29b0 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -4,6 +4,7 @@ import copy import itertools import json +from typing import List import frappe from frappe import _ @@ -36,6 +37,7 @@ from erpnext.setup.doctype.item_group.item_group import ( get_parent_item_groups, invalidate_cache_for, ) +from erpnext.stock.doctype.item_default.item_default import ItemDefault class DuplicateReorderRows(frappe.ValidationError): @@ -134,9 +136,9 @@ class Item(WebsiteGenerator): self.validate_fixed_asset() self.validate_retain_sample() self.validate_uom_conversion_factor() - self.validate_item_defaults() self.validate_customer_provided_part() self.update_defaults_from_item_group() + self.validate_item_defaults() self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() self.update_show_in_website() @@ -782,35 +784,39 @@ class Item(WebsiteGenerator): if len(companies) != len(self.item_defaults): frappe.throw(_("Cannot set multiple Item Defaults for a company.")) + validate_item_default_company_links(self.item_defaults) + + def update_defaults_from_item_group(self): """Get defaults from Item Group""" - if self.item_group and not self.item_defaults: - item_defaults = frappe.db.get_values("Item Default", {"parent": self.item_group}, - ['company', 'default_warehouse','default_price_list','buying_cost_center','default_supplier', - 'expense_account','selling_cost_center','income_account'], as_dict = 1) - if item_defaults: - for item in item_defaults: - self.append('item_defaults', { - 'company': item.company, - 'default_warehouse': item.default_warehouse, - 'default_price_list': item.default_price_list, - 'buying_cost_center': item.buying_cost_center, - 'default_supplier': item.default_supplier, - 'expense_account': item.expense_account, - 'selling_cost_center': item.selling_cost_center, - 'income_account': item.income_account - }) - else: - warehouse = '' - defaults = frappe.defaults.get_defaults() or {} + if self.item_defaults or not self.item_group: + return - # To check default warehouse is belong to the default company - if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse", - {'name': defaults.default_warehouse, 'company': defaults.company}): - self.append("item_defaults", { - "company": defaults.get("company"), - "default_warehouse": defaults.default_warehouse - }) + item_defaults = frappe.db.get_values("Item Default", {"parent": self.item_group}, + ['company', 'default_warehouse','default_price_list','buying_cost_center','default_supplier', + 'expense_account','selling_cost_center','income_account'], as_dict = 1) + if item_defaults: + for item in item_defaults: + self.append('item_defaults', { + 'company': item.company, + 'default_warehouse': item.default_warehouse, + 'default_price_list': item.default_price_list, + 'buying_cost_center': item.buying_cost_center, + 'default_supplier': item.default_supplier, + 'expense_account': item.expense_account, + 'selling_cost_center': item.selling_cost_center, + 'income_account': item.income_account + }) + else: + defaults = frappe.defaults.get_defaults() or {} + + # To check default warehouse is belong to the default company + if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse", + {'name': defaults.default_warehouse, 'company': defaults.company}): + self.append("item_defaults", { + "company": defaults.get("company"), + "default_warehouse": defaults.default_warehouse + }) def update_variants(self): if self.flags.dont_update_variants or \ @@ -1328,3 +1334,25 @@ def on_doctype_update(): @erpnext.allow_regional def set_item_tax_from_hsn_code(item): pass + + +def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None: + for item_default in item_defaults: + for doctype, field in [ + ['Warehouse', 'default_warehouse'], + ['Cost Center', 'buying_cost_center'], + ['Cost Center', 'selling_cost_center'], + ['Account', 'expense_account'], + ['Account', 'income_account'] + ]: + if item_default.get(field): + company = frappe.db.get_value(doctype, item_default.get(field), 'company', cache=True) + if company and company != item_default.company: + frappe.throw(_("Row #{}: {} {} doesn't belong to Company {}. Please select valid {}.") + .format( + item_default.idx, + doctype, + frappe.bold(item_default.get(field)), + frappe.bold(item_default.company), + frappe.bold(frappe.unscrub(field)) + ), title=_("Invalid Item Defaults")) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 0ed2761020..e911d35db3 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -232,6 +232,23 @@ class TestItem(unittest.TestCase): for key, value in purchase_item_check.items(): self.assertEqual(value, purchase_item_details.get(key)) + def test_item_default_validations(self): + + with self.assertRaises(frappe.ValidationError) as ve: + make_item("Bad Item defaults", { + "item_group": "_Test Item Group", + "item_defaults": [{ + "company": "_Test Company 1", + "default_warehouse": "_Test Warehouse - _TC", + "expense_account": "Stock In Hand - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC", + }] + }) + + self.assertTrue("belong to company" in str(ve.exception).lower(), + msg="Mismatching company entities in item defaults should not be allowed.") + def test_item_attribute_change_after_variant(self): frappe.delete_doc_if_exists("Item", "_Test Variant Item-L", force=1) From c9c89572502dab66595cbe0e0aa764143fc44b5a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 16 Sep 2021 19:33:57 +0530 Subject: [PATCH 088/416] feat: Merge POS invoices based on customer group (#27471) * feat: Merge POS invoices based on customer group * fix: Linting Issues * fix: fieldname Co-authored-by: Saqib --- .../pos_invoice_merge_log.js | 7 +++- .../pos_invoice_merge_log.json | 21 +++++++++- .../pos_invoice_merge_log.py | 12 +++++- .../doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/hooks.py | 2 +- erpnext/patches.txt | 1 + ...e_accounting_dimensions_in_pos_doctypes.py | 42 +++++++++++++++++++ 7 files changed, 80 insertions(+), 7 deletions(-) create mode 100644 erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js index 2f8081b95c..73c6290d7b 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.js @@ -4,7 +4,7 @@ frappe.ui.form.on('POS Invoice Merge Log', { setup: function(frm) { frm.set_query("pos_invoice", "pos_invoices", doc => { - return{ + return { filters: { 'docstatus': 1, 'customer': doc.customer, @@ -12,5 +12,10 @@ frappe.ui.form.on('POS Invoice Merge Log', { } } }); + }, + + merge_invoices_based_on: function(frm) { + frm.set_value('customer', ''); + frm.set_value('customer_group', ''); } }); diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json index da2984f05a..d762087078 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json @@ -6,9 +6,11 @@ "engine": "InnoDB", "field_order": [ "posting_date", - "customer", + "merge_invoices_based_on", "column_break_3", "pos_closing_entry", + "customer", + "customer_group", "section_break_3", "pos_invoices", "references_section", @@ -88,12 +90,27 @@ "fieldtype": "Link", "label": "POS Closing Entry", "options": "POS Closing Entry" + }, + { + "fieldname": "merge_invoices_based_on", + "fieldtype": "Select", + "label": "Merge Invoices Based On", + "options": "Customer\nCustomer Group", + "reqd": 1 + }, + { + "depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", + "options": "Customer Group" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2020-12-01 11:53:57.267579", + "modified": "2021-09-14 11:17:19.001142", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Merge Log", diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 0be8ca7ee6..9dae3a7b75 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -23,6 +23,9 @@ class POSInvoiceMergeLog(Document): self.validate_pos_invoice_status() def validate_customer(self): + if self.merge_invoices_based_on == 'Customer Group': + return + for d in self.pos_invoices: if d.customer != self.customer: frappe.throw(_("Row #{}: POS Invoice {} is not against customer {}").format(d.idx, d.pos_invoice, self.customer)) @@ -124,7 +127,7 @@ class POSInvoiceMergeLog(Document): found = False for i in items: if (i.item_code == item.item_code and not i.serial_no and not i.batch_no and - i.uom == item.uom and i.net_rate == item.net_rate): + i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse): found = True i.qty = i.qty + item.qty @@ -172,6 +175,11 @@ class POSInvoiceMergeLog(Document): invoice.discount_amount = 0.0 invoice.taxes_and_charges = None invoice.ignore_pricing_rule = 1 + invoice.customer = self.customer + + if self.merge_invoices_based_on == 'Customer Group': + invoice.flags.ignore_pos_profile = True + invoice.pos_profile = '' return invoice @@ -228,7 +236,7 @@ def get_all_unconsolidated_invoices(): return pos_invoices def get_invoice_customer_map(pos_invoices): - # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Custoemr 2' : [{}] } + # pos_invoice_customer_map = { 'Customer 1': [{}, {}, {}], 'Customer 2' : [{}] } pos_invoice_customer_map = {} for invoice in pos_invoices: customer = invoice.get('customer') diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ec249c2419..ca6a77a2af 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -499,7 +499,7 @@ class SalesInvoice(SellingController): self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account') from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details - if not self.pos_profile: + if not self.pos_profile and not self.flags.ignore_pos_profile: pos_profile = get_pos_profile(self.company) or {} if not pos_profile: return diff --git a/erpnext/hooks.py b/erpnext/hooks.py index be05e35113..a8f16171be 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -442,7 +442,7 @@ accounting_dimension_doctypes = ["GL Entry", "Sales Invoice", "Purchase Invoice" "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", - "Subscription Plan" + "Subscription Plan", "POS Invoice", "POS Invoice Item" ] regional_overrides = { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3bc40a17e0..2148e6eec0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,3 +307,4 @@ erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields +erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes diff --git a/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py new file mode 100644 index 0000000000..4450108810 --- /dev/null +++ b/erpnext/patches/v13_0/create_accounting_dimensions_in_pos_doctypes.py @@ -0,0 +1,42 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_field + + +def execute(): + frappe.reload_doc('accounts', 'doctype', 'accounting_dimension') + accounting_dimensions = frappe.db.sql("""select fieldname, label, document_type, disabled from + `tabAccounting Dimension`""", as_dict=1) + + if not accounting_dimensions: + return + + count = 1 + for d in accounting_dimensions: + + if count % 2 == 0: + insert_after_field = 'dimension_col_break' + else: + insert_after_field = 'accounting_dimensions_section' + + for doctype in ["POS Invoice", "POS Invoice Item"]: + + field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname}) + + if field: + continue + meta = frappe.get_meta(doctype, cached=False) + fieldnames = [d.fieldname for d in meta.get("fields")] + + df = { + "fieldname": d.fieldname, + "label": d.label, + "fieldtype": "Link", + "options": d.document_type, + "insert_after": insert_after_field + } + + if df['fieldname'] not in fieldnames: + create_custom_field(doctype, df) + frappe.clear_cache(doctype=doctype) + + count += 1 From 5c18654113d58363aa52dde30be3b65621747363 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Fri, 17 Sep 2021 00:10:41 +0530 Subject: [PATCH 089/416] fix: Renamed child table doctype, delete taxes for non nexus state --- .../__init__.py | 0 .../taxjar_nexus.json} | 2 +- .../taxjar_nexus.py} | 2 +- .../doctype/taxjar_settings/taxjar_settings.json | 4 ++-- .../erpnext_integrations/taxjar_integration.py | 16 +++++++++++++--- 5 files changed, 17 insertions(+), 7 deletions(-) rename erpnext/erpnext_integrations/doctype/{taxjar_nexus_list => taxjar_nexus}/__init__.py (100%) rename erpnext/erpnext_integrations/doctype/{taxjar_nexus_list/taxjar_nexus_list.json => taxjar_nexus/taxjar_nexus.json} (96%) rename erpnext/erpnext_integrations/doctype/{taxjar_nexus_list/taxjar_nexus_list.py => taxjar_nexus/taxjar_nexus.py} (85%) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/__init__.py b/erpnext/erpnext_integrations/doctype/taxjar_nexus/__init__.py similarity index 100% rename from erpnext/erpnext_integrations/doctype/taxjar_nexus_list/__init__.py rename to erpnext/erpnext_integrations/doctype/taxjar_nexus/__init__.py diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json similarity index 96% rename from erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json rename to erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json index d543403ef9..d4d4a512b5 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.json @@ -42,7 +42,7 @@ "modified": "2021-09-14 05:33:06.444710", "modified_by": "Administrator", "module": "ERPNext Integrations", - "name": "TaxJar Nexus List", + "name": "TaxJar Nexus", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py similarity index 85% rename from erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py rename to erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py index b93be5190f..c24aa8ca7d 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_nexus_list/taxjar_nexus_list.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_nexus/taxjar_nexus.py @@ -5,5 +5,5 @@ from frappe.model.document import Document -class TaxJarNexusList(Document): +class TaxJarNexus(Document): pass diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index da8e77f783..ccbac2c18a 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -100,13 +100,13 @@ "fieldname": "nexus", "fieldtype": "Table", "label": "Nexus", - "options": "TaxJar Nexus List", + "options": "TaxJar Nexus", "read_only": 1 } ], "issingle": 1, "links": [], - "modified": "2021-09-14 01:41:55.871028", + "modified": "2021-09-16 08:54:48.444487", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py index bd09557f78..2a7243c243 100644 --- a/erpnext/erpnext_integrations/taxjar_integration.py +++ b/erpnext/erpnext_integrations/taxjar_integration.py @@ -4,7 +4,7 @@ import frappe import taxjar from frappe import _ from frappe.contacts.doctype.address.address import get_company_address -from frappe.utils import cint +from frappe.utils import cint, flt from erpnext import get_default_company @@ -172,8 +172,7 @@ def set_sales_tax(doc, method): return # check if delivering within a nexus - if not frappe.db.get_value('TaxJar Nexus List', {'region_code': tax_dict["to_state"]}) - return + check_for_nexus(doc, tax_dict) tax_data = validate_tax_request(tax_dict) if tax_data is not None: @@ -202,6 +201,17 @@ def set_sales_tax(doc, method): doc.run_method("calculate_taxes_and_totals") +def check_for_nexus(doc, tax_dict): + if not frappe.db.get_value('TaxJar Nexus', {'region_code': tax_dict["to_state"]}): + for item in doc.get("items"): + item.tax_collectable = flt(0) + item.taxable_amount = flt(0) + + for tax in doc.taxes: + if tax.account_head == TAX_ACCOUNT_HEAD: + doc.taxes.remove(tax) + return + def check_sales_tax_exemption(doc): # if the party is exempt from sales tax, then set all tax account heads to zero sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \ From 940db71a8292fc9f4f89a00014138fbe6809a474 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:14:41 +0500 Subject: [PATCH 090/416] fix: indentations --- erpnext/hooks.py | 2 +- .../ksa_vat_setting/ksa_vat_setting.js | 2 +- .../ksa_vat_setting/ksa_vat_setting_list.js | 6 +-- erpnext/regional/report/ksa_vat/ksa_vat.py | 51 +++++++++---------- erpnext/regional/saudi_arabia/utils.py | 19 +++---- 5 files changed, 40 insertions(+), 40 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index c41a1081de..1e3ad8e2e9 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -275,7 +275,7 @@ doc_events = { "on_trash": [ "erpnext.regional.check_deletion_permission", "erpnext.regional.saudi_arabia.utils.delete_qr_code_file" - ], + ], "validate": [ "erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.update_taxable_values" diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js index 0238c7b306..00b62b9adf 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('KSA VAT Setting', { - onload: function(frm) { + onload: function () { frappe.breadcrumbs.add('Accounts', 'KSA VAT Setting'); } }); diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js index 23d28b9e68..269cbec5fb 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['KSA VAT Setting'] = { - onload(list) { - frappe.breadcrumbs.add('Accounts'); - } + onload () { + frappe.breadcrumbs.add('Accounts'); + } } \ No newline at end of file diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 1175fbf11f..1697bbbd4d 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -2,11 +2,13 @@ # For license information, please see license.txt from __future__ import unicode_literals + +import json + import frappe from frappe import _ from frappe.utils import get_url_to_list -from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data, get_rounded_tax_amount -import json + def execute(filters=None): columns = columns = get_columns() @@ -52,7 +54,7 @@ def get_data(filters): return data ksa_vat_setting = frappe.get_doc('KSA VAT Setting', company) - + # Sales Heading append_data(data, 'VAT on Sales', '', '', '') @@ -63,19 +65,19 @@ def get_data(filters): for vat_setting in ksa_vat_setting.ksa_vat_sales_accounts: total_taxable_amount, total_taxable_adjustment_amount, \ total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Sales Invoice') - + # Adding results to data - append_data(data, vat_setting.title, total_taxable_amount, + append_data(data, vat_setting.title, total_taxable_amount, total_taxable_adjustment_amount, total_tax) - + grand_total_taxable_amount += total_taxable_amount grand_total_taxable_adjustment_amount += total_taxable_adjustment_amount grand_total_tax += total_tax # Sales Grand Total - append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax ) - + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax) + # Blank Line append_data(data, '', '', '', '') @@ -89,9 +91,9 @@ def get_data(filters): for vat_setting in ksa_vat_setting.ksa_vat_purchase_accounts: total_taxable_amount, total_taxable_adjustment_amount, \ total_tax = get_tax_data_for_each_vat_setting(vat_setting, filters, 'Purchase Invoice') - + # Adding results to data - append_data(data, vat_setting.title, total_taxable_amount, + append_data(data, vat_setting.title, total_taxable_amount, total_taxable_adjustment_amount, total_tax) grand_total_taxable_amount += total_taxable_amount @@ -99,8 +101,8 @@ def get_data(filters): grand_total_tax += total_tax # Purchase Grand Total - append_data(data, 'Grand Total', grand_total_taxable_amount, - grand_total_taxable_adjustment_amount, grand_total_tax ) + append_data(data, 'Grand Total', grand_total_taxable_amount, + grand_total_taxable_adjustment_amount, grand_total_tax) return data @@ -117,36 +119,33 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): total_taxable_adjustment_amount = 0 total_tax = 0 # Fetch All Invoices - invoices = frappe.get_list(doctype, + invoices = frappe.get_list(doctype, filters ={ 'docstatus': 1, 'posting_date': ['between', [from_date, to_date]] - }, - fields =['name', 'is_return']) + }, fields =['name', 'is_return']) for invoice in invoices: - invoice_items = frappe.get_list(f'{doctype} Item', + invoice_items = frappe.get_list(f'{doctype} Item', filters ={ 'docstatus': 1, 'parent': invoice.name, 'item_tax_template': vat_setting.item_tax_template - }, - fields =['item_code', 'net_amount']) + }, fields =['item_code', 'net_amount']) - for item in invoice_items: # Summing up total taxable amount if invoice.is_return == 0: total_taxable_amount += item.net_amount - + if invoice.is_return == 1: total_taxable_adjustment_amount += item.net_amount # Summing up total tax total_tax += get_tax_amount(item.item_code, vat_setting.account, doctype, invoice.name) - + return total_taxable_amount, total_taxable_adjustment_amount, total_tax - + def append_data(data, title, amount, adjustment_amount, vat_amount): @@ -156,10 +155,10 @@ def append_data(data, title, amount, adjustment_amount, vat_amount): def get_tax_amount(item_code, account_head, doctype, parent): if doctype == 'Sales Invoice': tax_doctype = 'Sales Taxes and Charges' - + elif doctype == 'Purchase Invoice': tax_doctype = 'Purchase Taxes and Charges' - + item_wise_tax_detail = frappe.get_value(tax_doctype, { 'docstatus': 1, 'parent': parent, @@ -173,5 +172,5 @@ def get_tax_amount(item_code, account_head, doctype, parent): if key == item_code: tax_amount = value[1] break - + return tax_amount \ No newline at end of file diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 4431ba065e..e83d8c72ad 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -1,10 +1,11 @@ -import frappe -from frappe import _ -from erpnext import get_region -from pyqrcode import create as qr_create import io import os +import frappe +from pyqrcode import create as qr_create + +from erpnext import get_region + def create_qr_code(doc, method): """Create QR Code after inserting Sales Inv @@ -24,21 +25,21 @@ def create_qr_code(doc, method): return fields = frappe.get_meta('Sales Invoice').fields - + for field in fields: if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") - + # System Language language = frappe.get_system_settings('language') - + # creating qr code for the url url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?format={ default_print_format or 'Standard' }&_lang={ language }&key={ doc.get_signature() }" qr_image = io.BytesIO() url = qr_create(url, error='L') url.png(qr_image, scale=2, quiet_zone=1) - + # making file filename = f"QR-CODE-{doc.name}.png".replace(os.path.sep, "__") _file = frappe.get_doc({ @@ -59,7 +60,7 @@ def create_qr_code(doc, method): def delete_qr_code_file(doc, method): """Delete QR Code on deleted sales invoice""" - + region = get_region(doc.company) if region not in ['Saudi Arabia']: return From 95b3b9c8f5f3d2c53d82b6f7a9b9a85bae722d15 Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:27:37 +0500 Subject: [PATCH 091/416] Update erpnext/regional/saudi_arabia/utils.py Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index e83d8c72ad..5b8b7c1383 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -68,7 +68,7 @@ def delete_qr_code_file(doc, method): if hasattr(doc, 'qr_code'): if doc.get('qr_code'): file_doc = frappe.get_list('File', { - 'file_url': doc.qr_code + 'file_url': doc.get('qr_code') }) if len(file_doc): frappe.delete_doc('File', file_doc[0].name) \ No newline at end of file From 05321d7f4e1b72f59a93039ba6b2774bda7dc0fb Mon Sep 17 00:00:00 2001 From: Ahmad <7881486+ahmadpak@users.noreply.github.com> Date: Fri, 17 Sep 2021 01:28:52 +0500 Subject: [PATCH 092/416] Update erpnext/regional/saudi_arabia/utils.py Co-authored-by: Saqib --- erpnext/regional/saudi_arabia/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 5b8b7c1383..79a29de9ac 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -46,7 +46,10 @@ def create_qr_code(doc, method): "doctype": "File", "file_name": filename, "is_private": 0, - "content": qr_image.getvalue() + "content": qr_image.getvalue(), + "attached_to_doctype": doc.get("doctype"), + "attached_to_name": doc.get("name"), + "attached_to_field": "qr_code" }) _file.save() From f1e5a64c61b8bea35e9fff813e290e8f05a32b7a Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:33:32 +0500 Subject: [PATCH 093/416] refactor (regional): KSA utils --- erpnext/regional/saudi_arabia/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/saudi_arabia/utils.py b/erpnext/regional/saudi_arabia/utils.py index 79a29de9ac..cc6c0af7a5 100644 --- a/erpnext/regional/saudi_arabia/utils.py +++ b/erpnext/regional/saudi_arabia/utils.py @@ -24,10 +24,10 @@ def create_qr_code(doc, method): if qr_code and frappe.db.exists({"doctype": "File", "file_url": qr_code}): return - fields = frappe.get_meta('Sales Invoice').fields + meta = frappe.get_meta('Sales Invoice') - for field in fields: - if field.fieldname == 'qr_code' and field.fieldtype == 'Attach Image': + for field in meta.get_image_fields(): + if field.fieldname == 'qr_code': # Creating public url to print format default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") From 4ec733fcc0d2f1bae8c016edac5d6dbc5598e764 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:46:45 +0500 Subject: [PATCH 094/416] fix: pre-commit hooks --- .../wizard/operations/setup_ksa_vat_setting.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index 4bccdc35bf..c30fcffc37 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -1,11 +1,13 @@ -import frappe -import os import json +import os + +import frappe + from erpnext.setup.setup_wizard.operations.taxes_setup import setup_taxes_and_charges + def create_ksa_vat_setting(company): - """ - On creation of first company. Creates KSA VAT Setting""" + """On creation of first company. Creates KSA VAT Setting""" company = frappe.get_doc('Company', company) setup_taxes_and_charges(company.name, company.country) @@ -13,14 +15,14 @@ def create_ksa_vat_setting(company): file_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'ksa_vat_settings.json') with open(file_path, 'r') as json_file: account_data = json.load(json_file) - + # Creating KSA VAT Setting ksa_vat_setting = frappe.get_doc({ 'doctype': 'KSA VAT Setting', 'company': company.name }) - + for data in account_data: if data['type'] == 'Sales Account': for row in data['accounts']: @@ -31,7 +33,7 @@ def create_ksa_vat_setting(company): 'item_tax_template': f'{item_tax_template} - {company.abbr}', 'account': f'{account} - {company.abbr}' }) - + elif data['type'] == 'Purchase Account': for row in data['accounts']: item_tax_template = row['item_tax_template'] From 9dae36b81d88359da72efcc1d1020af83188799b Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:51:32 +0500 Subject: [PATCH 095/416] fix: pre-commit hooks --- erpnext/regional/saudi_arabia/setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py index 82f2f4fab0..6113f48d3f 100644 --- a/erpnext/regional/saudi_arabia/setup.py +++ b/erpnext/regional/saudi_arabia/setup.py @@ -14,7 +14,6 @@ def setup(company=None, patch=True): add_permissions() create_ksa_vat_setting(company) make_qrcode_field() - def add_permissions(): """Add Permissions for KSA VAT Setting.""" @@ -28,10 +27,11 @@ def add_permissions(): frappe.db.set_value('Report', 'KSA VAT', 'disabled', 0) def make_qrcode_field(): + """Created QR code Image file""" qr_code_field = dict( - fieldname='qr_code', - label='QR Code', - fieldtype='Attach Image', + fieldname='qr_code', + label='QR Code', + fieldtype='Attach Image', read_only=1, no_copy=1, hidden=1) - + create_custom_field('Sales Invoice', qr_code_field) From 0ab57d49c1e2da11b3a2f12d394515e5e2991bca Mon Sep 17 00:00:00 2001 From: Ahmad Date: Fri, 17 Sep 2021 01:54:59 +0500 Subject: [PATCH 096/416] fix: pre-commit hooks --- .../ksa_vat_purchase_account/ksa_vat_purchase_account.py | 1 + .../doctype/ksa_vat_sales_account/ksa_vat_sales_account.py | 1 + .../doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py | 1 + erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py | 1 + .../regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py | 1 + erpnext/regional/report/ksa_vat/ksa_vat.js | 2 +- 6 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py index 1302e314c3..3920bc546c 100644 --- a/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py +++ b/erpnext/regional/doctype/ksa_vat_purchase_account/ksa_vat_purchase_account.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class KSAVATPurchaseAccount(Document): pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py index 8e437f832b..7c2689f530 100644 --- a/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py +++ b/erpnext/regional/doctype/ksa_vat_sales_account/ksa_vat_sales_account.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class KSAVATSalesAccount(Document): pass diff --git a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py index d7f911343b..1d6a6a793d 100644 --- a/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py +++ b/erpnext/regional/doctype/ksa_vat_sales_account/test_ksa_vat_sales_account.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestKSAVATSalesAccount(unittest.TestCase): pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py index 6db6f9cc59..bdae1161fd 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py +++ b/erpnext/regional/doctype/ksa_vat_setting/ksa_vat_setting.py @@ -4,5 +4,6 @@ # import frappe from frappe.model.document import Document + class KSAVATSetting(Document): pass diff --git a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py index ae2defc61d..7207901fd4 100644 --- a/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py +++ b/erpnext/regional/doctype/ksa_vat_setting/test_ksa_vat_setting.py @@ -4,5 +4,6 @@ # import frappe import unittest + class TestKSAVATSetting(unittest.TestCase): pass diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.js b/erpnext/regional/report/ksa_vat/ksa_vat.js index 5dd9c92bf7..d46d260ac1 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.js +++ b/erpnext/regional/report/ksa_vat/ksa_vat.js @@ -4,7 +4,7 @@ frappe.query_reports["KSA VAT"] = { onload() { - frappe.breadcrumbs.add('Accounts'); + frappe.breadcrumbs.add('Accounts'); }, "filters": [ { From 41f11eca7221e8534f8ad4cfd660057de276e484 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 17 Sep 2021 10:27:29 +0530 Subject: [PATCH 097/416] fix: Remove duplicates from customer_code field (#27555) --- erpnext/stock/doctype/item/item.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 86737f29b0..768e5eae2d 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -563,8 +563,12 @@ class Item(WebsiteGenerator): _("Default BOM ({0}) must be active for this item or its template").format(bom_item)) def fill_customer_code(self): - """ Append all the customer codes and insert into "customer_code" field of item table """ - self.customer_code = ','.join(d.ref_code for d in self.get("customer_items", [])) + """ + Append all the customer codes and insert into "customer_code" field of item table. + Used to search Item by customer code. + """ + customer_codes = set(d.ref_code for d in self.get("customer_items", [])) + self.customer_code = ','.join(customer_codes) def check_item_tax(self): """Check whether Tax Rate is not entered twice for same Tax Type""" From d49346ac4561ab854dde6f97364b09af878ee877 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 17 Sep 2021 10:39:03 +0530 Subject: [PATCH 098/416] fix: Tax breakup based on items, missing GST fields (#27524) * fix: Tax breakup based on items * fix: added gst fields,warehouse validation to pos inv,patch * fix: tax breakup test fix, eway bill hsn fix Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- .../doctype/pos_invoice/pos_invoice.py | 1 + .../sales_invoice/test_sales_invoice.py | 13 ++++-- erpnext/patches.txt | 1 + .../v13_0/gst_fields_for_pos_invoice.py | 44 +++++++++++++++++++ erpnext/regional/india/setup.py | 2 + erpnext/regional/india/utils.py | 31 ++++++------- 6 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 erpnext/patches/v13_0/gst_fields_for_pos_invoice.py diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index d6e41e6f90..27d678b212 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -40,6 +40,7 @@ class POSInvoice(SalesInvoice): self.validate_change_amount() self.validate_change_account() self.validate_item_cost_centers() + self.validate_warehouse() self.validate_serialised_or_batched_item() self.validate_stock_availablility() self.validate_return_items_qty() diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index da0c315193..3720ac33bb 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1420,15 +1420,22 @@ class TestSalesInvoice(unittest.TestCase): itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) expected_itemised_tax = { - "999800": { + "_Test Item": { "Service Tax": { "tax_rate": 10.0, - "tax_amount": 1500.0 + "tax_amount": 1000.0 + } + }, + "_Test Item 2": { + "Service Tax": { + "tax_rate": 10.0, + "tax_amount": 500.0 } } } expected_itemised_taxable_amount = { - "999800": 15000.0 + "_Test Item": 10000.0, + "_Test Item 2": 5000.0 } self.assertEqual(itemised_tax, expected_itemised_tax) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2148e6eec0..cee796efbc 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,4 +307,5 @@ erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields +erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes diff --git a/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py new file mode 100644 index 0000000000..5b790d9f17 --- /dev/null +++ b/erpnext/patches/v13_0/gst_fields_for_pos_invoice.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name']) + if not company: + return + + hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', + fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', + allow_on_submit=1, print_hide=1, fetch_if_empty=1) + nil_rated_exempt = dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted', + fieldtype='Check', fetch_from='item_code.is_nil_exempt', insert_after='gst_hsn_code', + print_hide=1) + is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST', + fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt', + print_hide=1) + taxable_value = dict(fieldname='taxable_value', label='Taxable Value', + fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency", + print_hide=1) + sales_invoice_gst_fields = [ + dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', + fieldtype='Data', insert_after='customer_address', read_only=1, + fetch_from='customer_address.gstin', print_hide=1), + dict(fieldname='customer_gstin', label='Customer GSTIN', + fieldtype='Data', insert_after='shipping_address_name', + fetch_from='shipping_address_name.gstin', print_hide=1), + dict(fieldname='place_of_supply', label='Place of Supply', + fieldtype='Data', insert_after='customer_gstin', + print_hide=1, read_only=1), + dict(fieldname='company_gstin', label='Company GSTIN', + fieldtype='Data', insert_after='company_address', + fetch_from='company_address.gstin', print_hide=1, read_only=1), + ] + + custom_fields = { + 'POS Invoice': sales_invoice_gst_fields, + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + } + + create_custom_fields(custom_fields, update=True) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 79dc4b8fc9..ce346bcc5b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -483,6 +483,7 @@ def make_custom_fields(update=True): 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'POS Invoice': sales_invoice_gst_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, 'Payment Entry': payment_entry_fields, 'Journal Entry': journal_entry_fields, @@ -501,6 +502,7 @@ def make_custom_fields(update=True): 'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], + 'POS Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], 'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst], 'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value], diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index bf06d4a497..903168d009 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -117,7 +117,7 @@ def get_itemised_tax_breakup_header(item_doctype, tax_accounts): else: return [_("Item"), _("Taxable Amount")] + tax_accounts -def get_itemised_tax_breakup_data(doc, account_wise=False): +def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) itemised_taxable_amount = get_itemised_taxable_amount(doc.items) @@ -125,28 +125,29 @@ def get_itemised_tax_breakup_data(doc, account_wise=False): if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'): return itemised_tax, itemised_taxable_amount - item_hsn_map = frappe._dict() - for d in doc.items: - item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) + if hsn_wise: + item_hsn_map = frappe._dict() + for d in doc.items: + item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) hsn_tax = {} for item, taxes in itemised_tax.items(): - hsn_code = item_hsn_map.get(item) - hsn_tax.setdefault(hsn_code, frappe._dict()) + item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + hsn_tax.setdefault(item_or_hsn, frappe._dict()) for tax_desc, tax_detail in taxes.items(): key = tax_desc if account_wise: key = tax_detail.get('tax_account') - hsn_tax[hsn_code].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) - hsn_tax[hsn_code][key]["tax_rate"] = tax_detail.get("tax_rate") - hsn_tax[hsn_code][key]["tax_amount"] += tax_detail.get("tax_amount") + hsn_tax[item_or_hsn].setdefault(key, {"tax_rate": 0, "tax_amount": 0}) + hsn_tax[item_or_hsn][key]["tax_rate"] = tax_detail.get("tax_rate") + hsn_tax[item_or_hsn][key]["tax_amount"] += tax_detail.get("tax_amount") # set taxable amount hsn_taxable_amount = frappe._dict() for item in itemised_taxable_amount: - hsn_code = item_hsn_map.get(item) - hsn_taxable_amount.setdefault(hsn_code, 0) - hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item) + item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + hsn_taxable_amount.setdefault(item_or_hsn, 0) + hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item) return hsn_tax, hsn_taxable_amount @@ -440,7 +441,7 @@ def get_ewb_data(dt, dn): data.itemList = [] data.totalValue = doc.total - data = get_item_list(data, doc) + data = get_item_list(data, doc, hsn_wise=True) disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total') data.totInvValue = doc.grand_total if disable_rounded else doc.rounded_total @@ -551,7 +552,7 @@ def get_address_details(data, doc, company_address, billing_address, dispatch_ad return data -def get_item_list(data, doc): +def get_item_list(data, doc, hsn_wise=False): for attr in ['cgstValue', 'sgstValue', 'igstValue', 'cessValue', 'OthValue']: data[attr] = 0 @@ -563,7 +564,7 @@ def get_item_list(data, doc): 'cess_account': ['cessRate', 'cessValue'] } item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] - hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True) + hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) for hsn_code, taxable_amount in hsn_taxable_amount.items(): item_data = frappe._dict() if not hsn_code: From e03d9aa8890680baefe0d335dafdbfc5d0445fd4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 17 Sep 2021 13:03:27 +0530 Subject: [PATCH 099/416] fix: unecessary keyword args were passed in mapper functions (#27563) --- erpnext/public/js/utils.js | 7 +++++-- erpnext/stock/doctype/material_request/material_request.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 2a74d6015f..7f39b990bf 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -714,12 +714,15 @@ erpnext.utils.map_current_doc = function(opts) { child_columns: opts.child_columns, action: function(selections, args) { let values = selections; - if(values.length === 0){ + if (values.length === 0) { frappe.msgprint(__("Please select {0}", [opts.source_doctype])) return; } opts.source_name = values; - opts.args = args; + if (opts.allow_child_item_selection) { + // args contains filtered child docnames + opts.args = args; + } d.dialog.hide(); _map(); }, diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 2569c04251..cf98b19e7a 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -272,8 +272,9 @@ def update_status(name, status): material_request.update_status(status) @frappe.whitelist() -def make_purchase_order(source_name, target_doc=None, args={}): - +def make_purchase_order(source_name, target_doc=None, args=None): + if args is None: + args = {} if isinstance(args, string_types): args = json.loads(args) From bf2a590332100744b1641c3680e296f05f057dcb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 17 Sep 2021 18:39:21 +0530 Subject: [PATCH 100/416] fix: View Stock / Accounting Ledger button not showing in the stock entry (#27570) --- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index c819d49f50..7cb9665e85 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1100,4 +1100,4 @@ function check_should_not_attach_bom_items(bom_no) { ); } -$.extend(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); +extend_cscript(cur_frm.cscript, new erpnext.stock.StockEntry({frm: cur_frm})); From 4f7af79c31720eb32defed0e5f1b60e9837af728 Mon Sep 17 00:00:00 2001 From: Frappe PR Bot Date: Sat, 18 Sep 2021 13:34:27 +0530 Subject: [PATCH 101/416] fix: PO/PINV - Check if doctype has company_address field before setting the value (#27441) (#27576) Co-authored-by: Vama Mehta (cherry picked from commit 666eaae6ce976c5d820b3b9f91d23a0ed28a263a) Co-authored-by: vama --- erpnext/public/js/controllers/transaction.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index fd3d472137..91b1247e6f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -884,7 +884,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe if (r.message) { me.frm.set_value("billing_address", r.message); } else { - me.frm.set_value("company_address", ""); + if (frappe.meta.get_docfield(me.frm.doctype, 'company_address')) { + me.frm.set_value("company_address", ""); + } } } }); From 164a2ad28db541e1f68b0e9f4913991b067c721a Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 20 Sep 2021 04:33:30 +0530 Subject: [PATCH 102/416] fix: Calculate depreciation_left accurately --- erpnext/assets/doctype/asset/asset.py | 20 +++++++++++++++++--- erpnext/regional/india/utils.py | 5 +++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8ff4f9790a..7861c7e371 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -221,7 +221,7 @@ class Asset(AccountsController): # If depreciation is already completed (for double declining balance) if skip_row: continue - depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d) + depreciation_amount = get_depreciation_amount(self, value_after_depreciation, d, date_of_sale) if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: schedule_date = add_months(d.depreciation_start_date, @@ -835,8 +835,8 @@ def get_total_days(date, frequency): return date_diff(date, period_start_date) @erpnext.allow_regional -def get_depreciation_amount(asset, depreciable_value, row): - depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) +def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): + depreciation_left = get_depreciation_left(asset, row, date_of_sale) if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time @@ -852,3 +852,17 @@ def get_depreciation_amount(asset, depreciable_value, row): depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100)) return depreciation_amount + +def get_depreciation_left(asset, row, date_of_sale): + if not date_of_sale: + return flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + else: + if len(asset.finance_books) == 1: + return (flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)) - len(asset.schedules) + else: + depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) + for schedule in asset.get('schedules'): + if schedule.finance_book == row.finance_book: + depreciation_left -= 1 + + return depreciation_left diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 903168d009..b1cbe70d14 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -14,6 +14,7 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_ from erpnext.hr.utils import get_salary_assignment from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.regional.india import number_state_mapping, state_numbers, states +from erpnext.assets.doctype.asset.asset import get_depreciation_left GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - / GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$") @@ -842,8 +843,8 @@ def update_taxable_values(doc, method): diff = additional_taxes - total_charges doc.get('items')[item_count - 1].taxable_value += diff -def get_depreciation_amount(asset, depreciable_value, row): - depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked) +def get_depreciation_amount(asset, depreciable_value, row, date_of_sale=None): + depreciation_left = get_depreciation_left(asset, row, date_of_sale) if row.depreciation_method in ("Straight Line", "Manual"): # if the Depreciation Schedule is being prepared for the first time From f07ff92a35c876e024703856cc2c0d31437b6dd5 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 20 Sep 2021 11:12:39 +0530 Subject: [PATCH 103/416] fix: Improvements in COA Importer (#27584) --- .../chart_of_accounts_importer.js | 56 +++++++------------ .../chart_of_accounts_importer.py | 27 +++++---- 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index f795dfa83e..f67c59c254 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -79,7 +79,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file } else { generate_tree_preview(frm); - validate_csv_data(frm); } }, @@ -104,23 +103,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { } }); -var validate_csv_data = function(frm) { - frappe.call({ - method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_accounts", - args: {file_name: frm.doc.import_file}, - callback: function(r) { - if(r.message && r.message[0]===true) { - frm.page["show_import_button"] = true; - frm.page["total_accounts"] = r.message[1]; - frm.trigger("refresh"); - } else { - frm.page.set_indicator(__('Resolve error and upload again.'), 'orange'); - frappe.throw(__(r.message)); - } - } - }); -}; - var create_import_button = function(frm) { frm.page.set_primary_action(__("Import"), function () { frappe.call({ @@ -151,23 +133,25 @@ var create_reset_button = function(frm) { }; var generate_tree_preview = function(frm) { - let parent = __('All Accounts'); - $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data + if (frm.doc.import_file) { + let parent = __('All Accounts'); + $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data - // generate tree structure based on the csv data - new frappe.ui.Tree({ - parent: $(frm.fields_dict['chart_tree'].wrapper), - label: parent, - expandable: true, - method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', - args: { - file_name: frm.doc.import_file, - parent: parent, - doctype: 'Chart of Accounts Importer', - file_type: frm.doc.file_type - }, - onclick: function(node) { - parent = node.value; - } - }); + // generate tree structure based on the csv data + new frappe.ui.Tree({ + parent: $(frm.fields_dict['chart_tree'].wrapper), + label: parent, + expandable: true, + method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', + args: { + file_name: frm.doc.import_file, + parent: parent, + doctype: 'Chart of Accounts Importer', + file_type: frm.doc.file_type + }, + onclick: function(node) { + parent = node.value; + } + }); + } }; diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 61968cf627..9a0234a91f 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -25,8 +25,16 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import class ChartofAccountsImporter(Document): - def validate(self): - validate_accounts(self.import_file) + pass + +def validate_columns(data): + if not data: + frappe.throw(_('No data found. Seems like you uploaded a blank file')) + + no_of_columns = max([len(d) for d in data]) + + if no_of_columns > 7: + frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template')) @frappe.whitelist() def validate_company(company): @@ -131,6 +139,8 @@ def get_coa(doctype, parent, is_root=False, file_name=None): else: data = generate_data_from_excel(file_doc, extension) + validate_columns(data) + validate_accounts(data) forest = build_forest(data) accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form @@ -322,9 +332,6 @@ def validate_accounts(file_name): def validate_root(accounts): roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')] - if len(roots) < 4: - frappe.throw(_("Number of root accounts cannot be less than 4")) - error_messages = [] for account in roots: @@ -364,20 +371,12 @@ def get_mandatory_account_types(): def validate_account_types(accounts): account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"] - account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] + account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1] missing = list(set(account_types_for_ledger) - set(account_types)) if missing: frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))) - account_types_for_group = ["Bank", "Cash", "Stock"] - # fix logic bug - account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] - - missing = list(set(account_types_for_group) - set(account_groups)) - if missing: - frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))) - def unset_existing_data(company): linked = frappe.db.sql('''select fieldname from tabDocField where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True) From 648b2d72a547caa0703a334cd61111a7ba35e24b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 15:27:12 +0530 Subject: [PATCH 104/416] perf: extract loop invariant db calls --- erpnext/controllers/accounts_controller.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b5..6034cced8d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -980,6 +980,9 @@ class AccountsController(TransactionBase): item_allowance = {} global_qty_allowance, global_amount_allowance = None, None + role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') + user_roles = frappe.get_roles() + for item in self.get("items"): if item.get(item_ref_dn): ref_amt = flt(frappe.db.get_value(ref_dt + " Item", @@ -1009,9 +1012,7 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') - - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles(): + if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in user_roles: if self.doctype != "Purchase Invoice": self.throw_overbill_exception(item, max_allowed_amt) elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): From aa580769a62a499ab55a946216632a0639c99257 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Mon, 20 Sep 2021 15:00:54 +0530 Subject: [PATCH 105/416] chore: Code clean up --- .../doctype/sales_invoice/sales_invoice.py | 39 ------------------- .../non_profit/doctype/donation/donation.py | 3 ++ .../doctype/membership/membership.py | 3 ++ erpnext/non_profit/utils.py | 12 ++++++ .../patches/v11_0/refactor_naming_series.py | 2 - 5 files changed, 18 insertions(+), 41 deletions(-) create mode 100644 erpnext/non_profit/utils.py diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index af72d30a17..1e87504793 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1416,45 +1416,6 @@ class SalesInvoice(SellingController): if points_to_redeem < 1: # since points_to_redeem is integer break - # Healthcare - @frappe.whitelist() - def set_healthcare_services(self, checked_values): - self.set("items", []) - from erpnext.stock.get_item_details import get_item_details - for checked_item in checked_values: - item_line = self.append("items", {}) - price_list, price_list_currency = frappe.db.get_values("Price List", {"selling": 1}, ['name', 'currency'])[0] - args = { - 'doctype': "Sales Invoice", - 'item_code': checked_item['item'], - 'company': self.company, - 'customer': frappe.db.get_value("Patient", self.patient, "customer"), - 'selling_price_list': price_list, - 'price_list_currency': price_list_currency, - 'plc_conversion_rate': 1.0, - 'conversion_rate': 1.0 - } - item_details = get_item_details(args) - item_line.item_code = checked_item['item'] - item_line.qty = 1 - if checked_item['qty']: - item_line.qty = checked_item['qty'] - if checked_item['rate']: - item_line.rate = checked_item['rate'] - else: - item_line.rate = item_details.price_list_rate - item_line.amount = float(item_line.rate) * float(item_line.qty) - if checked_item['income_account']: - item_line.income_account = checked_item['income_account'] - if checked_item['dt']: - item_line.reference_dt = checked_item['dt'] - if checked_item['dn']: - item_line.reference_dn = checked_item['dn'] - if checked_item['description']: - item_line.description = checked_item['description'] - - self.set_missing_values(for_validate = True) - def set_status(self, update=False, status=None, update_modified=True): if self.is_new(): if self.get('amended_from'): diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 880a983df6..efbe496b6f 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -170,6 +170,9 @@ def create_donor(payment): def get_company_for_donations(): company = frappe.db.get_single_value('Non Profit Settings', 'donation_company') + if not company: + from erpnext.non_profit.utils import get_company + company = get_company() return company diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index b64daa57fe..8522d662a6 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -354,6 +354,9 @@ def process_request_data(data): def get_company_for_memberships(): company = frappe.db.get_single_value("Non Profit Settings", "company") + if not company: + from erpnext.non_profit.utils import get_company + company = get_company() return company diff --git a/erpnext/non_profit/utils.py b/erpnext/non_profit/utils.py new file mode 100644 index 0000000000..47ea5f5783 --- /dev/null +++ b/erpnext/non_profit/utils.py @@ -0,0 +1,12 @@ +import frappe + + +def get_company(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company = frappe.get_list("Company", limit=1) + if company: + return company[0].name + return None diff --git a/erpnext/patches/v11_0/refactor_naming_series.py b/erpnext/patches/v11_0/refactor_naming_series.py index a124ff835b..fd4dbdc081 100644 --- a/erpnext/patches/v11_0/refactor_naming_series.py +++ b/erpnext/patches/v11_0/refactor_naming_series.py @@ -15,7 +15,6 @@ doctype_series_map = { 'Blanket Order': 'MFG-BLR-.YYYY.-', 'C-Form': 'ACC-CF-.YYYY.-', 'Campaign': 'SAL-CAM-.YYYY.-', - 'Clinical Procedure': 'HLC-CPR-.YYYY.-', 'Course Schedule': 'EDU-CSH-.YYYY.-', 'Customer': 'CUST-.YYYY.-', 'Delivery Note': 'MAT-DN-.YYYY.-', @@ -31,7 +30,6 @@ doctype_series_map = { 'Instructor': 'EDU-INS-.YYYY.-', 'Issue': 'ISS-.YYYY.-', 'Journal Entry': 'ACC-JV-.YYYY.-', - 'Lab Test': 'HLC-LT-.YYYY.-', 'Landed Cost Voucher': 'MAT-LCV-.YYYY.-', 'Lead': 'CRM-LEAD-.YYYY.-', 'Leave Allocation': 'HR-LAL-.YYYY.-', From ef66beec51dc98a12207b4c528fe0632413118b4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 20 Sep 2021 15:33:29 +0530 Subject: [PATCH 106/416] fix(plaid): query to check if bank account exists --- .../doctype/plaid_settings/plaid_settings.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index d2748c2faa..310afed481 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -85,10 +85,8 @@ def add_bank_accounts(response, bank, company): if not acc_subtype: add_account_subtype(account["subtype"]) - existing_bank_account = frappe.db.exists("Bank Account", { - 'account_name': account["name"], - 'bank': bank["bank_name"] - }) + bank_account_name = "{} - {}".format(account["name"], bank["bank_name"]) + existing_bank_account = frappe.db.exists("Bank Account", bank_account_name) if not existing_bank_account: try: @@ -197,6 +195,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): plaid = PlaidConnector(access_token) + transactions = [] try: transactions = plaid.get_transactions(start_date=start_date, end_date=end_date, account_id=account_id) except ItemError as e: @@ -205,7 +204,7 @@ def get_transactions(bank, bank_account=None, start_date=None, end_date=None): msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " " frappe.log_error(msg, title=_("Plaid Link Refresh Required")) - return transactions or [] + return transactions def new_bank_transaction(transaction): From 0ff7367f390682587346ebd54dbddf1e8eb5bb9e Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 20 Sep 2021 16:13:36 +0530 Subject: [PATCH 107/416] fix: Tax Breakup table headers fix (#27596) --- erpnext/regional/india/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 903168d009..091cc8847c 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -112,10 +112,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): - if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): - return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts - else: - return [_("Item"), _("Taxable Amount")] + tax_accounts + return [_("Item"), _("Taxable Amount")] + tax_accounts def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) From 43bf82b58beb0d057b10b0f98c398908acc8c00a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:31:20 +0530 Subject: [PATCH 108/416] fix: warn when overbilling checks are skipped. --- erpnext/controllers/accounts_controller.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 6034cced8d..22efcdf1cc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -983,6 +983,8 @@ class AccountsController(TransactionBase): role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill') user_roles = frappe.get_roles() + total_overbilled_amt = 0.0 + for item in self.get("items"): if item.get(item_ref_dn): ref_amt = flt(frappe.db.get_value(ref_dt + " Item", @@ -1012,12 +1014,19 @@ class AccountsController(TransactionBase): total_billed_amt = abs(total_billed_amt) max_allowed_amt = abs(max_allowed_amt) - if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in user_roles: + overbill_amt = total_billed_amt - max_allowed_amt + total_overbilled_amt += overbill_amt + + if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: if self.doctype != "Purchase Invoice": self.throw_overbill_exception(item, max_allowed_amt) elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): self.throw_overbill_exception(item, max_allowed_amt) + if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: + frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill)) + def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") .format(item.item_code, item.idx, max_allowed_amt)) From 5e4fbba753f9a968a03324773cbff65c04d67f0c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:36:27 +0530 Subject: [PATCH 109/416] refactor: add guard clause in for loop Reduce overly indented code/improve readability. --- erpnext/controllers/accounts_controller.py | 69 +++++++++++----------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 22efcdf1cc..024a297870 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -986,46 +986,49 @@ class AccountsController(TransactionBase): total_overbilled_amt = 0.0 for item in self.get("items"): - if item.get(item_ref_dn): - ref_amt = flt(frappe.db.get_value(ref_dt + " Item", - item.get(item_ref_dn), based_on), self.precision(based_on, item)) - if not ref_amt: - frappe.msgprint( - _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") - .format(item.item_code, ref_dt)) - else: - already_billed = frappe.db.sql(""" - select sum(%s) - from `tab%s` - where %s=%s and docstatus=1 and parent != %s - """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + if not item.get(item_ref_dn): + continue - total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), - self.precision(based_on, item)) + ref_amt = flt(frappe.db.get_value(ref_dt + " Item", + item.get(item_ref_dn), based_on), self.precision(based_on, item)) + if not ref_amt: + frappe.msgprint( + _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") + .format(item.item_code, ref_dt)) + continue - allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ - get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") + already_billed = frappe.db.sql(""" + select sum(%s) + from `tab%s` + where %s=%s and docstatus=1 and parent != %s + """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), + (item.get(item_ref_dn), self.name))[0][0] - max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) + total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), + self.precision(based_on, item)) - if total_billed_amt < 0 and max_allowed_amt < 0: - # while making debit note against purchase return entry(purchase receipt) getting overbill error - total_billed_amt = abs(total_billed_amt) - max_allowed_amt = abs(max_allowed_amt) + allowance, item_allowance, global_qty_allowance, global_amount_allowance = \ + get_allowance_for(item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount") - overbill_amt = total_billed_amt - max_allowed_amt - total_overbilled_amt += overbill_amt + max_allowed_amt = flt(ref_amt * (100 + allowance) / 100) - if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: - if self.doctype != "Purchase Invoice": - self.throw_overbill_exception(item, max_allowed_amt) - elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): - self.throw_overbill_exception(item, max_allowed_amt) + if total_billed_amt < 0 and max_allowed_amt < 0: + # while making debit note against purchase return entry(purchase receipt) getting overbill error + total_billed_amt = abs(total_billed_amt) + max_allowed_amt = abs(max_allowed_amt) - if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: - frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill)) + overbill_amt = total_billed_amt - max_allowed_amt + total_overbilled_amt += overbill_amt + + if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles: + if self.doctype != "Purchase Invoice": + self.throw_overbill_exception(item, max_allowed_amt) + elif not cint(frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice")): + self.throw_overbill_exception(item, max_allowed_amt) + + if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: + frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill)) def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") From 21a955d20b83f6c55aafab294be0f1f4d4b89153 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 20 Sep 2021 16:58:23 +0530 Subject: [PATCH 110/416] fix(ux): better error message Co-authored-by: Saqib --- erpnext/controllers/accounts_controller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 024a297870..5359089698 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -993,8 +993,8 @@ class AccountsController(TransactionBase): item.get(item_ref_dn), based_on), self.precision(based_on, item)) if not ref_amt: frappe.msgprint( - _("Warning: System will not check overbilling since amount for Item {0} in {1} is zero") - .format(item.item_code, ref_dt)) + _("System will not check overbilling since amount for Item {0} in {1} is zero") + .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange") continue already_billed = frappe.db.sql(""" @@ -1002,7 +1002,7 @@ class AccountsController(TransactionBase): from `tab%s` where %s=%s and docstatus=1 and parent != %s """ % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'), - (item.get(item_ref_dn), self.name))[0][0] + (item.get(item_ref_dn), self.name))[0][0] total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), self.precision(based_on, item)) @@ -1027,8 +1027,8 @@ class AccountsController(TransactionBase): self.throw_overbill_exception(item, max_allowed_amt) if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: - frappe.msgprint(_("INFO: Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill)) + frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") + .format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange") def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") From 8396f24e70520eed6c6b73a90a7b92349cbb2139 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 20 Sep 2021 19:01:46 +0530 Subject: [PATCH 111/416] fix: Unlink PO on cancelling SO --- erpnext/controllers/accounts_controller.py | 49 ++++++++++++++++++- .../doctype/sales_order/sales_order.py | 6 +++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index b90db054b5..4da7a72b45 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -19,7 +19,8 @@ from frappe.utils import ( get_link_to_form, getdate, nowdate, - today, + now, + today ) from six import text_type @@ -811,6 +812,52 @@ class AccountsController(TransactionBase): if frappe.db.get_single_value('Accounts Settings', 'unlink_advance_payment_on_cancelation_of_order'): unlink_ref_doc_from_payment_entries(self) + if self.doctype == "Sales Order": + self.unlink_ref_doc_from_po() + + def unlink_ref_doc_from_po(self): + print("\n"*5, "*"*50, "\n"*5) + + so_items = [] + for item in self.items: + so_items.append(item.name) + + print("SO Items: ", so_items) + + linked_po = frappe.get_all( + 'Purchase Order Item', + filters = { + 'sales_order': self.name, + 'sales_order_item': ['in', so_items], + 'docstatus': ['<', 2] + }, + pluck='parent' + ) + + print("Before unlinking: ", linked_po) + + if linked_po: + frappe.db.sql("""update `tabPurchase Order Item` + set sales_order = null, sales_order_item = null, + modified = %s, modified_by = %s + where sales_order = %s and sales_order_item in %s + and docstatus < 2""", (now(), frappe.session.user, self.name, so_items)) + + frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po))) + + linked_po = frappe.get_all( + 'Purchase Order Item', + filters = { + 'sales_order': self.name, + 'sales_order_item': ['in', so_items], + 'docstatus': ['<', 2] + }, + pluck='parent' + ) + print("After unlinking: ", linked_po) + + print("\n"*5, "*"*50, "\n"*5) + def get_tax_map(self): tax_map = {} for tax in self.get('taxes'): diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 9367609421..4397d19dca 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -209,6 +209,12 @@ class SalesOrder(SellingController): from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count update_coupon_code_count(self.coupon_code,'cancelled') + def cancel(self): + import pdb + + pdb.set_trace() + super(SalesOrder, self).cancel() + def update_project(self): if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') != "Each Transaction": return From 3e8e6ac4e2f78e4030fb71ba10403c3a018935ba Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 20 Sep 2021 20:52:08 +0530 Subject: [PATCH 112/416] fix: Creating unique hash for slider id instead of slider name --- .../web_template/hero_slider/hero_slider.html | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html index 1e3d0d069a..2cff4b3707 100644 --- a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html +++ b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html @@ -27,12 +27,14 @@
{%- endmacro -%} -`; - }) - - let $row = $(`
-
- -
- ${contents} -
`); - - $row = list_row_data_items(head, $row, result, invoice_healthcare_services); - return $row; -}; - -var set_primary_action= function(frm, dialog, $results, invoice_healthcare_services) { - var me = this; - dialog.set_primary_action(__('Add'), function() { - let checked_values = get_checked_values($results); - if(checked_values.length > 0){ - if(invoice_healthcare_services) { - frm.set_value("patient", dialog.fields_dict.patient.input.value); - } - frm.set_value("items", []); - add_to_item_line(frm, checked_values, invoice_healthcare_services); - dialog.hide(); - } - else{ - if(invoice_healthcare_services){ - frappe.msgprint(__("Please select Healthcare Service")); - } - else{ - frappe.msgprint(__("Please select Drug")); - } - } - }); -}; - -var get_checked_values= function($results) { - return $results.find('.list-item-container').map(function() { - let checked_values = {}; - if ($(this).find('.list-row-check:checkbox:checked').length > 0 ) { - checked_values['dn'] = $(this).attr('data-dn'); - checked_values['dt'] = $(this).attr('data-dt'); - checked_values['item'] = $(this).attr('data-item'); - if($(this).attr('data-rate') != 'undefined'){ - checked_values['rate'] = $(this).attr('data-rate'); - } - else{ - checked_values['rate'] = false; - } - if($(this).attr('data-income-account') != 'undefined'){ - checked_values['income_account'] = $(this).attr('data-income-account'); - } - else{ - checked_values['income_account'] = false; - } - if($(this).attr('data-qty') != 'undefined'){ - checked_values['qty'] = $(this).attr('data-qty'); - } - else{ - checked_values['qty'] = false; - } - if($(this).attr('data-description') != 'undefined'){ - checked_values['description'] = $(this).attr('data-description'); - } - else{ - checked_values['description'] = false; - } - return checked_values; - } - }).get(); -}; - -var get_drugs_to_invoice = function(frm) { - var me = this; - let selected_encounter = ''; - var dialog = new frappe.ui.Dialog({ - title: __("Get Items from Prescriptions"), - fields:[ - { fieldtype: 'Link', options: 'Patient', label: 'Patient', fieldname: "patient", reqd: true }, - { fieldtype: 'Link', options: 'Patient Encounter', label: 'Patient Encounter', fieldname: "encounter", reqd: true, - description:'Quantity will be calculated only for items which has "Nos" as UoM. You may change as required for each invoice item.', - get_query: function(doc) { - return { - filters: { - patient: dialog.get_value("patient"), - company: frm.doc.company, - docstatus: 1 - } - }; - } - }, - { fieldtype: 'Section Break' }, - { fieldtype: 'HTML', fieldname: 'results_area' } - ] - }); - var $wrapper; - var $results; - var $placeholder; - dialog.set_values({ - 'patient': frm.doc.patient, - 'encounter': "" - }); - dialog.fields_dict["encounter"].df.onchange = () => { - var encounter = dialog.fields_dict.encounter.input.value; - if(encounter && encounter!=selected_encounter){ - selected_encounter = encounter; - var method = "healthcare.healthcare.utils.get_drugs_to_invoice"; - var args = {encounter: encounter}; - var columns = (["drug_code", "quantity", "description"]); - get_healthcare_items(frm, false, $results, $placeholder, method, args, columns); - } - else if(!encounter){ - selected_encounter = ''; - $results.empty(); - $results.append($placeholder); - } - } - $wrapper = dialog.fields_dict.results_area.$wrapper.append(`
`); - $results = $wrapper.find('.results'); - $placeholder = $(`
- - -

No Drug Prescription found

-
-
`); - $results.on('click', '.list-item--head :checkbox', (e) => { - $results.find('.list-item-container .list-row-check') - .prop("checked", ($(e.target).is(':checked'))); - }); - set_primary_action(frm, dialog, $results, false); - dialog.show(); -}; - -var list_row_data_items = function(head, $row, result, invoice_healthcare_services) { - if(invoice_healthcare_services){ - head ? $row.addClass('list-item--head') - : $row = $(`
-
`).append($row); - } - else{ - head ? $row.addClass('list-item--head') - : $row = $(`
-
`).append($row); - } - return $row -}; - -var add_to_item_line = function(frm, checked_values, invoice_healthcare_services){ - if(invoice_healthcare_services){ - frappe.call({ - doc: frm.doc, - method: "set_healthcare_services", - args:{ - checked_values: checked_values - }, - callback: function() { - frm.trigger("validate"); - frm.refresh_fields(); - } - }); - } - else{ - for(let i=0; i 1){ - frappe.model.set_value(si_item.doctype, si_item.name, 'qty', parseFloat(checked_values[i]['qty'])); - } - } - frm.refresh_fields(); - } -}; From 30f59b09fdc1c44999d996407f4c2a283270a4ab Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 12:20:16 +0530 Subject: [PATCH 139/416] chore: log modified invoices --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index 81b72cf40f..bcd7f13c16 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import json import frappe @@ -33,6 +34,9 @@ def execute(): parent """, as_dict=1) +if purchase_invoices + sales_invoices: + frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) doc.docstatus = 2 From 1f8ad7241861b16d99d9017335b2720e674d2d81 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 12:21:06 +0530 Subject: [PATCH 140/416] fix: indentation --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index bcd7f13c16..fab8d59884 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -34,8 +34,8 @@ def execute(): parent """, as_dict=1) -if purchase_invoices + sales_invoices: - frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + if purchase_invoices + sales_invoices: + frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) From 711395db22617757df612f6bdf179c969674748b Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 12:26:18 +0530 Subject: [PATCH 141/416] fix: linting errors --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index fab8d59884..fa8a86437d 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import json + import frappe From 54754f4eb8a615d92d52d1322dad41ccfe9e8556 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 27 Sep 2021 12:51:54 +0530 Subject: [PATCH 142/416] fix: updated patch, add fields only if fields are checked --- .../taxjar_settings/taxjar_settings.py | 58 ++++++++++++++++++- .../custom_fields_for_taxjar_integration.py | 10 +++- erpnext/regional/united_states/setup.py | 40 ------------- 3 files changed, 64 insertions(+), 44 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index c6d714b097..2ee3350e03 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -4,14 +4,30 @@ from __future__ import unicode_literals +import json +import os + import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.model.document import Document +from frappe.permissions import add_permission, update_permission_property from erpnext.erpnext_integrations.taxjar_integration import get_client class TaxJarSettings(Document): + def on_update(self): + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") + TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") + TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") + + if TAXJAR_CREATE_TRANSACTIONS or TAXJAR_CALCULATE_TAX or TAXJAR_SANDBOX_MODE: + add_product_tax_categories() + make_custom_fields() + add_permissions() + frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) + @frappe.whitelist() def update_nexus_list(self): client = get_client() @@ -21,4 +37,44 @@ class TaxJarSettings(Document): self.set('nexus', []) self.set('nexus', new_nexus_list) - self.save() \ No newline at end of file + self.save() + +def add_product_tax_categories(): + with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f: + tax_categories = json.loads(f.read()) + create_tax_categories(tax_categories['categories']) + +def create_tax_categories(data): + for d in data: + tax_category = frappe.new_doc('Product Tax Category') + tax_category.description = d.get("description") + tax_category.product_tax_code = d.get("product_tax_code") + tax_category.category_name = d.get("name") + try: + tax_category.db_insert() + except frappe.DuplicateEntryError: + pass + +def make_custom_fields(update=True): + custom_fields = { + 'Sales Invoice Item': [ + dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', + label='Product Tax Category', fetch_from='item_code.product_tax_category'), + dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', + label='Tax Collectable', read_only=1), + dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', + label='Taxable Amount', read_only=1) + ], + 'Item': [ + dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', + label='Product Tax Category') + ] + } + create_custom_fields(custom_fields, update=update) + +def add_permissions(): + doctype = "Product Tax Category" + for role in ('Accounts Manager', 'Accounts User', 'System Manager','Item Manager', 'Stock Manager'): + add_permission(doctype, role, 0) + update_permission_property(doctype, role, 0, 'write', 1) + update_permission_property(doctype, role, 0, 'create', 1) diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index eee9f1189e..139d8e43c1 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -3,12 +3,16 @@ from __future__ import unicode_literals import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields -from erpnext.regional.united_states.setup import add_permissions +from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import add_permissions def execute(): + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") + TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") + TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name']) - if not company: + + if not company or (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): return frappe.reload_doc("regional", "doctype", "product_tax_category") @@ -29,4 +33,4 @@ def execute(): } create_custom_fields(custom_fields, update=True) add_permissions() - frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=True) + frappe.enqueue('erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings.add_product_tax_categories', now=True) diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py index 9c183af1d1..cf78f927c5 100644 --- a/erpnext/regional/united_states/setup.py +++ b/erpnext/regional/united_states/setup.py @@ -14,30 +14,9 @@ def setup(company=None, patch=True): setup_company_independent_fixtures(patch=patch) def setup_company_independent_fixtures(company=None, patch=True): - add_product_tax_categories() make_custom_fields() - add_permissions() - frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) add_print_formats() -# Product Tax categories imported from taxjar api -def add_product_tax_categories(): - with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f: - tax_categories = json.loads(f.read()) - create_tax_categories(tax_categories['categories']) - -def create_tax_categories(data): - for d in data: - tax_category = frappe.new_doc('Product Tax Category') - tax_category.description = d.get("description") - tax_category.product_tax_code = d.get("product_tax_code") - tax_category.category_name = d.get("name") - try: - tax_category.db_insert() - except frappe.DuplicateEntryError: - pass - - def make_custom_fields(update=True): custom_fields = { 'Supplier': [ @@ -59,29 +38,10 @@ def make_custom_fields(update=True): 'Quotation': [ dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges', label='Is customer exempted from sales tax?') - ], - 'Sales Invoice Item': [ - dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', - label='Product Tax Category', fetch_from='item_code.product_tax_category'), - dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', - label='Tax Collectable', read_only=1), - dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', - label='Taxable Amount', read_only=1) - ], - 'Item': [ - dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category', - label='Product Tax Category') ] } create_custom_fields(custom_fields, update=update) -def add_permissions(): - doctype = "Product Tax Category" - for role in ('Accounts Manager', 'Accounts User', 'System Manager','Item Manager', 'Stock Manager'): - add_permission(doctype, role, 0) - update_permission_property(doctype, role, 0, 'write', 1) - update_permission_property(doctype, role, 0, 'create', 1) - def add_print_formats(): frappe.reload_doc("regional", "print_format", "irs_1099_form") frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0) From 0de735f20b7fe9301d82e2378c3edf68a059643b Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 27 Sep 2021 13:50:19 +0530 Subject: [PATCH 143/416] fix: Fixed alignment of Title, Subtitle, Action Button --- erpnext/public/scss/shopping_cart.scss | 10 ++++++++++ .../web_template/hero_slider/hero_slider.html | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss index 490a7c4af7..596376534e 100644 --- a/erpnext/public/scss/shopping_cart.scss +++ b/erpnext/public/scss/shopping_cart.scss @@ -31,6 +31,14 @@ body.product-page { .carousel-control-prev, .carousel-control-next { opacity: 1; + width: 8%; + + @media (max-width: 1200px) { + width: 10%; + } + @media (max-width: 768px) { + width: 15%; + } } .carousel-body { @@ -43,6 +51,8 @@ body.product-page { .carousel-content { max-width: 400px; + margin-left: 5rem; + margin-right: 5rem; } .card { diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html index 2cff4b3707..e560f4ad7d 100644 --- a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html +++ b/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html @@ -1,7 +1,7 @@ {%- macro slide(image, title, subtitle, action, label, index, align="Left", theme="Dark") -%} {%- set align_class = resolve_class({ 'text-right': align == 'Right', - 'text-centre': align == 'Center', + 'text-centre': align == 'Centre', 'text-left': align == 'Left', }) -%} @@ -15,7 +15,7 @@
{{ doc.get_formatted("total", doc) }}
From b68ac24cd53bf6fbd3c8da24e643346682ae5d21 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 27 Sep 2021 15:03:58 +0530 Subject: [PATCH 147/416] chore: add shipping address in eway bill test (#27662) --- .../sales_invoice/test_sales_invoice.py | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 619dc7b0ad..bdd30f380f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2352,6 +2352,7 @@ def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", "address_title": "_Test Address for Eway bill", "address_type": "Billing", "city": "_Test City", @@ -2373,11 +2374,12 @@ def make_test_address_for_ewaybill(): address.save() - if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Billing'): address = frappe.get_doc({ "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", "address_title": "_Test Customer-Address for Eway bill", - "address_type": "Shipping", + "address_type": "Billing", "city": "_Test City", "state": "Test State", "country": "India", @@ -2397,9 +2399,34 @@ def make_test_address_for_ewaybill(): address.save() + if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'): + address = frappe.get_doc({ + "address_line1": "_Test Address Line 1", + "address_line2": "_Test Address Line 2", + "address_title": "_Test Customer-Address for Eway bill", + "address_type": "Shipping", + "city": "_Test City", + "state": "Test State", + "country": "India", + "doctype": "Address", + "is_primary_address": 1, + "phone": "+910000000000", + "gst_state": "Maharashtra", + "gst_state_number": "27", + "pincode": "410098" + }).insert() + + address.append("links", { + "link_doctype": "Customer", + "link_name": "_Test Customer" + }) + + address.save() + if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'): address = frappe.get_doc({ "address_line1": "_Test Dispatch Address Line 1", + "address_line2": "_Test Dispatch Address Line 2", "address_title": "_Test Dispatch-Address for Eway bill", "address_type": "Shipping", "city": "_Test City", @@ -2414,11 +2441,6 @@ def make_test_address_for_ewaybill(): "pincode": "1100101" }).insert() - address.append("links", { - "link_doctype": "Company", - "link_name": "_Test Company" - }) - address.save() def make_test_transporter_for_ewaybill(): @@ -2458,7 +2480,8 @@ def make_sales_invoice_for_ewaybill(): si.distance = 2000 si.company_address = "_Test Address for Eway bill-Billing" - si.customer_address = "_Test Customer-Address for Eway bill-Shipping" + si.customer_address = "_Test Customer-Address for Eway bill-Billing" + si.shipping_address_name = "_Test Customer-Address for Eway bill-Shipping" si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping" si.vehicle_no = "KA12KA1234" si.gst_category = "Registered Regular" From 596cf3951ddd3fda3c2ee06a0ebd114ec30aac7a Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 27 Sep 2021 15:06:46 +0530 Subject: [PATCH 148/416] fix: Batch scans get overwritten on the same row --- erpnext/public/js/controllers/transaction.js | 154 ++++++++++++------- 1 file changed, 100 insertions(+), 54 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 91b1247e6f..f97d8cfe7f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -345,26 +345,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } scan_barcode() { - let scan_barcode_field = this.frm.fields_dict["scan_barcode"]; - - let show_description = function(idx, exist = null) { - if (exist) { - frappe.show_alert({ - message: __('Row #{0}: Qty increased by 1', [idx]), - indicator: 'green' - }); - } else { - frappe.show_alert({ - message: __('Row #{0}: Item added', [idx]), - indicator: 'green' - }); - } - } + let me = this; if(this.frm.doc.scan_barcode) { frappe.call({ method: "erpnext.selling.page.point_of_sale.point_of_sale.search_for_serial_or_batch_or_barcode_number", - args: { search_value: this.frm.doc.scan_barcode } + args: { + search_value: this.frm.doc.scan_barcode + } }).then(r => { const data = r && r.message; if (!data || Object.keys(data).length === 0) { @@ -375,49 +363,107 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return; } - let cur_grid = this.frm.fields_dict.items.grid; - - let row_to_modify = null; - const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code); - const blank_item_row = this.frm.doc.items.find(d => !d.item_code); - - if (existing_item_row) { - row_to_modify = existing_item_row; - } else if (blank_item_row) { - row_to_modify = blank_item_row; - } - - if (!row_to_modify) { - // add new row - row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); - } - - show_description(row_to_modify.idx, row_to_modify.item_code); - - this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1; - frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { - item_code: data.item_code, - qty: (row_to_modify.qty || 0) + 1 - }); - - ['serial_no', 'batch_no', 'barcode'].forEach(field => { - if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { - - let value = (row_to_modify[field] && field === "serial_no") - ? row_to_modify[field] + '\n' + data[field] : data[field]; - - frappe.model.set_value(row_to_modify.doctype, - row_to_modify.name, field, value); - } - }); - - scan_barcode_field.set_value(''); - refresh_field("items"); + me.modify_table_after_scan(data); }); } return false; } + modify_table_after_scan(data) { + let scan_barcode_field = this.frm.fields_dict["scan_barcode"]; + let cur_grid = this.frm.fields_dict.items.grid; + let row_to_modify = null; + + // Check if batch is scanned and table has batch no field + let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no"); + + if (batch_no_scan) { + let duplicate = this.check_duplicate_batch_scan(data.batch_no); + if (duplicate) { + scan_barcode_field.set_value(''); + return; + } + } else { + // serial or barcode scan + row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data); + } + + if (!row_to_modify || batch_no_scan) { + // add new row if new item scanned or batch is scanned + row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); + } + + this.show_scan_message(row_to_modify.idx, row_to_modify.item_code); + this.set_scanned_values(row_to_modify, data, scan_barcode_field); + } + + set_scanned_values(row_to_modify, data, scan_barcode_field) { + // increase qty and set scanned value and item in row + this.frm.from_barcode = this.frm.from_barcode ? this.frm.from_barcode + 1 : 1; + frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, { + item_code: data.item_code, + qty: (row_to_modify.qty || 0) + 1 + }); + + ['serial_no', 'batch_no', 'barcode'].forEach(field => { + if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { + let is_serial_no = row_to_modify[field] && field === "serial_no"; + + let value = data[field]; + if (is_serial_no) { + value = row_to_modify[field] + '\n' + data[field]; + } + + frappe.model.set_value(row_to_modify.doctype, row_to_modify.name, field, value); + } + }); + + scan_barcode_field.set_value(''); + refresh_field("items"); + } + + get_row_to_modify_on_scan(row_to_modify, data) { + // get an existing item row to increment or blank row to modify + const existing_item_row = this.frm.doc.items.find(d => d.item_code === data.item_code); + const blank_item_row = this.frm.doc.items.find(d => !d.item_code); + + if (existing_item_row) { + row_to_modify = existing_item_row; + } else if (blank_item_row) { + row_to_modify = blank_item_row; + } + + return row_to_modify; + } + + check_duplicate_batch_scan(batch_no) { + // ignore scan if batch already exists in table + const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no); + + if (existing_batch_row) { + frappe.show_alert({ + message: __('Batch {0} already added', [batch_no]), + indicator: 'orange' + }); + return true; + } + return false; + } + + show_scan_message (idx, exist = null) { + if (exist) { + frappe.show_alert({ + message: __('Row #{0}: Qty increased by 1', [idx]), + indicator: 'green' + }); + } else { + frappe.show_alert({ + message: __('Row #{0}: Item added', [idx]), + indicator: 'green' + }); + } + } + apply_default_taxes() { var me = this; var taxes_and_charges_field = frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges", From cc5dd5c67d9a37d1027527ef35d24cb9942a4d6f Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 27 Sep 2021 21:43:20 +0530 Subject: [PATCH 149/416] feat: TDS deduction using journal entry and other fixes (#27451) * fix: TDS deduction using journal entry * fix: Multi category application against single supplier * refactor: TDS payable monthly report * fix: Server side handling for default tax withholding category * fix: Supplier filter for Journal Entry * refactor: TDS computation summary report --- .../doctype/journal_entry/journal_entry.json | 20 +- .../doctype/journal_entry/journal_entry.py | 76 ++++++- .../purchase_invoice/purchase_invoice.py | 6 + .../tax_withholding_category.py | 32 ++- .../test_tax_withholding_category.py | 62 +++++- .../tds_computation_summary.json | 7 +- .../tds_computation_summary.py | 112 +++------- .../tds_payable_monthly.js | 83 +------ .../tds_payable_monthly.json | 4 +- .../tds_payable_monthly.py | 208 +++++++----------- 10 files changed, 317 insertions(+), 293 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index b7bbb74ce9..20678d787b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -13,10 +13,12 @@ "voucher_type", "naming_series", "finance_book", + "tax_withholding_category", "column_break1", "from_template", "company", "posting_date", + "apply_tds", "2_add_edit_gl_entries", "accounts", "section_break99", @@ -498,16 +500,32 @@ "options": "Journal Entry Template", "print_hide": 1, "report_hide": 1 + }, + { + "depends_on": "eval:doc.apply_tds", + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "mandatory_depends_on": "eval:doc.apply_tds", + "options": "Tax Withholding Category" + }, + { + "default": "0", + "depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)", + "fieldname": "apply_tds", + "fieldtype": "Check", + "label": "Apply Tax Withholding Amount " } ], "icon": "fa fa-file-text", "idx": 176, "is_submittable": 1, "links": [], - "modified": "2020-10-30 13:56:01.121995", + "modified": "2021-09-09 15:31:14.484029", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 24368f0441..e568a82761 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -15,6 +15,9 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import ( get_party_account_based_on_invoice_discounting, ) +from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( + get_party_tax_withholding_details, +) from erpnext.accounts.party import get_party_account from erpnext.accounts.utils import ( check_if_stock_and_account_balance_synced, @@ -57,7 +60,8 @@ class JournalEntry(AccountsController): self.validate_against_jv() self.validate_reference_doc() - self.set_against_account() + if self.docstatus == 0: + self.set_against_account() self.create_remarks() self.set_print_format_fields() self.validate_expense_claim() @@ -66,6 +70,10 @@ class JournalEntry(AccountsController): self.set_account_and_party_balance() self.validate_inter_company_accounts() self.validate_stock_accounts() + + if self.docstatus == 0: + self.apply_tax_withholding() + if not self.title: self.title = self.get_title() @@ -139,6 +147,72 @@ class JournalEntry(AccountsController): frappe.throw(_("Account: {0} can only be updated via Stock Transactions") .format(account), StockAccountInvalidTransaction) + def apply_tax_withholding(self): + from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map + + if not self.apply_tds or self.voucher_type not in ('Debit Note', 'Credit Note'): + return + + parties = [d.party for d in self.get('accounts') if d.party] + parties = list(set(parties)) + + if len(parties) > 1: + frappe.throw(_("Cannot apply TDS against multiple parties in one entry")) + + account_type_map = get_account_type_map(self.company) + party_type = 'supplier' if self.voucher_type == 'Credit Note' else 'customer' + doctype = 'Purchase Invoice' if self.voucher_type == 'Credit Note' else 'Sales Invoice' + debit_or_credit = 'debit_in_account_currency' if self.voucher_type == 'Credit Note' else 'credit_in_account_currency' + rev_debit_or_credit = 'credit_in_account_currency' if debit_or_credit == 'debit_in_account_currency' else 'debit_in_account_currency' + + party_account = get_party_account(party_type.title(), parties[0], self.company) + + net_total = sum(d.get(debit_or_credit) for d in self.get('accounts') if account_type_map.get(d.account) + not in ('Tax', 'Chargeable')) + + party_amount = sum(d.get(rev_debit_or_credit) for d in self.get('accounts') if d.account == party_account) + + inv = frappe._dict({ + party_type: parties[0], + 'doctype': doctype, + 'company': self.company, + 'posting_date': self.posting_date, + 'net_total': net_total + }) + + tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category) + + if not tax_withholding_details: + return + + accounts = [] + for d in self.get('accounts'): + if d.get('account') == tax_withholding_details.get("account_head"): + d.update({ + 'account': tax_withholding_details.get("account_head"), + debit_or_credit: tax_withholding_details.get('tax_amount') + }) + + accounts.append(d.get('account')) + + if d.get('account') == party_account: + d.update({ + rev_debit_or_credit: party_amount - tax_withholding_details.get('tax_amount') + }) + + if not accounts or tax_withholding_details.get("account_head") not in accounts: + self.append("accounts", { + 'account': tax_withholding_details.get("account_head"), + rev_debit_or_credit: tax_withholding_details.get('tax_amount'), + 'against_account': parties[0] + }) + + to_remove = [d for d in self.get('accounts') + if not d.get(rev_debit_or_credit) and d.account == tax_withholding_details.get("account_head")] + + for d in to_remove: + self.remove(d) + def update_inter_company_jv(self): if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference: frappe.db.set_value("Journal Entry", self.inter_company_journal_entry_reference,\ diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index dd4a005893..1c9943fd22 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1146,6 +1146,12 @@ class PurchaseInvoice(BuyingController): if not self.apply_tds: return + if self.apply_tds and not self.get('tax_withholding_category'): + self.tax_withholding_category = frappe.db.get_value('Supplier', self.supplier, 'tax_withholding_category') + + if not self.tax_withholding_category: + return + tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) if not tax_withholding_details: diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index fa4ea218e9..16ef5fc974 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -100,6 +100,7 @@ def get_tax_withholding_details(tax_withholding_category, posting_date, company) for account_detail in tax_withholding.accounts: if company == account_detail.company: return frappe._dict({ + "tax_withholding_category": tax_withholding_category, "account_head": account_detail.account, "rate": tax_rate_detail.tax_withholding_rate, "from_date": tax_rate_detail.from_date, @@ -206,18 +207,39 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' + doctype = 'Purchase Invoice' if party_type == 'Supplier' else 'Sales Invoice' filters = { - dr_or_cr: ['>', 0], 'company': company, - 'party_type': party_type, - 'party': ['in', parties], + frappe.scrub(party_type): ['in', parties], 'posting_date': ['between', (tax_details.from_date, tax_details.to_date)], 'is_opening': 'No', - 'is_cancelled': 0 + 'docstatus': 1 } - return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""] + if not tax_details.get('consider_party_ledger_amount') and doctype != "Sales Invoice": + filters.update({ + 'apply_tds': 1, + 'tax_withholding_category': tax_details.get('tax_withholding_category') + }) + + invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""] + + journal_entries = frappe.db.sql(""" + SELECT j.name + FROM `tabJournal Entry` j, `tabJournal Entry Account` ja + WHERE + j.docstatus = 1 + AND j.is_opening = 'No' + AND j.posting_date between %s and %s + AND ja.{dr_or_cr} > 0 + AND ja.party in %s + """.format(dr_or_cr=dr_or_cr), (tax_details.from_date, tax_details.to_date, tuple(parties)), as_list=1) + + if journal_entries: + journal_entries = journal_entries[0] + + return invoices + journal_entries def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type='Supplier'): # for advance vouchers, debit and credit is reversed diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 8a88d798d8..84b364b342 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -176,6 +176,29 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in invoices: d.cancel() + def test_multi_category_single_supplier(self): + frappe.db.set_value("Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category") + invoices = [] + + pi = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 500, do_not_save=True) + pi.tax_withholding_category = "Test Service Category" + pi.save() + pi.submit() + invoices.append(pi) + + # Second Invoice will apply TDS checked + pi1 = create_purchase_invoice(supplier = "Test TDS Supplier5", rate = 2500, do_not_save=True) + pi1.tax_withholding_category = "Test Goods Category" + pi1.save() + pi1.submit() + invoices.append(pi1) + + self.assertEqual(pi1.taxes[0].tax_amount, 250) + + #delete invoices to avoid clashing + for d in invoices: + d.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all("Purchase Invoice", { 'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']], @@ -251,7 +274,8 @@ def create_sales_invoice(**args): def create_records(): # create a new suppliers - for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', 'Test TDS Supplier4']: + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3', + 'Test TDS Supplier4', 'Test TDS Supplier5']: if frappe.db.exists('Supplier', name): continue @@ -390,3 +414,39 @@ def create_tax_with_holding_category(): 'account': 'TDS - _TC' }] }).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Service Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Test Service Category", + "category_name": "Test Service Category", + "rates": [{ + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], + 'tax_withholding_rate': 10, + 'single_threshold': 2000, + 'cumulative_threshold': 2000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() + + if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Test Goods Category", + "category_name": "Test Goods Category", + "rates": [{ + 'from_date': fiscal_year[1], + 'to_date': fiscal_year[2], + 'tax_withholding_rate': 10, + 'single_threshold': 2000, + 'cumulative_threshold': 2000 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json index dfc4b18e07..91f079824d 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.json @@ -1,12 +1,15 @@ { - "add_total_row": 0, + "add_total_row": 1, + "columns": [], "creation": "2018-08-21 11:25:00.551823", + "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2018-09-21 11:25:00.551823", + "modified": "2021-09-20 17:43:39.518851", "modified_by": "Administrator", "module": "Accounts", "name": "TDS Computation Summary", diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py index c4a8c7a899..536df1f1a1 100644 --- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py +++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py @@ -2,11 +2,10 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt -from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( - get_advance_vouchers, - get_debit_note_amount, +from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import ( + get_result, + get_tds_docs, ) from erpnext.accounts.utils import get_fiscal_year @@ -17,9 +16,12 @@ def execute(filters=None): filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') columns = get_columns(filters) - res = get_result(filters) + tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) - return columns, res + res = get_result(filters, tds_docs, tds_accounts, tax_category_map) + final_result = group_by_supplier_and_category(res) + + return columns, final_result def validate_filters(filters): ''' Validate if dates are properly set and lie in the same fiscal year''' @@ -33,81 +35,39 @@ def validate_filters(filters): filters["fiscal_year"] = from_year -def get_result(filters): - # if no supplier selected, fetch data for all tds applicable supplier - # else fetch relevant data for selected supplier - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - fields = ["name", pan+" as pan", "tax_withholding_category", "supplier_type", "supplier_name"] +def group_by_supplier_and_category(data): + supplier_category_wise_map = {} - if filters.supplier: - filters.supplier = frappe.db.get_list('Supplier', - {"name": filters.supplier}, fields) - else: - filters.supplier = frappe.db.get_list('Supplier', - {"tax_withholding_category": ["!=", ""]}, fields) + for row in data: + supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), { + 'pan': row.get('pan'), + 'supplier': row.get('supplier'), + 'supplier_name': row.get('supplier_name'), + 'section_code': row.get('section_code'), + 'entity_type': row.get('entity_type'), + 'tds_rate': row.get('tds_rate'), + 'total_amount_credited': 0.0, + 'tds_deducted': 0.0 + }) + supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \ + row.get('total_amount_credited', 0.0) + + supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \ + row.get('tds_deducted', 0.0) + + final_result = get_final_result(supplier_category_wise_map) + + return final_result + + +def get_final_result(supplier_category_wise_map): out = [] - for supplier in filters.supplier: - tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category) - rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year] - - if rate: - rate = rate[0] - - try: - account = [d.account for d in tds.accounts if d.company == filters.company][0] - - except IndexError: - account = [] - total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account, - filters.company, filters.from_date, filters.to_date, filters.fiscal_year) - - if total_invoiced_amount or tds_deducted: - row = [supplier.pan, supplier.name] - - if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) - - row.extend([tds.name, supplier.supplier_type, rate, total_invoiced_amount, tds_deducted]) - out.append(row) + for key, value in supplier_category_wise_map.items(): + out.append(value) return out -def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date, fiscal_year): - ''' calculate total invoice amount and total tds deducted for given supplier ''' - - entries = frappe.db.sql(""" - select voucher_no, credit - from `tabGL Entry` - where party in (%s) and credit > 0 - and company=%s and is_cancelled = 0 - and posting_date between %s and %s - """, (supplier, company, from_date, to_date), as_dict=1) - - supplier_credit_amount = flt(sum(d.credit for d in entries)) - - vouchers = [d.voucher_no for d in entries] - vouchers += get_advance_vouchers([supplier], company=company, - from_date=from_date, to_date=to_date) - - tds_deducted = 0 - if vouchers: - tds_deducted = flt(frappe.db.sql(""" - select sum(credit) - from `tabGL Entry` - where account=%s and posting_date between %s and %s - and company=%s and credit > 0 and voucher_no in ({0}) - """.format(', '.join("'%s'" % d for d in vouchers)), - (account, from_date, to_date, company))[0][0]) - - date_range_filter = [fiscal_year, from_date, to_date] - - debit_note_amount = get_debit_note_amount([supplier], date_range_filter, company=company) - - total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount - - return total_invoiced_amount, tds_deducted - def get_columns(filters): columns = [ { @@ -149,7 +109,7 @@ def get_columns(filters): { "label": _("TDS Rate %"), "fieldname": "tds_rate", - "fieldtype": "Float", + "fieldtype": "Percent", "width": 90 }, { diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js index 72de318a48..ff2aa30601 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js @@ -16,69 +16,6 @@ frappe.query_reports["TDS Payable Monthly"] = { "label": __("Supplier"), "fieldtype": "Link", "options": "Supplier", - "get_query": function() { - return { - "filters": { - "tax_withholding_category": ["!=", ""], - } - } - }, - on_change: function() { - frappe.query_report.set_filter_value("purchase_invoice", ""); - frappe.query_report.refresh(); - } - }, - { - "fieldname":"purchase_invoice", - "label": __("Purchase Invoice"), - "fieldtype": "Link", - "options": "Purchase Invoice", - "get_query": function() { - return { - "filters": { - "name": ["in", frappe.query_report.invoices] - } - } - }, - on_change: function() { - let supplier = frappe.query_report.get_filter_value('supplier'); - if(!supplier) return; // return if no supplier selected - - // filter invoices based on selected supplier - let invoices = []; - frappe.query_report.invoice_data.map(d => { - if(d.supplier==supplier) - invoices.push(d.name) - }); - frappe.query_report.invoices = invoices; - frappe.query_report.refresh(); - } - }, - { - "fieldname":"purchase_order", - "label": __("Purchase Order"), - "fieldtype": "Link", - "options": "Purchase Order", - "get_query": function() { - return { - "filters": { - "name": ["in", frappe.query_report.invoices] - } - } - }, - on_change: function() { - let supplier = frappe.query_report.get_filter_value('supplier'); - if(!supplier) return; // return if no supplier selected - - // filter invoices based on selected supplier - let invoices = []; - frappe.query_report.invoice_data.map(d => { - if(d.supplier==supplier) - invoices.push(d.name) - }); - frappe.query_report.invoices = invoices; - frappe.query_report.refresh(); - } }, { "fieldname":"from_date", @@ -96,23 +33,5 @@ frappe.query_reports["TDS Payable Monthly"] = { "reqd": 1, "width": "60px" } - ], - - onload: function(report) { - // fetch all tds applied invoices - frappe.call({ - "method": "erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly.get_tds_invoices_and_orders", - callback: function(r) { - let invoices = []; - - r.message.map(d => { - invoices.push(d.name); - }); - - report["invoice_data"] = r.message.invoices; - report["invoices"] = invoices; - - } - }); - } + ] } diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json index 557a62d8fe..4d555bd8ba 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.json @@ -1,13 +1,15 @@ { "add_total_row": 1, + "columns": [], "creation": "2018-08-21 11:32:30.874923", "disable_prepared_report": 0, "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2019-09-24 13:46:16.473711", + "modified": "2021-09-20 12:05:50.387572", "modified_by": "Administrator", "module": "Accounts", "name": "TDS Payable Monthly", diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 9e1382b922..621b697aca 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -8,19 +8,12 @@ from frappe import _ def execute(filters=None): - filters["invoices"] = frappe.cache().hget("invoices", frappe.session.user) validate_filters(filters) - set_filters(filters) - - # TDS payment entries - payment_entries = get_payment_entires(filters) + tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters) columns = get_columns(filters) - if not filters.get("invoices"): - return columns, [] - - res = get_result(filters, payment_entries) + res = get_result(filters, tds_docs, tds_accounts, tax_category_map) return columns, res def validate_filters(filters): @@ -28,109 +21,59 @@ def validate_filters(filters): if filters.from_date > filters.to_date: frappe.throw(_("From Date must be before To Date")) -def set_filters(filters): - invoices = [] - - if not filters.get("invoices"): - filters["invoices"] = get_tds_invoices_and_orders() - - if filters.supplier and filters.purchase_invoice: - for d in filters["invoices"]: - if d.name == filters.purchase_invoice and d.supplier == filters.supplier: - invoices.append(d) - elif filters.supplier and not filters.purchase_invoice: - for d in filters["invoices"]: - if d.supplier == filters.supplier: - invoices.append(d) - elif filters.purchase_invoice and not filters.supplier: - for d in filters["invoices"]: - if d.name == filters.purchase_invoice: - invoices.append(d) - elif filters.supplier and filters.purchase_order: - for d in filters.get("invoices"): - if d.name == filters.purchase_order and d.supplier == filters.supplier: - invoices.append(d) - elif filters.supplier and not filters.purchase_order: - for d in filters.get("invoices"): - if d.supplier == filters.supplier: - invoices.append(d) - elif filters.purchase_order and not filters.supplier: - for d in filters.get("invoices"): - if d.name == filters.purchase_order: - invoices.append(d) - - filters["invoices"] = invoices if invoices else filters["invoices"] - filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name') - - #print(filters.get('invoices')) - -def get_result(filters, payment_entries): - supplier_map, tds_docs = get_supplier_map(filters, payment_entries) - documents = [d.get('name') for d in filters.get('invoices')] + [d.get('name') for d in payment_entries] - - gle_map = get_gle_map(filters, documents) +def get_result(filters, tds_docs, tds_accounts, tax_category_map): + supplier_map = get_supplier_pan_map() + tax_rate_map = get_tax_rate_map(filters) + gle_map = get_gle_map(filters, tds_docs) out = [] - for d in gle_map: + for name, details in gle_map.items(): tds_deducted, total_amount_credited = 0, 0 - supplier = supplier_map[d] + tax_withholding_category = tax_category_map.get(name) + rate = tax_rate_map.get(tax_withholding_category) - tds_doc = tds_docs[supplier.tax_withholding_category] - account_list = [i.account for i in tds_doc.accounts if i.company == filters.company] + for entry in details: + supplier = entry.party or entry.against + posting_date = entry.posting_date + voucher_type = entry.voucher_type - if account_list: - account = account_list[0] + if entry.account in tds_accounts: + tds_deducted += (entry.credit - entry.debit) - for k in gle_map[d]: - if k.party == supplier_map[d] and k.credit > 0: - total_amount_credited += (k.credit - k.debit) - elif account_list and k.account == account and (k.credit - k.debit) > 0: - tds_deducted = (k.credit - k.debit) - total_amount_credited += (k.credit - k.debit) - voucher_type = k.voucher_type + total_amount_credited += (entry.credit - entry.debit) - rate = [i.tax_withholding_rate for i in tds_doc.rates - if i.fiscal_year == gle_map[d][0].fiscal_year] - - if rate and len(rate) > 0 and tds_deducted: - rate = rate[0] - - row = [supplier.pan, supplier.name] + if rate and tds_deducted: + row = { + 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan, + 'supplier': supplier_map.get(supplier).name + } if filters.naming_series == 'Naming Series': - row.append(supplier.supplier_name) + row.update({'supplier_name': supplier_map.get(supplier).supplier_name}) + + row.update({ + 'section_code': tax_withholding_category, + 'entity_type': supplier_map.get(supplier).supplier_type, + 'tds_rate': rate, + 'total_amount_credited': total_amount_credited, + 'tds_deducted': tds_deducted, + 'transaction_date': posting_date, + 'transaction_type': voucher_type, + 'ref_no': name + }) - row.extend([tds_doc.name, supplier.supplier_type, rate, total_amount_credited, - tds_deducted, gle_map[d][0].posting_date, voucher_type, d]) out.append(row) return out -def get_supplier_map(filters, payment_entries): - # create a supplier_map of the form {"purchase_invoice": {supplier_name, pan, tds_name}} - # pre-fetch all distinct applicable tds docs - supplier_map, tds_docs = {}, {} - pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id" - supplier_list = [d.supplier for d in filters["invoices"]] +def get_supplier_pan_map(): + supplier_map = frappe._dict() + suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name']) - supplier_detail = frappe.db.get_all('Supplier', - {"name": ["in", supplier_list]}, - ["tax_withholding_category", "name", pan+" as pan", "supplier_type", "supplier_name"]) + for d in suppliers: + supplier_map[d.name] = d - for d in filters["invoices"]: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - - for d in payment_entries: - supplier_map[d.get("name")] = [k for k in supplier_detail - if k.name == d.get("supplier")][0] - - for d in supplier_detail: - if d.get("tax_withholding_category") not in tds_docs: - tds_docs[d.get("tax_withholding_category")] = \ - frappe.get_doc("Tax Withholding Category", d.get("tax_withholding_category")) - - return supplier_map, tds_docs + return supplier_map def get_gle_map(filters, documents): # create gle_map of the form @@ -140,10 +83,9 @@ def get_gle_map(filters, documents): gle = frappe.db.get_all('GL Entry', { "voucher_no": ["in", documents], - 'is_cancelled': 0, - 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), + "credit": (">", 0) }, - ["fiscal_year", "credit", "debit", "account", "voucher_no", "posting_date", "voucher_type"], + ["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"], ) for d in gle: @@ -233,39 +175,57 @@ def get_columns(filters): return columns -def get_payment_entires(filters): - filter_dict = { - 'posting_date': ("between", [filters.get('from_date'), filters.get('to_date')]), - 'party_type': 'Supplier', - 'apply_tax_withholding_amount': 1 +def get_tds_docs(filters): + tds_documents = [] + purchase_invoices = [] + payment_entries = [] + journal_entries = [] + tax_category_map = {} + + tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')}, + pluck="account") + + query_filters = { + "credit": ('>', 0), + "account": ("in", tds_accounts), + "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]), + "is_cancelled": 0 } - if filters.get('purchase_invoice') or filters.get('purchase_order'): - parent = frappe.db.get_all('Payment Entry Reference', - {'reference_name': ('in', [d.get('name') for d in filters.get('invoices')])}, ['parent']) + if filters.get('supplier'): + query_filters.update({'against': filters.get('supplier')}) - filter_dict.update({'name': ('in', [d.get('parent') for d in parent])}) + tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"]) - payment_entries = frappe.get_all('Payment Entry', fields=['name', 'party_name as supplier'], - filters=filter_dict) + for d in tds_docs: + if d.voucher_type == "Purchase Invoice": + purchase_invoices.append(d.voucher_no) + elif d.voucher_type == "Payment Entry": + payment_entries.append(d.voucher_no) + elif d.voucher_type == "Journal Entry": + journal_entries.append(d.voucher_no) - return payment_entries + tds_documents.append(d.voucher_no) -@frappe.whitelist() -def get_tds_invoices_and_orders(): - # fetch tds applicable supplier and fetch invoices for these suppliers - suppliers = [d.name for d in frappe.db.get_list("Supplier", - {"tax_withholding_category": ["!=", ""]}, ["name"])] + if purchase_invoices: + get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map) - invoices = frappe.db.get_list("Purchase Invoice", - {"supplier": ["in", suppliers]}, ["name", "supplier"]) + if payment_entries: + get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map) - orders = frappe.db.get_list("Purchase Order", - {"supplier": ["in", suppliers]}, ["name", "supplier"]) + if journal_entries: + get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map) - invoices = invoices + orders - invoices = [d for d in invoices if d.supplier] + return tds_documents, tds_accounts, tax_category_map - frappe.cache().hset("invoices", frappe.session.user, invoices) +def get_tax_category_map(vouchers, doctype, tax_category_map): + tax_category_map.update(frappe._dict(frappe.get_all(doctype, + filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1))) - return invoices +def get_tax_rate_map(filters): + rate_map = frappe.get_all('Tax Withholding Rate', filters={ + 'from_date': ('<=', filters.get('from_date')), + 'to_date': ('>=', filters.get('to_date')) + }, fields=['parent', 'tax_withholding_rate'], as_list=1) + + return frappe._dict(rate_map) \ No newline at end of file From fdd9e6cc3c62a85573b7f02ff4940046d572e92b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 27 Sep 2021 22:14:16 +0530 Subject: [PATCH 150/416] fix: Replace asset.schedules with asset.get('schedules') --- erpnext/assets/doctype/asset/asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 653fa662fd..0138a1282a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -187,7 +187,7 @@ class Asset(AccountsController): d.precision("rate_of_depreciation")) def make_depreciation_schedule(self, date_of_sale): - if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules: + if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.get('schedules'): self.schedules = [] if not self.available_for_use_date: From 40ec2d622baeb43f45086fcb7298457100a94f40 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 27 Sep 2021 22:14:42 +0530 Subject: [PATCH 151/416] fix: Add tests for depreciation --- erpnext/assets/doctype/asset/test_asset.py | 256 ++++++++++++++++++++- 1 file changed, 252 insertions(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index b4e891e06c..e747204a69 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -276,6 +276,7 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 0) + # WDV: Written Down Value def test_depreciation_entry_for_wdv_without_pro_rata(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") @@ -479,6 +480,7 @@ class TestAsset(unittest.TestCase): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + # CWIP: Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=5000, do_not_submit=True, location="Test Location") @@ -676,6 +678,249 @@ class TestAsset(unittest.TestCase): # reset indian company frappe.flags.company = company_flag + def test_depreciation_without_pro_rata(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1) + + expected_values = [ + ["2020-12-31", 30000, 30000], + ["2021-12-31", 30000, 60000], + ["2022-12-31", 30000, 90000] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + + def test_depreciation_with_pro_rata(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), submit=1) + + expected_values = [ + ["2020-07-01", 15000, 15000], + ["2021-07-01", 30000, 45000], + ["2022-07-01", 30000, 75000], + ["2022-12-31", 15000, 90000] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + + def test_get_depreciation_amount(self): + """Tests if get_depreciation_amount() returns the right value.""" + + from erpnext.assets.doctype.asset.asset import get_depreciation_amount + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31")) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0]) + self.assertEqual(depreciation_amount, 30000) + + def test_make_depreciation_schedule(self): + """Tests if make_depreciation_schedule() returns the right values.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + asset.make_depreciation_schedule(date_of_sale=None) + + expected_values = [ + ['2020-12-31', 30000.0], + ['2021-12-31', 30000.0], + ['2022-12-31', 30000.0] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + + def test_set_accumulated_depreciation(self): + """Tests if set_accumulated_depreciation() returns the right values.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + asset.make_depreciation_schedule(date_of_sale=None) + asset.set_accumulated_depreciation() + + expected_values = [30000.0, 60000.0, 90000.0] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(expected_values[i], schedule.accumulated_depreciation_amount) + + def test_check_is_pro_rata(self): + """Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate).""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + + has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) + self.assertFalse(has_pro_rata) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-07-01") + }) + + has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) + self.assertTrue(has_pro_rata) + + def test_expected_value_after_useful_life(self): + """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=110000, depreciation_start_date=getdate("2020-07-01"), do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_depreciation_start_date(self): + """Tests if an error is raised when neither depreciation_start_date nor available_for_use_date are specified.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + total_number_of_depreciations=3, expected_value_after_useful_life=110000, do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_opening_accumulated_depreciation(self): + """Tests if an error is raised when opening_accumulated_depreciation > (gross_purchase_amount - expected_value_after_useful_life).""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), + opening_accumulated_depreciation=100000, do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_number_of_depreciations_booked(self): + """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), + opening_accumulated_depreciation=10000, do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_number_of_depreciations(self): + """Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), + opening_accumulated_depreciation=10000, number_of_depreciations_booked=5, + do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_depreciation_start_date_is_before_purchase_date(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2014-07-01"), + do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_depreciation_start_date_is_before_available_for_use_date(self): + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, + expected_value_after_useful_life=10000, depreciation_start_date=getdate("2018-07-01"), + do_not_save=1) + + self.assertRaises(frappe.ValidationError, asset.save) + + def test_post_depreciation_entries(self): + """Tests if post_depreciation_entries() works as expected.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + asset.submit() + + post_depreciation_entries(date="2021-06-01") + asset.load_from_db() + + self.assertTrue(asset.schedules[0].journal_entry) + self.assertFalse(asset.schedules[1].journal_entry) + self.assertFalse(asset.schedules[2].journal_entry) + + def test_clear_depreciation_schedule(self): + """Tests if clear_depreciation_schedule() works as expected.""" + + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date=getdate("2019-12-31"), do_not_save=1) + + asset.finance_books = [] + asset.append("finance_books", { + "depreciation_method": "Straight Line", + "frequency_of_depreciation": 12, + "total_number_of_depreciations": 3, + "expected_value_after_useful_life": 10000, + "depreciation_start_date": getdate("2020-12-31") + }) + asset.submit() + + post_depreciation_entries(date="2021-06-01") + asset.load_from_db() + + asset.clear_depreciation_schedule() + + self.assertEqual(len(asset.schedules), 1) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() @@ -702,6 +947,8 @@ def create_asset(**args): "company": args.company or"_Test Company", "purchase_date": "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, + "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, + "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, "gross_purchase_amount": 100000, "purchase_receipt_amount": 100000, "warehouse": args.warehouse or "_Test Warehouse - _TC", @@ -720,10 +967,11 @@ def create_asset(**args): "depreciation_start_date": args.depreciation_start_date }) - try: - asset.save() - except frappe.DuplicateEntryError: - pass + if not args.do_not_save: + try: + asset.save() + except frappe.DuplicateEntryError: + pass if args.submit: asset.submit() From 5c249decbb79c5b9436aa7a0f5ca6670993e143e Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 27 Sep 2021 22:34:27 +0530 Subject: [PATCH 152/416] fix(ux): added exception of template item in filters (#27560) --- erpnext/manufacturing/doctype/bom/bom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 232e3a0b0f..7cfec974fc 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1133,7 +1133,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): query_filters["has_variants"] = 0 if filters and filters.get("is_stock_item"): - query_filters["is_stock_item"] = 1 + or_cond_filters["is_stock_item"] = 1 + or_cond_filters["has_variants"] = 1 return frappe.get_list("Item", fields = fields, filters=query_filters, From c84c983073943ae711100b265a981dfdc73c5fd6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 28 Sep 2021 01:22:38 +0530 Subject: [PATCH 153/416] fix: Categorize into test suites --- erpnext/assets/doctype/asset/test_asset.py | 621 +++++++++++---------- 1 file changed, 312 insertions(+), 309 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index e747204a69..b5488750eb 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -20,13 +20,13 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( ) from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt - -class TestAsset(unittest.TestCase): +class AssetSetup(unittest.TestCase): def setUp(self): set_depreciation_settings_in_company() create_asset_data() frappe.db.sql("delete from `tabTax Rule`") +class TestAsset(AssetSetup): def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -89,287 +89,6 @@ class TestAsset(unittest.TestCase): doc.set_missing_values() self.assertEqual(doc.items[0].is_fixed_asset, 1) - def test_schedule_for_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() - - self.assertEqual(asset.status, "Draft") - expected_schedules = [ - ["2030-12-31", 30000.00, 30000.00], - ["2031-12-31", 30000.00, 60000.00], - ["2032-12-31", 30000.00, 90000.00] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_straight_line_method_for_existing_asset(self): - create_asset(is_existing_asset=1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 40000 - asset.available_for_use_date = "2030-06-06" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - self.assertEqual(asset.status, "Draft") - asset.save() - expected_schedules = [ - ["2030-12-31", 14246.58, 54246.58], - ["2031-12-31", 25000.00, 79246.58], - ["2032-06-06", 10753.42, 90000.00] - ] - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": '2030-12-31' - }) - asset.save() - self.assertEqual(asset.status, "Draft") - - expected_schedules = [ - ['2030-12-31', 66667.00, 66667.00], - ['2031-12-31', 22222.11, 88889.11], - ['2032-12-31', 1110.89, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_double_declining_method_for_existing_asset(self): - create_asset(is_existing_asset = 1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.is_existing_asset = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 50000 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2029-11-30' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() - self.assertEqual(asset.status, "Draft") - - expected_schedules = [ - ["2030-12-31", 33333.50, 83333.50], - ["2031-12-31", 6666.50, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_schedule_for_prorated_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2030-01-30' - asset.is_existing_asset = 0 - asset.available_for_use_date = "2030-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - - asset.save() - - expected_schedules = [ - ["2030-12-31", 27534.25, 27534.25], - ["2031-12-31", 30000.0, 57534.25], - ["2032-12-31", 30000.0, 87534.25], - ["2033-01-30", 2465.75, 90000.0] - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' - asset.available_for_use_date = "2020-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() - asset.load_from_db() - self.assertEqual(asset.status, "Submitted") - - frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") - post_depreciation_entries(date="2021-01-01") - asset.load_from_db() - - # check depreciation entry series - self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") - - expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), - ("_Test Depreciations - _TC", 30000.0, 0.0) - ) - - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where against_voucher_type='Asset' and against_voucher = %s - order by account""", asset.name) - - self.assertEqual(gle, expected_gle) - self.assertEqual(asset.get("value_after_depreciation"), 0) - - # WDV: Written Down Value - def test_depreciation_entry_for_wdv_without_pro_rata(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 4000.00, 4000.00], - ["2031-12-31", 2000.00, 6000.00], - ["2032-12-31", 1000.00, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_pro_rata_depreciation_entry_for_wdv(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) - - self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) - - expected_schedules = [ - ["2030-12-31", 2279.45, 2279.45], - ["2031-12-31", 2860.28, 5139.73], - ["2032-12-31", 1430.14, 6569.87], - ["2033-06-06", 430.13, 7000.0], - ] - - schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] - for d in asset.get("schedules")] - - self.assertEqual(schedules, expected_schedules) - - def test_depreciation_entry_cancellation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() - post_depreciation_entries(date="2021-01-01") - - asset.load_from_db() - - # cancel depreciation entry - depr_entry = asset.get("schedules")[0].journal_entry - self.assertTrue(depr_entry) - frappe.get_doc("Journal Entry", depr_entry).cancel() - - asset.load_from_db() - depr_entry = asset.get("schedules")[0].journal_entry - self.assertFalse(depr_entry) - def test_scrap_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -412,7 +131,7 @@ class TestAsset(unittest.TestCase): self.assertFalse(asset.journal_entry_for_scrap) self.assertEqual(asset.status, "Partially Depreciated") - def test_asset_sale(self): + def test_gle_made_by_asset_sale(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -456,30 +175,6 @@ class TestAsset(unittest.TestCase): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") - def test_asset_expected_value_after_useful_life(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10 - }) - asset.save() - accumulated_depreciation_after_full_schedule = \ - max(d.accumulated_depreciation_amount for d in asset.get("schedules")) - - asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule)) - - self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) - # CWIP: Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -639,6 +334,220 @@ class TestAsset(unittest.TestCase): frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) +class TestDepreciationMethods(AssetSetup): + def test_schedule_for_straight_line_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save() + + self.assertEqual(asset.status, "Draft") + expected_schedules = [ + ["2030-12-31", 30000.00, 30000.00], + ["2031-12-31", 30000.00, 60000.00], + ["2032-12-31", 30000.00, 90000.00] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_straight_line_method_for_existing_asset(self): + create_asset(is_existing_asset=1) + asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + asset.calculate_depreciation = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 40000 + asset.available_for_use_date = "2030-06-06" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + self.assertEqual(asset.status, "Draft") + asset.save() + expected_schedules = [ + ["2030-12-31", 14246.58, 54246.58], + ["2031-12-31", 25000.00, 79246.58], + ["2032-06-06", 10753.42, 90000.00] + ] + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_double_declining_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Double Declining Balance", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": '2030-12-31' + }) + asset.save() + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ['2030-12-31', 66667.00, 66667.00], + ['2031-12-31', 22222.11, 88889.11], + ['2032-12-31', 1110.89, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_double_declining_method_for_existing_asset(self): + create_asset(is_existing_asset = 1) + asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) + asset.calculate_depreciation = 1 + asset.is_existing_asset = 1 + asset.number_of_depreciations_booked = 1 + asset.opening_accumulated_depreciation = 50000 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2029-11-30' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Double Declining Balance", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save() + self.assertEqual(asset.status, "Draft") + + expected_schedules = [ + ["2030-12-31", 33333.50, 83333.50], + ["2031-12-31", 6666.50, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_schedule_for_prorated_straight_line_method(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.purchase_date = '2030-01-30' + asset.is_existing_asset = 0 + asset.available_for_use_date = "2030-01-30" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + + asset.save() + + expected_schedules = [ + ["2030-12-31", 27534.25, 27534.25], + ["2031-12-31", 30000.0, 57534.25], + ["2032-12-31", 30000.0, 87534.25], + ["2033-01-30", 2465.75, 90000.0] + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # WDV: Written Down Value method + def test_depreciation_entry_for_wdv_without_pro_rata(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 4000.00, 4000.00], + ["2031-12-31", 2000.00, 6000.00], + ["2032-12-31", 1000.00, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + # WDV: Written Down Value method + def test_pro_rata_depreciation_entry_for_wdv(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-06' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 2279.45, 2279.45], + ["2031-12-31", 2860.28, 5139.73], + ["2032-12-31", 1430.14, 6569.87], + ["2033-06-06", 430.13, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + def test_discounted_wdv_depreciation_rate_for_indian_region(self): # set indian company company_flag = frappe.flags.company @@ -678,6 +587,7 @@ class TestAsset(unittest.TestCase): # reset indian company frappe.flags.company = company_flag +class TestDepreciationBasics(AssetSetup): def test_depreciation_without_pro_rata(self): asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, @@ -811,7 +721,7 @@ class TestAsset(unittest.TestCase): has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) self.assertTrue(has_pro_rata) - def test_expected_value_after_useful_life(self): + def test_expected_value_after_useful_life_greater_than_purchase_amount(self): """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, @@ -921,6 +831,99 @@ class TestAsset(unittest.TestCase): self.assertEqual(len(asset.schedules), 1) + def test_depreciation_entry_cancellation(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.submit() + post_depreciation_entries(date="2021-01-01") + + asset.load_from_db() + + # cancel depreciation entry + depr_entry = asset.get("schedules")[0].journal_entry + self.assertTrue(depr_entry) + frappe.get_doc("Journal Entry", depr_entry).cancel() + + asset.load_from_db() + depr_entry = asset.get("schedules")[0].journal_entry + self.assertFalse(depr_entry) + + def test_asset_expected_value_after_useful_life(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2020-06-06' + asset.purchase_date = '2020-06-06' + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10 + }) + asset.save() + accumulated_depreciation_after_full_schedule = \ + max(d.accumulated_depreciation_amount for d in asset.get("schedules")) + + asset_value_after_full_schedule = (flt(asset.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule)) + + self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) + + def test_gle_made_by_depreciation_entries(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=100000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.purchase_date = '2020-01-30' + asset.available_for_use_date = "2020-01-30" + asset.append("finance_books", { + "expected_value_after_useful_life": 10000, + "depreciation_method": "Straight Line", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 10, + "depreciation_start_date": "2020-12-31" + }) + asset.submit() + asset.load_from_db() + self.assertEqual(asset.status, "Submitted") + + frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") + post_depreciation_entries(date="2021-01-01") + asset.load_from_db() + + # check depreciation entry series + self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") + + expected_gle = ( + ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), + ("_Test Depreciations - _TC", 30000.0, 0.0) + ) + + gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` + where against_voucher_type='Asset' and against_voucher = %s + order by account""", asset.name) + + self.assertEqual(gle, expected_gle) + self.assertEqual(asset.get("value_after_depreciation"), 0) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() From b91333afddae6b8302ad1429e420ce29b9c347ba Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Tue, 28 Sep 2021 12:11:37 +0530 Subject: [PATCH 154/416] fix: set item.qty as mandatory in picklist (#27680) --- erpnext/stock/doctype/pick_list_item/pick_list_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index 8665986004..805286ddcc 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -36,7 +36,8 @@ "fieldname": "qty", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty" + "label": "Qty", + "reqd": 1 }, { "fieldname": "picked_qty", @@ -180,7 +181,7 @@ ], "istable": 1, "links": [], - "modified": "2020-06-24 17:18:57.357120", + "modified": "2021-09-28 12:02:16.923056", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", From 72c081fd8fa3994d447944c58c5ea8cef0350a16 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Tue, 28 Sep 2021 12:56:26 +0530 Subject: [PATCH 155/416] fix: apply price list after batch or serial no insertion (#27566) --- erpnext/public/js/controllers/transaction.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 91b1247e6f..403c4988eb 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -617,6 +617,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe me.frm.script_manager.trigger('qty', item.doctype, item.name); if (!me.frm.doc.set_warehouse) me.frm.script_manager.trigger('warehouse', item.doctype, item.name); + me.apply_price_list(item, true); }, undefined, !frappe.flags.hide_serial_batch_dialog); } }, From 0a28fed679a6ce22a9d88d45ca3f05ea7cdb68d9 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Tue, 28 Sep 2021 13:54:01 +0530 Subject: [PATCH 156/416] fix: patch fix, fields disabling --- .../taxjar_settings/taxjar_settings.py | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index 2ee3350e03..408278342b 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -21,12 +21,28 @@ class TaxJarSettings(Document): TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") + fields_hidden = 0 - if TAXJAR_CREATE_TRANSACTIONS or TAXJAR_CALCULATE_TAX or TAXJAR_SANDBOX_MODE: - add_product_tax_categories() - make_custom_fields() - add_permissions() - frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) + custom_fields = [] + for dt in ['Item', 'Sales Invoice Item']: + doc = frappe.get_doc('Custom Field', {'dt': dt, 'fieldname':'product_tax_category'}) + custom_fields.append(doc) + fields_hidden = doc.get('hidden') if doc else 0 + fields_already_exist = True if custom_fields else False + + + if (TAXJAR_CREATE_TRANSACTIONS or TAXJAR_CALCULATE_TAX or TAXJAR_SANDBOX_MODE): + if not fields_already_exist: + add_product_tax_categories() + make_custom_fields() + add_permissions() + frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) + + elif fields_already_exist and fields_hidden: + toggle_tax_category_fields('1') + + elif not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE: + toggle_tax_category_fields('0') @frappe.whitelist() def update_nexus_list(self): @@ -39,6 +55,11 @@ class TaxJarSettings(Document): self.set('nexus', new_nexus_list) self.save() +def toggle_tax_category_fields(toggle): + frappe.set_value('Custom Field',{'document':'Sales Invoice Item', 'fieldname':'product_tax_category'},'hidden',toggle) + frappe.set_value('Custom Field',{'document':'Item', 'fieldname':'product_tax_category'},'hidden',toggle) + + def add_product_tax_categories(): with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f: tax_categories = json.loads(f.read()) From 424efd41e52d1e244227e9db7f606a8ecbfc268b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 28 Sep 2021 18:12:02 +0530 Subject: [PATCH 157/416] feat(regional): toggle for reduced depreciation rate as per IT Act (#27600) --- erpnext/assets/doctype/asset/asset.py | 4 ---- erpnext/assets/doctype/asset/test_asset.py | 6 ++++++ erpnext/patches.txt | 3 ++- .../create_custom_field_for_finance_book.py | 21 +++++++++++++++++++ erpnext/regional/india/setup.py | 9 ++++++++ erpnext/regional/india/utils.py | 13 ++++++------ 6 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 erpnext/patches/v13_0/create_custom_field_for_finance_book.py diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 8ff4f9790a..39f102e143 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -394,10 +394,6 @@ class Asset(AccountsController): if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations): frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(nowdate()): - frappe.msgprint(_("Depreciation Row {0}: Depreciation Start Date is entered as past date") - .format(row.idx), title=_('Warning'), indicator='red') - if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date") .format(row.idx)) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 4cc9be5b05..7183ee7e36 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -645,12 +645,18 @@ class TestAsset(unittest.TestCase): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") + finance_book = frappe.new_doc('Finance Book') + finance_book.finance_book_name = 'Income Tax' + finance_book.for_income_tax = 1 + finance_book.insert(ignore_if_duplicate=1) + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 asset.available_for_use_date = '2030-07-12' asset.purchase_date = '2030-01-01' asset.append("finance_books", { + "finance_book": finance_book.name, "expected_value_after_useful_life": 1000, "depreciation_method": "Written Down Value", "total_number_of_depreciations": 3, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index d26c92e749..1a88632b16 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,4 +309,5 @@ erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes -erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries \ No newline at end of file +erpnext.patches.v13_0.create_custom_field_for_finance_book +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries diff --git a/erpnext/patches/v13_0/create_custom_field_for_finance_book.py b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py new file mode 100644 index 0000000000..313b0e9a2e --- /dev/null +++ b/erpnext/patches/v13_0/create_custom_field_for_finance_book.py @@ -0,0 +1,21 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_field = { + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } + ] + } + create_custom_fields(custom_field, update=1) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 633064cf09..4a2c577e55 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -663,6 +663,15 @@ def make_custom_fields(update=True): 'fieldtype': 'Data', 'insert_after': 'email' } + ], + 'Finance Book': [ + { + 'fieldname': 'for_income_tax', + 'label': 'For Income Tax', + 'fieldtype': 'Check', + 'insert_after': 'finance_book_name', + 'description': 'If the asset is put to use for less than 180 days, the first Depreciation Rate will be reduced by 50%.' + } ] } create_custom_fields(custom_fields, update=update) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 091cc8847c..0faf80b002 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -857,12 +857,13 @@ def get_depreciation_amount(asset, depreciable_value, row): rate_of_depreciation = row.rate_of_depreciation # if its the first depreciation if depreciable_value == asset.gross_purchase_amount: - # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 - diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) - if diff <= 180: - rate_of_depreciation = rate_of_depreciation / 2 - frappe.msgprint( - _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) + if row.finance_book and frappe.db.get_value('Finance Book', row.finance_book, 'for_income_tax'): + # as per IT act, if the asset is purchased in the 2nd half of fiscal year, then rate is divided by 2 + diff = date_diff(row.depreciation_start_date, asset.available_for_use_date) + if diff <= 180: + rate_of_depreciation = rate_of_depreciation / 2 + frappe.msgprint( + _('As per IT Act, the rate of depreciation for the first depreciation entry is reduced by 50%.')) depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100)) From 0b040c7437ceafa5412681373fd1870e8fdc26d4 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 28 Sep 2021 18:13:01 +0530 Subject: [PATCH 158/416] fix: cannot delete a project if linked with sales order (#27536) --- erpnext/projects/doctype/project/project.py | 3 +++ .../projects/doctype/project/test_project.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 2a8870b230..df970f34c2 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -144,6 +144,9 @@ class Project(Document): if self.sales_order: frappe.db.set_value("Sales Order", self.sales_order, "project", self.name) + def on_trash(self): + frappe.db.set_value("Sales Order", {"project": self.name}, "project", "") + def update_percent_complete(self): if self.percent_complete_method == "Manual": if self.status == "Completed": diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py index ebc132626c..c64ac8d0ea 100644 --- a/erpnext/projects/doctype/project/test_project.py +++ b/erpnext/projects/doctype/project/test_project.py @@ -9,6 +9,8 @@ from frappe.utils import add_days, getdate, nowdate from erpnext.projects.doctype.project_template.test_project_template import make_project_template from erpnext.projects.doctype.task.test_task import create_task +from erpnext.selling.doctype.sales_order.sales_order import make_project as make_project_from_so +from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order test_records = frappe.get_test_records('Project') test_ignore = ["Sales Order"] @@ -96,6 +98,21 @@ class TestProject(unittest.TestCase): self.assertEqual(len(tasks), 2) + def test_project_linking_with_sales_order(self): + so = make_sales_order() + project = make_project_from_so(so.name) + + project.save() + self.assertEqual(project.sales_order, so.name) + + so.reload() + self.assertEqual(so.project, project.name) + + project.delete() + + so.reload() + self.assertFalse(so.project) + def get_project(name, template): project = frappe.get_doc(dict( From 8675ca5bdd838460d56be8eadf6790cadc605ba3 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 29 Sep 2021 11:11:55 +0530 Subject: [PATCH 159/416] fix: using db.exists and get_value instead of get_doc --- .../doctype/taxjar_settings/taxjar_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index 408278342b..dd83c174aa 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -25,9 +25,9 @@ class TaxJarSettings(Document): custom_fields = [] for dt in ['Item', 'Sales Invoice Item']: - doc = frappe.get_doc('Custom Field', {'dt': dt, 'fieldname':'product_tax_category'}) + doc = frappe.db.exists('Custom Field', {'dt': dt, 'fieldname':'product_tax_category'}) custom_fields.append(doc) - fields_hidden = doc.get('hidden') if doc else 0 + fields_hidden = frappe.db.get_value('Custom Field', {'dt': dt, 'fieldname':'product_tax_category'},'hidden') fields_already_exist = True if custom_fields else False From d3bb920e715a49ee47e52eef1b53d40dc573d877 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 29 Sep 2021 11:26:04 +0530 Subject: [PATCH 160/416] fix: dt instead of document in set_value query --- .../doctype/taxjar_settings/taxjar_settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index dd83c174aa..e03a2ac513 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -56,8 +56,8 @@ class TaxJarSettings(Document): self.save() def toggle_tax_category_fields(toggle): - frappe.set_value('Custom Field',{'document':'Sales Invoice Item', 'fieldname':'product_tax_category'},'hidden',toggle) - frappe.set_value('Custom Field',{'document':'Item', 'fieldname':'product_tax_category'},'hidden',toggle) + frappe.set_value('Custom Field',{'dt':'Sales Invoice Item', 'fieldname':'product_tax_category'},'hidden',toggle) + frappe.set_value('Custom Field',{'dt':'Item', 'fieldname':'product_tax_category'},'hidden',toggle) def add_product_tax_categories(): From 2b4959fb3be1507e86e86bd0d7c29cae74d3cded Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 29 Sep 2021 13:01:40 +0530 Subject: [PATCH 161/416] fix: return tax rate since fetch is removed --- erpnext/hr/doctype/expense_claim/test_expense_claim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 9cb65f7e08..941fd58c7b 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -176,7 +176,7 @@ def generate_taxes(): account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account) return {'taxes':[{ "account_head": account, - "rate": 0, + "rate": 9, "description": "CGST", "tax_amount": 10, "total": 210 From 254b20bc093805a6642e6ddedcd8917a535d42b1 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 29 Sep 2021 13:34:50 +0530 Subject: [PATCH 162/416] minor fixes --- .../taxjar_settings/taxjar_settings.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index e03a2ac513..725a61a396 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -39,10 +39,10 @@ class TaxJarSettings(Document): frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) elif fields_already_exist and fields_hidden: - toggle_tax_category_fields('1') + toggle_tax_category_fields(hidden='1') - elif not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE: - toggle_tax_category_fields('0') + else: + toggle_tax_category_fields(hidden='0') @frappe.whitelist() def update_nexus_list(self): @@ -55,9 +55,9 @@ class TaxJarSettings(Document): self.set('nexus', new_nexus_list) self.save() -def toggle_tax_category_fields(toggle): - frappe.set_value('Custom Field',{'dt':'Sales Invoice Item', 'fieldname':'product_tax_category'},'hidden',toggle) - frappe.set_value('Custom Field',{'dt':'Item', 'fieldname':'product_tax_category'},'hidden',toggle) +def toggle_tax_category_fields(hidden): + frappe.set_value('Custom Field',{'dt':'Sales Invoice Item', 'fieldname':'product_tax_category'},'hidden',hidden) + frappe.set_value('Custom Field',{'dt':'Item', 'fieldname':'product_tax_category'},'hidden',hidden) def add_product_tax_categories(): @@ -67,14 +67,12 @@ def add_product_tax_categories(): def create_tax_categories(data): for d in data: - tax_category = frappe.new_doc('Product Tax Category') - tax_category.description = d.get("description") - tax_category.product_tax_code = d.get("product_tax_code") - tax_category.category_name = d.get("name") - try: + if not frappe.db.exists('Product Tax Category',{'product_tax_code':d.get('product_tax_code')}): + tax_category = frappe.new_doc('Product Tax Category') + tax_category.description = d.get("description") + tax_category.product_tax_code = d.get("product_tax_code") + tax_category.category_name = d.get("name") tax_category.db_insert() - except frappe.DuplicateEntryError: - pass def make_custom_fields(update=True): custom_fields = { From b39f8a621504cf7cb3f3a40717638318ada31571 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 29 Sep 2021 15:54:35 +0530 Subject: [PATCH 163/416] fix: Ignore user permission for Represents Company field in Sales and Purchase docs (#27684) * fix: Ignore user permission for Represents Company field in Sales and Purchase docs * fix: Ignore user permission for fiscal year company --- .../fiscal_year_company.json | 86 ++++++------------- .../purchase_invoice/purchase_invoice.json | 3 +- .../doctype/sales_invoice/sales_invoice.json | 3 +- .../purchase_order/purchase_order.json | 3 +- .../doctype/sales_order/sales_order.json | 3 +- .../doctype/delivery_note/delivery_note.json | 3 +- .../purchase_receipt/purchase_receipt.json | 3 +- 7 files changed, 40 insertions(+), 64 deletions(-) diff --git a/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json b/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json index 3eb0d74ed3..67acb26c7e 100644 --- a/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json +++ b/erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json @@ -1,63 +1,33 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-10-02 13:35:44.155278", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 1, + "actions": [], + "creation": "2014-10-02 13:35:44.155278", + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "company", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Company", + "options": "Company" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-07-11 03:28:00.505946", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Fiscal Year Company", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-28 18:01:53.495929", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Fiscal Year Company", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index dde0328130..55e288eeef 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1303,6 +1303,7 @@ "fetch_from": "supplier.is_internal_supplier", "fieldname": "is_internal_supplier", "fieldtype": "Check", + "ignore_user_permissions": 1, "label": "Is Internal Supplier", "read_only": 1 }, @@ -1400,7 +1401,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-09-21 09:27:39.967811", + "modified": "2021-09-28 13:10:28.351810", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f383b52b40..2d6c04ebf9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1953,6 +1953,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -2022,7 +2023,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-09-21 09:27:50.191854", + "modified": "2021-09-28 13:09:34.391799", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index ef54538fcd..896208f25e 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1121,6 +1121,7 @@ "fetch_from": "supplier.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1143,7 +1144,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-08-30 20:03:14.008804", + "modified": "2021-09-28 13:10:47.955401", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 85282ca1a0..7c7ed9a960 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1480,6 +1480,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1512,7 +1513,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2021-09-01 15:12:24.115483", + "modified": "2021-09-28 13:09:51.515542", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index fdc8763baa..9bf142c4b4 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1277,6 +1277,7 @@ "fetch_from": "customer.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1308,7 +1309,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-08-27 20:14:40.215231", + "modified": "2021-09-28 13:10:09.761714", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 1a597343c0..112ddedac2 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1140,6 +1140,7 @@ "fetch_from": "supplier.represents_company", "fieldname": "represents_company", "fieldtype": "Link", + "ignore_user_permissions": 1, "label": "Represents Company", "options": "Company", "read_only": 1 @@ -1149,7 +1150,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2021-08-17 20:16:40.849885", + "modified": "2021-09-28 13:11:10.181328", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From f1fcb385f56741237b924d41683d0d2fe3e893f4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 29 Sep 2021 19:56:02 +0530 Subject: [PATCH 164/416] patch: trim sales invoice custom field lengths --- erpnext/patches.txt | 1 + .../trim_sales_invoice_custom_field_length.py | 18 ++++++++++++++++++ erpnext/regional/india/setup.py | 7 ++++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index cee796efbc..0c36c9f5e2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -309,3 +309,4 @@ erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes +erpnext.patches.v13_0.trim_sales_invoice_custom_field_length diff --git a/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py new file mode 100644 index 0000000000..fd48c0d902 --- /dev/null +++ b/erpnext/patches/v13_0/trim_sales_invoice_custom_field_length.py @@ -0,0 +1,18 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + +from erpnext.regional.india.setup import create_custom_fields, get_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': get_custom_fields().get('Sales Invoice') + } + + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index a2a4a0944e..93cbcca92d 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -132,6 +132,10 @@ def make_property_setters(patch=False): make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '') def make_custom_fields(update=True): + custom_fields = get_custom_fields() + create_custom_fields(custom_fields, update=update) + +def get_custom_fields(): hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description', allow_on_submit=1, print_hide=1, fetch_if_empty=1) @@ -672,7 +676,8 @@ def make_custom_fields(update=True): } ] } - create_custom_fields(custom_fields, update=update) + + return custom_fields def make_fixtures(company=None): docs = [] From c1f9997a67e4b7e86d72b2e5bb74a59dd56bd1f9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 29 Sep 2021 23:23:17 +0530 Subject: [PATCH 165/416] fix: added project name in the purchase order analysis --- .../purchase_order_analysis.js | 9 ++++++++- .../purchase_order_analysis.py | 19 ++++++++++++------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index 701da4380a..ca3be03da6 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -30,7 +30,14 @@ frappe.query_reports["Purchase Order Analysis"] = { "default": frappe.datetime.get_today() }, { - "fieldname": "purchase_order", + "fieldname":"project", + "label": __("Project"), + "fieldtype": "Link", + "width": "80", + "options": "Project" + }, + { + "fieldname": "name", "label": __("Purchase Order"), "fieldtype": "Link", "width": "80", diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 5d59456550..1b25dd45d2 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -41,14 +41,12 @@ def get_conditions(filters): if filters.get("from_date") and filters.get("to_date"): conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" - if filters.get("company"): - conditions += " and po.company = %(company)s" + for field in ['company', 'name', 'status']: + if filters.get(field): + conditions += f" and po.{field} = %({field})s" - if filters.get("purchase_order"): - conditions += " and po.name = %(purchase_order)s" - - if filters.get("status"): - conditions += " and po.status in %(status)s" + if filters.get('project'): + conditions += " and poi.project = %(project)s" return conditions @@ -57,6 +55,7 @@ def get_data(conditions, filters): SELECT po.transaction_date as date, poi.schedule_date as required_date, + poi.project, po.name as purchase_order, po.status, po.supplier, poi.item_code, poi.qty, poi.received_qty, @@ -175,6 +174,12 @@ def get_columns(filters): "fieldtype": "Link", "options": "Supplier", "width": 130 + },{ + "label": _("Project"), + "fieldname": "project", + "fieldtype": "Link", + "options": "Project", + "width": 130 }] if not filters.get("group_by_po"): From 13d2e7b19817e2dcd10129985773314f0d3a95e6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Sep 2021 18:54:57 +0530 Subject: [PATCH 166/416] fix: Deferred revenue entries post account freezing (cherry picked from commit e2eb72eb5ba1aaadddf8278dd77bfe75a178ef01) --- .../sales_invoice/test_sales_invoice.py | 36 +++++++++++++++++++ erpnext/accounts/general_ledger.py | 5 ++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index bdd30f380f..25d85d4c12 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1800,6 +1800,42 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") + def test_deferred_revenue_post_account_freeze_upto_by_admin(self): + deferred_account = create_account(account_name="Deferred Revenue", + parent_account="Current Liabilities - _TC", company="_Test Company") + + item = create_item("_Test Item for Deferred Accounting") + item.enable_deferred_revenue = 1 + item.deferred_revenue_account = deferred_account + item.no_of_months = 12 + item.save() + + si = create_sales_invoice(item=item.name, posting_date="2019-01-10", do_not_save=True) + si.items[0].enable_deferred_revenue = 1 + si.items[0].service_start_date = "2019-01-10" + si.items[0].service_end_date = "2019-03-15" + si.items[0].deferred_revenue_account = deferred_account + si.save() + si.submit() + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', getdate('2019-01-31')) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', 'System Manager') + + pda1 = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + posting_date=nowdate(), + start_date="2019-01-01", + end_date="2019-03-31", + type="Income", + company="_Test Company" + )) + + pda1.insert() + self.assertRaises(frappe.ValidationError, pda1.submit) + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) + def test_fixed_deferred_revenue(self): deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 4bf2b828ed..0cee6f5b3a 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -284,13 +284,16 @@ def check_freezing_date(posting_date, adv_adj=False): """ Nobody can do GL Entries where posting date is before freezing date except authorized person + + Administrator has all the roles so this check will be bypassed if any role is allowed to post + Hence stop admin to bypass if accounts are freezed """ if not adv_adj: acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') if acc_frozen_upto: frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') if getdate(posting_date) <= getdate(acc_frozen_upto) \ - and not frozen_accounts_modifier in frappe.get_roles(): + and not frozen_accounts_modifier in frappe.get_roles() or frappe.session.user == 'Administrator': frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) def set_as_cancel(voucher_type, voucher_no): From 774cd68f4c3f0a7b5951b627069158718fc1dd22 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 20 Sep 2021 11:06:10 +0530 Subject: [PATCH 167/416] fix: Test Case (cherry picked from commit 846d128c5d536b5003dc3055f3239e6f92a3146c) --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 25d85d4c12..8a2e9450e9 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1801,6 +1801,11 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, "2019-01-30") def test_deferred_revenue_post_account_freeze_upto_by_admin(self): + frappe.set_user("Administrator") + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + frappe.db.set_value('Accounts Settings', None, 'frozen_accounts_modifier', None) + deferred_account = create_account(account_name="Deferred Revenue", parent_account="Current Liabilities - _TC", company="_Test Company") From f1a669c2f747e3ee4848d4aef272281017016247 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 29 Sep 2021 22:26:33 +0530 Subject: [PATCH 168/416] fix: Test case (cherry picked from commit 23863c7663a94e32e32812301a7efca79575c470) --- erpnext/accounts/deferred_revenue.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index bcd07718a5..71957e67a3 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -374,12 +374,15 @@ def make_gl_entries(doc, credit_account, debit_account, against, try: make_gl_entries(gl_entries, cancel=(doc.docstatus == 2), merge_entries=True) frappe.db.commit() - except Exception: - frappe.db.rollback() - traceback = frappe.get_traceback() - frappe.log_error(message=traceback) + except Exception as e: + if frappe.flags.in_test: + raise e + else: + frappe.db.rollback() + traceback = frappe.get_traceback() + frappe.log_error(message=traceback) - frappe.flags.deferred_accounting_error = True + frappe.flags.deferred_accounting_error = True def send_mail(deferred_process): title = _("Error while processing deferred accounting for {0}").format(deferred_process) From 4685ed5a8c95a64c351b2fe87bc41ef44ebdb4fa Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 30 Sep 2021 09:58:59 +0530 Subject: [PATCH 169/416] fix: distribution of additional costs in mfg stock entry (#27629) * refactor: remove unnecessary list comprehensions * fix: correct cost distribution logic While apportioning costs same condition should be present on both sides so total value is representative of all items to be apportioned. Here while calculating incoming_items_cost only FG items are considered, but while apportioning all items with to_warehouse are considered. Solution: only apportion additional cost on FG items * test: test cost distribution * fix: patch for additional cost fix(patch): consider PCV while patching - consider Period closing voucher while patching - recomute rates for SLE of affected stock entries consider only FG/scrap item SLEs for recomputation of rates * fix: remove client side logic for addn cost All of this is done in python code hence removed client side code. --- erpnext/patches.txt | 1 + .../fix_additional_cost_in_mfg_stock_entry.py | 76 +++++++++++++++++++ .../stock/doctype/stock_entry/stock_entry.js | 42 ---------- .../stock/doctype/stock_entry/stock_entry.py | 57 ++++++++------ .../doctype/stock_entry/test_stock_entry.py | 33 ++++++++ 5 files changed, 145 insertions(+), 64 deletions(-) create mode 100644 erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1a88632b16..a481996581 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -311,3 +311,4 @@ erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries +erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry diff --git a/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py new file mode 100644 index 0000000000..aeb8d8eb58 --- /dev/null +++ b/erpnext/patches/v13_0/fix_additional_cost_in_mfg_stock_entry.py @@ -0,0 +1,76 @@ +from typing import List, NewType + +import frappe + +StockEntryCode = NewType("StockEntryCode", str) + + +def execute(): + stock_entry_codes = find_broken_stock_entries() + + for stock_entry_code in stock_entry_codes: + patched_stock_entry = patch_additional_cost(stock_entry_code) + create_repost_item_valuation(patched_stock_entry) + + +def find_broken_stock_entries() -> List[StockEntryCode]: + period_closing_date = frappe.db.get_value( + "Period Closing Voucher", {"docstatus": 1}, "posting_date", order_by="posting_date desc" + ) + + stock_entries_to_patch = frappe.db.sql( + """ + select se.name, sum(sed.additional_cost) as item_additional_cost, se.total_additional_costs + from `tabStock Entry` se + join `tabStock Entry Detail` sed + on sed.parent = se.name + where + se.docstatus = 1 and + se.posting_date > %s + group by + sed.parent + having + item_additional_cost != se.total_additional_costs + """, + period_closing_date, + as_dict=True, + ) + + return [d.name for d in stock_entries_to_patch] + + +def patch_additional_cost(code: StockEntryCode): + stock_entry = frappe.get_doc("Stock Entry", code) + stock_entry.distribute_additional_costs() + stock_entry.update_valuation_rate() + stock_entry.set_total_incoming_outgoing_value() + stock_entry.set_total_amount() + stock_entry.db_update() + for item in stock_entry.items: + item.db_update() + return stock_entry + + +def create_repost_item_valuation(stock_entry): + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + + # turn on recalculate flag so reposting corrects the incoming/outgoing rates. + frappe.db.set_value( + "Stock Ledger Entry", + {"voucher_no": stock_entry.name, "actual_qty": (">", 0)}, + "recalculate_rate", + 1, + update_modified=False, + ) + + create_repost_item_valuation_entry( + args=frappe._dict( + { + "posting_date": stock_entry.posting_date, + "posting_time": stock_entry.posting_time, + "voucher_type": stock_entry.doctype, + "voucher_no": stock_entry.name, + "company": stock_entry.company, + } + ) + ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 7cb9665e85..157904bc34 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -548,44 +548,7 @@ frappe.ui.form.on('Stock Entry', { calculate_basic_amount: function(frm, item) { item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate), precision("basic_amount", item)); - - frm.events.calculate_amount(frm); - }, - - calculate_amount: function(frm) { frm.events.calculate_total_additional_costs(frm); - let total_basic_amount = 0; - if (in_list(["Repack", "Manufacture"], frm.doc.purpose)) { - total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { - return i.is_finished_item ? flt(i.basic_amount) : 0; - }) - ); - } else { - total_basic_amount = frappe.utils.sum( - (frm.doc.items || []).map(function(i) { - return i.t_warehouse ? flt(i.basic_amount) : 0; - }) - ); - } - for (let i in frm.doc.items) { - let item = frm.doc.items[i]; - - if (((in_list(["Repack", "Manufacture"], frm.doc.purpose) && item.is_finished_item) || item.t_warehouse) && total_basic_amount) { - item.additional_cost = (flt(item.basic_amount) / total_basic_amount) * frm.doc.total_additional_costs; - } else { - item.additional_cost = 0; - } - - item.amount = flt(item.basic_amount + flt(item.additional_cost), precision("amount", item)); - - if (flt(item.transfer_qty)) { - item.valuation_rate = flt(flt(item.basic_rate) + (flt(item.additional_cost) / flt(item.transfer_qty)), - precision("valuation_rate", item)); - } - } - - refresh_field('items'); }, calculate_total_additional_costs: function(frm) { @@ -781,11 +744,6 @@ frappe.ui.form.on('Landed Cost Taxes and Charges', { amount: function(frm, cdt, cdn) { frm.events.set_base_amount(frm, cdt, cdn); - // Adding this check because same table in used in LCV - // This causes an error if you try to post an LCV immediately after a Stock Entry - if (frm.doc.doctype == 'Stock Entry') { - frm.events.calculate_amount(frm); - } }, expense_account: function(frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 1c9b9614f6..bd7d22bcbc 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -555,22 +555,27 @@ class StockEntry(StockController): def distribute_additional_costs(self): # If no incoming items, set additional costs blank - if not any([d.item_code for d in self.items if d.t_warehouse]): + if not any(d.item_code for d in self.items if d.t_warehouse): self.additional_costs = [] - self.total_additional_costs = sum([flt(t.base_amount) for t in self.get("additional_costs")]) + self.total_additional_costs = sum(flt(t.base_amount) for t in self.get("additional_costs")) if self.purpose in ("Repack", "Manufacture"): - incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.is_finished_item]) + incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) else: - incoming_items_cost = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + incoming_items_cost = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse) - if incoming_items_cost: - for d in self.get("items"): - if (self.purpose in ("Repack", "Manufacture") and d.is_finished_item) or d.t_warehouse: - d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs - else: - d.additional_cost = 0 + if not incoming_items_cost: + return + + for d in self.get("items"): + if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item: + d.additional_cost = 0 + continue + elif not d.t_warehouse: + d.additional_cost = 0 + continue + d.additional_cost = (flt(d.basic_amount) / incoming_items_cost) * self.total_additional_costs def update_valuation_rate(self): for d in self.get("items"): @@ -805,7 +810,11 @@ class StockEntry(StockController): def get_gl_entries(self, warehouse_account): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) - total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + if self.purpose in ("Repack", "Manufacture"): + total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item) + else: + total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.t_warehouse) + divide_based_on = total_basic_amount if self.get("additional_costs") and not total_basic_amount: @@ -816,20 +825,24 @@ class StockEntry(StockController): for t in self.get("additional_costs"): for d in self.get("items"): - if d.t_warehouse: - item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) - item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { - "amount": 0.0, - "base_amount": 0.0 - }) + if self.purpose in ("Repack", "Manufacture") and not d.is_finished_item: + continue + elif not d.t_warehouse: + continue - multiply_based_on = d.basic_amount if total_basic_amount else d.qty + item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) + item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, { + "amount": 0.0, + "base_amount": 0.0 + }) - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ - flt(t.amount * multiply_based_on) / divide_based_on + multiply_based_on = d.basic_amount if total_basic_amount else d.qty - item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ - flt(t.base_amount * multiply_based_on) / divide_based_on + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \ + flt(t.amount * multiply_based_on) / divide_based_on + + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \ + flt(t.base_amount * multiply_based_on) / divide_based_on if item_account_wise_additional_cost: for d in self.get("items"): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 46619eb1f3..c9d0af5f3b 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -837,6 +837,39 @@ class TestStockEntry(unittest.TestCase): frappe.db.set_default("allow_negative_stock", 0) + def test_additional_cost_distribution_manufacture(self): + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Manufacture", + additional_costs=[frappe._dict(base_amount=100)], + items=[ + frappe._dict(item_code="RM", basic_amount=10), + frappe._dict(item_code="FG", basic_amount=20, t_warehouse="X", is_finished_item=1), + frappe._dict(item_code="scrap", basic_amount=30, t_warehouse="X") + ], + ) + + se.distribute_additional_costs() + + distributed_costs = [d.additional_cost for d in se.items] + self.assertEqual([0.0, 100.0, 0.0], distributed_costs) + + def test_additional_cost_distribution_non_manufacture(self): + se = frappe.get_doc( + doctype="Stock Entry", + purpose="Material Receipt", + additional_costs=[frappe._dict(base_amount=100)], + items=[ + frappe._dict(item_code="RECEIVED_1", basic_amount=20, t_warehouse="X"), + frappe._dict(item_code="RECEIVED_2", basic_amount=30, t_warehouse="X") + ], + ) + + se.distribute_additional_costs() + + distributed_costs = [d.additional_cost for d in se.items] + self.assertEqual([40.0, 60.0], distributed_costs) + def make_serialized_item(**args): args = frappe._dict(args) se = frappe.copy_doc(test_records[0]) From 9df5df4d94a882bfd739f5c27ff0f56ae44166fb Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 30 Sep 2021 12:07:38 +0530 Subject: [PATCH 170/416] fix: wrong company selected when marking attendance for all employees (#27685) (#27707) * fix: wrong company selected when marking attendance for all employees * fix: enable caching for repeated queries of the same employee (cherry picked from commit b478e72cefbdffd3e906fdd1ef822becdead78d7) Co-authored-by: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> --- .../employee_attendance_tool/employee_attendance_tool.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py index 7c751a47a6..1a1bcb2e20 100644 --- a/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py +++ b/erpnext/hr/doctype/employee_attendance_tool/employee_attendance_tool.py @@ -55,8 +55,7 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa else: leave_type = None - if not company: - company = frappe.db.get_value("Employee", employee['employee'], "Company") + company = frappe.db.get_value("Employee", employee['employee'], "Company", cache=True) attendance=frappe.get_doc(dict( doctype='Attendance', @@ -68,4 +67,4 @@ def mark_employee_attendance(employee_list, status, date, leave_type=None, compa company=company )) attendance.insert() - attendance.submit() + attendance.submit() \ No newline at end of file From 83cc59759493770ce14ccfb57912a3f5214f49f9 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 30 Sep 2021 12:37:04 +0530 Subject: [PATCH 171/416] fix: do not set length for date field --- erpnext/regional/india/setup.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index b5c609a961..afb1b07ccc 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -255,7 +255,7 @@ def get_custom_fields(): depends_on="eval:doc.gst_category=='Overseas' ", length=50), dict(fieldname='shipping_bill_date', label='Shipping Bill Date', fieldtype='Date', insert_after='shipping_bill_number', print_hide=1, - depends_on="eval:doc.gst_category=='Overseas' ", length=15), + depends_on="eval:doc.gst_category=='Overseas' "), ] journal_entry_fields = [ @@ -434,8 +434,7 @@ def get_custom_fields(): 'fieldtype': 'Date', 'insert_after': 'driver_name', 'default': 'Today', - 'print_hide': 1, - 'length': 10 + 'print_hide': 1 }, { 'fieldname': 'gst_vehicle_type', From abf450dc706733e2da3463482610f772a817743f Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 30 Sep 2021 14:03:40 +0530 Subject: [PATCH 172/416] fix: Increment batch qty if pre-existing batch is scanned --- erpnext/public/js/controllers/transaction.js | 27 ++++++-------------- erpnext/www/shop-by-category/__init__.py | 0 2 files changed, 8 insertions(+), 19 deletions(-) create mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index f97d8cfe7f..149582f48b 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -378,18 +378,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe let batch_no_scan = Boolean(data.batch_no) && frappe.meta.has_field(cur_grid.doctype, "batch_no"); if (batch_no_scan) { - let duplicate = this.check_duplicate_batch_scan(data.batch_no); - if (duplicate) { - scan_barcode_field.set_value(''); - return; - } + row_to_modify = this.get_batch_row_to_modify(data.batch_no); } else { // serial or barcode scan row_to_modify = this.get_row_to_modify_on_scan(row_to_modify, data); } - if (!row_to_modify || batch_no_scan) { - // add new row if new item scanned or batch is scanned + if (!row_to_modify) { + // add new row if new item/batch is scanned row_to_modify = frappe.model.add_child(this.frm.doc, cur_grid.doctype, 'items'); } @@ -408,8 +404,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe ['serial_no', 'batch_no', 'barcode'].forEach(field => { if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { let is_serial_no = row_to_modify[field] && field === "serial_no"; - let value = data[field]; + if (is_serial_no) { value = row_to_modify[field] + '\n' + data[field]; } @@ -436,21 +432,14 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe return row_to_modify; } - check_duplicate_batch_scan(batch_no) { - // ignore scan if batch already exists in table + get_batch_row_to_modify(batch_no) { + // get row if batch already exists in table const existing_batch_row = this.frm.doc.items.find(d => d.batch_no === batch_no); - - if (existing_batch_row) { - frappe.show_alert({ - message: __('Batch {0} already added', [batch_no]), - indicator: 'orange' - }); - return true; - } - return false; + return existing_batch_row || null; } show_scan_message (idx, exist = null) { + // show new row or qty increase toast if (exist) { frappe.show_alert({ message: __('Row #{0}: Qty increased by 1', [idx]), diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From cc143bca0d4e6f30cc7cf1dd69911b09f7a0fd89 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 30 Sep 2021 14:08:45 +0530 Subject: [PATCH 173/416] fix: Maintenance Schedule child table status for legacy data (#27554) * fix: Maintenance Schedule child table status for legacy data * fix: Include legacy draft schedules in patch * fix: Pre-commit formatting --- .../maintenance_schedule_detail.json | 8 +++++--- .../v13_0/set_status_in_maintenance_schedule_table.py | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py diff --git a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json index 8ccef6a817..afe273f310 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json +++ b/erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json @@ -89,13 +89,14 @@ "width": "160px" }, { + "allow_on_submit": 1, "columns": 2, + "default": "Pending", "fieldname": "completion_status", "fieldtype": "Select", "in_list_view": 1, "label": "Completion Status", - "options": "Pending\nPartially Completed\nFully Completed", - "read_only": 1 + "options": "Pending\nPartially Completed\nFully Completed" }, { "fieldname": "column_break_3", @@ -125,10 +126,11 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-27 16:07:25.905015", + "modified": "2021-09-16 21:25:22.506485", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Schedule Detail", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py new file mode 100644 index 0000000000..bf6285dbf9 --- /dev/null +++ b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py @@ -0,0 +1,9 @@ +import frappe + + +def execute(): + frappe.db.sql(""" + UPDATE `tabMaintenance Schedule Detail` + SET completion_status = 'Pending' + WHERE docstatus < 2 + """) From 754626a1021bf89a161f152262489195c7cad45d Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 30 Sep 2021 14:12:19 +0530 Subject: [PATCH 174/416] fix: Delete accidental __init__.py file --- erpnext/www/shop-by-category/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 erpnext/www/shop-by-category/__init__.py diff --git a/erpnext/www/shop-by-category/__init__.py b/erpnext/www/shop-by-category/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 73ac3e0c40821aad27faa10688b961a4c9e929a4 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 30 Sep 2021 14:18:35 +0530 Subject: [PATCH 175/416] fix: Add patch to patches.txt for https://github.com/frappe/erpnext/pull/27554 --- erpnext/patches.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a481996581..3adc3e9ff6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -312,3 +312,4 @@ erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry +erpnext.patches.v13_0.set_status_in_maintenance_schedule_table From cbffbc38947142c4164453c6fc50d950eac51f28 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 30 Sep 2021 14:53:47 +0530 Subject: [PATCH 176/416] fix: reload doc in patch --- .../patches/v13_0/set_status_in_maintenance_schedule_table.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py index bf6285dbf9..9887ad9df0 100644 --- a/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py +++ b/erpnext/patches/v13_0/set_status_in_maintenance_schedule_table.py @@ -2,6 +2,7 @@ import frappe def execute(): + frappe.reload_doc("maintenance", "doctype", "Maintenance Schedule Detail") frappe.db.sql(""" UPDATE `tabMaintenance Schedule Detail` SET completion_status = 'Pending' From 9e08229b7bdcb5bf63146c7effe1e757e862416e Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 30 Sep 2021 18:32:10 +0530 Subject: [PATCH 177/416] fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special characters (#27717) * fix(Org Chart): use attribute selectors instead of ID selector for node IDs with special chars * fix: UI tests --- .../test_organizational_chart_desktop.js | 2 +- .../test_organizational_chart_mobile.js | 2 +- .../hierarchy_chart_desktop.js | 24 +++++++++---------- .../hierarchy_chart/hierarchy_chart_mobile.js | 22 ++++++++--------- erpnext/tests/ui_test_helpers.py | 2 ++ 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js index 39b00d3263..79e08b3bba 100644 --- a/cypress/integration/test_organizational_chart_desktop.js +++ b/cypress/integration/test_organizational_chart_desktop.js @@ -6,7 +6,7 @@ context('Organizational Chart', () => { it('navigates to org chart', () => { cy.visit('/app'); - cy.awesomebar('Organizational Chart'); + cy.visit('/app/organizational-chart'); cy.url().should('include', '/organizational-chart'); cy.window().its('frappe.csrf_token').then(csrf_token => { diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js index 6e75151396..161fae098a 100644 --- a/cypress/integration/test_organizational_chart_mobile.js +++ b/cypress/integration/test_organizational_chart_mobile.js @@ -7,7 +7,7 @@ context('Organizational Chart Mobile', () => { it('navigates to org chart', () => { cy.viewport(375, 667); cy.visit('/app'); - cy.awesomebar('Organizational Chart'); + cy.visit('/app/organizational-chart'); cy.url().should('include', '/organizational-chart'); cy.window().its('frappe.csrf_token').then(csrf_token => { diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js index 6286732753..7b358195c3 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js @@ -63,7 +63,7 @@ erpnext.HierarchyChart = class { }); node.parent.append(node_card); - node.$link = $(`#${node.id}`); + node.$link = $(`[id="${node.id}"]`); } show() { @@ -223,7 +223,7 @@ erpnext.HierarchyChart = class { let node = undefined; $.each(r.message, (_i, data) => { - if ($(`#${data.id}`).length) + if ($(`[id="${data.id}"]`).length) return; node = new me.Node({ @@ -263,7 +263,7 @@ erpnext.HierarchyChart = class { this.refresh_connectors(node.parent_id); // rebuild incoming connections - let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); this.refresh_connectors(grandparent); } @@ -282,7 +282,7 @@ erpnext.HierarchyChart = class { show_active_path(node) { // mark node parent on active path - $(`#${node.parent_id}`).addClass('active-path'); + $(`[id="${node.parent_id}"]`).addClass('active-path'); } load_children(node, deep=false) { @@ -317,7 +317,7 @@ erpnext.HierarchyChart = class { render_child_nodes(node, child_nodes) { const last_level = this.$hierarchy.find('.level:last').index(); - const current_level = $(`#${node.id}`).parent().parent().parent().index(); + const current_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); if (last_level === current_level) { this.$hierarchy.append(` @@ -382,7 +382,7 @@ erpnext.HierarchyChart = class { node.$children = $('
    '); const last_level = this.$hierarchy.find('.level:last').index(); - const node_level = $(`#${node.id}`).parent().parent().parent().index(); + const node_level = $(`[id="${node.id}"]`).parent().parent().parent().index(); if (last_level === node_level) { this.$hierarchy.append(` @@ -489,7 +489,7 @@ erpnext.HierarchyChart = class { set_path_attributes(path, parent_id, child_id) { path.setAttribute("data-parent", parent_id); path.setAttribute("data-child", child_id); - const parent = $(`#${parent_id}`); + const parent = $(`[id="${parent_id}"]`); if (parent.hasClass('active')) { path.setAttribute("class", "active-connector"); @@ -513,7 +513,7 @@ erpnext.HierarchyChart = class { } collapse_previous_level_nodes(node) { - let node_parent = $(`#${node.parent_id}`); + let node_parent = $(`[id="${node.parent_id}"]`); let previous_level_nodes = node_parent.parent().parent().children('li'); let node_card = undefined; @@ -545,7 +545,7 @@ erpnext.HierarchyChart = class { setup_node_click_action(node) { let me = this; - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); node_element.click(function() { const is_sibling = me.selected_node.parent_id === node.parent_id; @@ -563,7 +563,7 @@ erpnext.HierarchyChart = class { } setup_edit_node_action(node) { - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); let me = this; node_element.find('.btn-edit-node').click(function() { @@ -572,7 +572,7 @@ erpnext.HierarchyChart = class { } remove_levels_after_node(node) { - let level = $(`#${node.id}`).parent().parent().parent().index(); + let level = $(`[id="${node.id}"]`).parent().parent().parent().index(); level = $('.hierarchy > li:eq('+ level + ')'); level.nextAll('li').remove(); @@ -595,7 +595,7 @@ erpnext.HierarchyChart = class { const parent = $(path).data('parent'); const child = $(path).data('child'); - if ($(`#${parent}`).length && $(`#${child}`).length) + if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) return; $(path).remove(); diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js index b1a8879557..0a8ba78f64 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js @@ -54,7 +54,7 @@ erpnext.HierarchyChartMobile = class { }); node.parent.append(node_card); - node.$link = $(`#${node.id}`); + node.$link = $(`[id="${node.id}"]`); node.$link.addClass('mobile-node'); } @@ -184,7 +184,7 @@ erpnext.HierarchyChartMobile = class { this.refresh_connectors(node.parent_id, node.id); // rebuild incoming connections of parent - let grandparent = $(`#${node.parent_id}`).attr('data-parent'); + let grandparent = $(`[id="${node.parent_id}"]`).attr('data-parent'); this.refresh_connectors(grandparent, node.parent_id); } @@ -221,7 +221,7 @@ erpnext.HierarchyChartMobile = class { show_active_path(node) { // mark node parent on active path - $(`#${node.parent_id}`).addClass('active-path'); + $(`[id="${node.parent_id}"]`).addClass('active-path'); } load_children(node) { @@ -256,7 +256,7 @@ erpnext.HierarchyChartMobile = class { if (child_nodes) { $.each(child_nodes, (_i, data) => { this.add_node(node, data); - $(`#${data.id}`).addClass('active-child'); + $(`[id="${data.id}"]`).addClass('active-child'); setTimeout(() => { this.add_connector(node.id, data.id); @@ -293,9 +293,9 @@ erpnext.HierarchyChartMobile = class { let connector = undefined; - if ($(`#${parent_id}`).hasClass('active')) { + if ($(`[id="${parent_id}"]`).hasClass('active')) { connector = this.get_connector_for_active_node(parent_node, child_node); - } else if ($(`#${parent_id}`).hasClass('active-path')) { + } else if ($(`[id="${parent_id}"]`).hasClass('active-path')) { connector = this.get_connector_for_collapsed_node(parent_node, child_node); } @@ -351,7 +351,7 @@ erpnext.HierarchyChartMobile = class { set_path_attributes(path, parent_id, child_id) { path.setAttribute("data-parent", parent_id); path.setAttribute("data-child", child_id); - const parent = $(`#${parent_id}`); + const parent = $(`[id="${parent_id}"]`); if (parent.hasClass('active')) { path.setAttribute("class", "active-connector"); @@ -374,7 +374,7 @@ erpnext.HierarchyChartMobile = class { setup_node_click_action(node) { let me = this; - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); node_element.click(function() { let el = undefined; @@ -398,7 +398,7 @@ erpnext.HierarchyChartMobile = class { } setup_edit_node_action(node) { - let node_element = $(`#${node.id}`); + let node_element = $(`[id="${node.id}"]`); let me = this; node_element.find('.btn-edit-node').click(function() { @@ -512,7 +512,7 @@ erpnext.HierarchyChartMobile = class { } remove_levels_after_node(node) { - let level = $(`#${node.id}`).parent().parent().index(); + let level = $(`[id="${node.id}"]`).parent().parent().index(); level = $('.hierarchy-mobile > li:eq('+ level + ')'); level.nextAll('li').remove(); @@ -533,7 +533,7 @@ erpnext.HierarchyChartMobile = class { const parent = $(path).data('parent'); const child = $(path).data('child'); - if ($(`#${parent}`).length && $(`#${child}`).length) + if ($(`[id="${parent}"]`).length && $(`[id="${child}"]`).length) return; $(path).remove(); diff --git a/erpnext/tests/ui_test_helpers.py b/erpnext/tests/ui_test_helpers.py index 76c7608c91..9c8c371e05 100644 --- a/erpnext/tests/ui_test_helpers.py +++ b/erpnext/tests/ui_test_helpers.py @@ -7,6 +7,8 @@ def create_employee_records(): create_company() create_missing_designation() + frappe.db.sql("DELETE FROM tabEmployee WHERE company='Test Org Chart'") + emp1 = create_employee('Test Employee 1', 'CEO') emp2 = create_employee('Test Employee 2', 'CTO') emp3 = create_employee('Test Employee 3', 'Head of Marketing and Sales', emp1) From a04f9c904e3d2b4d44c3b3ad750e9fddca552296 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 1 Oct 2021 13:05:36 +0530 Subject: [PATCH 178/416] fix: option to limit reposting in certain timeslot (#27725) --- .../repost_item_valuation.py | 28 +++++++- .../test_repost_item_valuation.py | 71 ++++++++++++++++-- .../stock_reposting_settings/__init__.py | 0 .../stock_reposting_settings.js | 8 +++ .../stock_reposting_settings.json | 72 +++++++++++++++++++ .../stock_reposting_settings.py | 28 ++++++++ .../test_stock_reposting_settings.py | 9 +++ 7 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 erpnext/stock/doctype/stock_reposting_settings/__init__.py create mode 100644 erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js create mode 100644 erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json create mode 100644 erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py create mode 100644 erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 62b3a6adf7..d86e52fa64 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, get_link_to_form, now, today +from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime, today from frappe.utils.user import get_users_with_role from rq.timeouts import JobTimeoutException @@ -126,6 +126,9 @@ def notify_error_to_stock_managers(doc, traceback): frappe.sendmail(recipients=recipients, subject=subject, message=message) def repost_entries(): + if not in_configured_timeslot(): + return + riv_entries = get_repost_item_valuation_entries() for row in riv_entries: @@ -144,3 +147,26 @@ def get_repost_item_valuation_entries(): WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1 ORDER BY timestamp(posting_date, posting_time) asc, creation asc """, now(), as_dict=1) + + +def in_configured_timeslot(repost_settings=None, current_time=None): + """Check if current time is in configured timeslot for reposting.""" + + if repost_settings is None: + repost_settings = frappe.get_cached_doc("Stock Reposting Settings") + + if not repost_settings.limit_reposting_timeslot: + return True + + if get_weekday() == repost_settings.limits_dont_apply_on: + return True + + start_time = repost_settings.start_time + end_time = repost_settings.end_time + + now_time = current_time or nowtime() + + if start_time < end_time: + return end_time >= now_time >= start_time + else: + return now_time >= start_time or now_time <= end_time diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index c70a9ec7a8..c086f938b5 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -1,11 +1,72 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt -from __future__ import unicode_literals -# import frappe import unittest +import frappe + +from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import ( + in_configured_timeslot, +) + class TestRepostItemValuation(unittest.TestCase): - pass + def test_repost_time_slot(self): + repost_settings = frappe.get_doc("Stock Reposting Settings") + + positive_cases = [ + {"limit_reposting_timeslot": 0}, + { + "limit_reposting_timeslot": 1, + "start_time": "18:00:00", + "end_time": "09:00:00", + "current_time": "20:00:00", + }, + { + "limit_reposting_timeslot": 1, + "start_time": "09:00:00", + "end_time": "18:00:00", + "current_time": "12:00:00", + }, + { + "limit_reposting_timeslot": 1, + "start_time": "23:00:00", + "end_time": "09:00:00", + "current_time": "2:00:00", + }, + ] + + for case in positive_cases: + repost_settings.update(case) + self.assertTrue( + in_configured_timeslot(repost_settings, case.get("current_time")), + msg=f"Exepcted true from : {case}", + ) + + negative_cases = [ + { + "limit_reposting_timeslot": 1, + "start_time": "18:00:00", + "end_time": "09:00:00", + "current_time": "09:01:00", + }, + { + "limit_reposting_timeslot": 1, + "start_time": "09:00:00", + "end_time": "18:00:00", + "current_time": "19:00:00", + }, + { + "limit_reposting_timeslot": 1, + "start_time": "23:00:00", + "end_time": "09:00:00", + "current_time": "22:00:00", + }, + ] + + for case in negative_cases: + repost_settings.update(case) + self.assertFalse( + in_configured_timeslot(repost_settings, case.get("current_time")), + msg=f"Exepcted false from : {case}", + ) diff --git a/erpnext/stock/doctype/stock_reposting_settings/__init__.py b/erpnext/stock/doctype/stock_reposting_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js new file mode 100644 index 0000000000..42d0723d42 --- /dev/null +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Stock Reposting Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json new file mode 100644 index 0000000000..2474059003 --- /dev/null +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json @@ -0,0 +1,72 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2021-10-01 10:56:30.814787", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "scheduling_section", + "limit_reposting_timeslot", + "start_time", + "end_time", + "limits_dont_apply_on" + ], + "fields": [ + { + "fieldname": "scheduling_section", + "fieldtype": "Section Break", + "label": "Scheduling" + }, + { + "depends_on": "limit_reposting_timeslot", + "fieldname": "start_time", + "fieldtype": "Time", + "label": "Start Time", + "mandatory_depends_on": "limit_reposting_timeslot" + }, + { + "depends_on": "limit_reposting_timeslot", + "fieldname": "end_time", + "fieldtype": "Time", + "label": "End Time", + "mandatory_depends_on": "limit_reposting_timeslot" + }, + { + "depends_on": "limit_reposting_timeslot", + "fieldname": "limits_dont_apply_on", + "fieldtype": "Select", + "label": "Limits don't apply on", + "options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday" + }, + { + "default": "0", + "fieldname": "limit_reposting_timeslot", + "fieldtype": "Check", + "label": "Limit timeslot for Stock Reposting" + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-10-01 11:27:28.981594", + "modified_by": "Administrator", + "module": "Stock", + "name": "Stock Reposting Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py new file mode 100644 index 0000000000..bab521d69f --- /dev/null +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py @@ -0,0 +1,28 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from frappe.model.document import Document +from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_hours + + +class StockRepostingSettings(Document): + + + def validate(self): + self.set_minimum_reposting_time_slot() + + def set_minimum_reposting_time_slot(self): + """Ensure that timeslot for reposting is at least 12 hours.""" + if not self.limit_reposting_timeslot: + return + + start_time = get_datetime(self.start_time) + end_time = get_datetime(self.end_time) + + if start_time > end_time: + end_time = add_to_date(end_time, days=1, as_datetime=True) + + diff = time_diff_in_hours(end_time, start_time) + + if diff < 10: + self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True)) diff --git a/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py new file mode 100644 index 0000000000..fad74d355c --- /dev/null +++ b/erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +# import frappe +import unittest + + +class TestStockRepostingSettings(unittest.TestCase): + pass From b9942ad639903d644d5563ed32850d48b126b776 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 13:15:40 +0530 Subject: [PATCH 179/416] feat: Tracking Multi-round interview (#25482) (#27724) * feat: Tracking Multi-round interview * fix: releted to scheduler event and formating * fix: job applicant UI/UX and conflicts * test: Interview Round * fix(test): Employee referral, Employee Onboarding, Job Offer * fix: sider * feat: set default value in Hr settings * feat: added validation for designation * test: Interview * test: Added validatiolns for skill * test: Interview feedback * fix: sider * fix: remove unnecessary validations and form label cleanups * chore: clean-up Interview Round and Interview Type doctype * fix: remove redundant Rating Value, only keep Rating * fix: update interview details on feedback submission - make interview feedback submission dialog minimizable * fix: show submit feedback button only if feedback doesn't exist * refactor: Interview and Feedback statuses and workflow * fix(HR Settings): clean up interview settings * refactor: Interview * refactor: Interview Feedback, remove unnecessary validations * chore: update notification messages * chore: remove unnecessary formatting changes in attendance list and leave application * refactor: Job Applicant to Interview mapping * chore: sorted imports * chore: sorted imports * fix: sider issues * fix: linter issues * fix: sider issues * fix: tests * fix: sorted imports * fix: tests, sider * fix: therapy plan test * fix: sider issues * feat: Include From Time and To Time fields in Interview for cleaner data * feat: Interview Calendar * fix: allow renaming masters * fix: add more fields to list view and standard filter * fix: validate overlapping interviews * fix: update tests * fix: linter issues * refactor: replace reminder messages with Email Templates * fix: sider issues Co-authored-by: Rucha Mahabal (cherry picked from commit 57e66f958cd57d66a6fd3b19f6cd3593eab63666) Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> --- .../test_patient_medical_record.py | 1 + .../doctype/therapy_plan/test_therapy_plan.py | 5 +- .../doctype/therapy_plan/therapy_plan.py | 4 +- erpnext/hooks.py | 2 + .../hr/doctype/attendance/attendance_list.js | 138 +++++---- .../test_employee_onboarding.py | 1 + .../employee_referral/employee_referral.py | 2 + .../test_employee_referral.py | 9 + .../hr/doctype/expected_skill_set/__init__.py | 0 .../expected_skill_set.json | 40 +++ .../expected_skill_set/expected_skill_set.py | 12 + .../hr/doctype/hr_settings/hr_settings.json | 49 ++- erpnext/hr/doctype/interview/__init__.py | 0 erpnext/hr/doctype/interview/interview.js | 237 ++++++++++++++ erpnext/hr/doctype/interview/interview.json | 254 +++++++++++++++ erpnext/hr/doctype/interview/interview.py | 293 ++++++++++++++++++ .../doctype/interview/interview_calendar.js | 14 + .../interview_feedback_reminder_template.html | 5 + .../hr/doctype/interview/interview_list.js | 12 + ...erview_reminder_notification_template.html | 5 + .../hr/doctype/interview/test_interview.py | 174 +++++++++++ .../hr/doctype/interview_detail/__init__.py | 0 .../interview_detail/interview_detail.js | 8 + .../interview_detail/interview_detail.json | 74 +++++ .../interview_detail/interview_detail.py | 12 + .../interview_detail/test_interview_detail.py | 11 + .../hr/doctype/interview_feedback/__init__.py | 0 .../interview_feedback/interview_feedback.js | 54 ++++ .../interview_feedback.json | 171 ++++++++++ .../interview_feedback/interview_feedback.py | 88 ++++++ .../test_interview_feedback.py | 103 ++++++ .../hr/doctype/interview_round/__init__.py | 0 .../interview_round/interview_round.js | 24 ++ .../interview_round/interview_round.json | 118 +++++++ .../interview_round/interview_round.py | 35 +++ .../interview_round/test_interview_round.py | 13 + erpnext/hr/doctype/interview_type/__init__.py | 0 .../doctype/interview_type/interview_type.js | 8 + .../interview_type/interview_type.json | 73 +++++ .../doctype/interview_type/interview_type.py | 12 + .../interview_type/test_interview_type.py | 11 + erpnext/hr/doctype/interviewer/__init__.py | 0 .../hr/doctype/interviewer/interviewer.json | 31 ++ erpnext/hr/doctype/interviewer/interviewer.py | 12 + .../hr/doctype/job_applicant/job_applicant.js | 73 ++++- .../doctype/job_applicant/job_applicant.json | 32 +- .../hr/doctype/job_applicant/job_applicant.py | 50 ++- .../job_applicant_dashboard.html | 44 +++ .../job_applicant/job_applicant_dashboard.py | 25 +- .../job_applicant/test_job_applicant.py | 6 +- .../hr/doctype/job_offer/test_job_offer.py | 7 +- .../leave_application/leave_application.js | 36 +-- .../hr/doctype/skill_assessment/__init__.py | 0 .../skill_assessment/skill_assessment.json | 41 +++ .../skill_assessment/skill_assessment.py | 12 + erpnext/patches.txt | 1 + ...efault_interview_notification_templates.py | 37 +++ .../doctype/salary_slip/test_salary_slip.py | 2 - .../setup_wizard/operations/defaults_setup.py | 7 + .../operations/install_fixtures.py | 24 +- 60 files changed, 2385 insertions(+), 127 deletions(-) create mode 100644 erpnext/hr/doctype/expected_skill_set/__init__.py create mode 100644 erpnext/hr/doctype/expected_skill_set/expected_skill_set.json create mode 100644 erpnext/hr/doctype/expected_skill_set/expected_skill_set.py create mode 100644 erpnext/hr/doctype/interview/__init__.py create mode 100644 erpnext/hr/doctype/interview/interview.js create mode 100644 erpnext/hr/doctype/interview/interview.json create mode 100644 erpnext/hr/doctype/interview/interview.py create mode 100644 erpnext/hr/doctype/interview/interview_calendar.js create mode 100644 erpnext/hr/doctype/interview/interview_feedback_reminder_template.html create mode 100644 erpnext/hr/doctype/interview/interview_list.js create mode 100644 erpnext/hr/doctype/interview/interview_reminder_notification_template.html create mode 100644 erpnext/hr/doctype/interview/test_interview.py create mode 100644 erpnext/hr/doctype/interview_detail/__init__.py create mode 100644 erpnext/hr/doctype/interview_detail/interview_detail.js create mode 100644 erpnext/hr/doctype/interview_detail/interview_detail.json create mode 100644 erpnext/hr/doctype/interview_detail/interview_detail.py create mode 100644 erpnext/hr/doctype/interview_detail/test_interview_detail.py create mode 100644 erpnext/hr/doctype/interview_feedback/__init__.py create mode 100644 erpnext/hr/doctype/interview_feedback/interview_feedback.js create mode 100644 erpnext/hr/doctype/interview_feedback/interview_feedback.json create mode 100644 erpnext/hr/doctype/interview_feedback/interview_feedback.py create mode 100644 erpnext/hr/doctype/interview_feedback/test_interview_feedback.py create mode 100644 erpnext/hr/doctype/interview_round/__init__.py create mode 100644 erpnext/hr/doctype/interview_round/interview_round.js create mode 100644 erpnext/hr/doctype/interview_round/interview_round.json create mode 100644 erpnext/hr/doctype/interview_round/interview_round.py create mode 100644 erpnext/hr/doctype/interview_round/test_interview_round.py create mode 100644 erpnext/hr/doctype/interview_type/__init__.py create mode 100644 erpnext/hr/doctype/interview_type/interview_type.js create mode 100644 erpnext/hr/doctype/interview_type/interview_type.json create mode 100644 erpnext/hr/doctype/interview_type/interview_type.py create mode 100644 erpnext/hr/doctype/interview_type/test_interview_type.py create mode 100644 erpnext/hr/doctype/interviewer/__init__.py create mode 100644 erpnext/hr/doctype/interviewer/interviewer.json create mode 100644 erpnext/hr/doctype/interviewer/interviewer.py create mode 100644 erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html create mode 100644 erpnext/hr/doctype/skill_assessment/__init__.py create mode 100644 erpnext/hr/doctype/skill_assessment/skill_assessment.json create mode 100644 erpnext/hr/doctype/skill_assessment/skill_assessment.py create mode 100644 erpnext/patches/v13_0/add_default_interview_notification_templates.py diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py index 099146c7ee..9dd97a68ce 100644 --- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py +++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py @@ -21,6 +21,7 @@ class TestPatientMedicalRecord(unittest.TestCase): def setUp(self): frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0) frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1) + frappe.db.sql('delete from `tabPatient Appointment`') make_pos_profile() def test_medical_record(self): diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py index 4f96f6a706..021ba9bb1c 100644 --- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import flt, getdate, nowdate +from frappe.utils import add_days, flt, getdate, nowdate from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import ( create_appointment, @@ -33,10 +33,12 @@ class TestTherapyPlan(unittest.TestCase): self.assertEqual(plan.status, 'Not Started') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') + session.start_date = getdate() frappe.get_doc(session).submit() self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company') + session.start_date = add_days(getdate(), 1) frappe.get_doc(session).submit() self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') @@ -44,6 +46,7 @@ class TestTherapyPlan(unittest.TestCase): appointment = create_appointment(patient, practitioner, nowdate()) session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name) + session.start_date = add_days(getdate(), 2) session = frappe.get_doc(session) session.submit() self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Closed') diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index 6d63f39189..b31a9527a8 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.utils import flt, today +from frappe.utils import flt class TherapyPlan(Document): @@ -63,8 +63,6 @@ def make_therapy_session(therapy_plan, patient, therapy_type, company, appointme therapy_session.exercises = therapy_type.exercises therapy_session.appointment = appointment - if frappe.flags.in_test: - therapy_session.start_date = today() return therapy_session.as_dict() diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a8f16171be..b89b10bd7b 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -344,6 +344,7 @@ scheduler_events = { "all": [ "erpnext.projects.doctype.project.project.project_status_update_reminder", "erpnext.healthcare.doctype.patient_appointment.patient_appointment.send_appointment_reminder", + "erpnext.hr.doctype.interview.interview.send_interview_reminder", "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts" ], "hourly": [ @@ -388,6 +389,7 @@ scheduler_events = { "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.non_profit.doctype.membership.membership.set_expired_status" + "erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder" ], "daily_long": [ "erpnext.setup.doctype.email_digest.email_digest.send", diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 9a3bac0eb2..6b3c29a76b 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -9,83 +9,86 @@ frappe.listview_settings['Attendance'] = { return [__(doc.status), "orange", "status,=," + doc.status]; } }, + onload: function(list_view) { let me = this; - const months = moment.months() - list_view.page.add_inner_button( __("Mark Attendance"), function() { + const months = moment.months(); + list_view.page.add_inner_button(__("Mark Attendance"), function() { let dialog = new frappe.ui.Dialog({ title: __("Mark Attendance"), - fields: [ - { - fieldname: 'employee', - label: __('For Employee'), - fieldtype: 'Link', - options: 'Employee', - get_query: () => { - return {query: "erpnext.controllers.queries.employee_query"} - }, - reqd: 1, - onchange: function() { - dialog.set_df_property("unmarked_days", "hidden", 1); - dialog.set_df_property("status", "hidden", 1); - dialog.set_df_property("month", "value", ''); + fields: [{ + fieldname: 'employee', + label: __('For Employee'), + fieldtype: 'Link', + options: 'Employee', + get_query: () => { + return {query: "erpnext.controllers.queries.employee_query"}; + }, + reqd: 1, + onchange: function() { + dialog.set_df_property("unmarked_days", "hidden", 1); + dialog.set_df_property("status", "hidden", 1); + dialog.set_df_property("month", "value", ''); + dialog.set_df_property("unmarked_days", "options", []); + dialog.no_unmarked_days_left = false; + } + }, + { + label: __("For Month"), + fieldtype: "Select", + fieldname: "month", + options: months, + reqd: 1, + onchange: function() { + if (dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { + dialog.set_df_property("status", "hidden", 0); dialog.set_df_property("unmarked_days", "options", []); dialog.no_unmarked_days_left = false; + me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options => { + if (options.length > 0) { + dialog.set_df_property("unmarked_days", "hidden", 0); + dialog.set_df_property("unmarked_days", "options", options); + } else { + dialog.no_unmarked_days_left = true; + } + }); } - }, - { - label: __("For Month"), - fieldtype: "Select", - fieldname: "month", - options: months, - reqd: 1, - onchange: function() { - if(dialog.fields_dict.employee.value && dialog.fields_dict.month.value) { - dialog.set_df_property("status", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", []); - dialog.no_unmarked_days_left = false; - me.get_multi_select_options(dialog.fields_dict.employee.value, dialog.fields_dict.month.value).then(options =>{ - if (options.length > 0) { - dialog.set_df_property("unmarked_days", "hidden", 0); - dialog.set_df_property("unmarked_days", "options", options); - } else { - dialog.no_unmarked_days_left = true; - } - }); - } - } - }, - { - label: __("Status"), - fieldtype: "Select", - fieldname: "status", - options: ["Present", "Absent", "Half Day", "Work From Home"], - hidden:1, - reqd: 1, + } + }, + { + label: __("Status"), + fieldtype: "Select", + fieldname: "status", + options: ["Present", "Absent", "Half Day", "Work From Home"], + hidden: 1, + reqd: 1, - }, - { - label: __("Unmarked Attendance for days"), - fieldname: "unmarked_days", - fieldtype: "MultiCheck", - options: [], - columns: 2, - hidden: 1 - }, - ], - primary_action(data) { + }, + { + label: __("Unmarked Attendance for days"), + fieldname: "unmarked_days", + fieldtype: "MultiCheck", + options: [], + columns: 2, + hidden: 1 + }], + primary_action(data) { if (cur_dialog.no_unmarked_days_left) { - frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}",[dialog.fields_dict.month.value, dialog.fields_dict.employee.value])); + frappe.msgprint(__("Attendance for the month of {0} , has already been marked for the Employee {1}", + [dialog.fields_dict.month.value, dialog.fields_dict.employee.value])); } else { - frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status,data.month]), () => { + frappe.confirm(__('Mark attendance as {0} for {1} on selected dates?', [data.status, data.month]), () => { frappe.call({ method: "erpnext.hr.doctype.attendance.attendance.mark_bulk_attendance", args: { data: data }, - callback: function(r) { + callback: function (r) { if (r.message === 1) { - frappe.show_alert({message: __("Attendance Marked"), indicator: 'blue'}); + frappe.show_alert({ + message: __("Attendance Marked"), + indicator: 'blue' + }); cur_dialog.hide(); } } @@ -101,21 +104,26 @@ frappe.listview_settings['Attendance'] = { dialog.show(); }); }, - get_multi_select_options: function(employee, month){ + + get_multi_select_options: function(employee, month) { return new Promise(resolve => { frappe.call({ method: 'erpnext.hr.doctype.attendance.attendance.get_unmarked_days', async: false, - args:{ + args: { employee: employee, month: month, } }).then(r => { var options = []; - for(var d in r.message){ + for (var d in r.message) { var momentObj = moment(r.message[d], 'YYYY-MM-DD'); var date = momentObj.format('DD-MM-YYYY'); - options.push({ "label":date, "value": r.message[d] , "checked": 1}); + options.push({ + "label": date, + "value": r.message[d], + "checked": 1 + }); } resolve(options); }); diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py index eae600db7b..1e3b9cb278 100644 --- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py @@ -71,6 +71,7 @@ def get_job_applicant(): applicant = frappe.new_doc('Job Applicant') applicant.applicant_name = 'Test Researcher' applicant.email_id = 'test@researcher.com' + applicant.designation = 'Researcher' applicant.status = 'Open' applicant.cover_letter = 'I am a great Researcher.' applicant.insert() diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py index 5cb5bb5fd3..db356bf91f 100644 --- a/erpnext/hr/doctype/employee_referral/employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/employee_referral.py @@ -38,8 +38,10 @@ def create_job_applicant(source_name, target_doc=None): status = "Open" job_applicant = frappe.new_doc("Job Applicant") + job_applicant.source = "Employee Referral" job_applicant.employee_referral = emp_ref.name job_applicant.status = status + job_applicant.designation = emp_ref.for_designation job_applicant.applicant_name = emp_ref.full_name job_applicant.email_id = emp_ref.email job_applicant.phone_number = emp_ref.contact_no diff --git a/erpnext/hr/doctype/employee_referral/test_employee_referral.py b/erpnext/hr/doctype/employee_referral/test_employee_referral.py index d0ee2fcdea..1340f62bbf 100644 --- a/erpnext/hr/doctype/employee_referral/test_employee_referral.py +++ b/erpnext/hr/doctype/employee_referral/test_employee_referral.py @@ -17,6 +17,11 @@ from erpnext.hr.doctype.employee_referral.employee_referral import ( class TestEmployeeReferral(unittest.TestCase): + + def setUp(self): + frappe.db.sql("DELETE FROM `tabJob Applicant`") + frappe.db.sql("DELETE FROM `tabEmployee Referral`") + def test_workflow_and_status_sync(self): emp_ref = create_employee_referral() @@ -50,6 +55,10 @@ class TestEmployeeReferral(unittest.TestCase): add_sal = create_additional_salary(emp_ref) self.assertTrue(add_sal.ref_docname, emp_ref.name) + def tearDown(self): + frappe.db.sql("DELETE FROM `tabJob Applicant`") + frappe.db.sql("DELETE FROM `tabEmployee Referral`") + def create_employee_referral(): emp_ref = frappe.new_doc("Employee Referral") diff --git a/erpnext/hr/doctype/expected_skill_set/__init__.py b/erpnext/hr/doctype/expected_skill_set/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/expected_skill_set/expected_skill_set.json b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.json new file mode 100644 index 0000000000..899f5bd0ff --- /dev/null +++ b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "creation": "2021-04-12 13:05:06.741330", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "skill", + "description" + ], + "fields": [ + { + "fieldname": "skill", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Skill", + "options": "Skill", + "reqd": 1 + }, + { + "fetch_from": "skill.description", + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-12 14:26:33.062549", + "modified_by": "Administrator", + "module": "HR", + "name": "Expected Skill Set", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/expected_skill_set/expected_skill_set.py b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.py new file mode 100644 index 0000000000..27120c1fb3 --- /dev/null +++ b/erpnext/hr/doctype/expected_skill_set/expected_skill_set.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class ExpectedSkillSet(Document): + pass diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 8aa3c0ca9f..4bc066f334 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -30,7 +30,13 @@ "auto_leave_encashment", "restrict_backdated_leave_application", "hiring_settings", - "check_vacancies" + "check_vacancies", + "send_interview_reminder", + "interview_reminder_template", + "remind_before", + "column_break_29", + "send_interview_feedback_reminder", + "feedback_reminder_notification_template" ], "fields": [ { @@ -142,6 +148,13 @@ "fieldtype": "Int", "label": "Standard Working Hours" }, + { + "default": "00:15:00", + "depends_on": "send_interview_reminder", + "fieldname": "remind_before", + "fieldtype": "Time", + "label": "Remind Before" + }, { "collapsible": 1, "fieldname": "reminders_section", @@ -181,13 +194,45 @@ { "fieldname": "column_break_11", "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "send_interview_reminder", + "fieldtype": "Check", + "label": "Send Interview Reminder" + }, + { + "default": "0", + "fieldname": "send_interview_feedback_reminder", + "fieldtype": "Check", + "label": "Send Interview Feedback Reminder" + }, + { + "fieldname": "column_break_29", + "fieldtype": "Column Break" + }, + { + "depends_on": "send_interview_feedback_reminder", + "fieldname": "feedback_reminder_notification_template", + "fieldtype": "Link", + "label": "Feedback Reminder Notification Template", + "mandatory_depends_on": "send_interview_feedback_reminder", + "options": "Email Template" + }, + { + "depends_on": "send_interview_reminder", + "fieldname": "interview_reminder_template", + "fieldtype": "Link", + "label": "Interview Reminder Notification Template", + "mandatory_depends_on": "send_interview_reminder", + "options": "Email Template" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-08-24 14:54:12.834162", + "modified": "2021-09-30 22:42:14.683983", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/interview/__init__.py b/erpnext/hr/doctype/interview/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/interview/interview.js b/erpnext/hr/doctype/interview/interview.js new file mode 100644 index 0000000000..6341e3a62b --- /dev/null +++ b/erpnext/hr/doctype/interview/interview.js @@ -0,0 +1,237 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview', { + onload: function (frm) { + frm.events.set_job_applicant_query(frm); + + frm.set_query('interviewer', 'interview_details', function () { + return { + query: 'erpnext.hr.doctype.interview.interview.get_interviewer_list' + }; + }); + }, + + refresh: function (frm) { + if (frm.doc.docstatus != 2 && !frm.doc.__islocal) { + if (frm.doc.status === 'Pending') { + frm.add_custom_button(__('Reschedule Interview'), function() { + frm.events.show_reschedule_dialog(frm); + frm.refresh(); + }); + } + + let allowed_interviewers = []; + frm.doc.interview_details.forEach(values => { + allowed_interviewers.push(values.interviewer); + }); + + if ((allowed_interviewers.includes(frappe.session.user))) { + frappe.db.get_value('Interview Feedback', {'interviewer': frappe.session.user, 'interview': frm.doc.name, 'docstatus': 1}, 'name', (r) => { + if (Object.keys(r).length === 0) { + frm.add_custom_button(__('Submit Feedback'), function () { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.get_expected_skill_set', + args: { + interview_round: frm.doc.interview_round + }, + callback: function (r) { + frm.events.show_feedback_dialog(frm, r.message); + frm.refresh(); + } + }); + }).addClass('btn-primary'); + } + }); + } + } + }, + + show_reschedule_dialog: function (frm) { + let d = new frappe.ui.Dialog({ + title: 'Reschedule Interview', + fields: [ + { + label: 'Schedule On', + fieldname: 'scheduled_on', + fieldtype: 'Date', + reqd: 1 + }, + { + label: 'From Time', + fieldname: 'from_time', + fieldtype: 'Time', + reqd: 1 + }, + { + label: 'To Time', + fieldname: 'to_time', + fieldtype: 'Time', + reqd: 1 + } + ], + primary_action_label: 'Reschedule', + primary_action(values) { + frm.call({ + method: 'reschedule_interview', + doc: frm.doc, + args: { + scheduled_on: values.scheduled_on, + from_time: values.from_time, + to_time: values.to_time + } + }).then(() => { + frm.refresh(); + d.hide(); + }); + } + }); + d.show(); + }, + + show_feedback_dialog: function (frm, data) { + let fields = frm.events.get_fields_for_feedback(); + + let d = new frappe.ui.Dialog({ + title: __('Submit Feedback'), + fields: [ + { + fieldname: 'skill_set', + fieldtype: 'Table', + label: __('Skill Assessment'), + cannot_add_rows: false, + in_editable_grid: true, + reqd: 1, + fields: fields, + data: data + }, + { + fieldname: 'result', + fieldtype: 'Select', + options: ['', 'Cleared', 'Rejected'], + label: __('Result') + }, + { + fieldname: 'feedback', + fieldtype: 'Small Text', + label: __('Feedback') + } + ], + size: 'large', + minimizable: true, + primary_action: function(values) { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.create_interview_feedback', + args: { + data: values, + interview_name: frm.doc.name, + interviewer: frappe.session.user, + job_applicant: frm.doc.job_applicant + } + }).then(() => { + frm.refresh(); + }); + d.hide(); + } + }); + d.show(); + }, + + get_fields_for_feedback: function () { + return [{ + fieldtype: 'Link', + fieldname: 'skill', + options: 'Skill', + in_list_view: 1, + label: __('Skill') + }, { + fieldtype: 'Rating', + fieldname: 'rating', + label: __('Rating'), + in_list_view: 1, + reqd: 1, + }]; + }, + + set_job_applicant_query: function (frm) { + frm.set_query('job_applicant', function () { + let job_applicant_filters = { + status: ['!=', 'Rejected'] + }; + if (frm.doc.designation) { + job_applicant_filters.designation = frm.doc.designation; + } + return { + filters: job_applicant_filters + }; + }); + }, + + interview_round: async function (frm) { + frm.events.reset_values(frm); + frm.set_value('job_applicant', ''); + + let round_data = (await frappe.db.get_value('Interview Round', frm.doc.interview_round, 'designation')).message; + frm.set_value('designation', round_data.designation); + frm.events.set_job_applicant_query(frm); + + if (frm.doc.interview_round) { + frm.events.set_interview_details(frm); + } else { + frm.set_value('interview_details', []); + } + }, + + set_interview_details: function (frm) { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.get_interviewers', + args: { + interview_round: frm.doc.interview_round + }, + callback: function (data) { + let interview_details = data.message; + frm.set_value('interview_details', []); + if (data.message.length) { + frm.set_value('interview_details', interview_details); + } + } + }); + }, + + job_applicant: function (frm) { + if (!frm.doc.interview_round) { + frm.doc.job_applicant = ''; + frm.refresh(); + frappe.throw(__('Select Interview Round First')); + } + + if (frm.doc.job_applicant) { + frm.events.set_designation_and_job_opening(frm); + } else { + frm.events.reset_values(frm); + } + }, + + set_designation_and_job_opening: async function (frm) { + let round_data = (await frappe.db.get_value('Interview Round', frm.doc.interview_round, 'designation')).message; + frm.set_value('designation', round_data.designation); + frm.events.set_job_applicant_query(frm); + + let job_applicant_data = (await frappe.db.get_value( + 'Job Applicant', frm.doc.job_applicant, ['designation', 'job_title', 'resume_link'], + )).message; + + if (!round_data.designation) { + frm.set_value('designation', job_applicant_data.designation); + } + + frm.set_value('job_opening', job_applicant_data.job_title); + frm.set_value('resume_link', job_applicant_data.resume_link); + }, + + reset_values: function (frm) { + frm.set_value('designation', ''); + frm.set_value('job_opening', ''); + frm.set_value('resume_link', ''); + } +}); diff --git a/erpnext/hr/doctype/interview/interview.json b/erpnext/hr/doctype/interview/interview.json new file mode 100644 index 0000000000..0d393e7556 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview.json @@ -0,0 +1,254 @@ +{ + "actions": [], + "autoname": "HR-INT-.YYYY.-.####", + "creation": "2021-04-12 15:03:11.524090", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "interview_details_section", + "interview_round", + "job_applicant", + "job_opening", + "designation", + "resume_link", + "column_break_4", + "status", + "scheduled_on", + "from_time", + "to_time", + "interview_feedback_section", + "interview_details", + "ratings_section", + "expected_average_rating", + "column_break_12", + "average_rating", + "section_break_13", + "interview_summary", + "reminded", + "amended_from" + ], + "fields": [ + { + "fieldname": "job_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Job Applicant", + "options": "Job Applicant", + "reqd": 1 + }, + { + "fieldname": "job_opening", + "fieldtype": "Link", + "label": "Job Opening", + "options": "Job Opening", + "read_only": 1 + }, + { + "fieldname": "interview_round", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview Round", + "options": "Interview Round", + "reqd": 1 + }, + { + "default": "Pending", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "options": "Pending\nUnder Review\nCleared\nRejected", + "reqd": 1 + }, + { + "fieldname": "ratings_section", + "fieldtype": "Section Break", + "label": "Ratings" + }, + { + "allow_on_submit": 1, + "fieldname": "average_rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Obtained Average Rating", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "interview_summary", + "fieldtype": "Text" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "resume_link", + "fieldtype": "Data", + "label": "Resume link" + }, + { + "fieldname": "interview_details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fetch_from": "interview_round.expected_average_rating", + "fieldname": "expected_average_rating", + "fieldtype": "Rating", + "label": "Expected Average Rating", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "label": "Interview Summary" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fetch_from": "interview_round.designation", + "fieldname": "designation", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Designation", + "options": "Designation", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Interview", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "scheduled_on", + "fieldtype": "Date", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Scheduled On", + "reqd": 1, + "set_only_once": 1 + }, + { + "default": "0", + "fieldname": "reminded", + "fieldtype": "Check", + "hidden": 1, + "label": "Reminded" + }, + { + "allow_on_submit": 1, + "fieldname": "interview_details", + "fieldtype": "Table", + "options": "Interview Detail" + }, + { + "fieldname": "interview_feedback_section", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "fieldname": "from_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "From Time", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "to_time", + "fieldtype": "Time", + "in_list_view": 1, + "label": "To Time", + "reqd": 1, + "set_only_once": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [ + { + "link_doctype": "Interview Feedback", + "link_fieldname": "interview" + } + ], + "modified": "2021-09-30 13:30:05.421035", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview", + "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, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Interviewer", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "job_applicant", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py new file mode 100644 index 0000000000..955acca631 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import datetime + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import cstr, get_datetime, get_link_to_form + + +class DuplicateInterviewRoundError(frappe.ValidationError): + pass + +class Interview(Document): + def validate(self): + self.validate_duplicate_interview() + self.validate_designation() + self.validate_overlap() + + def on_submit(self): + if self.status not in ['Cleared', 'Rejected']: + frappe.throw(_('Only Interviews with Cleared or Rejected status can be submitted.'), title=_('Not Allowed')) + + def validate_duplicate_interview(self): + duplicate_interview = frappe.db.exists('Interview', { + 'job_applicant': self.job_applicant, + 'interview_round': self.interview_round, + 'docstatus': 1 + } + ) + + if duplicate_interview: + frappe.throw(_('Job Applicants are not allowed to appear twice for the same Interview round. Interview {0} already scheduled for Job Applicant {1}').format( + frappe.bold(get_link_to_form('Interview', duplicate_interview)), + frappe.bold(self.job_applicant) + )) + + def validate_designation(self): + applicant_designation = frappe.db.get_value('Job Applicant', self.job_applicant, 'designation') + if self.designation : + if self.designation != applicant_designation: + frappe.throw(_('Interview Round {0} is only for Designation {1}. Job Applicant has applied for the role {2}').format( + self.interview_round, frappe.bold(self.designation), applicant_designation), + exc=DuplicateInterviewRoundError) + else: + self.designation = applicant_designation + + def validate_overlap(self): + interviewers = [entry.interviewer for entry in self.interview_details] or [''] + + overlaps = frappe.db.sql(""" + SELECT interview.name + FROM `tabInterview` as interview + INNER JOIN `tabInterview Detail` as detail + WHERE + interview.scheduled_on = %s and interview.name != %s and interview.docstatus != 2 + and (interview.job_applicant = %s or detail.interviewer IN %s) and + ((from_time < %s and to_time > %s) or + (from_time > %s and to_time < %s) or + (from_time = %s)) + """, (self.scheduled_on, self.name, self.job_applicant, interviewers, + self.from_time, self.to_time, self.from_time, self.to_time, self.from_time)) + + if overlaps: + overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0])) + frappe.throw(overlapping_details, title=_('Overlap')) + + + @frappe.whitelist() + def reschedule_interview(self, scheduled_on, from_time, to_time): + original_date = self.scheduled_on + from_time = self.from_time + to_time = self.to_time + + self.db_set({ + 'scheduled_on': scheduled_on, + 'from_time': from_time, + 'to_time': to_time + }) + self.notify_update() + + recipients = get_recipients(self.name) + + try: + frappe.sendmail( + recipients= recipients, + subject=_('Interview: {0} Rescheduled').format(self.name), + message=_('Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}').format( + original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time), + reference_doctype=self.doctype, + reference_name=self.name + ) + except Exception: + frappe.msgprint(_('Failed to send the Interview Reschedule notification. Please configure your email account.')) + + frappe.msgprint(_('Interview Rescheduled successfully'), indicator='green') + + +def get_recipients(name, for_feedback=0): + interview = frappe.get_doc('Interview', name) + + if for_feedback: + recipients = [d.interviewer for d in interview.interview_details if not d.interview_feedback] + else: + recipients = [d.interviewer for d in interview.interview_details] + recipients.append(frappe.db.get_value('Job Applicant', interview.job_applicant, 'email_id')) + + return recipients + + +@frappe.whitelist() +def get_interviewers(interview_round): + return frappe.get_all('Interviewer', filters={'parent': interview_round}, fields=['user as interviewer']) + + +def send_interview_reminder(): + reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings', + ['send_interview_reminder', 'interview_reminder_template'], as_dict=True) + + if not reminder_settings.send_interview_reminder: + return + + remind_before = cstr(frappe.db.get_single_value('HR Settings', 'remind_before')) or '01:00:00' + remind_before = datetime.datetime.strptime(remind_before, '%H:%M:%S') + reminder_date_time = datetime.datetime.now() + datetime.timedelta( + hours=remind_before.hour, minutes=remind_before.minute, seconds=remind_before.second) + + interviews = frappe.get_all('Interview', filters={ + 'scheduled_on': ['between', (datetime.datetime.now(), reminder_date_time)], + 'status': 'Pending', + 'reminded': 0, + 'docstatus': ['!=', 2] + }) + + interview_template = frappe.get_doc('Email Template', reminder_settings.interview_reminder_template) + + for d in interviews: + doc = frappe.get_doc('Interview', d.name) + context = doc.as_dict() + message = frappe.render_template(interview_template.response, context) + recipients = get_recipients(doc.name) + + frappe.sendmail( + recipients= recipients, + subject=interview_template.subject, + message=message, + reference_doctype=doc.doctype, + reference_name=doc.name + ) + + doc.db_set('reminded', 1) + + +def send_daily_feedback_reminder(): + reminder_settings = frappe.db.get_value('HR Settings', 'HR Settings', + ['send_interview_feedback_reminder', 'feedback_reminder_notification_template'], as_dict=True) + + if not reminder_settings.send_interview_feedback_reminder: + return + + interview_feedback_template = frappe.get_doc('Email Template', reminder_settings.feedback_reminder_notification_template) + interviews = frappe.get_all('Interview', filters={'status': ['in', ['Under Review', 'Pending']], 'docstatus': ['!=', 2]}) + + for entry in interviews: + recipients = get_recipients(entry.name, for_feedback=1) + + doc = frappe.get_doc('Interview', entry.name) + context = doc.as_dict() + + message = frappe.render_template(interview_feedback_template.response, context) + + if len(recipients): + frappe.sendmail( + recipients= recipients, + subject=interview_feedback_template.subject, + message=message, + reference_doctype='Interview', + reference_name=entry.name + ) + + +@frappe.whitelist() +def get_expected_skill_set(interview_round): + return frappe.get_all('Expected Skill Set', filters ={'parent': interview_round}, fields=['skill']) + + +@frappe.whitelist() +def create_interview_feedback(data, interview_name, interviewer, job_applicant): + import json + + from six import string_types + + if isinstance(data, string_types): + data = frappe._dict(json.loads(data)) + + if frappe.session.user != interviewer: + frappe.throw(_('Only Interviewer Are allowed to submit Interview Feedback')) + + interview_feedback = frappe.new_doc('Interview Feedback') + interview_feedback.interview = interview_name + interview_feedback.interviewer = interviewer + interview_feedback.job_applicant = job_applicant + + for d in data.skill_set: + d = frappe._dict(d) + interview_feedback.append('skill_assessment', {'skill': d.skill, 'rating': d.rating}) + + interview_feedback.feedback = data.feedback + interview_feedback.result = data.result + + interview_feedback.save() + interview_feedback.submit() + + frappe.msgprint(_('Interview Feedback {0} submitted successfully').format( + get_link_to_form('Interview Feedback', interview_feedback.name))) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_interviewer_list(doctype, txt, searchfield, start, page_len, filters): + filters = [ + ['Has Role', 'parent', 'like', '%{}%'.format(txt)], + ['Has Role', 'role', '=', 'interviewer'], + ['Has Role', 'parenttype', '=', 'User'] + ] + + if filters and isinstance(filters, list): + filters.extend(filters) + + return frappe.get_all('Has Role', limit_start=start, limit_page_length=page_len, + filters=filters, fields = ['parent'], as_list=1) + + +@frappe.whitelist() +def get_events(start, end, filters=None): + """Returns events for Gantt / Calendar view rendering. + + :param start: Start date-time. + :param end: End date-time. + :param filters: Filters (JSON). + """ + from frappe.desk.calendar import get_event_conditions + + events = [] + + event_color = { + "Pending": "#fff4f0", + "Under Review": "#d3e8fc", + "Cleared": "#eaf5ed", + "Rejected": "#fce7e7" + } + + conditions = get_event_conditions('Interview', filters) + + interviews = frappe.db.sql(""" + SELECT DISTINCT + `tabInterview`.name, `tabInterview`.job_applicant, `tabInterview`.interview_round, + `tabInterview`.scheduled_on, `tabInterview`.status, `tabInterview`.from_time as from_time, + `tabInterview`.to_time as to_time + from + `tabInterview` + where + (`tabInterview`.scheduled_on between %(start)s and %(end)s) + and docstatus != 2 + {conditions} + """.format(conditions=conditions), { + "start": start, + "end": end + }, as_dict=True, update={"allDay": 0}) + + for d in interviews: + subject_data = [] + for field in ["name", "job_applicant", "interview_round"]: + if not d.get(field): + continue + subject_data.append(d.get(field)) + + color = event_color.get(d.status) + interview_data = { + 'from': get_datetime('%s %s' % (d.scheduled_on, d.from_time or '00:00:00')), + 'to': get_datetime('%s %s' % (d.scheduled_on, d.to_time or '00:00:00')), + 'name': d.name, + 'subject': '\n'.join(subject_data), + 'color': color if color else "#89bcde" + } + + events.append(interview_data) + + return events \ No newline at end of file diff --git a/erpnext/hr/doctype/interview/interview_calendar.js b/erpnext/hr/doctype/interview/interview_calendar.js new file mode 100644 index 0000000000..b46b72ecb2 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_calendar.js @@ -0,0 +1,14 @@ + +frappe.views.calendar['Interview'] = { + field_map: { + 'start': 'from', + 'end': 'to', + 'id': 'name', + 'title': 'subject', + 'allDay': 'allDay', + 'color': 'color' + }, + order_by: 'scheduled_on', + gantt: true, + get_events_method: 'erpnext.hr.doctype.interview.interview.get_events' +}; diff --git a/erpnext/hr/doctype/interview/interview_feedback_reminder_template.html b/erpnext/hr/doctype/interview/interview_feedback_reminder_template.html new file mode 100644 index 0000000000..8d39fb54ef --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_feedback_reminder_template.html @@ -0,0 +1,5 @@ +

    Interview Feedback Reminder

    + +

    + Interview Feedback for Interview {{ name }} is not submitted yet. Please submit your feedback. Thank you, good day! +

    diff --git a/erpnext/hr/doctype/interview/interview_list.js b/erpnext/hr/doctype/interview/interview_list.js new file mode 100644 index 0000000000..b1f072f0d4 --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_list.js @@ -0,0 +1,12 @@ +frappe.listview_settings['Interview'] = { + has_indicator_for_draft: 1, + get_indicator: function(doc) { + let status_color = { + 'Pending': 'orange', + 'Under Review': 'blue', + 'Cleared': 'green', + 'Rejected': 'red', + }; + return [__(doc.status), status_color[doc.status], 'status,=,'+doc.status]; + } +}; diff --git a/erpnext/hr/doctype/interview/interview_reminder_notification_template.html b/erpnext/hr/doctype/interview/interview_reminder_notification_template.html new file mode 100644 index 0000000000..76de46e28d --- /dev/null +++ b/erpnext/hr/doctype/interview/interview_reminder_notification_template.html @@ -0,0 +1,5 @@ +

    Interview Reminder

    + +

    + Interview: {{name}} is scheduled on {{scheduled_on}} from {{from_time}} to {{to_time}} +

    diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py new file mode 100644 index 0000000000..4612e17db0 --- /dev/null +++ b/erpnext/hr/doctype/interview/test_interview.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import datetime +import os +import unittest + +import frappe +from frappe import _ +from frappe.core.doctype.user_permission.test_user_permission import create_user +from frappe.utils import add_days, getdate, nowtime + +from erpnext.hr.doctype.designation.test_designation import create_designation +from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError +from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant + + +class TestInterview(unittest.TestCase): + def test_validations_for_designation(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, designation='_Test_Sales_manager', save=0) + self.assertRaises(DuplicateInterviewRoundError, interview.save) + + def test_notification_on_rescheduling(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -4)) + + previous_scheduled_date = interview.scheduled_on + frappe.db.sql("DELETE FROM `tabEmail Queue`") + + interview.reschedule_interview(add_days(getdate(previous_scheduled_date), 2), + from_time=nowtime(), to_time=nowtime()) + interview.reload() + + self.assertEqual(interview.scheduled_on, add_days(getdate(previous_scheduled_date), 2)) + + notification = frappe.get_all("Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")}) + self.assertIsNotNone(notification) + + def test_notification_for_scheduling(self): + from erpnext.hr.doctype.interview.interview import send_interview_reminder + + setup_reminder_settings() + + job_applicant = create_job_applicant() + scheduled_on = datetime.datetime.now() + datetime.timedelta(minutes=10) + + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=scheduled_on) + + frappe.db.sql("DELETE FROM `tabEmail Queue`") + send_interview_reminder() + + interview.reload() + + email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) + self.assertTrue("Subject: Interview Reminder" in email_queue[0].message) + + def test_notification_for_feedback_submission(self): + from erpnext.hr.doctype.interview.interview import send_daily_feedback_reminder + + setup_reminder_settings() + + job_applicant = create_job_applicant() + scheduled_on = add_days(getdate(), -4) + create_interview_and_dependencies(job_applicant.name, scheduled_on=scheduled_on) + + frappe.db.sql("DELETE FROM `tabEmail Queue`") + send_daily_feedback_reminder() + + email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) + self.assertTrue("Subject: Interview Feedback Reminder" in email_queue[0].message) + + def tearDown(self): + frappe.db.rollback() + + +def create_interview_and_dependencies(job_applicant, scheduled_on=None, from_time=None, to_time=None, designation=None, save=1): + if designation: + designation=create_designation(designation_name = "_Test_Sales_manager").name + + interviewer_1 = create_user("test_interviewer1@example.com", "Interviewer") + interviewer_2 = create_user("test_interviewer2@example.com", "Interviewer") + + interview_round = create_interview_round( + "Technical Round", ["Python", "JS"], + designation=designation, save=True + ) + + interview = frappe.new_doc("Interview") + interview.interview_round = interview_round.name + interview.job_applicant = job_applicant + interview.scheduled_on = scheduled_on or getdate() + interview.from_time = from_time or nowtime() + interview.to_time = to_time or nowtime() + + interview.append("interview_details", {"interviewer": interviewer_1.name}) + interview.append("interview_details", {"interviewer": interviewer_2.name}) + + if save: + interview.save() + + return interview + +def create_interview_round(name, skill_set, interviewers=[], designation=None, save=True): + create_skill_set(skill_set) + interview_round = frappe.new_doc("Interview Round") + interview_round.round_name = name + interview_round.interview_type = create_interview_type() + interview_round.expected_average_rating = 4 + if designation: + interview_round.designation = designation + + for skill in skill_set: + interview_round.append("expected_skill_set", {"skill": skill}) + + for interviewer in interviewers: + interview_round.append("interviewer", { + "user": interviewer + }) + + if save: + interview_round.save() + + return interview_round + +def create_skill_set(skill_set): + for skill in skill_set: + if not frappe.db.exists("Skill", skill): + doc = frappe.new_doc("Skill") + doc.skill_name = skill + doc.save() + +def create_interview_type(name="test_interview_type"): + if frappe.db.exists("Interview Type", name): + return frappe.get_doc("Interview Type", name).name + else: + doc = frappe.new_doc("Interview Type") + doc.name = name + doc.description = "_Test_Description" + doc.save() + + return doc.name + +def setup_reminder_settings(): + if not frappe.db.exists('Email Template', _('Interview Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Reminder'), + 'response': response, + 'subject': _('Interview Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Feedback Reminder'), + 'response': response, + 'subject': _('Interview Feedback Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + hr_settings = frappe.get_doc('HR Settings') + hr_settings.interview_reminder_template = _('Interview Reminder') + hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder') + hr_settings.save() diff --git a/erpnext/hr/doctype/interview_detail/__init__.py b/erpnext/hr/doctype/interview_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/interview_detail/interview_detail.js b/erpnext/hr/doctype/interview_detail/interview_detail.js new file mode 100644 index 0000000000..88518ca4cc --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/interview_detail.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview Detail', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/interview_detail/interview_detail.json b/erpnext/hr/doctype/interview_detail/interview_detail.json new file mode 100644 index 0000000000..b5b49c0993 --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/interview_detail.json @@ -0,0 +1,74 @@ +{ + "actions": [], + "creation": "2021-04-12 16:24:10.382863", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "interviewer", + "interview_feedback", + "average_rating", + "result", + "column_break_4", + "comments" + ], + "fields": [ + { + "fieldname": "interviewer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Interviewer", + "options": "User" + }, + { + "allow_on_submit": 1, + "fieldname": "interview_feedback", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Interview Feedback", + "options": "Interview Feedback", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "average_rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Average Rating", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "allow_on_submit": 1, + "fetch_from": "interview_feedback.feedback", + "fieldname": "comments", + "fieldtype": "Text", + "label": "Comments", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "result", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Result", + "options": "\nCleared\nRejected", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-09-29 13:13:25.865063", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_detail/interview_detail.py b/erpnext/hr/doctype/interview_detail/interview_detail.py new file mode 100644 index 0000000000..8be3d34fad --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/interview_detail.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class InterviewDetail(Document): + pass diff --git a/erpnext/hr/doctype/interview_detail/test_interview_detail.py b/erpnext/hr/doctype/interview_detail/test_interview_detail.py new file mode 100644 index 0000000000..a29dffff77 --- /dev/null +++ b/erpnext/hr/doctype/interview_detail/test_interview_detail.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + + +class TestInterviewDetail(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/interview_feedback/__init__.py b/erpnext/hr/doctype/interview_feedback/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.js b/erpnext/hr/doctype/interview_feedback/interview_feedback.js new file mode 100644 index 0000000000..dec559fcea --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.js @@ -0,0 +1,54 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview Feedback', { + onload: function(frm) { + frm.ignore_doctypes_on_cancel_all = ['Interview']; + + frm.set_query('interview', function() { + return { + filters: { + docstatus: ['!=', 2] + } + }; + }); + }, + + interview_round: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.interview.interview.get_expected_skill_set', + args: { + interview_round: frm.doc.interview_round + }, + callback: function(r) { + frm.set_value('skill_assessment', r.message); + } + }); + }, + + interview: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.interview_feedback.interview_feedback.get_applicable_interviewers', + args: { + interview: frm.doc.interview || '' + }, + callback: function(r) { + frm.set_query('interviewer', function() { + return { + filters: { + name: ['in', r.message] + } + }; + }); + } + }); + + }, + + interviewer: function(frm) { + if (!frm.doc.interview) { + frappe.throw(__('Select Interview first')); + frm.set_value('interviewer', ''); + } + } +}); diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.json b/erpnext/hr/doctype/interview_feedback/interview_feedback.json new file mode 100644 index 0000000000..6a2f7e8696 --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.json @@ -0,0 +1,171 @@ +{ + "actions": [], + "autoname": "HR-INT-FEED-.####", + "creation": "2021-04-12 17:03:13.833285", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "details_section", + "interview", + "interview_round", + "job_applicant", + "column_break_3", + "interviewer", + "result", + "section_break_4", + "skill_assessment", + "average_rating", + "section_break_7", + "feedback", + "amended_from" + ], + "fields": [ + { + "allow_in_quick_entry": 1, + "fieldname": "interview", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview", + "options": "Interview", + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fetch_from": "interview.interview_round", + "fieldname": "interview_round", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interview Round", + "options": "Interview Round", + "read_only": 1, + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "interviewer", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Interviewer", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Skill Assessment" + }, + { + "allow_in_quick_entry": 1, + "fieldname": "skill_assessment", + "fieldtype": "Table", + "options": "Skill Assessment", + "reqd": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "average_rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Average Rating", + "read_only": 1 + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Feedback" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Interview Feedback", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_in_quick_entry": 1, + "fieldname": "feedback", + "fieldtype": "Text" + }, + { + "fieldname": "result", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Result", + "options": "\nCleared\nRejected", + "reqd": 1 + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fetch_from": "interview.job_applicant", + "fieldname": "job_applicant", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Job Applicant", + "options": "Job Applicant", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "is_submittable": 1, + "links": [], + "modified": "2021-09-30 13:30:49.955352", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview Feedback", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Interviewer", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "interviewer", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py new file mode 100644 index 0000000000..1c5a4948f2 --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.utils import flt, get_link_to_form, getdate + + +class InterviewFeedback(Document): + def validate(self): + self.validate_interviewer() + self.validate_interview_date() + self.validate_duplicate() + self.calculate_average_rating() + + def on_submit(self): + self.update_interview_details() + + def on_cancel(self): + self.update_interview_details() + + def validate_interviewer(self): + applicable_interviewers = get_applicable_interviewers(self.interview) + if self.interviewer not in applicable_interviewers: + frappe.throw(_('{0} is not allowed to submit Interview Feedback for the Interview: {1}').format( + frappe.bold(self.interviewer), frappe.bold(self.interview))) + + def validate_interview_date(self): + scheduled_date = frappe.db.get_value('Interview', self.interview, 'scheduled_on') + + if getdate() < getdate(scheduled_date) and self.docstatus == 1: + frappe.throw(_('{0} submission before {1} is not allowed').format( + frappe.bold('Interview Feedback'), + frappe.bold('Interview Scheduled Date') + )) + + def validate_duplicate(self): + duplicate_feedback = frappe.db.exists('Interview Feedback', { + 'interviewer': self.interviewer, + 'interview': self.interview, + 'docstatus': 1 + }) + + if duplicate_feedback: + frappe.throw(_('Feedback already submitted for the Interview {0}. Please cancel the previous Interview Feedback {1} to continue.').format( + self.interview, get_link_to_form('Interview Feedback', duplicate_feedback))) + + def calculate_average_rating(self): + total_rating = 0 + for d in self.skill_assessment: + if d.rating: + total_rating += d.rating + + self.average_rating = flt(total_rating / len(self.skill_assessment) if len(self.skill_assessment) else 0) + + def update_interview_details(self): + doc = frappe.get_doc('Interview', self.interview) + total_rating = 0 + + if self.docstatus == 2: + for entry in doc.interview_details: + if entry.interview_feedback == self.name: + entry.average_rating = entry.interview_feedback = entry.comments = entry.result = None + break + else: + for entry in doc.interview_details: + if entry.interviewer == self.interviewer: + entry.average_rating = self.average_rating + entry.interview_feedback = self.name + entry.comments = self.feedback + entry.result = self.result + + if entry.average_rating: + total_rating += entry.average_rating + + doc.average_rating = flt(total_rating / len(doc.interview_details) if len(doc.interview_details) else 0) + doc.save() + doc.notify_update() + + +@frappe.whitelist() +def get_applicable_interviewers(interview): + data = frappe.get_all('Interview Detail', filters={'parent': interview}, fields=['interviewer']) + return [d.interviewer for d in data] diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py new file mode 100644 index 0000000000..c4b7981833 --- /dev/null +++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +import frappe +from frappe.utils import add_days, flt, getdate + +from erpnext.hr.doctype.interview.test_interview import ( + create_interview_and_dependencies, + create_skill_set, +) +from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant + + +class TestInterviewFeedback(unittest.TestCase): + def test_validation_for_skill_set(self): + frappe.set_user("Administrator") + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1)) + skill_ratings = get_skills_rating(interview.interview_round) + + interviewer = interview.interview_details[0].interviewer + create_skill_set(['Leadership']) + + interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings) + interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 4}) + frappe.set_user(interviewer) + + self.assertRaises(frappe.ValidationError, interview_feedback.save) + + frappe.set_user("Administrator") + + def test_average_ratings_on_feedback_submission_and_cancellation(self): + job_applicant = create_job_applicant() + interview = create_interview_and_dependencies(job_applicant.name, scheduled_on=add_days(getdate(), -1)) + skill_ratings = get_skills_rating(interview.interview_round) + + # For First Interviewer Feedback + interviewer = interview.interview_details[0].interviewer + frappe.set_user(interviewer) + + # calculating Average + feedback_1 = create_interview_feedback(interview.name, interviewer, skill_ratings) + + total_rating = 0 + for d in feedback_1.skill_assessment: + if d.rating: + total_rating += d.rating + + avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0) + + self.assertEqual(flt(avg_rating, 3), feedback_1.average_rating) + + avg_on_interview_detail = frappe.db.get_value('Interview Detail', { + 'parent': feedback_1.interview, + 'interviewer': feedback_1.interviewer, + 'interview_feedback': feedback_1.name + }, 'average_rating') + + # 1. average should be reflected in Interview Detail. + self.assertEqual(avg_on_interview_detail, round(feedback_1.average_rating)) + + '''For Second Interviewer Feedback''' + interviewer = interview.interview_details[1].interviewer + frappe.set_user(interviewer) + + feedback_2 = create_interview_feedback(interview.name, interviewer, skill_ratings) + interview.reload() + + feedback_2.cancel() + interview.reload() + + frappe.set_user("Administrator") + + def tearDown(self): + frappe.db.rollback() + + +def create_interview_feedback(interview, interviewer, skills_ratings): + interview_feedback = frappe.new_doc("Interview Feedback") + interview_feedback.interview = interview + interview_feedback.interviewer = interviewer + interview_feedback.result = "Cleared" + + for rating in skills_ratings: + interview_feedback.append("skill_assessment", rating) + + interview_feedback.save() + interview_feedback.submit() + + return interview_feedback + + +def get_skills_rating(interview_round): + import random + + skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"]) + for d in skills: + d["rating"] = random.randint(1, 5) + return skills diff --git a/erpnext/hr/doctype/interview_round/__init__.py b/erpnext/hr/doctype/interview_round/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/interview_round/interview_round.js b/erpnext/hr/doctype/interview_round/interview_round.js new file mode 100644 index 0000000000..6a608b03d2 --- /dev/null +++ b/erpnext/hr/doctype/interview_round/interview_round.js @@ -0,0 +1,24 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on("Interview Round", { + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.add_custom_button(__("Create Interview"), function() { + frm.events.create_interview(frm); + }); + } + }, + create_interview: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.interview_round.interview_round.create_interview", + args: { + doc: frm.doc + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); + } +}); diff --git a/erpnext/hr/doctype/interview_round/interview_round.json b/erpnext/hr/doctype/interview_round/interview_round.json new file mode 100644 index 0000000000..9c95185e9c --- /dev/null +++ b/erpnext/hr/doctype/interview_round/interview_round.json @@ -0,0 +1,118 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:round_name", + "creation": "2021-04-12 12:57:19.902866", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "round_name", + "interview_type", + "interviewers", + "column_break_3", + "designation", + "expected_average_rating", + "expected_skills_section", + "expected_skill_set" + ], + "fields": [ + { + "fieldname": "round_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Round Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" + }, + { + "fieldname": "expected_skills_section", + "fieldtype": "Section Break", + "label": "Expected Skillset" + }, + { + "fieldname": "expected_skill_set", + "fieldtype": "Table", + "options": "Expected Skill Set", + "reqd": 1 + }, + { + "fieldname": "expected_average_rating", + "fieldtype": "Rating", + "label": "Expected Average Rating", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "interview_type", + "fieldtype": "Link", + "label": "Interview Type", + "options": "Interview Type", + "reqd": 1 + }, + { + "fieldname": "interviewers", + "fieldtype": "Table MultiSelect", + "label": "Interviewers", + "options": "Interviewer" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-09-30 13:01:25.666660", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview Round", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Interviewer", + "select": 1, + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_round/interview_round.py b/erpnext/hr/doctype/interview_round/interview_round.py new file mode 100644 index 0000000000..8230c78585 --- /dev/null +++ b/erpnext/hr/doctype/interview_round/interview_round.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import json + +import frappe +from frappe.model.document import Document + + +class InterviewRound(Document): + pass + +@frappe.whitelist() +def create_interview(doc): + if isinstance(doc, str): + doc = json.loads(doc) + doc = frappe.get_doc(doc) + + interview = frappe.new_doc("Interview") + interview.interview_round = doc.name + interview.designation = doc.designation + + if doc.interviewers: + interview.interview_details = [] + for data in doc.interviewers: + interview.append("interview_details", { + "interviewer": data.user + }) + return interview + + + diff --git a/erpnext/hr/doctype/interview_round/test_interview_round.py b/erpnext/hr/doctype/interview_round/test_interview_round.py new file mode 100644 index 0000000000..932d3defc2 --- /dev/null +++ b/erpnext/hr/doctype/interview_round/test_interview_round.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import unittest + +# import frappe + + +class TestInterviewRound(unittest.TestCase): + pass + diff --git a/erpnext/hr/doctype/interview_type/__init__.py b/erpnext/hr/doctype/interview_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/interview_type/interview_type.js b/erpnext/hr/doctype/interview_type/interview_type.js new file mode 100644 index 0000000000..af77b527d4 --- /dev/null +++ b/erpnext/hr/doctype/interview_type/interview_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Interview Type', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/interview_type/interview_type.json b/erpnext/hr/doctype/interview_type/interview_type.json new file mode 100644 index 0000000000..14636a18cb --- /dev/null +++ b/erpnext/hr/doctype/interview_type/interview_type.json @@ -0,0 +1,73 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2021-04-12 14:44:40.664034", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "description" + ], + "fields": [ + { + "fieldname": "description", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Description" + } + ], + "index_web_pages_for_search": 1, + "links": [ + { + "link_doctype": "Interview Round", + "link_fieldname": "interview_type" + } + ], + "modified": "2021-09-30 13:00:16.471518", + "modified_by": "Administrator", + "module": "HR", + "name": "Interview 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": "HR Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interview_type/interview_type.py b/erpnext/hr/doctype/interview_type/interview_type.py new file mode 100644 index 0000000000..ee5be54c75 --- /dev/null +++ b/erpnext/hr/doctype/interview_type/interview_type.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class InterviewType(Document): + pass diff --git a/erpnext/hr/doctype/interview_type/test_interview_type.py b/erpnext/hr/doctype/interview_type/test_interview_type.py new file mode 100644 index 0000000000..a5d3cf9922 --- /dev/null +++ b/erpnext/hr/doctype/interview_type/test_interview_type.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + + +class TestInterviewType(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/interviewer/__init__.py b/erpnext/hr/doctype/interviewer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/interviewer/interviewer.json b/erpnext/hr/doctype/interviewer/interviewer.json new file mode 100644 index 0000000000..a37b8b0e4e --- /dev/null +++ b/erpnext/hr/doctype/interviewer/interviewer.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2021-04-12 17:38:19.354734", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-13 13:41:35.817568", + "modified_by": "Administrator", + "module": "HR", + "name": "Interviewer", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/interviewer/interviewer.py b/erpnext/hr/doctype/interviewer/interviewer.py new file mode 100644 index 0000000000..1c8dbbed59 --- /dev/null +++ b/erpnext/hr/doctype/interviewer/interviewer.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class Interviewer(Document): + pass diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js index 7658bc9353..d7b1c6c9df 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.js +++ b/erpnext/hr/doctype/job_applicant/job_applicant.js @@ -8,6 +8,24 @@ cur_frm.email_field = "email_id"; frappe.ui.form.on("Job Applicant", { refresh: function(frm) { + frm.set_query("job_title", function() { + return { + filters: { + 'status': 'Open' + } + }; + }); + frm.events.create_custom_buttons(frm); + frm.events.make_dashboard(frm); + }, + + create_custom_buttons: function(frm) { + if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") { + frm.add_custom_button(__("Create Interview"), function() { + frm.events.create_dialog(frm); + }); + } + if (!frm.doc.__islocal) { if (frm.doc.__onload && frm.doc.__onload.job_offer) { $('[data-doctype="Employee Onboarding"]').find("button").show(); @@ -28,14 +46,57 @@ frappe.ui.form.on("Job Applicant", { }); } } + }, - frm.set_query("job_title", function() { - return { - filters: { - 'status': 'Open' - } - }; + make_dashboard: function(frm) { + frappe.call({ + method: "erpnext.hr.doctype.job_applicant.job_applicant.get_interview_details", + args: { + job_applicant: frm.doc.name + }, + callback: function(r) { + $("div").remove(".form-dashboard-section.custom"); + frm.dashboard.add_section( + frappe.render_template('job_applicant_dashboard', { + data: r.message + }), + __("Interview Summary") + ); + } }); + }, + create_dialog: function(frm) { + let d = new frappe.ui.Dialog({ + title: 'Enter Interview Round', + fields: [ + { + label: 'Interview Round', + fieldname: 'interview_round', + fieldtype: 'Link', + options: 'Interview Round' + }, + ], + primary_action_label: 'Create Interview', + primary_action(values) { + frm.events.create_interview(frm, values); + d.hide(); + } + }); + d.show(); + }, + + create_interview: function (frm, values) { + frappe.call({ + method: "erpnext.hr.doctype.job_applicant.job_applicant.create_interview", + args: { + doc: frm.doc, + interview_round: values.interview_round + }, + callback: function (r) { + var doclist = frappe.model.sync(r.message); + frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + } + }); } }); diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.json b/erpnext/hr/doctype/job_applicant/job_applicant.json index bcea5f50d9..200f675221 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.json +++ b/erpnext/hr/doctype/job_applicant/job_applicant.json @@ -9,16 +9,20 @@ "email_append_to": 1, "engine": "InnoDB", "field_order": [ + "details_section", "applicant_name", "email_id", "phone_number", "country", - "status", "column_break_3", "job_title", + "designation", + "status", + "source_and_rating_section", "source", "source_name", "employee_referral", + "column_break_13", "applicant_rating", "section_break_6", "notes", @@ -84,7 +88,8 @@ }, { "fieldname": "section_break_6", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Resume" }, { "fieldname": "cover_letter", @@ -160,13 +165,34 @@ "label": "Employee Referral", "options": "Employee Referral", "read_only": 1 + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fieldname": "source_and_rating_section", + "fieldtype": "Section Break", + "label": "Source and Rating" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fetch_from": "job_opening.designation", + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" } ], "icon": "fa fa-user", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-03-24 15:51:11.117517", + "modified": "2021-09-29 23:06:10.904260", "modified_by": "Administrator", "module": "HR", "name": "Job Applicant", diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index 6971e5b4fe..151f49248f 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -8,7 +8,9 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import comma_and, validate_email_address +from frappe.utils import validate_email_address + +from erpnext.hr.doctype.interview.interview import get_interviewers class DuplicationError(frappe.ValidationError): pass @@ -26,7 +28,6 @@ class JobApplicant(Document): self.name = " - ".join(keys) def validate(self): - self.check_email_id_is_unique() if self.email_id: validate_email_address(self.email_id, True) @@ -44,11 +45,44 @@ class JobApplicant(Document): elif self.status in ["Accepted", "Rejected"]: emp_ref.db_set("status", self.status) +@frappe.whitelist() +def create_interview(doc, interview_round): + import json - def check_email_id_is_unique(self): - if self.email_id: - names = frappe.db.sql_list("""select name from `tabJob Applicant` - where email_id=%s and name!=%s and job_title=%s""", (self.email_id, self.name, self.job_title)) + from six import string_types - if names: - frappe.throw(_("Email Address must be unique, already exists for {0}").format(comma_and(names)), frappe.DuplicateEntryError) + if isinstance(doc, string_types): + doc = json.loads(doc) + doc = frappe.get_doc(doc) + + round_designation = frappe.db.get_value("Interview Round", interview_round, "designation") + + if round_designation and doc.designation and round_designation != doc.designation: + frappe.throw(_("Interview Round {0} is only applicable for the Designation {1}").format(interview_round, round_designation)) + + interview = frappe.new_doc("Interview") + interview.interview_round = interview_round + interview.job_applicant = doc.name + interview.designation = doc.designation + interview.resume_link = doc.resume_link + interview.job_opening = doc.job_title + interviewer_detail = get_interviewers(interview_round) + + for d in interviewer_detail: + interview.append("interview_details", { + "interviewer": d.interviewer + }) + return interview + +@frappe.whitelist() +def get_interview_details(job_applicant): + interview_details = frappe.db.get_all("Interview", + filters={"job_applicant":job_applicant, "docstatus": ["!=", 2]}, + fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"] + ) + interview_detail_map = {} + + for detail in interview_details: + interview_detail_map[detail.name] = detail + + return interview_detail_map diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html new file mode 100644 index 0000000000..c286787a55 --- /dev/null +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html @@ -0,0 +1,44 @@ + +{% if not jQuery.isEmptyObject(data) %} + + + + + + + + + + + + + {% for(const [key, value] of Object.entries(data)) { %} + + + + + + + + {% } %} + +
    {{ __("Interview") }}{{ __("Interview Round") }}{{ __("Status") }}{{ __("Expected Rating") }}{{ __("Rating") }}
    {%= key %} {%= value["interview_round"] %} {%= value["status"] %} + {% for (i = 0; i < value["expected_average_rating"]; i++) { %} + + {% } %} + {% for (i = 0; i < (5-value["expected_average_rating"]); i++) { %} + + {% } %} + + {% if(value["average_rating"]){ %} + {% for (i = 0; i < value["average_rating"]; i++) { %} + + {% } %} + {% for (i = 0; i < (5-value["average_rating"]); i++) { %} + + {% } %} + {% } %} +
    +{% else %} +

    No Interview has been scheduled.

    +{% endif %} diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py index c0059431cf..2f7795fc08 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.py @@ -2,14 +2,17 @@ from __future__ import unicode_literals def get_data(): - return { - 'fieldname': 'job_applicant', - 'transactions': [ - { - 'items': ['Employee', 'Employee Onboarding'] - }, - { - 'items': ['Job Offer'] - }, - ], - } + return { + 'fieldname': 'job_applicant', + 'transactions': [ + { + 'items': ['Employee', 'Employee Onboarding'] + }, + { + 'items': ['Job Offer', 'Appointment Letter'] + }, + { + 'items': ['Interview'] + } + ], + } diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py index e583e25eae..8fc1290742 100644 --- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py @@ -7,7 +7,8 @@ import unittest import frappe -# test_records = frappe.get_test_records('Job Applicant') +from erpnext.hr.doctype.designation.test_designation import create_designation + class TestJobApplicant(unittest.TestCase): pass @@ -25,7 +26,8 @@ def create_job_applicant(**args): job_applicant = frappe.get_doc({ "doctype": "Job Applicant", - "status": args.status or "Open" + "status": args.status or "Open", + "designation": create_designation().name }) job_applicant.update(filters) diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py index 3f3eca17e6..162b245d13 100644 --- a/erpnext/hr/doctype/job_offer/test_job_offer.py +++ b/erpnext/hr/doctype/job_offer/test_job_offer.py @@ -32,6 +32,7 @@ class TestJobOffer(unittest.TestCase): self.assertTrue(frappe.db.exists("Job Offer", job_offer.name)) def test_job_applicant_update(self): + frappe.db.set_value("HR Settings", None, "check_vacancies", 0) create_staffing_plan() job_applicant = create_job_applicant(email_id="test_job_applicants@example.com") job_offer = create_job_offer(job_applicant=job_applicant.name) @@ -43,7 +44,11 @@ class TestJobOffer(unittest.TestCase): job_offer.status = "Rejected" job_offer.submit() job_applicant.reload() - self.assertEqual(job_applicant.status, "Rejected") + self.assertEquals(job_applicant.status, "Rejected") + frappe.db.set_value("HR Settings", None, "check_vacancies", 1) + + def tearDown(self): + frappe.db.sql("DELETE FROM `tabJob Offer` WHERE 1") def create_job_offer(**args): args = frappe._dict(args) diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 9ccb915908..2eaaeec88a 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -1,8 +1,8 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch('employee','employee_name','employee_name'); -cur_frm.add_fetch('employee','company','company'); +cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); +cur_frm.add_fetch('employee', 'company', 'company'); frappe.ui.form.on("Leave Application", { setup: function(frm) { @@ -19,7 +19,6 @@ frappe.ui.form.on("Leave Application", { frm.set_query("employee", erpnext.queries.employee); }, onload: function(frm) { - // Ignore cancellation of doctype on cancel all. frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; @@ -42,9 +41,9 @@ frappe.ui.form.on("Leave Application", { }, validate: function(frm) { - if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1){ + if (frm.doc.from_date == frm.doc.to_date && frm.doc.half_day == 1) { frm.doc.half_day_date = frm.doc.from_date; - }else if (frm.doc.half_day == 0){ + } else if (frm.doc.half_day == 0) { frm.doc.half_day_date = ""; } frm.toggle_reqd("half_day_date", frm.doc.half_day == 1); @@ -79,14 +78,14 @@ frappe.ui.form.on("Leave Application", { __("Allocated Leaves") ); frm.dashboard.show(); - let allowed_leave_types = Object.keys(leave_details); + let allowed_leave_types = Object.keys(leave_details); // lwps should be allowed, lwps don't have any allocation allowed_leave_types = allowed_leave_types.concat(lwps); - frm.set_query('leave_type', function(){ + frm.set_query('leave_type', function() { return { - filters : [ + filters: [ ['leave_type_name', 'in', allowed_leave_types] ] }; @@ -99,7 +98,7 @@ frappe.ui.form.on("Leave Application", { frm.trigger("calculate_total_days"); } cur_frm.set_intro(""); - if(frm.doc.__islocal && !in_list(frappe.user_roles, "Employee")) { + if (frm.doc.__islocal && !in_list(frappe.user_roles, "Employee")) { frm.set_intro(__("Fill the form and save it")); } @@ -118,7 +117,7 @@ frappe.ui.form.on("Leave Application", { }, leave_approver: function(frm) { - if(frm.doc.leave_approver){ + if (frm.doc.leave_approver) { frm.set_value("leave_approver_name", frappe.user.full_name(frm.doc.leave_approver)); } }, @@ -131,12 +130,10 @@ frappe.ui.form.on("Leave Application", { if (frm.doc.half_day) { if (frm.doc.from_date == frm.doc.to_date) { frm.set_value("half_day_date", frm.doc.from_date); - } - else { + } else { frm.trigger("half_day_datepicker"); } - } - else { + } else { frm.set_value("half_day_date", ""); } frm.trigger("calculate_total_days"); @@ -167,7 +164,7 @@ frappe.ui.form.on("Leave Application", { }, get_leave_balance: function(frm) { - if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { + if (frm.doc.docstatus === 0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { return frappe.call({ method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { @@ -180,8 +177,7 @@ frappe.ui.form.on("Leave Application", { callback: function(r) { if (!r.exc && r.message) { frm.set_value('leave_balance', r.message); - } - else { + } else { frm.set_value('leave_balance', "0"); } } @@ -190,12 +186,12 @@ frappe.ui.form.on("Leave Application", { }, calculate_total_days: function(frm) { - if(frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) { + if (frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) { var from_date = Date.parse(frm.doc.from_date); var to_date = Date.parse(frm.doc.to_date); - if(to_date < from_date){ + if (to_date < from_date) { frappe.msgprint(__("To Date cannot be less than From Date")); frm.set_value('to_date', ''); return; @@ -222,7 +218,7 @@ frappe.ui.form.on("Leave Application", { }, set_leave_approver: function(frm) { - if(frm.doc.employee) { + if (frm.doc.employee) { // server call is done to include holidays in leave days calculations return frappe.call({ method: 'erpnext.hr.doctype.leave_application.leave_application.get_leave_approver', diff --git a/erpnext/hr/doctype/skill_assessment/__init__.py b/erpnext/hr/doctype/skill_assessment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/skill_assessment/skill_assessment.json b/erpnext/hr/doctype/skill_assessment/skill_assessment.json new file mode 100644 index 0000000000..8b935c4073 --- /dev/null +++ b/erpnext/hr/doctype/skill_assessment/skill_assessment.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "creation": "2021-04-12 17:07:39.656289", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "skill", + "rating" + ], + "fields": [ + { + "fieldname": "skill", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Skill", + "options": "Skill", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "rating", + "fieldtype": "Rating", + "in_list_view": 1, + "label": "Rating", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-04-12 17:18:14.032298", + "modified_by": "Administrator", + "module": "HR", + "name": "Skill Assessment", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/skill_assessment/skill_assessment.py b/erpnext/hr/doctype/skill_assessment/skill_assessment.py new file mode 100644 index 0000000000..3b74c4ed5f --- /dev/null +++ b/erpnext/hr/doctype/skill_assessment/skill_assessment.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class SkillAssessment(Document): + pass diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3adc3e9ff6..9e38a9c7ea 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -313,3 +313,4 @@ erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table +erpnext.patches.v13_0.add_default_interview_notification_templates diff --git a/erpnext/patches/v13_0/add_default_interview_notification_templates.py b/erpnext/patches/v13_0/add_default_interview_notification_templates.py new file mode 100644 index 0000000000..5e8a27fa40 --- /dev/null +++ b/erpnext/patches/v13_0/add_default_interview_notification_templates.py @@ -0,0 +1,37 @@ +from __future__ import unicode_literals + +import os + +import frappe +from frappe import _ + + +def execute(): + if not frappe.db.exists('Email Template', _('Interview Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_reminder_notification_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Reminder'), + 'response': response, + 'subject': _('Interview Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + if not frappe.db.exists('Email Template', _('Interview Feedback Reminder')): + base_path = frappe.get_app_path('erpnext', 'hr', 'doctype') + response = frappe.read_file(os.path.join(base_path, 'interview/interview_feedback_reminder_template.html')) + + frappe.get_doc({ + 'doctype': 'Email Template', + 'name': _('Interview Feedback Reminder'), + 'response': response, + 'subject': _('Interview Feedback Reminder'), + 'owner': frappe.session.user, + }).insert(ignore_permissions=True) + + hr_settings = frappe.get_doc('HR Settings') + hr_settings.interview_reminder_template = _('Interview Reminder') + hr_settings.feedback_reminder_notification_template = _('Interview Feedback Reminder') + hr_settings.save() diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index bff36a4149..9ed6686d48 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -171,8 +171,6 @@ class TestSalarySlip(unittest.TestCase): days_in_month = no_of_days[0] no_of_holidays = no_of_days[1] - self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 1) - ss.reload() payment_days_based_comp_amount = 0 for component in ss.earnings: diff --git a/erpnext/setup/setup_wizard/operations/defaults_setup.py b/erpnext/setup/setup_wizard/operations/defaults_setup.py index 6dd0fb1403..55d5ec8630 100644 --- a/erpnext/setup/setup_wizard/operations/defaults_setup.py +++ b/erpnext/setup/setup_wizard/operations/defaults_setup.py @@ -62,6 +62,13 @@ def set_default_settings(args): hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") + + hr_settings.send_interview_reminder = 1 + hr_settings.interview_reminder_template = _("Interview Reminder") + hr_settings.remind_before = "00:15:00" + + hr_settings.send_interview_feedback_reminder = 1 + hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder") hr_settings.save() def set_no_copy_fields_in_variant_settings(): diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 907967c83f..c473395a9a 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -264,16 +264,26 @@ def install(country=None): base_path = frappe.get_app_path("erpnext", "hr", "doctype") response = frappe.read_file(os.path.join(base_path, "leave_application/leave_application_email_template.html")) - records += [{'doctype': 'Email Template', 'name': _("Leave Approval Notification"), 'response': response,\ + records += [{'doctype': 'Email Template', 'name': _("Leave Approval Notification"), 'response': response, 'subject': _("Leave Approval Notification"), 'owner': frappe.session.user}] - records += [{'doctype': 'Email Template', 'name': _("Leave Status Notification"), 'response': response,\ + records += [{'doctype': 'Email Template', 'name': _("Leave Status Notification"), 'response': response, 'subject': _("Leave Status Notification"), 'owner': frappe.session.user}] + response = frappe.read_file(os.path.join(base_path, "interview/interview_reminder_notification_template.html")) + + records += [{'doctype': 'Email Template', 'name': _('Interview Reminder'), 'response': response, + 'subject': _('Interview Reminder'), 'owner': frappe.session.user}] + + response = frappe.read_file(os.path.join(base_path, "interview/interview_feedback_reminder_template.html")) + + records += [{'doctype': 'Email Template', 'name': _('Interview Feedback Reminder'), 'response': response, + 'subject': _('Interview Feedback Reminder'), 'owner': frappe.session.user}] + base_path = frappe.get_app_path("erpnext", "stock", "doctype") response = frappe.read_file(os.path.join(base_path, "delivery_trip/dispatch_notification_template.html")) - records += [{'doctype': 'Email Template', 'name': _("Dispatch Notification"), 'response': response,\ + records += [{'doctype': 'Email Template', 'name': _("Dispatch Notification"), 'response': response, 'subject': _("Your order is out for delivery!"), 'owner': frappe.session.user}] # Records for the Supplier Scorecard @@ -317,6 +327,14 @@ def update_hr_defaults(): hr_settings.emp_created_by = "Naming Series" hr_settings.leave_approval_notification_template = _("Leave Approval Notification") hr_settings.leave_status_notification_template = _("Leave Status Notification") + + hr_settings.send_interview_reminder = 1 + hr_settings.interview_reminder_template = _("Interview Reminder") + hr_settings.remind_before = "00:15:00" + + hr_settings.send_interview_feedback_reminder = 1 + hr_settings.feedback_reminder_notification_template = _("Interview Feedback Reminder") + hr_settings.save() def update_item_variant_settings(): From 632f7848a32f577b9bd094d5368acf675f5121ca Mon Sep 17 00:00:00 2001 From: Goh Yan Chang Date: Fri, 1 Oct 2021 16:30:33 +0800 Subject: [PATCH 180/416] Update employee_leave_balance.py fix: Employee Leave Balance report showing wrong figures --- .../hr/report/employee_leave_balance/employee_leave_balance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 6bca1368d3..d463b9b62a 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -182,10 +182,11 @@ def get_allocated_and_expired_leaves(from_date, to_date, employee, leave_type): records= frappe.db.sql(""" SELECT employee, leave_type, from_date, to_date, leaves, transaction_name, - is_carry_forward, is_expired + transaction_type, is_carry_forward, is_expired FROM `tabLeave Ledger Entry` WHERE employee=%(employee)s AND leave_type=%(leave_type)s AND docstatus=1 + AND transaction_type = 'Leave Allocation' AND (from_date between %(from_date)s AND %(to_date)s OR to_date between %(from_date)s AND %(to_date)s OR (from_date < %(from_date)s AND to_date > %(to_date)s)) From ece446ffe5fb3512c1d86b5182b0aa1c0473ea34 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Fri, 1 Oct 2021 20:32:13 +0530 Subject: [PATCH 181/416] fix: update variant qty in BOM, Create Work Order dialog (#27686) --- erpnext/manufacturing/doctype/bom/bom.js | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 8a9241391a..5f5c20a595 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -215,7 +215,32 @@ frappe.ui.form.on("BOM", { label: __('Qty To Manufacture'), fieldname: 'qty', reqd: 1, - default: 1 + default: 1, + onchange: () => { + const { quantity, items: rm } = frm.doc; + const variant_items_map = rm.reduce((acc, item) => { + acc[item.item_code] = item.qty; + return acc; + }, {}); + const mf_qty = cur_dialog.fields_list.filter( + (f) => f.df.fieldname === "qty" + )[0]?.value; + const items = cur_dialog.fields.filter( + (f) => f.fieldname === "items" + )[0]?.data; + + if (!items) { + return; + } + + items.forEach((item) => { + item.qty = + (variant_items_map[item.item_code] * mf_qty) / + quantity; + }); + + cur_dialog.refresh(); + } }); } From 7ac02de61395072d5280517841c265b6df972c38 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 2 Oct 2021 11:34:47 +0530 Subject: [PATCH 182/416] fix: sync_jobs fails --- .../service_level_agreement/service_level_agreement.json | 5 +++-- .../service_level_agreement/service_level_agreement.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json index b649b8768b..5f470aad67 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.json +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.json @@ -203,10 +203,11 @@ } ], "links": [], - "modified": "2021-07-27 11:16:45.596579", + "modified": "2021-10-02 11:32:55.556024", "modified_by": "Administrator", "module": "Support", "name": "Service Level Agreement", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -237,4 +238,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py index bd478119b6..6bdd8f2205 100644 --- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py +++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py @@ -339,7 +339,7 @@ def set_documents_with_active_service_level_agreement(): def apply(doc, method=None): # Applies SLA to document on validate - if frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_setup_wizard or \ + if frappe.flags.in_patch or frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_setup_wizard or \ doc.doctype not in get_documents_with_active_service_level_agreement(): return From 418b9f85451d20c2202b3cec4a1b7030083c2775 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 13:28:39 +0530 Subject: [PATCH 183/416] feat(HR): Some Enhancements and Onboarding (backport #25741) (#27741) * feat(HR): Some Enhancements and Onboarding (#25741) * feat: Hr settings restructure * feat: remove validation and make As warning * feat: made leave policy Assignment feild read only * feat: send leave Notification via 'Notification' * patch: for field name change * feat: removed defaults value for removed field * feat: removed leave Notification fields * feat: better label and description * feat: Hr Module onboarding and Onboarding slides * fix: sider, test, translations * chore: remove unnecessary code formatting changes * refactor: HR Onboarding * refactor: HR Settings * revert: Notification changes * chore: remove unnecessary descriptions from leave type * fix: linter issues Co-authored-by: Rucha Mahabal (cherry picked from commit 4837238f3dbbad336bcd21fcbfa19b438cbad16a) # Conflicts: # erpnext/hr/doctype/employee/employee.js * fix: conflicts * fix: conflicts Co-authored-by: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Co-authored-by: Rucha Mahabal --- erpnext/hr/doctype/employee/employee.js | 99 ++++++++++++---- .../hr/doctype/holiday_list/holiday_list.js | 37 +++++- erpnext/hr/doctype/hr_settings/hr_settings.js | 21 +++- .../hr/doctype/hr_settings/hr_settings.json | 112 +++++++++--------- .../leave_allocation/leave_allocation.js | 48 ++++++-- .../leave_allocation/leave_allocation.json | 5 +- .../leave_application/leave_application.js | 37 +++++- .../leave_application/leave_application.py | 9 +- .../test_leave_application.py | 1 + erpnext/hr/doctype/leave_type/leave_type.js | 34 ++++++ erpnext/hr/doctype/leave_type/leave_type.json | 8 +- .../human_resource/human_resource.json | 13 +- .../create_employee/create_employee.json | 8 +- .../create_holiday_list.json | 8 +- .../create_leave_allocation.json | 8 +- .../create_leave_application.json | 8 +- .../create_leave_type/create_leave_type.json | 8 +- .../data_import/data_import.json | 21 ++++ .../hr_settings/hr_settings.json | 8 +- erpnext/patches.txt | 2 +- 20 files changed, 365 insertions(+), 130 deletions(-) create mode 100644 erpnext/hr/onboarding_step/data_import/data_import.json diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js index 5639cc9ea4..13b33e2e74 100755 --- a/erpnext/hr/doctype/employee/employee.js +++ b/erpnext/hr/doctype/employee/employee.js @@ -15,19 +15,20 @@ erpnext.hr.EmployeeController = class EmployeeController extends frappe.ui.form. } refresh() { - var me = this; erpnext.toggle_naming_series(); } date_of_birth() { return cur_frm.call({ method: "get_retirement_date", - args: {date_of_birth: this.frm.doc.date_of_birth} + args: { + date_of_birth: this.frm.doc.date_of_birth + } }); } salutation() { - if(this.frm.doc.salutation) { + if (this.frm.doc.salutation) { this.frm.set_value("gender", { "Mr": "Male", "Ms": "Female" @@ -36,8 +37,9 @@ erpnext.hr.EmployeeController = class EmployeeController extends frappe.ui.form. } }; -frappe.ui.form.on('Employee',{ - setup: function(frm) { + +frappe.ui.form.on('Employee', { + setup: function (frm) { frm.set_query("leave_policy", function() { return { "filters": { @@ -46,7 +48,7 @@ frappe.ui.form.on('Employee',{ }; }); }, - onload:function(frm) { + onload: function (frm) { frm.set_query("department", function() { return { "filters": { @@ -55,23 +57,28 @@ frappe.ui.form.on('Employee',{ }; }); }, - prefered_contact_email:function(frm){ - frm.events.update_contact(frm) + prefered_contact_email: function(frm) { + frm.events.update_contact(frm); }, - personal_email:function(frm){ - frm.events.update_contact(frm) + + personal_email: function(frm) { + frm.events.update_contact(frm); }, - company_email:function(frm){ - frm.events.update_contact(frm) + + company_email: function(frm) { + frm.events.update_contact(frm); }, - user_id:function(frm){ - frm.events.update_contact(frm) + + user_id: function(frm) { + frm.events.update_contact(frm); }, - update_contact:function(frm){ + + update_contact: function(frm) { var prefered_email_fieldname = frappe.model.scrub(frm.doc.prefered_contact_email) || 'user_id'; frm.set_value("prefered_email", - frm.fields_dict[prefered_email_fieldname].value) + frm.fields_dict[prefered_email_fieldname].value); }, + status: function(frm) { return frm.call({ method: "deactivate_sales_person", @@ -81,19 +88,63 @@ frappe.ui.form.on('Employee',{ } }); }, + create_user: function(frm) { - if (!frm.doc.prefered_email) - { - frappe.throw(__("Please enter Preferred Contact Email")) + if (!frm.doc.prefered_email) { + frappe.throw(__("Please enter Preferred Contact Email")); } frappe.call({ method: "erpnext.hr.doctype.employee.employee.create_user", - args: { employee: frm.doc.name, email: frm.doc.prefered_email }, - callback: function(r) - { - frm.set_value("user_id", r.message) + args: { + employee: frm.doc.name, + email: frm.doc.prefered_email + }, + callback: function (r) { + frm.set_value("user_id", r.message); } }); } }); -cur_frm.cscript = new erpnext.hr.EmployeeController({frm: cur_frm}); + +cur_frm.cscript = new erpnext.hr.EmployeeController({ + frm: cur_frm +}); + + +frappe.tour['Employee'] = [ + { + fieldname: "first_name", + title: "First Name", + description: __("Enter First and Last name of Employee, based on Which Full Name will be updated. IN transactions, it will be Full Name which will be fetched.") + }, + { + fieldname: "company", + title: "Company", + description: __("Select a Company this Employee belongs to. Other HR features like Payroll. Expense Claims and Leaves for this Employee will be created for a given company only.") + }, + { + fieldname: "date_of_birth", + title: "Date of Birth", + description: __("Select Date of Birth. This will validate Employees age and prevent hiring of under-age staff.") + }, + { + fieldname: "date_of_joining", + title: "Date of Joining", + description: __("Select Date of joining. It will have impact on the first salary calculation, Leave allocation on pro-rata bases.") + }, + { + fieldname: "holiday_list", + title: "Holiday List", + description: __("Select a default Holiday List for this Employee. The days listed in Holiday List will not be counted in Leave Application.") + }, + { + fieldname: "reports_to", + title: "Reports To", + description: __("Here, you can select a senior of this Employee. Based on this, Organization Chart will be populated.") + }, + { + fieldname: "leave_approver", + title: "Leave Approver", + description: __("Select Leave Approver for an employee. The user one who will look after his/her Leave application") + }, +]; diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.js b/erpnext/hr/doctype/holiday_list/holiday_list.js index 462bd8bb67..ea033c7ed9 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.js +++ b/erpnext/hr/doctype/holiday_list/holiday_list.js @@ -1,10 +1,10 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Holiday List', { +frappe.ui.form.on("Holiday List", { refresh: function(frm) { if (frm.doc.holidays) { - frm.set_value('total_holidays', frm.doc.holidays.length); + frm.set_value("total_holidays", frm.doc.holidays.length); } }, from_date: function(frm) { @@ -14,3 +14,36 @@ frappe.ui.form.on('Holiday List', { } } }); + +frappe.tour["Holiday List"] = [ + { + fieldname: "holiday_list_name", + title: "Holiday List Name", + description: __("Enter a name for this Holiday List."), + }, + { + fieldname: "from_date", + title: "From Date", + description: __("Based on your HR Policy, select your leave allocation period's start date"), + }, + { + fieldname: "to_date", + title: "To Date", + description: __("Based on your HR Policy, select your leave allocation period's end date"), + }, + { + fieldname: "weekly_off", + title: "Weekly Off", + description: __("Select your weekly off day"), + }, + { + fieldname: "get_weekly_off_dates", + title: "Add Holidays", + description: __("Click on Add to Holidays. This will populate the holidays table with all the dates that fall on the selected weekly off. Repeat the process for populating the dates for all your weekly holidays"), + }, + { + fieldname: "holidays", + title: "Holidays", + description: __("Here, your weekly offs are pre-populated based on the previous selections. You can add more rows to also add public and national holidays individually.") + }, +]; diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.js b/erpnext/hr/doctype/hr_settings/hr_settings.js index ec99472d9b..6e26a1fa71 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.js +++ b/erpnext/hr/doctype/hr_settings/hr_settings.js @@ -2,7 +2,22 @@ // For license information, please see license.txt frappe.ui.form.on('HR Settings', { - restrict_backdated_leave_application: function(frm) { - frm.toggle_reqd("role_allowed_to_create_backdated_leave_application", frm.doc.restrict_backdated_leave_application); - } }); + +frappe.tour['HR Settings'] = [ + { + fieldname: 'emp_created_by', + title: 'Employee Naming By', + description: __('Employee can be named by Employee ID if you assign one, or via Naming Series. Select your preference here.'), + }, + { + fieldname: 'standard_working_hours', + title: 'Standard Working Hours', + description: __('Enter the Standard Working Hours for a normal work day. These hours will be used in calculations of reports such as Employee Hours Utilization and Project Profitability analysis.'), + }, + { + fieldname: 'leave_and_expense_claim_settings', + title: 'Leave and Expense Clain Settings', + description: __('Review various other settings related to Employee Leaves and Expense Claim') + } +]; diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 4bc066f334..5148435c13 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -7,34 +7,34 @@ "engine": "InnoDB", "field_order": [ "employee_settings", - "retirement_age", "emp_created_by", - "column_break_4", "standard_working_hours", - "expense_approver_mandatory_in_expense_claim", + "column_break_9", + "retirement_age", "reminders_section", "send_birthday_reminders", - "column_break_9", - "send_work_anniversary_reminders", "column_break_11", + "send_work_anniversary_reminders", + "column_break_18", "send_holiday_reminders", "frequency", - "leave_settings", + "leave_and_expense_claim_settings", "send_leave_notification", "leave_approval_notification_template", "leave_status_notification_template", - "role_allowed_to_create_backdated_leave_application", - "column_break_18", "leave_approver_mandatory_in_leave_application", + "restrict_backdated_leave_application", + "role_allowed_to_create_backdated_leave_application", + "column_break_29", + "expense_approver_mandatory_in_expense_claim", "show_leaves_of_all_department_members_in_calendar", "auto_leave_encashment", - "restrict_backdated_leave_application", - "hiring_settings", + "hiring_settings_section", "check_vacancies", "send_interview_reminder", "interview_reminder_template", "remind_before", - "column_break_29", + "column_break_4", "send_interview_feedback_reminder", "feedback_reminder_notification_template" ], @@ -45,17 +45,16 @@ "label": "Employee Settings" }, { - "description": "Enter retirement age in years", "fieldname": "retirement_age", "fieldtype": "Data", - "label": "Retirement Age" + "label": "Retirement Age (In Years)" }, { "default": "Naming Series", - "description": "Employee records are created using the selected field", + "description": "Employee records are created using the selected option", "fieldname": "emp_created_by", "fieldtype": "Select", - "label": "Employee Records to be created by", + "label": "Employee Naming By", "options": "Naming Series\nEmployee Number\nFull Name" }, { @@ -68,28 +67,6 @@ "fieldtype": "Check", "label": "Expense Approver Mandatory In Expense Claim" }, - { - "collapsible": 1, - "fieldname": "leave_settings", - "fieldtype": "Section Break", - "label": "Leave Settings" - }, - { - "depends_on": "eval: doc.send_leave_notification == 1", - "fieldname": "leave_approval_notification_template", - "fieldtype": "Link", - "label": "Leave Approval Notification Template", - "mandatory_depends_on": "eval: doc.send_leave_notification == 1", - "options": "Email Template" - }, - { - "depends_on": "eval: doc.send_leave_notification == 1", - "fieldname": "leave_status_notification_template", - "fieldtype": "Link", - "label": "Leave Status Notification Template", - "mandatory_depends_on": "eval: doc.send_leave_notification == 1", - "options": "Email Template" - }, { "fieldname": "column_break_18", "fieldtype": "Column Break" @@ -106,35 +83,18 @@ "fieldtype": "Check", "label": "Show Leaves Of All Department Members In Calendar" }, - { - "collapsible": 1, - "fieldname": "hiring_settings", - "fieldtype": "Section Break", - "label": "Hiring Settings" - }, - { - "default": "0", - "fieldname": "check_vacancies", - "fieldtype": "Check", - "label": "Check Vacancies On Job Offer Creation" - }, { "default": "0", "fieldname": "auto_leave_encashment", "fieldtype": "Check", "label": "Auto Leave Encashment" }, - { - "default": "0", - "fieldname": "restrict_backdated_leave_application", - "fieldtype": "Check", - "label": "Restrict Backdated Leave Application" - }, { "depends_on": "eval:doc.restrict_backdated_leave_application == 1", "fieldname": "role_allowed_to_create_backdated_leave_application", "fieldtype": "Link", "label": "Role Allowed to Create Backdated Leave Application", + "mandatory_depends_on": "eval:doc.restrict_backdated_leave_application == 1", "options": "Role" }, { @@ -143,11 +103,33 @@ "fieldtype": "Check", "label": "Send Leave Notification" }, + { + "depends_on": "eval: doc.send_leave_notification == 1", + "fieldname": "leave_approval_notification_template", + "fieldtype": "Link", + "label": "Leave Approval Notification Template", + "mandatory_depends_on": "eval: doc.send_leave_notification == 1", + "options": "Email Template" + }, + { + "depends_on": "eval: doc.send_leave_notification == 1", + "fieldname": "leave_status_notification_template", + "fieldtype": "Link", + "label": "Leave Status Notification Template", + "mandatory_depends_on": "eval: doc.send_leave_notification == 1", + "options": "Email Template" + }, { "fieldname": "standard_working_hours", "fieldtype": "Int", "label": "Standard Working Hours" }, + { + "collapsible": 1, + "fieldname": "leave_and_expense_claim_settings", + "fieldtype": "Section Break", + "label": "Leave and Expense Claim Settings" + }, { "default": "00:15:00", "depends_on": "send_interview_reminder", @@ -179,6 +161,7 @@ "fieldname": "frequency", "fieldtype": "Select", "label": "Set the frequency for holiday reminders", + "mandatory_depends_on": "send_holiday_reminders", "options": "Weekly\nMonthly" }, { @@ -226,13 +209,30 @@ "label": "Interview Reminder Notification Template", "mandatory_depends_on": "send_interview_reminder", "options": "Email Template" + }, + { + "default": "0", + "fieldname": "restrict_backdated_leave_application", + "fieldtype": "Check", + "label": "Restrict Backdated Leave Application" + }, + { + "fieldname": "hiring_settings_section", + "fieldtype": "Section Break", + "label": "Hiring Settings" + }, + { + "default": "0", + "fieldname": "check_vacancies", + "fieldtype": "Check", + "label": "Check Vacancies On Job Offer Creation" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2021-09-30 22:42:14.683983", + "modified": "2021-10-01 23:46:11.098236", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index d94764104d..9742387c16 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -1,14 +1,14 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch('employee','employee_name','employee_name'); +cur_frm.add_fetch('employee', 'employee_name', 'employee_name'); frappe.ui.form.on("Leave Allocation", { onload: function(frm) { // Ignore cancellation of doctype on cancel all. frm.ignore_doctypes_on_cancel_all = ["Leave Ledger Entry"]; - if(!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); + if (!frm.doc.from_date) frm.set_value("from_date", frappe.datetime.get_today()); frm.set_query("employee", function() { return { @@ -25,9 +25,9 @@ frappe.ui.form.on("Leave Allocation", { }, refresh: function(frm) { - if(frm.doc.docstatus === 1 && frm.doc.expired) { + if (frm.doc.docstatus === 1 && frm.doc.expired) { var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date); - if(valid_expiry) { + if (valid_expiry) { // expire current allocation frm.add_custom_button(__('Expire Allocation'), function() { frm.trigger("expire_allocation"); @@ -44,8 +44,8 @@ frappe.ui.form.on("Leave Allocation", { 'expiry_date': frappe.datetime.get_today() }, freeze: true, - callback: function(r){ - if(!r.exc){ + callback: function(r) { + if (!r.exc) { frappe.msgprint(__("Allocation Expired!")); } frm.refresh(); @@ -77,8 +77,8 @@ frappe.ui.form.on("Leave Allocation", { }, leave_policy: function(frm) { - if(frm.doc.leave_policy && frm.doc.leave_type) { - frappe.db.get_value("Leave Policy Detail",{ + if (frm.doc.leave_policy && frm.doc.leave_type) { + frappe.db.get_value("Leave Policy Detail", { 'parent': frm.doc.leave_policy, 'leave_type': frm.doc.leave_type }, 'annual_allocation', (r) => { @@ -91,13 +91,41 @@ frappe.ui.form.on("Leave Allocation", { return frappe.call({ method: "set_total_leaves_allocated", doc: frm.doc, - callback: function(r) { + callback: function() { frm.refresh_fields(); } - }) + }); } else if (cint(frm.doc.carry_forward) == 0) { frm.set_value("unused_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } }); + +frappe.tour["Leave Allocation"] = [ + { + fieldname: "employee", + title: "Employee", + description: __("Select the Employee for which you want to allocate leaves.") + }, + { + fieldname: "leave_type", + title: "Leave Type", + description: __("Select the Leave Type like Sick leave, Privilege Leave, Casual Leave, etc.") + }, + { + fieldname: "from_date", + title: "From Date", + description: __("Select the date from which this Leave Allocation will be valid.") + }, + { + fieldname: "to_date", + title: "To Date", + description: __("Select the date after which this Leave Allocation will expire.") + }, + { + fieldname: "new_leaves_allocated", + title: "New Leaves Allocated", + description: __("Enter the number of leaves you want to allocate for the period.") + } +]; diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 3a6539ece9..52ee463db0 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -219,7 +219,8 @@ "fieldname": "leave_policy_assignment", "fieldtype": "Link", "label": "Leave Policy Assignment", - "options": "Leave Policy Assignment" + "options": "Leave Policy Assignment", + "read_only": 1 }, { "fetch_from": "employee.company", @@ -236,7 +237,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-06-03 15:28:26.335104", + "modified": "2021-10-01 15:28:26.335104", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 2eaaeec88a..9e8cb5516f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -160,7 +160,7 @@ frappe.ui.form.on("Leave Application", { half_day_datepicker.update({ minDate: frappe.datetime.str_to_obj(frm.doc.from_date), maxDate: frappe.datetime.str_to_obj(frm.doc.to_date) - }) + }); }, get_leave_balance: function(frm) { @@ -174,7 +174,7 @@ frappe.ui.form.on("Leave Application", { leave_type: frm.doc.leave_type, consider_all_leaves_in_the_allocation_period: true }, - callback: function(r) { + callback: function (r) { if (!r.exc && r.message) { frm.set_value('leave_balance', r.message); } else { @@ -234,3 +234,36 @@ frappe.ui.form.on("Leave Application", { } } }); + +frappe.tour["Leave Application"] = [ + { + fieldname: "employee", + title: "Employee", + description: __("Select the Employee.") + }, + { + fieldname: "leave_type", + title: "Leave Type", + description: __("Select type of leave the employee wants to apply for, like Sick Leave, Privilege Leave, Casual Leave, etc.") + }, + { + fieldname: "from_date", + title: "From Date", + description: __("Select the start date for your Leave Application.") + }, + { + fieldname: "to_date", + title: "To Date", + description: __("Select the end date for your Leave Application.") + }, + { + fieldname: "half_day", + title: "Half Day", + description: __("To apply for a Half Day check 'Half Day' and select the Half Day Date") + }, + { + fieldname: "leave_approver", + title: "Leave Approver", + description: __("Select your Leave Approver i.e. the person who approves or rejects your leaves.") + } +]; diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 9e6fc6d0f1..349ed7ad22 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -76,6 +76,7 @@ class LeaveApplication(Document): # notify leave applier about approval if frappe.db.get_single_value("HR Settings", "send_leave_notification"): self.notify_employee() + self.create_leave_ledger_entry() self.reload() @@ -108,7 +109,13 @@ class LeaveApplication(Document): if frappe.db.get_single_value("HR Settings", "restrict_backdated_leave_application"): if self.from_date and getdate(self.from_date) < getdate(): allowed_role = frappe.db.get_single_value("HR Settings", "role_allowed_to_create_backdated_leave_application") - if allowed_role not in frappe.get_roles(): + user = frappe.get_doc("User", frappe.session.user) + user_roles = [d.role for d in user.roles] + if not allowed_role: + frappe.throw(_("Backdated Leave Application is restricted. Please set the {} in {}").format( + frappe.bold("Role Allowed to Create Backdated Leave Application"), get_link_to_form("HR Settings", "HR Settings"))) + + if (allowed_role and allowed_role not in user_roles): frappe.throw(_("Only users with the {0} role can create backdated leave applications").format(allowed_role)) if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index b9c785a8a9..629b20e768 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -121,6 +121,7 @@ class TestLeaveApplication(unittest.TestCase): application = self.get_application(_test_records[0]) application.insert() + application.reload() application.status = "Approved" self.assertRaises(LeaveDayBlockedError, application.submit) diff --git a/erpnext/hr/doctype/leave_type/leave_type.js b/erpnext/hr/doctype/leave_type/leave_type.js index 8622309848..b930dedaca 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.js +++ b/erpnext/hr/doctype/leave_type/leave_type.js @@ -2,3 +2,37 @@ frappe.ui.form.on("Leave Type", { refresh: function(frm) { } }); + + +frappe.tour["Leave Type"] = [ + { + fieldname: "max_leaves_allowed", + title: "Maximum Leave Allocation Allowed", + description: __("This field allows you to set the maximum number of leaves that can be allocated annually for this Leave Type while creating the Leave Policy") + }, + { + fieldname: "max_continuous_days_allowed", + title: "Maximum Consecutive Leaves Allowed", + description: __("This field allows you to set the maximum number of consecutive leaves an Employee can apply for.") + }, + { + fieldname: "is_optional_leave", + title: "Is Optional Leave", + description: __("Optional Leaves are holidays that Employees can choose to avail from a list of holidays published by the company.") + }, + { + fieldname: "is_compensatory", + title: "Is Compensatory Leave", + description: __("Leaves you can avail against a holiday you worked on. You can claim Compensatory Off Leave using Compensatory Leave request. Click") + "
    here " + __('to know more') + }, + { + fieldname: "allow_encashment", + title: "Allow Encashment", + description: __("From here, you can enable encashment for the balance leaves.") + }, + { + fieldname: "is_earned_leave", + title: "Is Earned Leaves", + description: __("Earned Leaves are leaves earned by an Employee after working with the company for a certain amount of time. Enabling this will allocate leaves on pro-rata basis by automatically updating Leave Allocation for leaves of this type at intervals set by 'Earned Leave Frequency.") + } +]; \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 8f2ae6eb15..06ca4cdedb 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -50,7 +50,7 @@ { "fieldname": "max_leaves_allowed", "fieldtype": "Int", - "label": "Max Leaves Allowed" + "label": "Maximum Leave Allocation Allowed" }, { "fieldname": "applicable_after", @@ -61,7 +61,7 @@ "fieldname": "max_continuous_days_allowed", "fieldtype": "Int", "in_list_view": 1, - "label": "Maximum Continuous Days Applicable", + "label": "Maximum Consecutive Leaves Allowed", "oldfieldname": "max_days_allowed", "oldfieldtype": "Data" }, @@ -87,6 +87,7 @@ }, { "default": "0", + "description": "These leaves are holidays permitted by the company however, availing it is optional for an Employee.", "fieldname": "is_optional_leave", "fieldtype": "Check", "label": "Is Optional Leave" @@ -205,6 +206,7 @@ }, { "depends_on": "eval:doc.is_ppl == 1", + "description": "For a day of leave taken, if you still pay (say) 50% of the daily salary, then enter 0.50 in this field.", "fieldname": "fraction_of_daily_salary_per_leave", "fieldtype": "Float", "label": "Fraction of Daily Salary per Leave", @@ -214,7 +216,7 @@ "icon": "fa fa-flag", "idx": 1, "links": [], - "modified": "2021-08-12 16:10:36.464690", + "modified": "2021-10-02 11:59:40.503359", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/module_onboarding/human_resource/human_resource.json b/erpnext/hr/module_onboarding/human_resource/human_resource.json index 518c002bca..cd11bd1102 100644 --- a/erpnext/hr/module_onboarding/human_resource/human_resource.json +++ b/erpnext/hr/module_onboarding/human_resource/human_resource.json @@ -13,17 +13,14 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/human-resources", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:47.018799", + "modified": "2021-05-19 05:32:01.794628", "modified_by": "Administrator", "module": "HR", "name": "Human Resource", "owner": "Administrator", "steps": [ { - "step": "Create Department" - }, - { - "step": "Create Designation" + "step": "HR Settings" }, { "step": "Create Holiday list" @@ -31,6 +28,9 @@ { "step": "Create Employee" }, + { + "step": "Data import" + }, { "step": "Create Leave Type" }, @@ -39,9 +39,6 @@ }, { "step": "Create Leave Application" - }, - { - "step": "HR Settings" } ], "subtitle": "Employee, Leaves, and more.", diff --git a/erpnext/hr/onboarding_step/create_employee/create_employee.json b/erpnext/hr/onboarding_step/create_employee/create_employee.json index 3aa33c6d86..47828186bf 100644 --- a/erpnext/hr/onboarding_step/create_employee/create_employee.json +++ b/erpnext/hr/onboarding_step/create_employee/create_employee.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-14 11:43:25.561152", + "description": "

    Employee

    \n\nAn individual who works and is recognized for his rights and duties in your company is your Employee. You can manage the Employee master. It captures the demographic, personal and professional details, joining and leave details, etc.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 12:26:28.629074", + "modified": "2021-05-19 04:50:02.240321", "modified_by": "Administrator", "name": "Create Employee", "owner": "Administrator", "reference_document": "Employee", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Employee", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json index 32472b4b3f..a08e85fff0 100644 --- a/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json +++ b/erpnext/hr/onboarding_step/create_holiday_list/create_holiday_list.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-28 11:47:34.700174", + "description": "

    Holiday List.

    \n\nHoliday List is a list which contains the dates of holidays. Most organizations have a standard Holiday List for their employees. However, some of them may have different holiday lists based on different Locations or Departments. In ERPNext, you can configure multiple Holiday Lists.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 12:25:38.068582", + "modified": "2021-05-19 04:19:52.305199", "modified_by": "Administrator", "name": "Create Holiday list", "owner": "Administrator", "reference_document": "Holiday List", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Holiday List", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json index fa9941e6b9..0b0ce3fc8b 100644 --- a/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json +++ b/erpnext/hr/onboarding_step/create_leave_allocation/create_leave_allocation.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-14 11:48:56.123718", + "description": "

    Leave Allocation

    \n\nLeave Allocation enables you to allocate a specific number of leaves of a particular type to an Employee so that, an employee will be able to create a Leave Application only if Leaves are allocated. ", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 11:48:56.123718", + "modified": "2021-05-19 04:22:34.220238", "modified_by": "Administrator", "name": "Create Leave Allocation", "owner": "Administrator", "reference_document": "Leave Allocation", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Leave Allocation", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json index 1ed074e9a1..af63aa59ed 100644 --- a/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json +++ b/erpnext/hr/onboarding_step/create_leave_application/create_leave_application.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-14 11:49:45.400764", + "description": "

    Leave Application

    \n\nLeave Application is a formal document created by an Employee to apply for Leaves for a particular time period based on there leave allocation and leave type according to there need.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-14 11:49:45.400764", + "modified": "2021-05-19 04:39:09.893474", "modified_by": "Administrator", "name": "Create Leave Application", "owner": "Administrator", "reference_document": "Leave Application", + "show_form_tour": 0, "show_full_form": 0, "title": "Create Leave Application", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json index 8cbfc5c81f..397f5cde49 100644 --- a/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json +++ b/erpnext/hr/onboarding_step/create_leave_type/create_leave_type.json @@ -1,18 +1,20 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Show Tour", "creation": "2020-05-27 11:17:31.119312", + "description": "

    Leave Type

    \n\nLeave type is defined based on many factors and features like encashment, earned leaves, partially paid, without pay and, a lot more. To check other options and to define your leave type click on Show Tour.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-20 11:17:31.119312", + "modified": "2021-05-19 04:32:48.135406", "modified_by": "Administrator", "name": "Create Leave Type", "owner": "Administrator", "reference_document": "Leave Type", + "show_form_tour": 0, "show_full_form": 1, "title": "Create Leave Type", "validate_action": 0 diff --git a/erpnext/hr/onboarding_step/data_import/data_import.json b/erpnext/hr/onboarding_step/data_import/data_import.json new file mode 100644 index 0000000000..ac343c6775 --- /dev/null +++ b/erpnext/hr/onboarding_step/data_import/data_import.json @@ -0,0 +1,21 @@ +{ + "action": "Watch Video", + "action_label": "", + "creation": "2021-05-19 05:29:16.809610", + "description": "

    Data Import

    \n\nData import is the tool to migrate your existing data like Employee, Customer, Supplier, and a lot more to our ERPNext system.\nGo through the video for a detailed explanation of this tool.", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2021-05-19 05:29:16.809610", + "modified_by": "Administrator", + "name": "Data import", + "owner": "Administrator", + "show_form_tour": 0, + "show_full_form": 0, + "title": "Data Import", + "validate_action": 1, + "video_url": "https://www.youtube.com/watch?v=DQyqeurPI64" +} \ No newline at end of file diff --git a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json index 0a1d0baf8a..355664fbc5 100644 --- a/erpnext/hr/onboarding_step/hr_settings/hr_settings.json +++ b/erpnext/hr/onboarding_step/hr_settings/hr_settings.json @@ -1,18 +1,20 @@ { - "action": "Update Settings", + "action": "Show Form Tour", + "action_label": "Explore", "creation": "2020-05-28 13:13:52.427711", + "description": "

    HR Settings

    \n\nHr Settings consists of major settings related to Employee Lifecycle, Leave Management, etc. Click on Explore, to explore Hr Settings.", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 1, "is_skipped": 0, - "modified": "2020-05-20 11:16:42.430974", + "modified": "2021-05-18 07:02:05.747548", "modified_by": "Administrator", "name": "HR Settings", "owner": "Administrator", "reference_document": "HR Settings", + "show_form_tour": 0, "show_full_form": 0, "title": "HR Settings", "validate_action": 0 diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9e38a9c7ea..3d5ecbd110 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -313,4 +313,4 @@ erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table -erpnext.patches.v13_0.add_default_interview_notification_templates +erpnext.patches.v13_0.add_default_interview_notification_templates \ No newline at end of file From 9f14695743f2d0243c572d2cd7285ea71c624687 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sat, 2 Oct 2021 21:02:07 +0530 Subject: [PATCH 184/416] fix: Display appropriate message if different Payment Terms are used in PE and its Payment References --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a5303215d5..8037ca16aa 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -390,6 +390,9 @@ class PaymentEntry(AccountsController): invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) for key, allocated_amount in iteritems(invoice_payment_amount_map): + if not invoice_paid_amount_map.get(key): + frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1])) + outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding')) discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt')) From 9244fe563a019cabf91028c56ff9edf0a1e8f54b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 2 Oct 2021 20:35:11 +0530 Subject: [PATCH 185/416] fix: Chart Of Accounts import button not visible (cherry picked from commit 3529622a0d28e9cdd6e602b5450ca79d13cfa0ed) --- .../chart_of_accounts_importer.js | 72 ++++++++++++------- .../chart_of_accounts_importer.py | 49 +++++++------ 2 files changed, 74 insertions(+), 47 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index f67c59c254..9766a463d1 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -12,11 +12,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1); frm.set_df_property('chart_preview', 'hidden', $(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1); - - // Show import button when file is successfully attached - if (frm.page && frm.page.show_import_button) { - create_import_button(frm); - } }, download_template: function(frm) { @@ -78,7 +73,12 @@ frappe.ui.form.on('Chart of Accounts Importer', { frm.page.set_indicator(""); $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file } else { - generate_tree_preview(frm); + frappe.run_serially([ + () => validate_coa(frm), + () => generate_tree_preview(frm), + () => create_import_button(frm), + () => frm.set_df_property('chart_preview', 'hidden', 0), + ]); } }, @@ -104,24 +104,26 @@ frappe.ui.form.on('Chart of Accounts Importer', { }); var create_import_button = function(frm) { - frm.page.set_primary_action(__("Import"), function () { - frappe.call({ - method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa", - args: { - file_name: frm.doc.import_file, - company: frm.doc.company - }, - freeze: true, - freeze_message: __("Creating Accounts..."), - callback: function(r) { - if(!r.exc) { - clearInterval(frm.page["interval"]); - frm.page.set_indicator(__('Import Successful'), 'blue'); - create_reset_button(frm); + if (frm.page.show_import_button) { + frm.page.set_primary_action(__("Import"), function () { + return frappe.call({ + method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa", + args: { + file_name: frm.doc.import_file, + company: frm.doc.company + }, + freeze: true, + freeze_message: __("Creating Accounts..."), + callback: function(r) { + if(!r.exc) { + clearInterval(frm.page["interval"]); + frm.page.set_indicator(__('Import Successful'), 'blue'); + create_reset_button(frm); + } } - } - }); - }).addClass('btn btn-primary'); + }); + }).addClass('btn btn-primary'); + } }; var create_reset_button = function(frm) { @@ -132,13 +134,35 @@ var create_reset_button = function(frm) { }).addClass('btn btn-primary'); }; +var validate_coa = function(frm) { + if (frm.doc.import_file) { + let parent = __('All Accounts'); + + return frappe.call({ + 'method': 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', + 'args': { + file_name: frm.doc.import_file, + parent: parent, + doctype: 'Chart of Accounts Importer', + file_type: frm.doc.file_type, + for_validate: 1 + }, + callback: function(r) { + if (r.message['show_import_button']) { + frm.page['show_import_button'] = Boolean(r.message['show_import_button']); + } + } + }) + } +}; + var generate_tree_preview = function(frm) { if (frm.doc.import_file) { let parent = __('All Accounts'); $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data // generate tree structure based on the csv data - new frappe.ui.Tree({ + return new frappe.ui.Tree({ parent: $(frm.fields_dict['chart_tree'].wrapper), label: parent, expandable: true, diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 9a0234a91f..fa61ca21f8 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -64,6 +64,7 @@ def import_coa(file_name, company): else: data = generate_data_from_excel(file_doc, extension) + frappe.local.flags.ignore_root_company_validation = True forest = build_forest(data) create_charts(company, custom_chart=forest) @@ -128,7 +129,7 @@ def generate_data_from_excel(file_doc, extension, as_dict=False): return data @frappe.whitelist() -def get_coa(doctype, parent, is_root=False, file_name=None): +def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0): ''' called by tree view (to fetch node's children) ''' file_doc, extension = get_file(file_name) @@ -140,14 +141,20 @@ def get_coa(doctype, parent, is_root=False, file_name=None): data = generate_data_from_excel(file_doc, extension) validate_columns(data) - validate_accounts(data) - forest = build_forest(data) - accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form + validate_accounts(file_doc, extension) - # filter out to show data for the selected node only - accounts = [d for d in accounts if d['parent_account']==parent] + if not for_validate: + forest = build_forest(data) + accounts = build_tree_from_json("", chart_data=forest) # returns a list of dict in a tree render-able form - return accounts + # filter out to show data for the selected node only + accounts = [d for d in accounts if d['parent_account']==parent] + + return accounts + else: + return { + 'show_import_button': 1 + } def build_forest(data): ''' @@ -279,7 +286,7 @@ def get_template(template_type): def get_sample_template(writer): template = [ ["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"], - ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], + # ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], ["Equity", "", "", "", 1, "", "Equity"], ["Expenses", "", "", "", 1, "", "Expense"], ["Income", "", "", "", 1, "", "Income"], @@ -304,10 +311,7 @@ def get_sample_template(writer): @frappe.whitelist() -def validate_accounts(file_name): - - file_doc, extension = get_file(file_name) - +def validate_accounts(file_doc, extension): if extension == 'csv': accounts = generate_data_from_csv(file_doc, as_dict=True) else: @@ -326,8 +330,6 @@ def validate_accounts(file_name): validate_root(accounts_dict) - validate_account_types(accounts_dict) - return [True, len(accounts)] def validate_root(accounts): @@ -340,9 +342,19 @@ def validate_root(accounts): elif account.get("root_type") not in get_root_types() and account.get("account_name"): error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name"))) + validate_missing_roots(roots) + if error_messages: frappe.throw("
    ".join(error_messages)) +def validate_missing_roots(roots): + root_types_added = set(d.get('root_type') for d in roots) + + missing = list(set(get_root_types()) - root_types_added) + + if missing: + frappe.throw(_("Please add Root Account for - {0}").format(' , '.join(missing))) + def get_root_types(): return ('Asset', 'Liability', 'Expense', 'Income', 'Equity') @@ -368,15 +380,6 @@ def get_mandatory_account_types(): {'account_type': 'Stock', 'root_type': 'Asset'} ] - -def validate_account_types(accounts): - account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"] - account_types = [accounts[d]["account_type"] for d in accounts if not cint(accounts[d]['is_group']) == 1] - - missing = list(set(account_types_for_ledger) - set(account_types)) - if missing: - frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))) - def unset_existing_data(company): linked = frappe.db.sql('''select fieldname from tabDocField where fieldtype="Link" and options="Account" and parent="Company"''', as_dict=True) From 237b1a91de8ab2786b4530503719ef07caf65d12 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 2 Oct 2021 20:37:15 +0530 Subject: [PATCH 186/416] fix: Remove unwanted comments (cherry picked from commit e4b89d2fcd6c1eb788f94ad5f9563ccec07a0f2a) --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index fa61ca21f8..bd2a6f1b08 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -286,7 +286,7 @@ def get_template(template_type): def get_sample_template(writer): template = [ ["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"], - # ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], + ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"], ["Equity", "", "", "", 1, "", "Equity"], ["Expenses", "", "", "", 1, "", "Expense"], ["Income", "", "", "", 1, "", "Income"], From f109f6fd16920152b6f13b83cec8e470beafc83b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 2 Oct 2021 20:46:20 +0530 Subject: [PATCH 187/416] fix: Linting issues (cherry picked from commit ff570f48a0ba56925528eb8696b8b7d862252d8f) --- .../chart_of_accounts_importer/chart_of_accounts_importer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index 9766a463d1..66a269e7a7 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -115,7 +115,7 @@ var create_import_button = function(frm) { freeze: true, freeze_message: __("Creating Accounts..."), callback: function(r) { - if(!r.exc) { + if (!r.exc) { clearInterval(frm.page["interval"]); frm.page.set_indicator(__('Import Successful'), 'blue'); create_reset_button(frm); @@ -152,7 +152,7 @@ var validate_coa = function(frm) { frm.page['show_import_button'] = Boolean(r.message['show_import_button']); } } - }) + }); } }; From da47fe2cfb05be5de65b8fc5581252def925b2a2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 30 Sep 2021 13:28:53 +0530 Subject: [PATCH 188/416] fix(India): Internal transfer check fix (cherry picked from commit f0af24fc6d26040fa34fd7248bf38301897d9ca1) --- erpnext/regional/india/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0faf80b002..94936143d8 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -249,6 +249,9 @@ def is_internal_transfer(party_details, doctype): elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): destination_gstin = party_details.supplier_gstin + if not destination_gstin or party_details.gstin: + return False + if party_details.gstin == destination_gstin: return True else: From 01bea14622f6905f6a9034f0dd388f4e68821bd0 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 Oct 2021 09:58:38 +0530 Subject: [PATCH 189/416] fix: sider --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 7cd45c0ed5..d708f9209f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -81,7 +81,7 @@ website_generators = ["Item Group", "Item", "BOM", "Sales Partner", "Job Opening", "Student Admission"] website_context = { - "favicon": "/assets/erpnext/images/erpnext-favicon.svg", + "favicon": "/assets/erpnext/images/erpnext-favicon.svg", "splash_image": "/assets/erpnext/images/erpnext-logo.svg" } From 1b7414e948c89a86cd1d6bad3d0ec286440f00a5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 16 Sep 2021 15:54:48 +0530 Subject: [PATCH 190/416] fix: cannot add deductions in internal transfer payment entry --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index a5303215d5..dc0b585c96 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -502,12 +502,13 @@ class PaymentEntry(AccountsController): def validate_received_amount(self): if self.paid_from_account_currency == self.paid_to_account_currency: - if self.paid_amount != self.received_amount: - frappe.throw(_("Received Amount cannot be greater than Paid Amount")) + if self.paid_amount < self.received_amount: + frappe.throw(_("Received Amount cannot be greater tha Paid Amount")) def set_received_amount(self): self.base_received_amount = self.base_paid_amount - if self.paid_from_account_currency == self.paid_to_account_currency: + if self.paid_from_account_currency == self.paid_to_account_currency \ + and not self.payment_type == 'Internal Transfer': self.received_amount = self.paid_amount def set_amounts_after_tax(self): From 9051735529dfac9c890b0c8078c3986bb6316c4d Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 4 Oct 2021 11:44:46 +0530 Subject: [PATCH 191/416] perf: fetching of account balance in chart of accounts (#27661) --- .../accounts/doctype/account/account_tree.js | 61 +++++++++++++------ erpnext/accounts/utils.py | 29 ++++++--- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 7516134baf..a4b6e0b45a 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -45,6 +45,49 @@ frappe.treeview_settings["Account"] = { ], root_label: "Accounts", get_tree_nodes: 'erpnext.accounts.utils.get_children', + on_get_node: function(nodes, deep=false) { + if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; + + let accounts = []; + if (deep) { + // in case of `get_all_nodes` + accounts = nodes.reduce((acc, node) => [...acc, ...node.data], []); + } else { + accounts = nodes; + } + + const get_balances = frappe.call({ + method: 'erpnext.accounts.utils.get_account_balances', + args: { + accounts: accounts, + company: cur_tree.args.company + }, + }); + + get_balances.then(r => { + if (!r.message || r.message.length == 0) return; + + for (let account of r.message) { + + const node = cur_tree.nodes && cur_tree.nodes[account.value]; + if (!node || node.is_root) continue; + + // show Dr if positive since balance is calculated as debit - credit else show Cr + const balance = account.balance_in_account_currency || account.balance; + const dr_or_cr = balance > 0 ? "Dr": "Cr"; + const format = (value, currency) => format_currency(Math.abs(value), currency); + + if (account.balance!==undefined) { + $('' + + (account.balance_in_account_currency ? + (format(account.balance_in_account_currency, account.account_currency) + " / ") : "") + + format(account.balance, account.company_currency) + + " " + dr_or_cr + + '').insertBefore(node.$ul); + } + } + }); + }, add_tree_node: 'erpnext.accounts.utils.add_ac', menu_items:[ { @@ -122,24 +165,6 @@ frappe.treeview_settings["Account"] = { } }, "add"); }, - onrender: function(node) { - if (frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { - - // show Dr if positive since balance is calculated as debit - credit else show Cr - let balance = node.data.balance_in_account_currency || node.data.balance; - let dr_or_cr = balance > 0 ? "Dr": "Cr"; - - if (node.data && node.data.balance!==undefined) { - $('' - + (node.data.balance_in_account_currency ? - (format_currency(Math.abs(node.data.balance_in_account_currency), - node.data.account_currency) + " / ") : "") - + format_currency(Math.abs(node.data.balance), node.data.company_currency) - + " " + dr_or_cr - + '').insertBefore(node.$ul); - } - } - }, toolbar: [ { label:__("Add Child"), diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index fbad171b78..fdd8d092eb 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -4,11 +4,14 @@ from __future__ import unicode_literals +from json import loads + import frappe import frappe.defaults from frappe import _, throw from frappe.model.meta import get_field_precision from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate +from six import string_types import erpnext @@ -787,16 +790,28 @@ def get_children(doctype, parent, company, is_root=False): if doctype == 'Account': sort_accounts(acc, is_root, key="value") - company_currency = frappe.get_cached_value('Company', company, "default_currency") - for each in acc: - each["company_currency"] = company_currency - each["balance"] = flt(get_balance_on(each.get("value"), in_account_currency=False, company=company)) - - if each.account_currency != company_currency: - each["balance_in_account_currency"] = flt(get_balance_on(each.get("value"), company=company)) return acc +@frappe.whitelist() +def get_account_balances(accounts, company): + + if isinstance(accounts, string_types): + accounts = loads(accounts) + + if not accounts: + return [] + + company_currency = frappe.get_cached_value("Company", company, "default_currency") + + for account in accounts: + account["company_currency"] = company_currency + account["balance"] = flt(get_balance_on(account["value"], in_account_currency=False, company=company)) + if account["account_currency"] and account["account_currency"] != company_currency: + account["balance_in_account_currency"] = flt(get_balance_on(account["value"], company=company)) + + return accounts + def create_payment_gateway_account(gateway, payment_channel="Email"): from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account From b483f173a6ea1082d9e5b6e0c232f99f0367fa73 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 <81952590+Komal-Saraf0609@users.noreply.github.com> Date: Mon, 4 Oct 2021 15:42:43 +0530 Subject: [PATCH 192/416] fix: Merge "Accounting Ledger" and "Accounts Receivable" in "View" button (#27769) * fix: Added a new button "View" and merged "Accounting Ledger" and "Accounts Receivable" into it * fix: sider issues * chore: dead code --- erpnext/selling/doctype/customer/customer.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index cb00019cf5..4b0bbd5a11 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -116,14 +116,15 @@ frappe.ui.form.on("Customer", { frappe.contacts.render_address_and_contact(frm); // custom buttons - frm.add_custom_button(__('Accounting Ledger'), function() { - frappe.set_route('query-report', 'General Ledger', - {party_type:'Customer', party:frm.doc.name}); - }); - frm.add_custom_button(__('Accounts Receivable'), function() { + frm.add_custom_button(__('Accounts Receivable'), function () { frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name}); - }); + }, __('View')); + + frm.add_custom_button(__('Accounting Ledger'), function () { + frappe.set_route('query-report', 'General Ledger', + {party_type: 'Customer', party: frm.doc.name}); + }, __('View')); frm.add_custom_button(__('Pricing Rule'), function () { erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name); From 09ccdee2dbb1c56b553a531d95497860830ec90b Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 4 Oct 2021 16:41:24 +0530 Subject: [PATCH 193/416] feat: add `total_billing_hours` to Sales Invoice (fp #26783) (#27742) * feat: add `total_billing_hours` to Sales Invoice * fix: re-save doctypes * fix: indentation * fix: replace reference to old function --- .../doctype/sales_invoice/sales_invoice.js | 176 ++++++++++-------- .../doctype/sales_invoice/sales_invoice.json | 10 +- .../doctype/sales_invoice/sales_invoice.py | 11 +- .../sales_invoice_timesheet.json | 50 ++++- .../projects/doctype/timesheet/timesheet.py | 50 +++-- 5 files changed, 192 insertions(+), 105 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 828d5bd54c..73e1284304 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -453,7 +453,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e let row = frappe.get_doc(d.doctype, d.name) set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail) }); - calculate_total_billing_amount(this.frm); + frm.trigger("calculate_timesheet_totals"); } } }; @@ -725,19 +725,6 @@ frappe.ui.form.on('Sales Invoice', { } }, - project: function(frm){ - if (!frm.doc.is_return) { - frm.call({ - method: "add_timesheet_data", - doc: frm.doc, - callback: function(r, rt) { - refresh_field(['timesheets']) - } - }) - frm.refresh(); - } - }, - onload: function(frm) { frm.redemption_conversion_factor = null; }, @@ -848,25 +835,92 @@ frappe.ui.form.on('Sales Invoice', { } }, - add_timesheet_row: function(frm, row, exchange_rate) { - frm.add_child('timesheets', { - 'activity_type': row.activity_type, - 'description': row.description, - 'time_sheet': row.parent, - 'billing_hours': row.billing_hours, - 'billing_amount': flt(row.billing_amount) * flt(exchange_rate), - 'timesheet_detail': row.name, - 'project_name': row.project_name + project: function(frm) { + if (frm.doc.project) { + frm.events.add_timesheet_data(frm, { + project: frm.doc.project + }); + } + }, + + async add_timesheet_data(frm, kwargs) { + if (kwargs === "Sales Invoice") { + // called via frm.trigger() + kwargs = Object(); + } + + if (!kwargs.hasOwnProperty("project") && frm.doc.project) { + kwargs.project = frm.doc.project; + } + + const timesheets = await frm.events.get_timesheet_data(frm, kwargs); + return frm.events.set_timesheet_data(frm, timesheets); + }, + + async get_timesheet_data(frm, kwargs) { + return frappe.call({ + method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data", + args: kwargs + }).then(r => { + if (!r.exc && r.message.length > 0) { + return r.message + } else { + return [] + } }); - frm.refresh_field('timesheets'); - calculate_total_billing_amount(frm); + }, + + set_timesheet_data: function(frm, timesheets) { + frm.clear_table("timesheets") + timesheets.forEach(timesheet => { + if (frm.doc.currency != timesheet.currency) { + frappe.call({ + method: "erpnext.setup.utils.get_exchange_rate", + args: { + from_currency: timesheet.currency, + to_currency: frm.doc.currency + }, + callback: function(r) { + if (r.message) { + exchange_rate = r.message; + frm.events.append_time_log(frm, timesheet, exchange_rate); + } + } + }); + } else { + frm.events.append_time_log(frm, timesheet, 1.0); + } + }); + }, + + append_time_log: function(frm, time_log, exchange_rate) { + const row = frm.add_child("timesheets"); + row.activity_type = time_log.activity_type; + row.description = time_log.description; + row.time_sheet = time_log.time_sheet; + row.from_time = time_log.from_time; + row.to_time = time_log.to_time; + row.billing_hours = time_log.billing_hours; + row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate); + row.timesheet_detail = time_log.name; + row.project_name = time_log.project_name; + + frm.refresh_field("timesheets"); + frm.trigger("calculate_timesheet_totals"); + }, + + calculate_timesheet_totals: function(frm) { + frm.set_value("total_billing_amount", + frm.doc.timesheets.reduce((a, b) => a + (b["billing_amount"] || 0.0), 0.0)); + frm.set_value("total_billing_hours", + frm.doc.timesheets.reduce((a, b) => a + (b["billing_hours"] || 0.0), 0.0)); }, refresh: function(frm) { if (frm.doc.docstatus===0 && !frm.doc.is_return) { - frm.add_custom_button(__('Fetch Timesheet'), function() { + frm.add_custom_button(__("Fetch Timesheet"), function() { let d = new frappe.ui.Dialog({ - title: __('Fetch Timesheet'), + title: __("Fetch Timesheet"), fields: [ { "label" : __("From"), @@ -875,8 +929,8 @@ frappe.ui.form.on('Sales Invoice', { "reqd": 1, }, { - fieldtype: 'Column Break', - fieldname: 'col_break_1', + fieldtype: "Column Break", + fieldname: "col_break_1", }, { "label" : __("To"), @@ -893,48 +947,18 @@ frappe.ui.form.on('Sales Invoice', { }, ], primary_action: function() { - let data = d.get_values(); - frappe.call({ - method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data", - args: { - from_time: data.from_time, - to_time: data.to_time, - project: data.project - }, - callback: function(r) { - if (!r.exc && r.message.length > 0) { - frm.clear_table('timesheets') - r.message.forEach((d) => { - let exchange_rate = 1.0; - if (frm.doc.currency != d.currency) { - frappe.call({ - method: 'erpnext.setup.utils.get_exchange_rate', - args: { - from_currency: d.currency, - to_currency: frm.doc.currency - }, - callback: function(r) { - if (r.message) { - exchange_rate = r.message; - frm.events.add_timesheet_row(frm, d, exchange_rate); - } - } - }); - } else { - frm.events.add_timesheet_row(frm, d, exchange_rate); - } - }); - } else { - frappe.msgprint(__('No Timesheets found with the selected filters.')) - } - d.hide(); - } + const data = d.get_values(); + frm.events.add_timesheet_data(frm, { + from_time: data.from_time, + to_time: data.to_time, + project: data.project }); + d.hide(); }, - primary_action_label: __('Get Timesheets') + primary_action_label: __("Get Timesheets") }); d.show(); - }) + }); } if (frm.doc.is_debit_note) { @@ -967,26 +991,20 @@ frappe.ui.form.on('Sales Invoice', { frm: frm }); }, + create_dunning: function(frm) { frappe.model.open_mapped_doc({ method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.create_dunning", frm: frm }); } -}) +}); -var calculate_total_billing_amount = function(frm) { - var doc = frm.doc; - - doc.total_billing_amount = 0.0 - if (doc.timesheets) { - doc.timesheets.forEach((d) => { - doc.total_billing_amount += flt(d.billing_amount) - }); +frappe.ui.form.on("Sales Invoice Timesheet", { + timesheets_remove(frm) { + frm.trigger("calculate_timesheet_totals"); } - - refresh_field('total_billing_amount') -} +}); var set_timesheet_detail_rate = function(cdt, cdn, currency, timelog) { frappe.call({ diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 2d6c04ebf9..f3adb898aa 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -74,6 +74,7 @@ "time_sheet_list", "timesheets", "total_billing_amount", + "total_billing_hours", "section_break_30", "total_qty", "base_total", @@ -2011,6 +2012,13 @@ "hidden": 1, "label": "Ignore Default Payment Terms Template", "read_only": 1 + }, + { + "fieldname": "total_billing_hours", + "fieldtype": "Float", + "label": "Total Billing Hours", + "print_hide": 1, + "read_only": 1 } ], "icon": "fa fa-file-text", @@ -2023,7 +2031,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-09-28 13:09:34.391799", + "modified": "2021-10-02 03:36:10.251715", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 100d943037..eb26aa2afa 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -758,7 +758,7 @@ class SalesInvoice(SellingController): if self.project: for data in get_projectwise_timesheet_data(self.project): self.append('timesheets', { - 'time_sheet': data.parent, + 'time_sheet': data.time_sheet, 'billing_hours': data.billing_hours, 'billing_amount': data.billing_amount, 'timesheet_detail': data.name, @@ -769,12 +769,11 @@ class SalesInvoice(SellingController): self.calculate_billing_amount_for_timesheet() def calculate_billing_amount_for_timesheet(self): - total_billing_amount = 0.0 - for data in self.timesheets: - if data.billing_amount: - total_billing_amount += data.billing_amount + def timesheet_sum(field): + return sum((ts.get(field) or 0.0) for ts in self.timesheets) - self.total_billing_amount = total_billing_amount + self.total_billing_amount = timesheet_sum("billing_amount") + self.total_billing_hours = timesheet_sum("billing_hours") def get_warehouse(self): user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile` diff --git a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json index c90297328e..69b7c129f0 100644 --- a/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json +++ b/erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json @@ -7,12 +7,19 @@ "field_order": [ "activity_type", "description", - "billing_hours", - "billing_amount", + "section_break_3", + "from_time", "column_break_5", + "to_time", + "section_break_7", + "billing_hours", + "column_break_9", + "billing_amount", + "section_break_11", "time_sheet", - "project_name", - "timesheet_detail" + "timesheet_detail", + "column_break_13", + "project_name" ], "fields": [ { @@ -64,20 +71,53 @@ "label": "Description", "read_only": 1 }, + { + "fieldname": "from_time", + "fieldtype": "Datetime", + "label": "From Time" + }, + { + "fieldname": "to_time", + "fieldtype": "Datetime", + "label": "To Time" + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break", + "label": "Time" + }, { "fieldname": "column_break_5", "fieldtype": "Column Break" }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break", + "label": "Totals" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Reference" + }, { "fieldname": "project_name", "fieldtype": "Data", "label": "Project Name", "read_only": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2021-06-08 14:43:02.748981", + "modified": "2021-10-02 03:48:44.979777", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Timesheet", diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index e144e82a7d..363c3b6a3c 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -215,25 +215,47 @@ class Timesheet(Document): @frappe.whitelist() def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to_time=None): - condition = '' + condition = "" if project: - condition += "and tsd.project = %(project)s" + condition += "AND tsd.project = %(project)s " if parent: - condition += "AND tsd.parent = %(parent)s" + condition += "AND tsd.parent = %(parent)s " if from_time and to_time: condition += "AND CAST(tsd.from_time as DATE) BETWEEN %(from_time)s AND %(to_time)s" - return frappe.db.sql("""SELECT tsd.name as name, - tsd.parent as parent, tsd.billing_hours as billing_hours, - tsd.billing_amount as billing_amount, tsd.activity_type as activity_type, - tsd.description as description, ts.currency as currency, - tsd.project_name as project_name - FROM `tabTimesheet Detail` tsd - INNER JOIN `tabTimesheet` ts ON ts.name = tsd.parent - WHERE tsd.parenttype = 'Timesheet' - and tsd.docstatus=1 {0} - and tsd.is_billable = 1 - and tsd.sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) + query = f""" + SELECT + tsd.name as name, + tsd.parent as time_sheet, + tsd.from_time as from_time, + tsd.to_time as to_time, + tsd.billing_hours as billing_hours, + tsd.billing_amount as billing_amount, + tsd.activity_type as activity_type, + tsd.description as description, + ts.currency as currency, + tsd.project_name as project_name + FROM `tabTimesheet Detail` tsd + INNER JOIN `tabTimesheet` ts + ON ts.name = tsd.parent + WHERE + tsd.parenttype = 'Timesheet' + AND tsd.docstatus = 1 + AND tsd.is_billable = 1 + AND tsd.sales_invoice is NULL + {condition} + ORDER BY tsd.from_time ASC + """ + + filters = { + "project": project, + "parent": parent, + "from_time": from_time, + "to_time": to_time + } + + return frappe.db.sql(query, filters, as_dict=1) + @frappe.whitelist() def get_timesheet_detail_rate(timelog, currency): From 4159361d52e84e9e70ce0e045822cdf3993e8200 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 4 Oct 2021 18:02:37 +0530 Subject: [PATCH 194/416] ci(Mergify): configuration update (#27777) Signed-off-by: Ankush Menat --- .mergify.yml | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .mergify.yml diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 0000000000..f3d04096cf --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,58 @@ +pull_request_rules: + - name: Auto-close PRs on stable branch + conditions: + - and: + - and: + - author!=surajshetty3416 + - author!=gavindsouza + - author!=rohitwaghchaure + - author!=nabinhait + - or: + - base=version-13 + - base=version-12 + actions: + close: + comment: + message: | + @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch. + https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch + + - name: backport to version-13-hotfix + conditions: + - label="backport version-13-hotfix" + actions: + backport: + branches: + - version-13-hotfix + assignees: + - "{{ author }}" + + - name: backport to version-13-pre-release + conditions: + - label="backport version-13-pre-release" + actions: + backport: + branches: + - version-13-pre-release + assignees: + - "{{ author }}" + + - name: backport to version-12-hotfix + conditions: + - label="backport version-12-hotfix" + actions: + backport: + branches: + - version-12-hotfix + assignees: + - "{{ author }}" + + - name: backport to version-12-pre-release + conditions: + - label="backport version-12-pre-release" + actions: + backport: + branches: + - version-12-pre-release + assignees: + - "{{ author }}" \ No newline at end of file From 5c372202d5fb024843fc1b13f843901f832532f7 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Mon, 4 Oct 2021 22:38:04 +0530 Subject: [PATCH 195/416] fix: set item uom as stock_uom if it isn't set (#27623) * fix: set item uom as stock_uom if it isn't set --- erpnext/stock/doctype/stock_entry/stock_entry.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 157904bc34..ac8303eda3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -323,6 +323,12 @@ frappe.ui.form.on('Stock Entry', { attach_bom_items(frm.doc.bom_no) }, + before_save: function(frm) { + frm.doc.items.forEach((item) => { + item.uom = item.uom || item.stock_uom; + }) + }, + stock_entry_type: function(frm){ frm.remove_custom_button('Bill of Materials', "Get Items From"); frm.events.show_bom_custom_button(frm); From 38c7e42f0c4e8be7d36b3ebd61d26fed28c08ea5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 5 Oct 2021 10:14:01 +0530 Subject: [PATCH 196/416] fix: Delete linked Transaction Deletion Record docs on deleting company --- erpnext/setup/doctype/company/company.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 6257d560ec..87d67a5f9d 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -388,6 +388,7 @@ class Company(NestedSet): frappe.db.sql("delete from tabEmployee where company=%s", self.name) frappe.db.sql("delete from tabDepartment where company=%s", self.name) frappe.db.sql("delete from `tabTax Withholding Account` where company=%s", self.name) + frappe.db.sql("delete from `tabTransaction Deletion Record` where company=%s", self.name) # delete tax templates frappe.db.sql("delete from `tabSales Taxes and Charges Template` where company=%s", self.name) From 065a2ce98330b57043a6b202a1814e106145128b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 5 Oct 2021 11:14:16 +0530 Subject: [PATCH 197/416] fix(asset): expected value after useful life validation (#27539) --- erpnext/assets/doctype/asset/asset.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 39f102e143..7e135be30b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -140,11 +140,6 @@ class Asset(AccountsController): if self.is_existing_asset: return - docname = self.purchase_receipt or self.purchase_invoice - if docname: - doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice' - date = frappe.db.get_value(doctype, docname, 'posting_date') - if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): frappe.throw(_("Available-for-use Date should be after purchase date")) @@ -440,9 +435,10 @@ class Asset(AccountsController): if accumulated_depreciation_after_full_schedule: accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule) - asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule), - self.precision('gross_purchase_amount')) + asset_value_after_full_schedule = flt( + flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation) - + flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount')) if (row.expected_value_after_useful_life and row.expected_value_after_useful_life < asset_value_after_full_schedule): From ad03eb25df6c07beb744a0fe3003a4c6fb19b98e Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Tue, 5 Oct 2021 12:26:59 +0530 Subject: [PATCH 198/416] fix: Only calculate first_respone_time if SLA is set (#27789) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/support/doctype/issue/issue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 7d7399d097..0fe1068a76 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -228,7 +228,7 @@ def get_time_in_timedelta(time): def set_first_response_time(communication, method): if communication.get('reference_doctype') == "Issue": issue = get_parent_doc(communication) - if is_first_response(issue): + if is_first_response(issue) and issue.service_level_agreement: first_response_time = calculate_first_response_time(issue, get_datetime(issue.first_responded_on)) issue.db_set("first_response_time", first_response_time) From 7da777880bea281bd6612a3b229dd944feccf8ee Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Tue, 5 Oct 2021 12:35:23 +0530 Subject: [PATCH 199/416] fix: add (uom, brand) Item details in an Item Price (#27561) * fix: add (uom, brand) and update (uom) Item details in an Item Price * fix: order of query interpolation args Co-authored-by: Marica * fix: named interpolation, remove item price * fix: sql error Co-authored-by: Marica Co-authored-by: Ankush Menat --- erpnext/stock/doctype/item/item.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 768e5eae2d..8cc9f74a42 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -181,6 +181,8 @@ class Item(WebsiteGenerator): "doctype": "Item Price", "price_list": price_list, "item_code": self.name, + "uom": self.stock_uom, + "brand": self.brand, "currency": erpnext.get_default_currency(), "price_list_rate": self.standard_rate }) @@ -634,9 +636,21 @@ class Item(WebsiteGenerator): _("An Item Group exists with same name, please change the item name or rename the item group")) def update_item_price(self): - frappe.db.sql("""update `tabItem Price` set item_name=%s, - item_description=%s, brand=%s where item_code=%s""", - (self.item_name, self.description, self.brand, self.name)) + frappe.db.sql(""" + UPDATE `tabItem Price` + SET + item_name=%(item_name)s, + item_description=%(item_description)s, + brand=%(brand)s + WHERE item_code=%(item_code)s + """, + dict( + item_name=self.item_name, + item_description=self.description, + brand=self.brand, + item_code=self.name + ) + ) def on_trash(self): super(Item, self).on_trash() From f37e4d52d5b30f794fde4f14513ab63c4c538829 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Tue, 5 Oct 2021 12:49:08 +0530 Subject: [PATCH 200/416] docs: frappe school link in readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 847904d1dd..87d7d73d5a 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ The ERPNext code is licensed as GNU General Public License (v3) and the Document --- +## Learning + +1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. + +--- + ## Logo and Trademark The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd. From f5e0cad6a1c3036bff7a07a4dcc77766ffa36c79 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 19 Aug 2021 14:18:50 +0530 Subject: [PATCH 201/416] refactor: updated onboarding cards and tours --- .../buying_settings/buying_settings.js | 4 +- .../buying_settings/buying_settings.json | 77 +++++++++++++++ .../purchase_order/purchase_order.json | 82 ++++++++++++++++ .../module_onboarding/buying/buying.json | 14 +-- .../create_a_material_request.json | 10 +- .../create_your_first_purchase_order.json | 10 +- .../introduction_to_buying.json | 15 +-- .../material_request/material_request.json | 97 +++++++++++++++++++ 8 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 erpnext/buying/form_tour/buying_settings/buying_settings.json create mode 100644 erpnext/buying/form_tour/purchase_order/purchase_order.json create mode 100644 erpnext/stock/form_tour/material_request/material_request.json diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js index e496e9628d..32431fc391 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.js +++ b/erpnext/buying/doctype/buying_settings/buying_settings.js @@ -11,7 +11,7 @@ frappe.tour['Buying Settings'] = [ { fieldname: "supp_master_name", title: "Supplier Naming By", - description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a ") + "Naming Series" + __(" choose the 'Naming Series' option."), + description: __("By default, the Supplier Name is set as per the Supplier Name entered. If you want Suppliers to be named by a Naming Series choose the 'Naming Series' option."), }, { fieldname: "buying_price_list", @@ -28,4 +28,4 @@ frappe.tour['Buying Settings'] = [ title: "Purchase Receipt Required for Purchase Invoice Creation", description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first. This configuration can be overridden for a particular supplier by enabling the 'Allow Purchase Invoice Creation Without Purchase Receipt' checkbox in the Supplier master.") } -]; \ No newline at end of file +]; diff --git a/erpnext/buying/form_tour/buying_settings/buying_settings.json b/erpnext/buying/form_tour/buying_settings/buying_settings.json new file mode 100644 index 0000000000..fa8c80d6cd --- /dev/null +++ b/erpnext/buying/form_tour/buying_settings/buying_settings.json @@ -0,0 +1,77 @@ +{ + "creation": "2021-07-28 11:51:42.319984", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-10-05 13:06:56.414584", + "modified_by": "Administrator", + "module": "Buying", + "name": "Buying Settings", + "owner": "Administrator", + "reference_doctype": "Buying Settings", + "save_on_complete": 0, + "steps": [ + { + "description": "When a Supplier is saved, system generates a unique identity or name for that Supplier which can be used to refer the Supplier in various Buying transactions.", + "field": "", + "fieldname": "supp_master_name", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Supplier Naming By", + "parent_field": "", + "position": "Bottom", + "title": "Supplier Naming By" + }, + { + "description": "Configure what should be the default value of Supplier Group when creating a new Supplier.", + "field": "", + "fieldname": "supplier_group", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Supplier Group", + "parent_field": "", + "position": "Right", + "title": "Default Supplier Group" + }, + { + "description": "Item prices will be fetched from this Price List.", + "field": "", + "fieldname": "buying_price_list", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Default Buying Price List", + "parent_field": "", + "position": "Bottom", + "title": "Default Buying Price List" + }, + { + "description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice or a Purchase Receipt directly without creating a Purchase Order first.", + "field": "", + "fieldname": "po_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?", + "parent_field": "", + "position": "Bottom", + "title": "Purchase Order Required" + }, + { + "description": "If this option is configured \"Yes\", ERPNext will prevent you from creating a Purchase Invoice without creating a Purchase Receipt first.", + "field": "", + "fieldname": "pr_required", + "fieldtype": "Select", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Is Purchase Receipt Required for Purchase Invoice Creation?", + "parent_field": "", + "position": "Bottom", + "title": "Purchase Receipt Required" + } + ], + "title": "Buying Settings" +} \ No newline at end of file diff --git a/erpnext/buying/form_tour/purchase_order/purchase_order.json b/erpnext/buying/form_tour/purchase_order/purchase_order.json new file mode 100644 index 0000000000..3cc88fbf4f --- /dev/null +++ b/erpnext/buying/form_tour/purchase_order/purchase_order.json @@ -0,0 +1,82 @@ +{ + "creation": "2021-07-29 14:11:58.271113", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-10-05 13:11:31.436135", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order", + "owner": "Administrator", + "reference_doctype": "Purchase Order", + "save_on_complete": 1, + "steps": [ + { + "description": "Select a Supplier", + "field": "", + "fieldname": "supplier", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Supplier", + "parent_field": "", + "position": "Right", + "title": "Supplier" + }, + { + "description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.", + "field": "", + "fieldname": "schedule_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Required By", + "parent_field": "", + "position": "Left", + "title": "Required By" + }, + { + "description": "Items to be purchased can be added here.", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "Items Table" + }, + { + "child_doctype": "Purchase Order Item", + "description": "Enter the Item Code.", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 1, + "label": "Item Code", + "next_step_condition": "eval: doc.item_code", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Item Code" + }, + { + "child_doctype": "Purchase Order Item", + "description": "Enter the required quantity for the material.", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Quantity", + "parent_field": "", + "parent_fieldname": "items", + "position": "Bottom", + "title": "Quantity" + } + ], + "title": "Purchase Order" +} \ No newline at end of file diff --git a/erpnext/buying/module_onboarding/buying/buying.json b/erpnext/buying/module_onboarding/buying/buying.json index 887f85b82d..84e97a2d4d 100644 --- a/erpnext/buying/module_onboarding/buying/buying.json +++ b/erpnext/buying/module_onboarding/buying/buying.json @@ -19,7 +19,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/buying", "idx": 0, "is_complete": 0, - "modified": "2020-07-08 14:05:28.273641", + "modified": "2021-08-24 18:13:42.463776", "modified_by": "Administrator", "module": "Buying", "name": "Buying", @@ -28,23 +28,11 @@ { "step": "Introduction to Buying" }, - { - "step": "Create a Supplier" - }, - { - "step": "Setup your Warehouse" - }, - { - "step": "Create a Product" - }, { "step": "Create a Material Request" }, { "step": "Create your first Purchase Order" - }, - { - "step": "Buying Settings" } ], "subtitle": "Products, Purchases, Analysis, and more.", diff --git a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json index 9dc493dd49..28e86ab064 100644 --- a/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json +++ b/erpnext/buying/onboarding_step/create_a_material_request/create_a_material_request.json @@ -1,19 +1,21 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Let\u2019s create your first Material Request", "creation": "2020-05-15 14:39:09.818764", + "description": "# Track Material Request\n\n\nAlso known as Purchase Request or an Indent, is a document identifying a requirement of a set of items (products or services) for various purposes like procurement, transfer, issue, or manufacturing. Once the Material Request is validated, a purchase manager can take the next actions for purchasing items like requesting RFQ from a supplier or directly placing an order with an identified Supplier.\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 1, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-15 14:39:09.818764", + "modified": "2021-08-24 18:08:08.347501", "modified_by": "Administrator", "name": "Create a Material Request", "owner": "Administrator", "reference_document": "Material Request", + "show_form_tour": 1, "show_full_form": 1, - "title": "Create a Material Request", + "title": "Track Material Request", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json index 9dbed23978..18a3931586 100644 --- a/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json +++ b/erpnext/buying/onboarding_step/create_your_first_purchase_order/create_your_first_purchase_order.json @@ -1,19 +1,21 @@ { - "action": "Create Entry", + "action": "Show Form Tour", + "action_label": "Let\u2019s create your first Purchase Order", "creation": "2020-05-12 18:17:49.976035", + "description": "# Create first Purchase Order\n\nPurchase Order is at the heart of your buying transactions. In ERPNext, Purchase Order can can be created against a Purchase Material Request (indent) and Supplier Quotation as well. Purchase Orders is also linked to Purchase Receipt and Purchase Invoices, allowing you to keep a birds-eye view on your purchase deals.\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, "is_single": 0, "is_skipped": 0, - "modified": "2020-05-12 18:31:56.856112", + "modified": "2021-08-24 18:08:08.936484", "modified_by": "Administrator", "name": "Create your first Purchase Order", "owner": "Administrator", "reference_document": "Purchase Order", + "show_form_tour": 0, "show_full_form": 0, - "title": "Create your first Purchase Order", + "title": "Create first Purchase Order", "validate_action": 1 } \ No newline at end of file diff --git a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json index fd98fddafa..01ac8b8176 100644 --- a/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json +++ b/erpnext/buying/onboarding_step/introduction_to_buying/introduction_to_buying.json @@ -1,19 +1,22 @@ { - "action": "Watch Video", + "action": "Show Form Tour", + "action_label": "Let\u2019s walk-through few Buying Settings", "creation": "2020-05-06 15:37:09.477765", + "description": "# Buying Settings\n\n\nBuying module\u2019s features are highly configurable as per your business needs. Buying Settings is the place where you can set your preferences for:\n\n- Supplier naming and default values\n- Billing and shipping preference in buying transactions\n\n\n", "docstatus": 0, "doctype": "Onboarding Step", "idx": 0, "is_complete": 0, - "is_mandatory": 0, - "is_single": 0, + "is_single": 1, "is_skipped": 0, - "modified": "2020-05-12 18:25:08.509900", + "modified": "2021-08-24 18:08:08.345735", "modified_by": "Administrator", "name": "Introduction to Buying", "owner": "Administrator", - "show_full_form": 0, - "title": "Introduction to Buying", + "reference_document": "Buying Settings", + "show_form_tour": 1, + "show_full_form": 1, + "title": "Buying Settings", "validate_action": 1, "video_url": "https://youtu.be/efFajTTQBa8" } \ No newline at end of file diff --git a/erpnext/stock/form_tour/material_request/material_request.json b/erpnext/stock/form_tour/material_request/material_request.json new file mode 100644 index 0000000000..145b4a06c2 --- /dev/null +++ b/erpnext/stock/form_tour/material_request/material_request.json @@ -0,0 +1,97 @@ +{ + "creation": "2021-07-29 12:32:08.929900", + "docstatus": 0, + "doctype": "Form Tour", + "idx": 0, + "is_standard": 1, + "modified": "2021-10-05 13:11:13.119453", + "modified_by": "Administrator", + "module": "Stock", + "name": "Material Request", + "owner": "Administrator", + "reference_doctype": "Material Request", + "save_on_complete": 1, + "steps": [ + { + "description": "The purpose of the material request can be selected here. For now select \"Purchase\" as the purpose.", + "field": "", + "fieldname": "material_request_type", + "fieldtype": "Select", + "has_next_condition": 1, + "is_table_field": 0, + "label": "Purpose", + "next_step_condition": "eval: doc.material_request_type == \"Purchase\"", + "parent_field": "", + "position": "Bottom", + "title": "Purpose" + }, + { + "description": "Set the \"Required By\" date for the materials. This sets the \"Required By\" date for all the items.", + "field": "", + "fieldname": "schedule_date", + "fieldtype": "Date", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Required By", + "next_step_condition": "", + "parent_field": "", + "position": "Left", + "title": "Required By" + }, + { + "description": "Setting the target warehouse sets it for all the items.", + "field": "", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Set Target Warehouse", + "next_step_condition": "", + "parent_field": "", + "position": "Left", + "title": "Target Warehouse" + }, + { + "description": "Items table", + "field": "", + "fieldname": "items", + "fieldtype": "Table", + "has_next_condition": 0, + "is_table_field": 0, + "label": "Items", + "parent_field": "", + "position": "Bottom", + "title": "Items" + }, + { + "child_doctype": "Material Request Item", + "description": "Select an Item code. Item details will be fetched automatically.", + "field": "", + "fieldname": "item_code", + "fieldtype": "Link", + "has_next_condition": 1, + "is_table_field": 1, + "label": "Item Code", + "next_step_condition": "eval: doc.item_code", + "parent_field": "", + "parent_fieldname": "items", + "position": "Right", + "title": "Item Code" + }, + { + "child_doctype": "Material Request Item", + "description": "Enter the required quantity for the material.", + "field": "", + "fieldname": "qty", + "fieldtype": "Float", + "has_next_condition": 0, + "is_table_field": 1, + "label": "Quantity", + "parent_field": "", + "parent_fieldname": "items", + "position": "Bottom", + "title": "Quantity" + } + ], + "title": "Material Request" +} \ No newline at end of file From 3132dcd94aae1dc68037f94a3ef63f7fd1be9b17 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 5 Oct 2021 14:40:40 +0530 Subject: [PATCH 202/416] fix: batch_no not mapped from PR to Stock Entry (#27794) --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 07a568db86..47c8df9a2c 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -842,7 +842,8 @@ def make_stock_entry(source_name,target_doc=None): "doctype": "Stock Entry Detail", "field_map": { "warehouse": "s_warehouse", - "parent": "reference_purchase_receipt" + "parent": "reference_purchase_receipt", + "batch_no": "batch_no" }, }, }, target_doc, set_missing_values) From 340859cb3c2bed7bc7e56ddbb7a0e4e0ce9ff93c Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 5 Oct 2021 15:11:45 +0530 Subject: [PATCH 203/416] fix: broken lead form actions --- erpnext/crm/doctype/lead/lead.js | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 95cf03241b..999599ce95 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -51,7 +51,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller } } - add_lead_to_prospect (frm) { + add_lead_to_prospect () { frappe.prompt([ { fieldname: 'prospect', @@ -65,7 +65,7 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller frappe.call({ method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect', args: { - 'lead': frm.doc.name, + 'lead': cur_frm.doc.name, 'prospect': data.prospect }, callback: function(r) { @@ -79,41 +79,41 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller }, __('Add Lead to Prospect'), __('Add')); } - make_customer (frm) { + make_customer () { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_customer", - frm: frm + frm: cur_frm }) } - make_opportunity (frm) { + make_opportunity () { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_opportunity", - frm: frm + frm: cur_frm }) } - make_quotation (frm) { + make_quotation () { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_quotation", - frm: frm + frm: cur_frm }) } - make_prospect (frm) { + make_prospect () { frappe.model.with_doctype("Prospect", function() { let prospect = frappe.model.get_new_doc("Prospect"); - prospect.company_name = frm.doc.company_name; - prospect.no_of_employees = frm.doc.no_of_employees; - prospect.industry = frm.doc.industry; - prospect.market_segment = frm.doc.market_segment; - prospect.territory = frm.doc.territory; - prospect.fax = frm.doc.fax; - prospect.website = frm.doc.website; - prospect.prospect_owner = frm.doc.lead_owner; + prospect.company_name = cur_frm.doc.company_name; + prospect.no_of_employees = cur_frm.doc.no_of_employees; + prospect.industry = cur_frm.doc.industry; + prospect.market_segment = cur_frm.doc.market_segment; + prospect.territory = cur_frm.doc.territory; + prospect.fax = cur_frm.doc.fax; + prospect.website = cur_frm.doc.website; + prospect.prospect_owner = cur_frm.doc.lead_owner; let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead'); - lead_prospect_row.lead = frm.doc.name; + lead_prospect_row.lead = cur_frm.doc.name; frappe.set_route("Form", "Prospect", prospect.name); }); From 020f94532bf8e1893fcf4c7e6bb78b9088d8deac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 5 Oct 2021 12:20:14 +0530 Subject: [PATCH 204/416] fix: COA Importer showing blank validations (cherry picked from commit 0660d6ed01e0668ce828cf9bae9790aead874233) --- .../chart_of_accounts_importer.js | 91 +++++++++---------- .../chart_of_accounts_importer.py | 7 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js index 66a269e7a7..d61f8a6c01 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js @@ -10,6 +10,15 @@ frappe.ui.form.on('Chart of Accounts Importer', { // make company mandatory frm.set_df_property('company', 'reqd', frm.doc.company ? 0 : 1); frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1); + + if (frm.doc.import_file) { + frappe.run_serially([ + () => generate_tree_preview(frm), + () => create_import_button(frm), + () => frm.set_df_property('chart_preview', 'hidden', 0) + ]); + } + frm.set_df_property('chart_preview', 'hidden', $(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1); }, @@ -72,13 +81,6 @@ frappe.ui.form.on('Chart of Accounts Importer', { if (!frm.doc.import_file) { frm.page.set_indicator(""); $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file - } else { - frappe.run_serially([ - () => validate_coa(frm), - () => generate_tree_preview(frm), - () => create_import_button(frm), - () => frm.set_df_property('chart_preview', 'hidden', 0), - ]); } }, @@ -104,26 +106,24 @@ frappe.ui.form.on('Chart of Accounts Importer', { }); var create_import_button = function(frm) { - if (frm.page.show_import_button) { - frm.page.set_primary_action(__("Import"), function () { - return frappe.call({ - method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa", - args: { - file_name: frm.doc.import_file, - company: frm.doc.company - }, - freeze: true, - freeze_message: __("Creating Accounts..."), - callback: function(r) { - if (!r.exc) { - clearInterval(frm.page["interval"]); - frm.page.set_indicator(__('Import Successful'), 'blue'); - create_reset_button(frm); - } + frm.page.set_primary_action(__("Import"), function () { + return frappe.call({ + method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa", + args: { + file_name: frm.doc.import_file, + company: frm.doc.company + }, + freeze: true, + freeze_message: __("Creating Accounts..."), + callback: function(r) { + if (!r.exc) { + clearInterval(frm.page["interval"]); + frm.page.set_indicator(__('Import Successful'), 'blue'); + create_reset_button(frm); } - }); - }).addClass('btn btn-primary'); - } + } + }); + }).addClass('btn btn-primary'); }; var create_reset_button = function(frm) { @@ -137,7 +137,6 @@ var create_reset_button = function(frm) { var validate_coa = function(frm) { if (frm.doc.import_file) { let parent = __('All Accounts'); - return frappe.call({ 'method': 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', 'args': { @@ -157,25 +156,23 @@ var validate_coa = function(frm) { }; var generate_tree_preview = function(frm) { - if (frm.doc.import_file) { - let parent = __('All Accounts'); - $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data + let parent = __('All Accounts'); + $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data - // generate tree structure based on the csv data - return new frappe.ui.Tree({ - parent: $(frm.fields_dict['chart_tree'].wrapper), - label: parent, - expandable: true, - method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', - args: { - file_name: frm.doc.import_file, - parent: parent, - doctype: 'Chart of Accounts Importer', - file_type: frm.doc.file_type - }, - onclick: function(node) { - parent = node.value; - } - }); - } + // generate tree structure based on the csv data + return new frappe.ui.Tree({ + parent: $(frm.fields_dict['chart_tree'].wrapper), + label: parent, + expandable: true, + method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', + args: { + file_name: frm.doc.import_file, + parent: parent, + doctype: 'Chart of Accounts Importer', + file_type: frm.doc.file_type + }, + onclick: function(node) { + parent = node.value; + } + }); }; diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index bd2a6f1b08..5e596f8677 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -25,7 +25,9 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import class ChartofAccountsImporter(Document): - pass + def validate(self): + if self.import_file: + get_coa('Chart of Accounts Importer', 'All Accounts', file_name=self.import_file, for_validate=1) def validate_columns(data): if not data: @@ -34,7 +36,8 @@ def validate_columns(data): no_of_columns = max([len(d) for d in data]) if no_of_columns > 7: - frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template')) + frappe.throw(_('More columns found than expected. Please compare the uploaded file with standard template'), + title=(_("Wrong Template"))) @frappe.whitelist() def validate_company(company): From a2b5e678fec3c7157fe3a280ff3e0dbcc15123ec Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 5 Oct 2021 14:51:35 +0530 Subject: [PATCH 205/416] fix: Use get_list instead of get_all to avoid perm issues (cherry picked from commit 9507b2d752a40c0dd9e8b43ae3ef64435457c85e) --- erpnext/setup/setup_wizard/operations/taxes_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index faa25dfbaa..58a14d20f2 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -192,7 +192,7 @@ def get_or_create_account(company_name, account): default_root_type = 'Liability' root_type = account.get('root_type', default_root_type) - existing_accounts = frappe.get_list('Account', + existing_accounts = frappe.get_all('Account', filters={ 'company': company_name, 'root_type': root_type @@ -247,7 +247,7 @@ def get_or_create_tax_group(company_name, root_type): # Create a new group account named 'Duties and Taxes' or 'Tax Assets' just # below the root account - root_account = frappe.get_list('Account', { + root_account = frappe.get_all('Account', { 'is_group': 1, 'root_type': root_type, 'company': company_name, From 3d3655ed730f793ddac24d610da397741ffc3009 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 5 Oct 2021 15:52:11 +0530 Subject: [PATCH 206/416] fix: ignore random periodicity in validations --- .../doctype/maintenance_schedule/maintenance_schedule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index 0bf5aeae71..a1df9cfd0e 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -47,7 +47,7 @@ class MaintenanceSchedule(TransactionBase): "Yearly": 365 } for item in self.items: - if item.periodicity and item.start_date: + if item.periodicity and item.periodicity != "Random" and item.start_date: if not item.end_date: if item.no_of_visits: item.end_date = add_days(item.start_date, item.no_of_visits * days_in_period[item.periodicity]) From 233bf5dd294382ce6d5a5cdd7ad02aafe921a584 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:16:21 +0530 Subject: [PATCH 207/416] fix: using DN for transfer w/o internal customer (backport #27798) (backport #27805) (#27807) * fix: using DN for transfer w/o internal customer (backport #27798) (#27805) * fix: using DN for transfer w/o internal customer (#27798) This used to be work before though not "advertised", since a lot of users have started using it as feature, it can't be broken now. (cherry picked from commit df1f8fddf6e2f4106c1a2778614ba9a1dc5ff67e) * fix(ux): use toast instead of popup Co-authored-by: Ankush Menat (cherry picked from commit fa944382c53bdeeb92f2dcc876e2c11ed03e20cc) # Conflicts: # erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py * fix: resolve conflict * fix: resolve conflicts Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Ankush Menat --- .../doctype/sales_invoice/test_sales_invoice.py | 12 ++++-------- erpnext/controllers/selling_controller.py | 8 +++++++- erpnext/stock/doctype/delivery_note/delivery_note.py | 1 - .../delivery_note_item/delivery_note_item.json | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 8a2e9450e9..f492a03daf 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2023,11 +2023,7 @@ class TestSalesInvoice(unittest.TestCase): frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock) - def test_sle_if_target_warehouse_exists_accidentally(self): - """ - Check if inward entry exists if Target Warehouse accidentally exists - but Customer is not an internal customer. - """ + def test_sle_for_target_warehouse(self): se = make_stock_entry( item_code="138-CMS Shoe", target="Finished Goods - _TC", @@ -2048,9 +2044,9 @@ class TestSalesInvoice(unittest.TestCase): sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name}, fields=["name", "actual_qty"]) - # check if only one SLE for outward entry is created - self.assertEqual(len(sles), 1) - self.assertEqual(sles[0].actual_qty, -1) + # check if both SLEs are created + self.assertEqual(len(sles), 2) + self.assertEqual(sum(d.actual_qty for d in sles), 0.0) # tear down si.cancel() diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 0158a1120f..bb269f3db2 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -424,7 +424,7 @@ class SellingController(StockController): or (cint(self.is_return) and self.docstatus==2)): sl_entries.append(self.get_sle_for_source_warehouse(d)) - if d.target_warehouse and self.get("is_internal_customer"): + if d.target_warehouse: sl_entries.append(self.get_sle_for_target_warehouse(d)) if d.warehouse and ((not cint(self.is_return) and self.docstatus==2) @@ -559,6 +559,12 @@ class SellingController(StockController): frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same") .format(d.idx, warehouse, warehouse)) + if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items): + msg = _("Target Warehouse is set for some items but the customer is not an internal customer.") + msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype)) + frappe.msgprint(msg, title="Internal Transfer", alert=True) + + def validate_items(self): # validate items to see if they have is_sales_item enabled from erpnext.controllers.buying_controller import validate_item_type diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 5542cd00d4..f75b52cec8 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -185,7 +185,6 @@ class DeliveryNote(SellingController): if not d['warehouse'] and frappe.db.get_value("Item", d['item_code'], "is_stock_item") == 1: frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"])) - def update_current_stock(self): if self.get("_action") and self._action != "update_after_submit": for d in self.get('items'): diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json index b05090a237..a96c29925e 100644 --- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json +++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json @@ -468,7 +468,7 @@ "width": "100px" }, { - "depends_on": "eval:parent.is_internal_customer", + "depends_on": "eval:parent.is_internal_customer || doc.target_warehouse", "fieldname": "target_warehouse", "fieldtype": "Link", "hidden": 1, @@ -759,7 +759,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-02-23 01:04:08.588104", + "modified": "2021-10-05 12:12:44.018872", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note Item", @@ -767,4 +767,4 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} From cec66d2d10b258c5dd7662e9f173f890e3d62d56 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 5 Oct 2021 19:14:39 +0530 Subject: [PATCH 208/416] fix: removed redundant piece of code (#27817) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index eb26aa2afa..d909814921 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -229,9 +229,6 @@ class SalesInvoice(SellingController): # this sequence because outstanding may get -ve self.make_gl_entries() - if self.update_stock == 1: - self.repost_future_sle_and_gle() - if self.update_stock == 1: self.repost_future_sle_and_gle() From 62fea8a5aa02b14e05eeb7aa5eb6496d65ceef2d Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 5 Oct 2021 21:38:39 +0530 Subject: [PATCH 209/416] fix: Rename tests --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 31f911203d..79d46c5e56 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2202,7 +2202,7 @@ class TestSalesInvoice(unittest.TestCase): check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1)) enable_discount_accounting(enable=0) - def test_asset_depreciation_on_sale(self): + def test_asset_depreciation_on_sale_with_pro_rata(self): """ Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale. """ @@ -2226,7 +2226,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) - def test_depreciation_on_sale_for_depreciated_asset(self): + def test_asset_depreciation_on_sale_without_pro_rata(self): """ Tests if an Asset set to depreciate yearly on Dec 31, that gets sold on Dec 31 after two years, created an additional depreciation entry on its date of sale. """ From 35e30bdcaf592d190ae52d67525b344704c9ccd6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 5 Oct 2021 21:44:34 +0530 Subject: [PATCH 210/416] ci: fail build if asset bundling fails (#27820) --- .github/workflows/ui-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml index 658892c20e..d765f0482c 100644 --- a/.github/workflows/ui-tests.yml +++ b/.github/workflows/ui-tests.yml @@ -104,6 +104,8 @@ jobs: - name: Build Assets run: cd ~/frappe-bench/ && bench build + env: + CI: Yes - name: UI Tests run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless From f51bd44929275949652dfc97f1b5a8107f64e6cf Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 6 Oct 2021 01:18:05 +0530 Subject: [PATCH 211/416] fix: Unlink Depreciation Entry made on sale if the Asset is returned --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e135490cad..44a5fae6fb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1060,10 +1060,15 @@ class SalesInvoice(SellingController): if schedule.schedule_date == posting_date_of_original_invoice: if not self.sale_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_original_invoice) \ or self.sale_happens_in_the_future(posting_date_of_original_invoice): + reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() reverse_journal_entry.submit() + asset.flags.ignore_validate_update_after_submit = True + schedule.journal_entry = None + asset.save() + def get_posting_date_of_sales_invoice(self): return frappe.db.get_value('Sales Invoice', self.return_against, 'posting_date') From adebf2d71b2336037e45e85b214920e1f91deaae Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 6 Oct 2021 02:04:05 +0530 Subject: [PATCH 212/416] fix: Adjust depreciation_amount in final row --- erpnext/assets/doctype/asset/asset.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 0138a1282a..2a1d51beb4 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -262,11 +262,15 @@ class Asset(AccountsController): self.to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) + depreciation_amount_without_pro_rata = depreciation_amount + depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, schedule_date, self.to_date) - monthly_schedule_date = add_months(schedule_date, 1) + depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata, + depreciation_amount, d.finance_book) + monthly_schedule_date = add_months(schedule_date, 1) schedule_date = add_days(schedule_date, days) last_schedule_date = schedule_date @@ -406,6 +410,27 @@ class Asset(AccountsController): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") .format(row.idx)) + # to ensure that final accumulated depreciation amount is accurate + def get_adjusted_depreciation_amount(self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row, finance_book): + depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row(finance_book) + + if depreciation_amount_for_first_row + depreciation_amount_for_last_row != depreciation_amount_without_pro_rata: + depreciation_amount_for_last_row = depreciation_amount_without_pro_rata - depreciation_amount_for_first_row + + return depreciation_amount_for_last_row + + def get_depreciation_amount_for_first_row(self, finance_book): + if self.has_only_one_finance_book(): + return self.schedules[0].depreciation_amount + else: + for schedule in self.schedules: + if schedule.finance_book == finance_book: + return schedule.depreciation_amount + + def has_only_one_finance_book(self): + if len(self.finance_books) == 1: + return True + def set_accumulated_depreciation(self, date_of_sale=None, date_of_return=None, ignore_booked_entry = False): straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] finance_books = [] From 273fccf0ddfd5e6d79150b0a2c7e010b12753e21 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 6 Oct 2021 02:08:28 +0530 Subject: [PATCH 213/416] fix: Add test for depreciation on return of sold Asset --- .../sales_invoice/test_sales_invoice.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 79d46c5e56..0e9ceead7f 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2252,6 +2252,33 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) self.assertTrue(schedule.journal_entry) + def test_depreciation_on_return_of_sold_asset(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + create_asset_data() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, submit=1) + post_depreciation_entries(getdate("2021-09-30")) + + si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000, posting_date=getdate("2021-09-30")) + return_si = make_return_doc("Sales Invoice", si.name) + return_si.submit() + asset.load_from_db() + + expected_values = [ + ["2020-06-30", 1311.48, 1311.48, True], + ["2021-06-30", 20000.0, 21311.48, True], + ["2022-06-30", 20000.0, 41311.48, False], + ["2023-06-30", 20000.0, 61311.48, False], + ["2024-06-30", 20000.0, 81311.48, False], + ["2025-06-06", 18688.52, 100000.0, False] + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + self.assertEqual(schedule.journal_entry, schedule.journal_entry) + def test_sales_invoice_against_supplier(self): from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( make_customer, From ff7506d4a6bacc1c04e75d0844f965dff4f9b917 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 6 Oct 2021 13:35:22 +0530 Subject: [PATCH 214/416] fix: revert "fix: missing link in dashboard missing on SI from DN" (#27832) --- .../accounts/doctype/sales_invoice/sales_invoice_dashboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py index 2956cdc097..64b35b2987 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py @@ -7,6 +7,7 @@ def get_data(): return { 'fieldname': 'sales_invoice', 'non_standard_fieldnames': { + 'Delivery Note': 'against_sales_invoice', 'Journal Entry': 'reference_name', 'Payment Entry': 'reference_name', 'Payment Request': 'reference_name', @@ -14,8 +15,7 @@ def get_data(): 'Auto Repeat': 'reference_document', }, 'internal_links': { - 'Sales Order': ['items', 'sales_order'], - 'Delivery Note': ['items', 'delivery_note'], + 'Sales Order': ['items', 'sales_order'] }, 'transactions': [ { From d36849e4f8935fb481b9fa45d517fe9fda70f43b Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 6 Oct 2021 17:13:34 +0530 Subject: [PATCH 215/416] test: optimise `test_component_amount_dependent_on_another_payment_days_based_component` (#27836) --- .../doctype/salary_slip/test_salary_slip.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 9ed6686d48..178cd5c9d0 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -141,7 +141,6 @@ class TestSalarySlip(unittest.TestCase): create_salary_structure_assignment, ) - no_of_days = self.get_no_of_days() # Payroll based on attendance frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance") @@ -168,9 +167,6 @@ class TestSalarySlip(unittest.TestCase): ss = make_salary_slip_for_payment_days_dependency_test("test_payment_days_based_component@salary.com", salary_structure.name) self.assertEqual(ss.absent_days, 1) - days_in_month = no_of_days[0] - no_of_holidays = no_of_days[1] - ss.reload() payment_days_based_comp_amount = 0 for component in ss.earnings: @@ -992,13 +988,14 @@ def make_salary_structure_for_payment_days_based_component_dependency(): return salary_structure_doc def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure): - employee = frappe.db.get_value("Employee", { - "user_id": employee - }, + employee = frappe.db.get_value( + "Employee", + {"user_id": employee}, ["name", "company", "employee_name"], - as_dict=True) + as_dict=True + ) - salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": frappe.db.get_value("Employee", {"user_id": employee})}) + salary_slip_name = frappe.db.get_value("Salary Slip", {"employee": employee.name}) if not salary_slip_name: salary_slip = make_salary_slip(salary_structure, employee=employee.name) @@ -1009,4 +1006,4 @@ def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure else: salary_slip = frappe.get_doc("Salary Slip", salary_slip_name) - return salary_slip \ No newline at end of file + return salary_slip From d4b2471cea2d43815b8384dd6bcabf246e285105 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 6 Oct 2021 18:16:33 +0530 Subject: [PATCH 216/416] fix: use ceil in case of whole uoms for reorder qty (#27834) * fix: use ceil in case of whole uoms for reorder qty * fix: cache uom query --- erpnext/stock/reorder_item.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 3cd4cd2761..7c6fbfd9cd 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import json +from math import ceil import frappe from frappe import _ @@ -149,11 +150,16 @@ def create_material_request(material_requests): conversion_factor = frappe.db.get_value("UOM Conversion Detail", {'parent': item.name, 'uom': uom}, 'conversion_factor') or 1.0 + must_be_whole_number = frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) + qty = d.reorder_qty / conversion_factor + if must_be_whole_number: + qty = ceil(qty) + mr.append("items", { "doctype": "Material Request Item", "item_code": d.item_code, "schedule_date": add_days(nowdate(),cint(item.lead_time_days)), - "qty": d.reorder_qty / conversion_factor, + "qty": qty, "uom": uom, "stock_uom": item.stock_uom, "warehouse": d.warehouse, From 0f03b19109ec0c47243574f8a976d55f638ce339 Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 4 Oct 2021 18:48:48 +0100 Subject: [PATCH 217/416] fix: reorder updation of end date --- .../doctype/subscription/subscription.py | 6 ++- .../doctype/subscription/test_subscription.py | 39 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 8171b3b019..092002fb15 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -542,8 +542,7 @@ class Subscription(Document): else: self.set_status_grace_period() - if getdate() > getdate(self.current_invoice_end): - self.update_subscription_period(add_days(self.current_invoice_end, 1)) + update_subscription = True if getdate() > getdate(self.current_invoice_end) else False # Generate invoices periodically even if current invoice are unpaid if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() @@ -551,6 +550,9 @@ class Subscription(Document): prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) + if update_subscription: + self.update_subscription_period(add_days(self.current_invoice_end, 1)) + @staticmethod def is_paid(invoice): """ diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index e2cf4d5a44..ec8b257455 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -20,6 +20,43 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto def create_plan(): + + if not frappe.db.exists('UOM', {"name": '_Test UOM'}): + frappe.get_doc({ + "doctype": "UOM", + "uom_name": "_Test UOM" + }).insert() + + if not frappe.db.exists('Item Group', {"name": '_Test Item Group Desktops'}): + frappe.get_doc({ + "doctype": "Item Group", + "is_group": 0, + "item_group_name": "_Test Item Group Desktops", + "parent_item_group": "All Item Groups" + }).insert() + + if not frappe.db.exists('Item', {"item_name": '_Test Non Stock Item'}): + frappe.get_doc({ + "description": "_Test Non Stock Item 7", + "doctype": "Item", + "has_batch_no": 0, + "has_serial_no": 0, + "inspection_required": 0, + "is_stock_item": 0, + "is_sub_contracted_item": 0, + "item_code": "_Test Non Stock Item", + "item_group": "_Test Item Group Desktops", + "item_name": "_Test Non Stock Item", + "stock_uom": "_Test UOM", + "gst_hsn_code": "999800", + "item_defaults": [{ + "company": "_Test Company", + "default_warehouse": "Finished Goods - _TC", + "expense_account": "Gegenkonto zu Konto 9260 - 9268 - _TC", + "income_account": "Kurzfristige Rückstellungen - _TC" + }] + }).insert() + if not frappe.db.exists('Subscription Plan', '_Test Plan Name'): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' @@ -619,7 +656,7 @@ class TestSubscription(unittest.TestCase): # subscription subscription.process() - self.assertEqual(len(subscription.invoices), 2) + self.assertEqual(len(subscription.invoices), 1) def test_subscription_without_generate_invoice_past_due(self): subscription = frappe.new_doc('Subscription') From fc375c5bdebe187e63a2d20892be049966741ed0 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 6 Oct 2021 18:04:54 +0530 Subject: [PATCH 218/416] fix: remove newline --- erpnext/accounts/doctype/subscription/test_subscription.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index ec8b257455..3a96d849ff 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -20,7 +20,6 @@ from erpnext.accounts.doctype.subscription.subscription import get_prorata_facto def create_plan(): - if not frappe.db.exists('UOM', {"name": '_Test UOM'}): frappe.get_doc({ "doctype": "UOM", From 656015d99d8d98ef614abef45914b5088c866476 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 6 Oct 2021 18:35:48 +0530 Subject: [PATCH 219/416] test: use `test_dependencies` instead of duplication --- .../doctype/subscription/test_subscription.py | 38 +------------------ 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 3a96d849ff..a01363be6e 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -18,44 +18,9 @@ from frappe.utils.data import ( from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor +test_dependencies = ("UOM", "Item Group", "Item") def create_plan(): - if not frappe.db.exists('UOM', {"name": '_Test UOM'}): - frappe.get_doc({ - "doctype": "UOM", - "uom_name": "_Test UOM" - }).insert() - - if not frappe.db.exists('Item Group', {"name": '_Test Item Group Desktops'}): - frappe.get_doc({ - "doctype": "Item Group", - "is_group": 0, - "item_group_name": "_Test Item Group Desktops", - "parent_item_group": "All Item Groups" - }).insert() - - if not frappe.db.exists('Item', {"item_name": '_Test Non Stock Item'}): - frappe.get_doc({ - "description": "_Test Non Stock Item 7", - "doctype": "Item", - "has_batch_no": 0, - "has_serial_no": 0, - "inspection_required": 0, - "is_stock_item": 0, - "is_sub_contracted_item": 0, - "item_code": "_Test Non Stock Item", - "item_group": "_Test Item Group Desktops", - "item_name": "_Test Non Stock Item", - "stock_uom": "_Test UOM", - "gst_hsn_code": "999800", - "item_defaults": [{ - "company": "_Test Company", - "default_warehouse": "Finished Goods - _TC", - "expense_account": "Gegenkonto zu Konto 9260 - 9268 - _TC", - "income_account": "Kurzfristige Rückstellungen - _TC" - }] - }).insert() - if not frappe.db.exists('Subscription Plan', '_Test Plan Name'): plan = frappe.new_doc('Subscription Plan') plan.plan_name = '_Test Plan Name' @@ -104,7 +69,6 @@ def create_plan(): supplier.insert() class TestSubscription(unittest.TestCase): - def setUp(self): create_plan() From 7114659eccfa13c50ba6ce8d7fb2ad74838169d5 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 6 Oct 2021 21:31:19 +0530 Subject: [PATCH 220/416] fix: improved on_update method, added validation for tax calculation, sandbox mode checks --- .../taxjar_settings/taxjar_settings.json | 6 ++-- .../taxjar_settings/taxjar_settings.py | 28 ++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json index ccbac2c18a..2d17f2ed83 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.json @@ -6,8 +6,8 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "is_sandbox", "taxjar_calculate_tax", + "is_sandbox", "taxjar_create_transactions", "credentials", "api_key", @@ -57,6 +57,7 @@ }, { "default": "0", + "depends_on": "taxjar_calculate_tax", "fieldname": "is_sandbox", "fieldtype": "Check", "label": "Sandbox Mode" @@ -72,6 +73,7 @@ }, { "default": "0", + "depends_on": "taxjar_calculate_tax", "fieldname": "taxjar_create_transactions", "fieldtype": "Check", "label": "Create TaxJar Transaction" @@ -106,7 +108,7 @@ ], "issingle": 1, "links": [], - "modified": "2021-09-16 08:54:48.444487", + "modified": "2021-10-06 10:59:13.475442", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "TaxJar Settings", diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index 725a61a396..d116552309 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -21,15 +21,9 @@ class TaxJarSettings(Document): TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") - fields_hidden = 0 - - custom_fields = [] - for dt in ['Item', 'Sales Invoice Item']: - doc = frappe.db.exists('Custom Field', {'dt': dt, 'fieldname':'product_tax_category'}) - custom_fields.append(doc) - fields_hidden = frappe.db.get_value('Custom Field', {'dt': dt, 'fieldname':'product_tax_category'},'hidden') - fields_already_exist = True if custom_fields else False + fields_already_exist = frappe.db.exists('Custom Field', {'dt': ('in', ['Item','Sales Invoice Item']), 'fieldname':'product_tax_category'}) + fields_hidden = frappe.get_value('Custom Field', {'dt': ('in', ['Sales Invoice Item'])}, 'hidden') if (TAXJAR_CREATE_TRANSACTIONS or TAXJAR_CALCULATE_TAX or TAXJAR_SANDBOX_MODE): if not fields_already_exist: @@ -39,10 +33,13 @@ class TaxJarSettings(Document): frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False) elif fields_already_exist and fields_hidden: - toggle_tax_category_fields(hidden='1') + toggle_tax_category_fields(hidden='0') - else: - toggle_tax_category_fields(hidden='0') + elif fields_already_exist: + toggle_tax_category_fields(hidden='1') + + def validate(self): + self.calculate_taxes_validation_for_create_transactions() @frappe.whitelist() def update_nexus_list(self): @@ -55,9 +52,14 @@ class TaxJarSettings(Document): self.set('nexus', new_nexus_list) self.save() + def calculate_taxes_validation_for_create_transactions(self): + if not self.taxjar_calculate_tax and (self.taxjar_create_transactions or self.is_sandbox): + frappe.throw('Before enabling Create Transaction or Sandbox Mode, you need to check the Enable Tax Calculation box') + + def toggle_tax_category_fields(hidden): - frappe.set_value('Custom Field',{'dt':'Sales Invoice Item', 'fieldname':'product_tax_category'},'hidden',hidden) - frappe.set_value('Custom Field',{'dt':'Item', 'fieldname':'product_tax_category'},'hidden',hidden) + frappe.set_value('Custom Field', {'dt':'Sales Invoice Item', 'fieldname':'product_tax_category'}, 'hidden', hidden) + frappe.set_value('Custom Field', {'dt':'Item', 'fieldname':'product_tax_category'}, 'hidden', hidden) def add_product_tax_categories(): From 3ece05a9f7976a031efcb441defa77d51c351145 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Wed, 6 Oct 2021 21:35:04 +0530 Subject: [PATCH 221/416] fix: linters fix --- .../doctype/taxjar_settings/taxjar_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py index d116552309..f430a9e9ba 100644 --- a/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py +++ b/erpnext/erpnext_integrations/doctype/taxjar_settings/taxjar_settings.py @@ -54,7 +54,7 @@ class TaxJarSettings(Document): def calculate_taxes_validation_for_create_transactions(self): if not self.taxjar_calculate_tax and (self.taxjar_create_transactions or self.is_sandbox): - frappe.throw('Before enabling Create Transaction or Sandbox Mode, you need to check the Enable Tax Calculation box') + frappe.throw(frappe._('Before enabling Create Transaction or Sandbox Mode, you need to check the Enable Tax Calculation box')) def toggle_tax_category_fields(hidden): From ae657c7e4ee7510705513948c329968e9330e24f Mon Sep 17 00:00:00 2001 From: hrwx Date: Wed, 6 Oct 2021 16:49:19 +0100 Subject: [PATCH 222/416] fix: create past invoices --- .../doctype/subscription/subscription.py | 81 +++++++++++-------- .../doctype/subscription/test_subscription.py | 2 +- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 092002fb15..01a5363697 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -33,7 +33,7 @@ class Subscription(Document): # update start just before the subscription doc is created self.update_subscription_period(self.start_date) - def update_subscription_period(self, date=None): + def update_subscription_period(self, date=None, return_date=False): """ Subscription period is the period to be billed. This method updates the beginning of the billing period and end of the billing period. @@ -41,28 +41,41 @@ class Subscription(Document): The beginning of the billing period is represented in the doctype as `current_invoice_start` and the end of the billing period is represented as `current_invoice_end`. - """ - self.set_current_invoice_start(date) - self.set_current_invoice_end() - def set_current_invoice_start(self, date=None): + If return_date is True, it wont update the start and end dates. + This is implemented to get the dates to check if is_current_invoice_generated """ - This sets the date of the beginning of the current billing period. + _current_invoice_start = self.get_current_invoice_start(date) + _current_invoice_end = self.get_current_invoice_end(_current_invoice_start) + + if return_date: + return _current_invoice_start, _current_invoice_end + + self.current_invoice_start = _current_invoice_start + self.current_invoice_end = _current_invoice_end + + def get_current_invoice_start(self, date=None): + """ + This returns the date of the beginning of the current billing period. If the `date` parameter is not given , it will be automatically set as today's date. """ - if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date): - self.current_invoice_start = add_days(self.trial_period_end, 1) - elif self.trial_period_start and self.is_trialling(): - self.current_invoice_start = self.trial_period_start - elif date: - self.current_invoice_start = date - else: - self.current_invoice_start = nowdate() + _current_invoice_start = None - def set_current_invoice_end(self): + if self.is_new_subscription() and self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date): + _current_invoice_start = add_days(self.trial_period_end, 1) + elif self.trial_period_start and self.is_trialling(): + _current_invoice_start = self.trial_period_start + elif date: + _current_invoice_start = date + else: + _current_invoice_start = nowdate() + + return _current_invoice_start + + def get_current_invoice_end(self, date=None): """ - This sets the date of the end of the current billing period. + This returns the date of the end of the current billing period. If the subscription is in trial period, it will be set as the end of the trial period. @@ -71,44 +84,47 @@ class Subscription(Document): current billing period where `x` is the billing interval from the `Subscription Plan` in the `Subscription`. """ - if self.is_trialling() and getdate(self.current_invoice_start) < getdate(self.trial_period_end): - self.current_invoice_end = self.trial_period_end + _current_invoice_end = None + + if self.is_trialling() and getdate(date) < getdate(self.trial_period_end): + _current_invoice_end = self.trial_period_end else: billing_cycle_info = self.get_billing_cycle_data() if billing_cycle_info: - if self.is_new_subscription() and getdate(self.start_date) < getdate(self.current_invoice_start): - self.current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) + if self.is_new_subscription() and getdate(self.start_date) < getdate(date): + _current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) # For cases where trial period is for an entire billing interval - if getdate(self.current_invoice_end) < getdate(self.current_invoice_start): - self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) + if getdate(self.current_invoice_end) < getdate(date): + _current_invoice_end = add_to_date(date, **billing_cycle_info) else: - self.current_invoice_end = add_to_date(self.current_invoice_start, **billing_cycle_info) + _current_invoice_end = add_to_date(date, **billing_cycle_info) else: - self.current_invoice_end = get_last_day(self.current_invoice_start) + _current_invoice_end = get_last_day(date) if self.follow_calendar_months: billing_info = self.get_billing_cycle_and_interval() billing_interval_count = billing_info[0]['billing_interval_count'] calendar_months = get_calendar_months(billing_interval_count) calendar_month = 0 - current_invoice_end_month = getdate(self.current_invoice_end).month - current_invoice_end_year = getdate(self.current_invoice_end).year + current_invoice_end_month = getdate(_current_invoice_end).month + current_invoice_end_year = getdate(_current_invoice_end).year for month in calendar_months: if month <= current_invoice_end_month: calendar_month = month if cint(calendar_month - billing_interval_count) <= 0 and \ - getdate(self.current_invoice_start).month != 1: + getdate(date).month != 1: calendar_month = 12 current_invoice_end_year -= 1 - self.current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' \ - + cstr(calendar_month) + '-01') + _current_invoice_end = get_last_day(cstr(current_invoice_end_year) + '-' + cstr(calendar_month) + '-01') - if self.end_date and getdate(self.current_invoice_end) > getdate(self.end_date): - self.current_invoice_end = self.end_date + if self.end_date and getdate(_current_invoice_end) > getdate(self.end_date): + _current_invoice_end = self.end_date + + return _current_invoice_end @staticmethod def validate_plans_billing_cycle(billing_cycle_data): @@ -488,8 +504,9 @@ class Subscription(Document): def is_current_invoice_generated(self): invoice = self.get_current_invoice() + _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) - if invoice and getdate(self.current_invoice_start) <= getdate(invoice.posting_date) <= getdate(self.current_invoice_end): + if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date): return True return False diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index a01363be6e..0f7a0a86a4 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -619,7 +619,7 @@ class TestSubscription(unittest.TestCase): # subscription subscription.process() - self.assertEqual(len(subscription.invoices), 1) + self.assertEqual(len(subscription.invoices), 2) def test_subscription_without_generate_invoice_past_due(self): subscription = frappe.new_doc('Subscription') From 772d4753e7fdf79b24398accb34990b259a63bbc Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Wed, 6 Oct 2021 22:28:48 +0530 Subject: [PATCH 223/416] refactor: Clean up mutable defaults and add CI check (#27828) * refactor: Clean up mutable defaults and add CI check --- .github/helper/.flake8_strict | 5 +++++ .pre-commit-config.yaml | 5 ++++- .../accounts/doctype/pos_profile/test_pos_profile.py | 4 +++- erpnext/accounts/doctype/pricing_rule/utils.py | 4 +++- .../doctype/promotional_scheme/promotional_scheme.py | 8 ++++++-- erpnext/accounts/report/cash_flow/cash_flow.py | 4 ++-- erpnext/education/doctype/student/student.py | 4 +++- erpnext/hr/doctype/holiday_list/holiday_list.py | 5 +++-- .../hr/doctype/shift_assignment/shift_assignment.py | 12 +++++++++--- erpnext/hr/doctype/staffing_plan/staffing_plan.py | 6 +++++- .../doctype/material_request/material_request.py | 4 ++-- erpnext/stock/doctype/serial_no/serial_no.py | 4 +++- erpnext/stock/get_item_details.py | 2 +- 13 files changed, 49 insertions(+), 18 deletions(-) diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict index 4c7f5f82cf..c8337a9c12 100644 --- a/.github/helper/.flake8_strict +++ b/.github/helper/.flake8_strict @@ -65,6 +65,11 @@ ignore = E713, E712, +enable-extensions = + M90 + +select = + M511 max-line-length = 200 exclude=.github/helper/semgrep_rules,test_*.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b3a471f77..e411f11301 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,10 @@ repos: rev: 3.9.2 hooks: - id: flake8 - args: ['--config', '.github/helper/.flake8_strict'] + additional_dependencies: [ + 'flake8-mutable', + ] + args: ['--select=M511', '--config', '.github/helper/.flake8_strict'] exclude: ".*setup.py$" - repo: https://github.com/timothycrosley/isort diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index 83ecfb47bb..7c53f4a0b0 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -33,7 +33,9 @@ class TestPOSProfile(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") -def get_customers_list(pos_profile={}): +def get_customers_list(pos_profile=None): + if pos_profile is None: + pos_profile = {} cond = "1=1" customer_groups = [] if pos_profile.get('customer_groups'): diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 12b486e45e..0637fdaef0 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -398,7 +398,9 @@ def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules): pricing_rules[0].apply_rule_on_other_items = items return pricing_rules -def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]): +def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None): + if items is None: + items = [] sum_qty, sum_amt = [0, 0] doctype = doc.get('parenttype') or doc.doctype diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py index d09f7dc2da..f5391ca4cc 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py @@ -69,7 +69,9 @@ class PromotionalScheme(Document): {'promotional_scheme': self.name}): frappe.delete_doc('Pricing Rule', rule.name) -def get_pricing_rules(doc, rules = {}): +def get_pricing_rules(doc, rules=None): + if rules is None: + rules = {} new_doc = [] for child_doc, fields in {'price_discount_slabs': price_discount_fields, 'product_discount_slabs': product_discount_fields}.items(): @@ -78,7 +80,9 @@ def get_pricing_rules(doc, rules = {}): return new_doc -def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}): +def _get_pricing_rules(doc, child_doc, discount_fields, rules=None): + if rules is None: + rules = {} new_doc = [] args = get_args_for_pricing_rule(doc) applicable_for = frappe.scrub(doc.get('applicable_for')) diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index d5271885b7..bb8138bfc2 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -139,9 +139,9 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_ data["total"] = total return data -def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters={}): +def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters=None): cond = "" - filters = frappe._dict(filters) + filters = frappe._dict(filters or {}) if filters.include_default_book_entries: company_fb = frappe.db.get_value("Company", company, 'default_finance_book') diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index ae498ba57d..be4ee560a5 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -138,7 +138,9 @@ class Student(Document): enrollment.submit() return enrollment - def enroll_in_course(self, course_name, program_enrollment, enrollment_date=frappe.utils.datetime.datetime.now()): + def enroll_in_course(self, course_name, program_enrollment, enrollment_date=None): + if enrollment_date is None: + enrollment_date = frappe.utils.datetime.datetime.now() try: enrollment = frappe.get_doc({ "doctype": "Course Enrollment", diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index f46f14d841..7d1b991642 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -1,4 +1,3 @@ - # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt @@ -94,9 +93,11 @@ def get_events(start, end, filters=None): update={"allDay": 1}) -def is_holiday(holiday_list, date=today()): +def is_holiday(holiday_list, date=None): """Returns true if the given date is a holiday in the given holiday list """ + if date is None: + date = today() if holiday_list: return bool(frappe.get_all('Holiday List', dict(name=holiday_list, holiday_date=date))) diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py index 69af5c54c3..05b74a0dde 100644 --- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py +++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py @@ -139,7 +139,7 @@ def get_shift_type_timing(shift_types): return shift_timing_map -def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=False, next_shift_direction=None): +def get_employee_shift(employee, for_date=None, consider_default_shift=False, next_shift_direction=None): """Returns a Shift Type for the given employee on the given date. (excluding the holidays) :param employee: Employee for which shift is required. @@ -147,6 +147,8 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals :param consider_default_shift: If set to true, default shift is taken when no shift assignment is found. :param next_shift_direction: One of: None, 'forward', 'reverse'. Direction to look for next shift if shift not found on given date. """ + if for_date is None: + for_date = nowdate() default_shift = frappe.db.get_value('Employee', employee, 'default_shift') shift_type_name = None shift_assignment_details = frappe.db.get_value('Shift Assignment', {'employee':employee, 'start_date':('<=', for_date), 'docstatus': '1', 'status': "Active"}, ['shift_type', 'end_date']) @@ -200,9 +202,11 @@ def get_employee_shift(employee, for_date=nowdate(), consider_default_shift=Fals return get_shift_details(shift_type_name, for_date) -def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_default_shift=False): +def get_employee_shift_timings(employee, for_timestamp=None, consider_default_shift=False): """Returns previous shift, current/upcoming shift, next_shift for the given timestamp and employee """ + if for_timestamp is None: + for_timestamp = now_datetime() # write and verify a test case for midnight shift. prev_shift = curr_shift = next_shift = None curr_shift = get_employee_shift(employee, for_timestamp.date(), consider_default_shift, 'forward') @@ -220,7 +224,7 @@ def get_employee_shift_timings(employee, for_timestamp=now_datetime(), consider_ return prev_shift, curr_shift, next_shift -def get_shift_details(shift_type_name, for_date=nowdate()): +def get_shift_details(shift_type_name, for_date=None): """Returns Shift Details which contain some additional information as described below. 'shift_details' contains the following keys: 'shift_type' - Object of DocType Shift Type, @@ -234,6 +238,8 @@ def get_shift_details(shift_type_name, for_date=nowdate()): """ if not shift_type_name: return None + if not for_date: + for_date = nowdate() shift_type = frappe.get_doc('Shift Type', shift_type_name) start_datetime = datetime.combine(for_date, datetime.min.time()) + shift_type.start_time for_date = for_date + timedelta(days=1) if shift_type.start_time > shift_type.end_time else for_date diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 57a92b0587..93cd4e1f62 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -155,7 +155,11 @@ def get_designation_counts(designation, company): return employee_counts @frappe.whitelist() -def get_active_staffing_plan_details(company, designation, from_date=getdate(nowdate()), to_date=getdate(nowdate())): +def get_active_staffing_plan_details(company, designation, from_date=None, to_date=None): + if from_date is None: + from_date = getdate(nowdate()) + if to_date is None: + to_date = getdate(nowdate()) if not company or not designation: frappe.throw(_("Please select Company and Designation")) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index cf98b19e7a..17df9777b1 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -296,7 +296,7 @@ def make_purchase_order(source_name, target_doc=None, args=None): return d.ordered_qty < d.stock_qty and child_filter - doclist = get_mapped_doc("Material Request", source_name, { + doclist = get_mapped_doc("Material Request", source_name, { "Material Request": { "doctype": "Purchase Order", "validation": { @@ -323,7 +323,7 @@ def make_purchase_order(source_name, target_doc=None, args=None): @frappe.whitelist() def make_request_for_quotation(source_name, target_doc=None): - doclist = get_mapped_doc("Material Request", source_name, { + doclist = get_mapped_doc("Material Request", source_name, { "Material Request": { "doctype": "Request for Quotation", "validation": { diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 82d8aaed5b..a9254fb9ec 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -611,7 +611,9 @@ def get_pos_reserved_serial_nos(filters): return reserved_sr_nos -def fetch_serial_numbers(filters, qty, do_not_include=[]): +def fetch_serial_numbers(filters, qty, do_not_include=None): + if do_not_include is None: + do_not_include = [] batch_join_selection = "" batch_no_condition = "" batch_nos = filters.get("batch_no") diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 19597c3d99..cbff2149d6 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -382,7 +382,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): return out -def get_item_warehouse(item, args, overwrite_warehouse, defaults={}): +def get_item_warehouse(item, args, overwrite_warehouse, defaults=None): if not defaults: defaults = frappe._dict({ 'item_defaults' : get_item_defaults(item.name, args.company), From eaa3614155838df0ddcdb11f3154a9cb94f86b11 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Thu, 7 Oct 2021 12:20:56 +0530 Subject: [PATCH 224/416] fix: patch fix added reload_doctype --- .../patches/v13_0/custom_fields_for_taxjar_integration.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index 139d8e43c1..1678434842 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -7,6 +7,9 @@ from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import def execute(): + + frappe.reload_doctype("Taxjar Settings") + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") @@ -15,7 +18,7 @@ def execute(): if not company or (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): return - frappe.reload_doc("regional", "doctype", "product_tax_category") + frappe.reload_doctype("Product Tax Category") custom_fields = { 'Sales Invoice Item': [ From 64c1347d1e8b5d06a05de1db3f4d642d2da648cb Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 7 Oct 2021 12:35:34 +0530 Subject: [PATCH 225/416] refactor!: remove abbreviation renaming (#27766) Co-authored-by: Ankush Menat --- erpnext/setup/doctype/company/company.js | 41 ---------------------- erpnext/setup/doctype/company/company.json | 8 +++-- erpnext/setup/doctype/company/company.py | 38 -------------------- 3 files changed, 5 insertions(+), 82 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 8403193df5..95ca3867ee 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -46,43 +46,6 @@ frappe.ui.form.on("Company", { }); }, - change_abbreviation(frm) { - var dialog = new frappe.ui.Dialog({ - title: "Replace Abbr", - fields: [ - {"fieldtype": "Data", "label": "New Abbreviation", "fieldname": "new_abbr", - "reqd": 1 }, - {"fieldtype": "Button", "label": "Update", "fieldname": "update"}, - ] - }); - - dialog.fields_dict.update.$input.click(function() { - var args = dialog.get_values(); - if (!args) return; - frappe.show_alert(__("Update in progress. It might take a while.")); - return frappe.call({ - method: "erpnext.setup.doctype.company.company.enqueue_replace_abbr", - args: { - "company": frm.doc.name, - "old": frm.doc.abbr, - "new": args.new_abbr - }, - callback: function(r) { - if (r.exc) { - frappe.msgprint(__("There were errors.")); - return; - } else { - frm.set_value("abbr", args.new_abbr); - } - dialog.hide(); - frm.refresh(); - }, - btn: this - }); - }); - dialog.show(); - }, - company_name: function(frm) { if(frm.doc.__islocal) { // add missing " " arg in split method @@ -164,10 +127,6 @@ frappe.ui.form.on("Company", { }, __('Manage')); } } - - frm.add_custom_button(__('Change Abbreviation'), () => { - frm.trigger('change_abbreviation'); - }, __('Manage')); } erpnext.company.set_chart_of_accounts_options(frm.doc); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 58cb52c04d..63d96bf85e 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -125,7 +125,8 @@ "label": "Abbr", "oldfieldname": "abbr", "oldfieldtype": "Data", - "reqd": 1 + "reqd": 1, + "set_only_once": 1 }, { "bold": 1, @@ -747,10 +748,11 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2021-07-12 11:27:06.353860", + "modified": "2021-10-04 12:09:25.833133", "modified_by": "Administrator", "module": "Setup", "name": "Company", + "naming_rule": "By fieldname", "nsm_parent_field": "parent_company", "owner": "Administrator", "permissions": [ @@ -808,4 +810,4 @@ "sort_field": "modified", "sort_order": "ASC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 87d67a5f9d..0b1b4a1ec0 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -399,44 +399,6 @@ class Company(NestedSet): if not frappe.db.get_value('GL Entry', {'company': self.name}): frappe.db.sql("delete from `tabProcess Deferred Accounting` where company=%s", self.name) -@frappe.whitelist() -def enqueue_replace_abbr(company, old, new): - kwargs = dict(queue="long", company=company, old=old, new=new) - frappe.enqueue('erpnext.setup.doctype.company.company.replace_abbr', **kwargs) - - -@frappe.whitelist() -def replace_abbr(company, old, new): - new = new.strip() - if not new: - frappe.throw(_("Abbr can not be blank or space")) - - frappe.only_for("System Manager") - - def _rename_record(doc): - parts = doc[0].rsplit(" - ", 1) - if len(parts) == 1 or parts[1].lower() == old.lower(): - frappe.rename_doc(dt, doc[0], parts[0] + " - " + new, force=True) - - def _rename_records(dt): - # rename is expensive so let's be economical with memory usage - doc = (d for d in frappe.db.sql("select name from `tab%s` where company=%s" % (dt, '%s'), company)) - for d in doc: - _rename_record(d) - try: - frappe.db.auto_commit_on_many_writes = 1 - for dt in ["Warehouse", "Account", "Cost Center", "Department", - "Sales Taxes and Charges Template", "Purchase Taxes and Charges Template"]: - _rename_records(dt) - frappe.db.commit() - frappe.db.set_value("Company", company, "abbr", new) - - except Exception: - frappe.log_error(title=_('Abbreviation Rename Error')) - finally: - frappe.db.auto_commit_on_many_writes = 0 - - def get_name_with_abbr(name, company): company_abbr = frappe.get_cached_value('Company', company, "abbr") parts = name.split(" - ") From 921b4be3481af9745811ca197b08dded8296da95 Mon Sep 17 00:00:00 2001 From: pateljannat Date: Thu, 7 Oct 2021 17:10:45 +0530 Subject: [PATCH 226/416] fix: exclude inactive employees from auto attendance --- erpnext/hr/doctype/shift_type/shift_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index e53373df27..7a35b28ac4 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -97,7 +97,7 @@ class ShiftType(Document): assigned_employees = [x[0] for x in assigned_employees] if consider_default_shift: - filters = {'default_shift': self.name} + filters = {'default_shift': self.name, 'status': ['!=', 'Inactive']} default_shift_employees = frappe.get_all('Employee', 'name', filters, as_list=True) default_shift_employees = [x[0] for x in default_shift_employees] return list(set(assigned_employees+default_shift_employees)) From 69ffddf747f774c8924d4d628222e45fe28ba7d8 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 7 Oct 2021 17:33:58 +0530 Subject: [PATCH 227/416] feat: option to set the width for the multi-select dialog box --- erpnext/public/js/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 7f39b990bf..0323a426f0 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -712,6 +712,7 @@ erpnext.utils.map_current_doc = function(opts) { allow_child_item_selection: opts.allow_child_item_selection, child_fieldname: opts.child_fielname, child_columns: opts.child_columns, + size: opts.size, action: function(selections, args) { let values = selections; if (values.length === 0) { From 646fd29f0e022c57aa1b60a141bbbf8f5a14d60a Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Thu, 7 Oct 2021 18:38:37 +0530 Subject: [PATCH 228/416] fix: update help links for Sales Invoice page (#27853) --- erpnext/public/js/help_links.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index d0c935f488..641f1f5b57 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -300,7 +300,7 @@ frappe.help.help_links["List/Sales Order"] = [ }, { label: "Recurring Sales Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Applying Discount", @@ -552,7 +552,7 @@ frappe.help.help_links["Form/Sales Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balance", }, { label: "Sales Return", From 07c680d7ccd4dd4f15b23f4315f455445d311178 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Thu, 7 Oct 2021 20:27:10 +0530 Subject: [PATCH 229/416] fix: help links for purchase cycle and JV (#27856) --- erpnext/public/js/help_links.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index 641f1f5b57..f3dc9b74f5 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -397,7 +397,7 @@ frappe.help.help_links["List/Purchase Order"] = [ }, { label: "Recurring Purchase Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -420,7 +420,7 @@ frappe.help.help_links["Form/Purchase Order"] = [ }, { label: "Recurring Purchase Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Subcontracting", @@ -604,11 +604,11 @@ frappe.help.help_links["List/Purchase Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balance", }, { label: "Recurring Purchase Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -623,7 +623,7 @@ frappe.help.help_links["List/Journal Entry"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balance", }, ]; From 5cc3ea0aa71261366436d3e02ab726b89a21a410 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Thu, 7 Oct 2021 21:09:15 +0530 Subject: [PATCH 230/416] fix(Payment Reconciliation): minor ux fixes (#27779) * fix: minor fixes * fix: Linters check * fix: sider check * fix: kept unallocated payment amount hidden in allocation * fix: removed Add row button from the tables (redundant) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> Co-authored-by: Saqib --- .../payment_reconciliation.js | 14 +++++++++++++ .../payment_reconciliation.json | 21 ++++++++++++------- .../payment_reconciliation_allocation.json | 7 ++++--- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index b1f3e6fd01..412833bd19 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -52,21 +52,35 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo refresh() { this.frm.disable_save(); + this.frm.set_df_property('invoices', 'cannot_delete_rows', true); + this.frm.set_df_property('payments', 'cannot_delete_rows', true); + this.frm.set_df_property('allocation', 'cannot_delete_rows', true); + + this.frm.set_df_property('invoices', 'cannot_add_rows', true); + this.frm.set_df_property('payments', 'cannot_add_rows', true); + this.frm.set_df_property('allocation', 'cannot_add_rows', true); + if (this.frm.doc.receivable_payable_account) { this.frm.add_custom_button(__('Get Unreconciled Entries'), () => this.frm.trigger("get_unreconciled_entries") ); + this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary'); } if (this.frm.doc.invoices.length && this.frm.doc.payments.length) { this.frm.add_custom_button(__('Allocate'), () => this.frm.trigger("allocate") ); + this.frm.change_custom_button_type('Allocate', null, 'primary'); + this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default'); } if (this.frm.doc.allocation.length) { this.frm.add_custom_button(__('Reconcile'), () => this.frm.trigger("reconcile") ); + this.frm.change_custom_button_type('Reconcile', null, 'primary'); + this.frm.change_custom_button_type('Get Unreconciled Entries', null, 'default'); + this.frm.change_custom_button_type('Allocate', null, 'default'); } } diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json index 9023b3646f..eb0c20f92d 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -12,15 +12,16 @@ "receivable_payable_account", "col_break1", "from_invoice_date", - "to_invoice_date", - "minimum_invoice_amount", - "maximum_invoice_amount", - "invoice_limit", - "column_break_13", "from_payment_date", - "to_payment_date", + "minimum_invoice_amount", "minimum_payment_amount", + "column_break_11", + "to_invoice_date", + "to_payment_date", + "maximum_invoice_amount", "maximum_payment_amount", + "column_break_13", + "invoice_limit", "payment_limit", "bank_cash_account", "sec_break1", @@ -79,6 +80,7 @@ }, { "depends_on": "eval:(doc.payments).length || (doc.invoices).length", + "description": "If you need to reconcile particular transactions against each other, then please select accordingly. If not, all the transactions will be allocated in FIFO order.", "fieldname": "sec_break1", "fieldtype": "Section Break", "label": "Unreconciled Entries" @@ -163,6 +165,7 @@ "label": "Maximum Payment Amount" }, { + "description": "System will fetch all the entries if limit value is zero.", "fieldname": "payment_limit", "fieldtype": "Int", "label": "Payment Limit" @@ -171,13 +174,17 @@ "fieldname": "maximum_invoice_amount", "fieldtype": "Currency", "label": "Maximum Invoice Amount" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" } ], "hide_toolbar": 1, "icon": "icon-resize-horizontal", "issingle": 1, "links": [], - "modified": "2021-08-30 13:05:51.977861", + "modified": "2021-10-04 20:27:11.114194", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation", diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json index b8c65eea84..6a21692c6a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json +++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json @@ -14,8 +14,8 @@ "section_break_6", "allocated_amount", "unreconciled_amount", - "amount", "column_break_8", + "amount", "is_advance", "section_break_5", "difference_amount", @@ -127,12 +127,13 @@ "fieldname": "reference_row", "fieldtype": "Data", "hidden": 1, - "label": "Reference Row" + "label": "Reference Row", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-09-20 17:23:09.455803", + "modified": "2021-10-06 11:48:59.616562", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Reconciliation Allocation", From dc4206428d86304d7b441532e0674b725b55d48d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 Oct 2021 10:41:18 +0530 Subject: [PATCH 231/416] fix: consolidated report not consider company currency --- erpnext/accounts/doctype/account/account.py | 6 +- .../consolidated_financial_statement.js | 5 +- .../consolidated_financial_statement.py | 72 ++++++++++++------- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index f6198eb23b..6cfbc6d152 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +import erpnext from frappe import _, throw from frappe.utils import cint, cstr from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of @@ -196,7 +197,7 @@ class Account(NestedSet): "company": company, # parent account's currency should be passed down to child account's curreny # if it is None, it picks it up from default company currency, which might be unintended - "account_currency": self.account_currency, + "account_currency": erpnext.get_company_currency(company), "parent_account": parent_acc_name_map[company] }) @@ -207,8 +208,7 @@ class Account(NestedSet): # update the parent company's value in child companies doc = frappe.get_doc("Account", child_account) parent_value_changed = False - for field in ['account_type', 'account_currency', - 'freeze_account', 'balance_must_be']: + for field in ['account_type', 'freeze_account', 'balance_must_be']: if doc.get(field) != self.get(field): parent_value_changed = True doc.set(field, self.get(field)) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js index 6a8301a6f9..e24a5f9918 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js @@ -103,8 +103,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() { column.is_tree = true; } - value = default_formatter(value, row, column, data); + if (data && data.account && column.apply_currency_formatter) { + data.currency = erpnext.get_currency(column.company_name); + } + value = default_formatter(value, row, column, data); if (!data.parent_account) { value = $(`${value}`); diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index b0cfbac9cb..bca13d5079 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -7,6 +7,7 @@ import frappe from frappe import _ from frappe.utils import cint, flt, getdate +import erpnext from erpnext.accounts.report.balance_sheet.balance_sheet import ( check_opening_balance, get_chart_data, @@ -31,7 +32,7 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import ( get_report_summary as get_pl_summary, ) -from erpnext.accounts.report.utils import convert_to_presentation_currency +from erpnext.accounts.report.utils import convert, convert_to_presentation_currency def execute(filters=None): @@ -42,7 +43,7 @@ def execute(filters=None): fiscal_year = get_fiscal_year_data(filters.get('from_fiscal_year'), filters.get('to_fiscal_year')) companies_column, companies = get_companies(filters) - columns = get_columns(companies_column) + columns = get_columns(companies_column, filters) if filters.get('report') == "Balance Sheet": data, message, chart, report_summary = get_balance_sheet_data(fiscal_year, companies, columns, filters) @@ -193,30 +194,37 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters): data["total"] = total return data -def get_columns(companies): - columns = [{ - "fieldname": "account", - "label": _("Account"), - "fieldtype": "Link", - "options": "Account", - "width": 300 - }] - - columns.append({ - "fieldname": "currency", - "label": _("Currency"), - "fieldtype": "Link", - "options": "Currency", - "hidden": 1 - }) +def get_columns(companies, filters): + columns = [ + { + "fieldname": "account", + "label": _("Account"), + "fieldtype": "Link", + "options": "Account", + "width": 300 + }, { + "fieldname": "currency", + "label": _("Currency"), + "fieldtype": "Link", + "options": "Currency", + "hidden": 1 + } + ] for company in companies: + apply_currency_formatter = 1 if not filters.presentation_currency else 0 + currency = filters.presentation_currency + if not currency: + currency = erpnext.get_company_currency(company) + columns.append({ "fieldname": company, - "label": company, + "label": f'{company} ({currency})', "fieldtype": "Currency", "options": "currency", - "width": 150 + "width": 150, + "apply_currency_formatter": apply_currency_formatter, + "company_name": company }) return columns @@ -236,6 +244,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i start_date = filters.period_start_date if filters.report != 'Balance Sheet' else None end_date = filters.period_end_date + filters.end_date = end_date + gl_entries_by_account = {} for root in frappe.db.sql("""select lft, rgt from tabAccount where root_type=%s and ifnull(parent_account, '') = ''""", root_type, as_dict=1): @@ -246,7 +256,7 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters) accumulate_values_into_parents(accounts, accounts_by_name, companies) - out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency) + out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters) if out: add_total_row(out, root_type, balance_must_be, companies, company_currency) @@ -271,10 +281,20 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d # check if posting date is within the period if (entry.company == company or (filters.get('accumulated_in_group_company')) and entry.company in companies.get(company)): - d[company] = d.get(company, 0.0) + flt(entry.debit) - flt(entry.credit) + parent_company_currency = erpnext.get_company_currency(d.company) + child_company_currency = erpnext.get_company_currency(entry.company) + + debit, credit = flt(entry.debit), flt(entry.credit) + + if (entry.company != company and parent_company_currency != child_company_currency + and filters.get('accumulated_in_group_company')): + debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date) + credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date) + + d[company] = d.get(company, 0.0) + flt(debit) - flt(credit) if entry.posting_date < getdate(start_date): - d["opening_balance"] = d.get("opening_balance", 0.0) + flt(entry.debit) - flt(entry.credit) + d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit) def accumulate_values_into_parents(accounts, accounts_by_name, companies): """accumulate children's values in parent accounts""" @@ -353,7 +373,7 @@ def get_accounts(root_type, filters): `tabAccount` where company = %s and root_type = %s """ , (filters.get('company'), root_type), as_dict=1) -def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency): +def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters): data = [] for d in accounts: @@ -368,9 +388,10 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com "indent": flt(d.indent), "year_start_date": start_date, "year_end_date": end_date, - "currency": company_currency, + "currency": filters.presentation_currency, "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1) }) + for company in companies: if d.get(company) and balance_must_be == "Credit": # change sign based on Debit or Credit, since calculation is done using (debit - credit) @@ -385,6 +406,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com row["has_value"] = has_value row["total"] = total + data.append(row) return data From e85c6e3fc5d32d3eaef3e4c4032a916d42c4bb28 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:19:56 +0530 Subject: [PATCH 232/416] fix: SO delivery Date not getting set via data import (#27862) (#27864) * fix: SO delivery Date not getting set via data import * fix: logic to add delivery dates * fix: linting issue Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> Co-authored-by: Afshan (cherry picked from commit d9a219850a02539f82929b5d5490554835fcde8b) Co-authored-by: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> --- erpnext/selling/doctype/sales_order/sales_order.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 9367609421..dcf478bda6 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -110,7 +110,7 @@ class SalesOrder(SellingController): if self.order_type == 'Sales' and not self.skip_delivery_note: delivery_date_list = [d.delivery_date for d in self.get("items") if d.delivery_date] max_delivery_date = max(delivery_date_list) if delivery_date_list else None - if not self.delivery_date: + if (max_delivery_date and not self.delivery_date) or (max_delivery_date and getdate(self.delivery_date) != getdate(max_delivery_date)): self.delivery_date = max_delivery_date if self.delivery_date: for d in self.get("items"): @@ -119,8 +119,6 @@ class SalesOrder(SellingController): if getdate(self.transaction_date) > getdate(d.delivery_date): frappe.msgprint(_("Expected Delivery Date should be after Sales Order Date"), indicator='orange', title=_('Warning')) - if getdate(self.delivery_date) != getdate(max_delivery_date): - self.delivery_date = max_delivery_date else: frappe.throw(_("Please enter Delivery Date")) From 90a249527de1d3ccf952d2333acefff023e2884c Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:27:16 +0530 Subject: [PATCH 233/416] fix: update dead links in help_links.js (#27860) --- erpnext/public/js/help_links.js | 199 ++++++++++++-------------------- 1 file changed, 76 insertions(+), 123 deletions(-) diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js index f3dc9b74f5..b643ccae94 100644 --- a/erpnext/public/js/help_links.js +++ b/erpnext/public/js/help_links.js @@ -5,7 +5,7 @@ const docsUrl = "https://erpnext.com/docs/"; frappe.help.help_links["Form/Rename Tool"] = [ { label: "Bulk Rename", - url: docsUrl + "user/manual/en/setting-up/data/bulk-rename", + url: docsUrl + "user/manual/en/using-erpnext/articles/bulk-rename", }, ]; @@ -59,10 +59,23 @@ frappe.help.help_links["Form/System Settings"] = [ }, ]; -frappe.help.help_links["data-import-tool"] = [ +frappe.help.help_links["Form/Data Import"] = [ { label: "Importing and Exporting Data", - url: docsUrl + "user/manual/en/setting-up/data/data-import-tool", + url: docsUrl + "user/manual/en/setting-up/data/data-import", + }, + { + label: "Overwriting Data from Data Import Tool", + url: + docsUrl + + "user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool", + }, +]; + +frappe.help.help_links["List/Data Import"] = [ + { + label: "Importing and Exporting Data", + url: docsUrl + "user/manual/en/setting-up/data/data-import", }, { label: "Overwriting Data from Data Import Tool", @@ -101,14 +114,14 @@ frappe.help.help_links["Form/Global Defaults"] = [ }, ]; -frappe.help.help_links["Form/Email Digest"] = [ +frappe.help.help_links["List/Print Heading"] = [ { - label: "Email Digest", - url: docsUrl + "user/manual/en/setting-up/email/email-digest", + label: "Print Heading", + url: docsUrl + "user/manual/en/setting-up/print/print-headings", }, ]; -frappe.help.help_links["List/Print Heading"] = [ +frappe.help.help_links["Form/Print Heading"] = [ { label: "Print Heading", url: docsUrl + "user/manual/en/setting-up/print/print-headings", @@ -153,18 +166,25 @@ frappe.help.help_links["List/Email Account"] = [ frappe.help.help_links["List/Notification"] = [ { label: "Notification", - url: docsUrl + "user/manual/en/setting-up/email/notifications", + url: docsUrl + "user/manual/en/setting-up/notifications", }, ]; frappe.help.help_links["Form/Notification"] = [ { label: "Notification", - url: docsUrl + "user/manual/en/setting-up/email/notifications", + url: docsUrl + "user/manual/en/setting-up/notifications", }, ]; -frappe.help.help_links["List/Email Digest"] = [ +frappe.help.help_links["Form/Email Digest"] = [ + { + label: "Email Digest", + url: docsUrl + "user/manual/en/setting-up/email/email-digest", + }, +]; + +frappe.help.help_links["Form/Email Digest"] = [ { label: "Email Digest", url: docsUrl + "user/manual/en/setting-up/email/email-digest", @@ -174,7 +194,7 @@ frappe.help.help_links["List/Email Digest"] = [ frappe.help.help_links["List/Auto Email Report"] = [ { label: "Auto Email Reports", - url: docsUrl + "user/manual/en/setting-up/email/email-reports", + url: docsUrl + "user/manual/en/setting-up/email/auto-email-reports", }, ]; @@ -188,14 +208,7 @@ frappe.help.help_links["Form/Print Settings"] = [ frappe.help.help_links["print-format-builder"] = [ { label: "Print Format Builder", - url: docsUrl + "user/manual/en/setting-up/print/print-settings", - }, -]; - -frappe.help.help_links["List/Print Heading"] = [ - { - label: "Print Heading", - url: docsUrl + "user/manual/en/setting-up/print/print-headings", + url: docsUrl + "user/manual/en/setting-up/print/print-format-builder", }, ]; @@ -315,7 +328,7 @@ frappe.help.help_links["Form/Sales Order"] = [ }, { label: "Recurring Sales Order", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, { label: "Applying Discount", @@ -344,14 +357,14 @@ frappe.help.help_links["Form/Sales Order"] = [ frappe.help.help_links["Form/Product Bundle"] = [ { label: "Product Bundle", - url: docsUrl + "user/manual/en/selling/setup/product-bundle", + url: docsUrl + "user/manual/en/selling/product-bundle", }, ]; frappe.help.help_links["Form/Selling Settings"] = [ { label: "Selling Settings", - url: docsUrl + "user/manual/en/selling/setup/selling-settings", + url: docsUrl + "user/manual/en/selling/selling-settings", }, ]; @@ -435,24 +448,17 @@ frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [ }, ]; -frappe.help.help_links["List/POS Profile"] = [ - { - label: "POS Profile", - url: docsUrl + "user/manual/en/setting-up/pos-setting", - }, -]; - frappe.help.help_links["List/Price List"] = [ { label: "Price List", - url: docsUrl + "user/manual/en/setting-up/price-lists", + url: docsUrl + "user/manual/en/stock/price-lists", }, ]; frappe.help.help_links["List/Authorization Rule"] = [ { label: "Authorization Rule", - url: docsUrl + "user/manual/en/setting-up/authorization-rule", + url: docsUrl + "user/manual/en/customize-erpnext/authorization-rule", }, ]; @@ -468,27 +474,14 @@ frappe.help.help_links["List/Stock Reconciliation"] = [ label: "Stock Reconciliation", url: docsUrl + - "user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item", + "user/manual/en/stock/stock-reconciliation", }, ]; frappe.help.help_links["Tree/Territory"] = [ { label: "Territory", - url: docsUrl + "user/manual/en/setting-up/territory", - }, -]; - -frappe.help.help_links["Form/Dropbox Backup"] = [ - { - label: "Dropbox Backup", - url: docsUrl + "user/manual/en/setting-up/third-party-backups", - }, - { - label: "Setting Up Dropbox Backup", - url: - docsUrl + - "user/manual/en/setting-up/articles/setting-up-dropbox-backups", + url: docsUrl + "user/manual/en/selling/territory", }, ]; @@ -501,12 +494,6 @@ frappe.help.help_links["List/Company"] = [ label: "Company", url: docsUrl + "user/manual/en/setting-up/company-setup", }, - { - label: "Managing Multiple Companies", - url: - docsUrl + - "user/manual/en/setting-up/articles/managing-multiple-companies", - }, { label: "Delete All Related Transactions for a Company", url: @@ -517,21 +504,6 @@ frappe.help.help_links["List/Company"] = [ //Accounts -frappe.help.help_links["modules/Accounts"] = [ - { - label: "Introduction to Accounts", - url: docsUrl + "user/manual/en/accounts/", - }, - { - label: "Chart of Accounts", - url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html", - }, - { - label: "Multi Currency Accounting", - url: docsUrl + "user/manual/en/accounts/multi-currency-accounting", - }, -]; - frappe.help.help_links["Tree/Account"] = [ { label: "Chart of Accounts", @@ -560,7 +532,7 @@ frappe.help.help_links["Form/Sales Invoice"] = [ }, { label: "Recurring Sales Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; @@ -571,7 +543,7 @@ frappe.help.help_links["List/Sales Invoice"] = [ }, { label: "Accounts Opening Balance", - url: docsUrl + "user/manual/en/accounts/opening-accounts", + url: docsUrl + "user/manual/en/accounts/opening-balances", }, { label: "Sales Return", @@ -579,21 +551,28 @@ frappe.help.help_links["List/Sales Invoice"] = [ }, { label: "Recurring Sales Invoice", - url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices", + url: docsUrl + "user/manual/en/accounts/articles/recurring-orders-and-invoices", }, ]; -frappe.help.help_links["pos"] = [ +frappe.help.help_links["point-of-sale"] = [ { label: "Point of Sale Invoice", - url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice", + url: docsUrl + "user/manual/en/accounts/point-of-sales", }, ]; frappe.help.help_links["List/POS Profile"] = [ { label: "Point of Sale Profile", - url: docsUrl + "user/manual/en/setting-up/pos-setting", + url: docsUrl + "user/manual/en/accounts/pos-profile", + }, +]; + +frappe.help.help_links["Form/POS Profile"] = [ + { + label: "POS Profile", + url: docsUrl + "user/manual/en/accounts/pos-profile", }, ]; @@ -644,7 +623,7 @@ frappe.help.help_links["List/Payment Request"] = [ frappe.help.help_links["List/Asset"] = [ { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, ]; @@ -659,6 +638,8 @@ frappe.help.help_links["Tree/Cost Center"] = [ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, ]; +//Stock + frappe.help.help_links["List/Item"] = [ { label: "Item", url: docsUrl + "user/manual/en/stock/item" }, { @@ -676,7 +657,7 @@ frappe.help.help_links["List/Item"] = [ }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, { label: "Item Codification", @@ -711,7 +692,7 @@ frappe.help.help_links["Form/Item"] = [ }, { label: "Managing Fixed Assets", - url: docsUrl + "user/manual/en/accounts/opening-balance/fixed_assets", + url: docsUrl + "user/manual/en/asset", }, { label: "Item Codification", @@ -771,10 +752,6 @@ frappe.help.help_links["Form/Delivery Note"] = [ url: docsUrl + "user/manual/en/stock/articles/track-items-using-barcode", }, - { - label: "Subcontracting", - url: docsUrl + "user/manual/en/manufacturing/subcontracting", - }, ]; frappe.help.help_links["List/Installation Note"] = [ @@ -784,21 +761,10 @@ frappe.help.help_links["List/Installation Note"] = [ }, ]; -frappe.help.help_links["Tree"] = [ - { - label: "Managing Tree Structure Masters", - url: - docsUrl + - "user/manual/en/setting-up/articles/managing-tree-structure-masters", - }, -]; - frappe.help.help_links["List/Budget"] = [ { label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" }, ]; -//Stock - frappe.help.help_links["List/Material Request"] = [ { label: "Material Request", @@ -861,6 +827,10 @@ frappe.help.help_links["Form/Serial No"] = [ { label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" }, ]; +frappe.help.help_links["List/Batch"] = [ + { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, +]; + frappe.help.help_links["Form/Batch"] = [ { label: "Batch", url: docsUrl + "user/manual/en/stock/batch" }, ]; @@ -868,35 +838,35 @@ frappe.help.help_links["Form/Batch"] = [ frappe.help.help_links["Form/Packing Slip"] = [ { label: "Packing Slip", - url: docsUrl + "user/manual/en/stock/tools/packing-slip", + url: docsUrl + "user/manual/en/stock/packing-slip", }, ]; frappe.help.help_links["Form/Quality Inspection"] = [ { label: "Quality Inspection", - url: docsUrl + "user/manual/en/stock/tools/quality-inspection", + url: docsUrl + "user/manual/en/stock/quality-inspection", }, ]; frappe.help.help_links["Form/Landed Cost Voucher"] = [ { label: "Landed Cost Voucher", - url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher", + url: docsUrl + "user/manual/en/stock/landed-cost-voucher", }, ]; frappe.help.help_links["Tree/Item Group"] = [ { label: "Item Group", - url: docsUrl + "user/manual/en/stock/setup/item-group", + url: docsUrl + "user/manual/en/stock/item-group", }, ]; frappe.help.help_links["Form/Item Attribute"] = [ { label: "Item Attribute", - url: docsUrl + "user/manual/en/stock/setup/item-attribute", + url: docsUrl + "user/manual/en/stock/item-attribute", }, ]; @@ -911,7 +881,7 @@ frappe.help.help_links["Form/UOM"] = [ frappe.help.help_links["Form/Stock Reconciliation"] = [ { label: "Opening Stock Entry", - url: docsUrl + "user/manual/en/stock/opening-stock", + url: docsUrl + "user/manual/en/stock/stock-reconciliation", }, ]; @@ -938,13 +908,13 @@ frappe.help.help_links["Form/Newsletter"] = [ ]; frappe.help.help_links["Form/Campaign"] = [ - { label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" }, + { label: "Campaign", url: docsUrl + "user/manual/en/CRM/campaign" }, ]; frappe.help.help_links["Tree/Sales Person"] = [ { label: "Sales Person", - url: docsUrl + "user/manual/en/CRM/setup/sales-person", + url: docsUrl + "user/manual/en/CRM/sales-person", }, ]; @@ -953,30 +923,13 @@ frappe.help.help_links["Form/Sales Person"] = [ label: "Sales Person Target", url: docsUrl + - "user/manual/en/selling/setup/sales-person-target-allocation", + "user/manual/en/selling/sales-person-target-allocation", }, -]; - -//Support - -frappe.help.help_links["List/Feedback Trigger"] = [ { - label: "Feedback Trigger", - url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback", - }, -]; - -frappe.help.help_links["List/Feedback Request"] = [ - { - label: "Feedback Request", - url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", - }, -]; - -frappe.help.help_links["List/Feedback Request"] = [ - { - label: "Feedback Request", - url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback", + label: "Sales Person in Transactions", + url: + docsUrl + + "user/manual/en/selling/articles/sales-persons-in-the-sales-transactions", }, ]; @@ -1019,7 +972,7 @@ frappe.help.help_links["Form/Operation"] = [ frappe.help.help_links["Form/BOM Update Tool"] = [ { label: "BOM Update Tool", - url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool", + url: docsUrl + "user/manual/en/manufacturing/bom-update-tool", }, ]; @@ -1036,7 +989,7 @@ frappe.help.help_links["Form/Customize Form"] = [ }, ]; -frappe.help.help_links["Form/Custom Field"] = [ +frappe.help.help_links["List/Custom Field"] = [ { label: "Custom Field", url: docsUrl + "user/manual/en/customize-erpnext/custom-field", From d3d4a3da620fcbc7cd0f608305a504dfa65befc0 Mon Sep 17 00:00:00 2001 From: yadavyk <32797974+yadavyk@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:39:33 +0530 Subject: [PATCH 234/416] fix: Salary Slip Label fixes (#27865) Co-authored-by: Rucha Mahabal --- erpnext/payroll/doctype/salary_slip/salary_slip.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 19744037a5..7a80e69374 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -329,7 +329,7 @@ { "fieldname": "earning_deduction", "fieldtype": "Section Break", - "label": "Earning & Deduction", + "label": "Earnings & Deductions", "oldfieldtype": "Section Break" }, { @@ -380,7 +380,7 @@ "depends_on": "total_loan_repayment", "fieldname": "loan_repayment", "fieldtype": "Section Break", - "label": "Loan repayment" + "label": "Loan Repayment" }, { "fieldname": "loans", @@ -425,7 +425,7 @@ { "fieldname": "net_pay_info", "fieldtype": "Section Break", - "label": "net pay info" + "label": "Net Pay Info" }, { "fieldname": "net_pay", @@ -647,7 +647,7 @@ "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-09-01 10:35:52.374549", + "modified": "2021-10-08 11:47:47.098248", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", From 41fefa356f3cbade7a65527bb14a70534475aab1 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Fri, 8 Oct 2021 14:31:13 +0530 Subject: [PATCH 235/416] fix: remove readonly from billing address --- erpnext/stock/doctype/delivery_note/delivery_note.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 9bf142c4b4..ad1b3b43ae 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -395,8 +395,7 @@ "fieldtype": "Link", "label": "Billing Address Name", "options": "Address", - "print_hide": 1, - "read_only": 1 + "print_hide": 1 }, { "fieldname": "tax_id", @@ -1309,7 +1308,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2021-09-28 13:10:09.761714", + "modified": "2021-10-08 14:29:13.428984", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", From 6019f60d0aa5e35ce8a218c328476cb0c9b3f6bf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 8 Oct 2021 16:12:15 +0530 Subject: [PATCH 236/416] fix(perf): index creation on voucher_detail_no (#27866) voucher_detail_no is supposed to have an index, it was added on on_doctype_update function of table, however this function is only called if DocType itself is updated and `on_update` is called on DocType. Stock ledger Entry doctype hasn't changed since addition of this index in function. Before: Lack of this index was causing full table scan in get_future_sle_to_fix function. (~50 seconds in a reposting job) After: Single row is fetched (~0.5 second in full reposting job) Learnings: 1. Add simple indexes via doctype only 2. For complex indexes always change doctype.json file for it to take effect. --- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.json | 5 +++-- .../stock/doctype/stock_ledger_entry/stock_ledger_entry.py | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 2463a21ed6..40ae340bfe 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -142,6 +142,7 @@ "oldfieldtype": "Data", "print_width": "150px", "read_only": 1, + "search_index": 1, "width": "150px" }, { @@ -316,7 +317,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-09-07 11:10:35.318872", + "modified": "2021-10-08 12:42:51.857631", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", @@ -338,4 +339,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index caa1d42b66..382fdfa4bf 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -181,4 +181,3 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) - frappe.db.add_index("Stock Ledger Entry", ["voucher_detail_no"]) From 44306bd0e593887e1287b0558da0e47e2c7be40a Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 9 Oct 2021 09:37:54 +0100 Subject: [PATCH 237/416] Update subscription.py --- erpnext/accounts/doctype/subscription/subscription.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 01a5363697..de9550233f 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -559,15 +559,13 @@ class Subscription(Document): else: self.set_status_grace_period() - update_subscription = True if getdate() > getdate(self.current_invoice_end) else False - # Generate invoices periodically even if current invoice are unpaid if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) - if update_subscription: + if getdate() > getdate(self.current_invoice_end): self.update_subscription_period(add_days(self.current_invoice_end, 1)) @staticmethod From ef3f2fcb3a9ccd4be37b9359b31458ba48e91865 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sun, 10 Oct 2021 03:50:45 +0530 Subject: [PATCH 238/416] fix: Replace setUp() with setUpClass() --- erpnext/assets/doctype/asset/test_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index b5488750eb..461e110a99 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -21,7 +21,8 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class AssetSetup(unittest.TestCase): - def setUp(self): + @classmethod + def setUpClass(cls): set_depreciation_settings_in_company() create_asset_data() frappe.db.sql("delete from `tabTax Rule`") From 4918e9533b10bbc56461a5514d9e8556352d7dd2 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Sun, 10 Oct 2021 03:51:35 +0530 Subject: [PATCH 239/416] fix: Add tearDownClass() --- erpnext/assets/doctype/asset/test_asset.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 461e110a99..45a1ae4a7c 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -27,6 +27,10 @@ class AssetSetup(unittest.TestCase): create_asset_data() frappe.db.sql("delete from `tabTax Rule`") + @classmethod + def tearDownClass(cls): + frappe.db.rollback() + class TestAsset(AssetSetup): def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", From b0aa4a6e1cbf9d108fa51213db8888b67733537f Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 10 Oct 2021 11:42:39 +0530 Subject: [PATCH 240/416] fix: patch fails if accounts are frozen --- erpnext/patches.txt | 2 +- .../patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 22a6313994..5ea666f00f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -299,7 +299,7 @@ erpnext.patches.v13_0.gst_fields_for_pos_invoice erpnext.patches.v13_0.create_accounting_dimensions_in_pos_doctypes erpnext.patches.v13_0.trim_sales_invoice_custom_field_length erpnext.patches.v13_0.create_custom_field_for_finance_book -erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries +erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2 erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates \ No newline at end of file diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index fa8a86437d..c2902ce9ef 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -17,7 +17,7 @@ def execute(): where ref_exchange_rate = 1 and docstatus = 1 - and ifnull(exchange_gain_loss, '') != '' + and ifnull(exchange_gain_loss, 0) != 0 group by parent """, as_dict=1) @@ -30,7 +30,7 @@ def execute(): where ref_exchange_rate = 1 and docstatus = 1 - and ifnull(exchange_gain_loss, '') != '' + and ifnull(exchange_gain_loss, 0) != 0 group by parent """, as_dict=1) @@ -38,6 +38,8 @@ def execute(): if purchase_invoices + sales_invoices: frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") + acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) for invoice in purchase_invoices + sales_invoices: doc = frappe.get_doc(invoice.type, invoice.name) doc.docstatus = 2 @@ -46,4 +48,5 @@ def execute(): if advance.ref_exchange_rate == 1: advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 - doc.make_gl_entries() \ No newline at end of file + doc.make_gl_entries() + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file From 353ad5f6ffa7e4aaa472f3882d6ef19faa9d3431 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sun, 10 Oct 2021 11:47:48 +0530 Subject: [PATCH 241/416] feat: handle exceptions --- .../modify_invalid_gain_loss_gl_entries.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index c2902ce9ef..ddf70aa814 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -39,14 +39,21 @@ def execute(): frappe.log_error(json.dumps(purchase_invoices + sales_invoices, indent=2), title="Patch Log") acc_frozen_upto = frappe.db.get_value('Accounts Settings', None, 'acc_frozen_upto') - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + for invoice in purchase_invoices + sales_invoices: - doc = frappe.get_doc(invoice.type, invoice.name) - doc.docstatus = 2 - doc.make_gl_entries() - for advance in doc.advances: - if advance.ref_exchange_rate == 1: - advance.db_set('exchange_gain_loss', 0, False) - doc.docstatus = 1 - doc.make_gl_entries() - frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file + try: + doc = frappe.get_doc(invoice.type, invoice.name) + doc.docstatus = 2 + doc.make_gl_entries() + for advance in doc.advances: + if advance.ref_exchange_rate == 1: + advance.db_set('exchange_gain_loss', 0, False) + doc.docstatus = 1 + doc.make_gl_entries() + except Exception: + print(f'Failed to correct gl entries of {invoice.name}') + + if acc_frozen_upto: + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', acc_frozen_upto) \ No newline at end of file From 19d14da0d485c82223a58dd57d3c22512ef337c0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 10 Oct 2021 14:00:25 +0530 Subject: [PATCH 242/416] fix: opening balance to calculate 'Unclosed Fiscal Years Profit / Loss (Credit)' --- erpnext/accounts/doctype/account/account.py | 3 +- .../consolidated_financial_statement.py | 96 +++++++++++++++---- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 6cfbc6d152..605262f7b3 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import frappe -import erpnext from frappe import _, throw from frappe.utils import cint, cstr from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of +import erpnext + class RootNotEditable(frappe.ValidationError): pass class BalanceMismatchError(frappe.ValidationError): pass diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index bca13d5079..e093f9618b 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -8,8 +8,8 @@ from frappe import _ from frappe.utils import cint, flt, getdate import erpnext +from collections import defaultdict from erpnext.accounts.report.balance_sheet.balance_sheet import ( - check_opening_balance, get_chart_data, get_provisional_profit_loss, ) @@ -74,21 +74,24 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): provisional_profit_loss, total_credit = get_provisional_profit_loss(asset, liability, equity, companies, filters.get('company'), company_currency, True) - message, opening_balance = check_opening_balance(asset, liability, equity) + message, opening_balance = prepare_companywise_opening_balance(asset, liability, equity, companies) - if opening_balance and round(opening_balance,2) !=0: - unclosed ={ + if opening_balance: + unclosed = { "account_name": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "account": "'" + _("Unclosed Fiscal Years Profit / Loss (Credit)") + "'", "warn_if_negative": True, "currency": company_currency } - for company in companies: - unclosed[company] = opening_balance - if provisional_profit_loss: - provisional_profit_loss[company] = provisional_profit_loss[company] - opening_balance - unclosed["total"]=opening_balance + for company in companies: + unclosed[company] = opening_balance.get(company) + if provisional_profit_loss and provisional_profit_loss.get(company): + provisional_profit_loss[company] = ( + flt(provisional_profit_loss[company]) - flt(opening_balance.get(company)) + ) + + unclosed["total"] = opening_balance.get(company) data.append(unclosed) if provisional_profit_loss: @@ -103,6 +106,37 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): return data, message, chart, report_summary +def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, companies): + opening_balance = {} + for company in companies: + opening_value = 0 + + # opening_value = Aseet - liability - equity + for data in [asset_data, liability_data, equity_data]: + account_name = get_root_account_name(data[0].root_type, company) + opening_value += get_opening_balance(account_name, data, company) + + opening_balance[company] = opening_value + + if opening_balance: + return _("Previous Financial Year is not closed"), opening_balance + + return '', {} + +def get_opening_balance(account_name, data, company): + for row in data: + if row.get('account_name') == account_name: + return row.get('company_wise_opening_bal', {}).get(company, 0.0) + +def get_root_account_name(root_type, company): + return frappe.get_all( + 'Account', + fields=['account_name'], + filters = {'root_type': root_type, 'is_group': 1, + 'company': company, 'parent_account': ('is', 'not set')}, + as_list=1 + )[0][0] + def get_profit_loss_data(fiscal_year, companies, columns, filters): income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters) company_currency = get_company_currency(filters) @@ -254,8 +288,9 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i end_date, root.lft, root.rgt, filters, gl_entries_by_account, accounts_by_name, accounts, ignore_closing_entries=False) - calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters) + calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year) accumulate_values_into_parents(accounts, accounts_by_name, companies) + out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters) if out: @@ -267,7 +302,10 @@ def get_company_currency(filters=None): return (filters.get('presentation_currency') or frappe.get_cached_value('Company', filters.company, "default_currency")) -def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_date, filters): +def calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year): + start_date = (fiscal_year.year_start_date + if filters.filter_based_on == 'Fiscal Year' else filters.period_start_date) + for entries in gl_entries_by_account.values(): for entry in entries: if entry.account_number: @@ -276,7 +314,9 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d account_name = entry.account_name d = accounts_by_name.get(account_name) + if d: + debit, credit = 0, 0 for company in companies: # check if posting date is within the period if (entry.company == company or (filters.get('accumulated_in_group_company')) @@ -286,13 +326,18 @@ def calculate_values(accounts_by_name, gl_entries_by_account, companies, start_d debit, credit = flt(entry.debit), flt(entry.credit) - if (entry.company != company and parent_company_currency != child_company_currency + if (not filters.get('presentation_currency') + and entry.company != company + and parent_company_currency != child_company_currency and filters.get('accumulated_in_group_company')): debit = convert(debit, parent_company_currency, child_company_currency, filters.end_date) credit = convert(credit, parent_company_currency, child_company_currency, filters.end_date) d[company] = d.get(company, 0.0) + flt(debit) - flt(credit) + if entry.posting_date < getdate(start_date): + d['company_wise_opening_bal'][company] += (flt(debit) - flt(credit)) + if entry.posting_date < getdate(start_date): d["opening_balance"] = d.get("opening_balance", 0.0) + flt(debit) - flt(credit) @@ -302,17 +347,18 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies): if d.parent_account: account = d.parent_account_name - if not accounts_by_name.get(account): - continue + # if not accounts_by_name.get(account): + # continue for company in companies: accounts_by_name[account][company] = \ accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0) + accounts_by_name[account]['company_wise_opening_bal'][company] += d.get('company_wise_opening_bal', {}).get(company, 0.0) + accounts_by_name[account]["opening_balance"] = \ accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0) - def get_account_heads(root_type, companies, filters): accounts = get_accounts(root_type, filters) @@ -387,8 +433,10 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com "parent_account": _(d.parent_account), "indent": flt(d.indent), "year_start_date": start_date, + "root_type": d.root_type, "year_end_date": end_date, "currency": filters.presentation_currency, + "company_wise_opening_bal": d.company_wise_opening_bal, "opening_balance": d.get("opening_balance", 0.0) * (1 if balance_must_be == "Debit" else -1) }) @@ -469,6 +517,7 @@ def get_account_details(account): 'is_group', 'account_name', 'account_number', 'parent_account', 'lft', 'rgt'], as_dict=1) def validate_entries(key, entry, accounts_by_name, accounts): + # If an account present in the child company and not in the parent company if key not in accounts_by_name: args = get_account_details(entry.account) @@ -478,12 +527,23 @@ def validate_entries(key, entry, accounts_by_name, accounts): args.update({ 'lft': parent_args.lft + 1, 'rgt': parent_args.rgt - 1, + 'indent': 3, 'root_type': parent_args.root_type, - 'report_type': parent_args.report_type + 'report_type': parent_args.report_type, + 'parent_account_name': parent_args.account_name, + 'company_wise_opening_bal': defaultdict(float) }) accounts_by_name.setdefault(key, args) - accounts.append(args) + + idx = len(accounts) + # To identify parent account index + for index, row in enumerate(accounts): + if row.parent_account_name == args.parent_account_name: + idx = index + break + + accounts.insert(idx+1, args) def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions = [] @@ -513,7 +573,6 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency): for company in companies: total_row.setdefault(company, 0.0) total_row[company] += row.get(company, 0.0) - row[company] = 0.0 total_row.setdefault("total", 0.0) total_row["total"] += flt(row["total"]) @@ -533,6 +592,7 @@ def filter_accounts(accounts, depth=10): account_name = d.account_number + ' - ' + d.account_name else: account_name = d.account_name + d['company_wise_opening_bal'] = defaultdict(float) accounts_by_name[account_name] = d parent_children_map.setdefault(d.parent_account or None, []).append(d) From cb2213ef0cc066eb2ba40d6cb1d8ecf3d377d842 Mon Sep 17 00:00:00 2001 From: Ahmad Date: Sun, 10 Oct 2021 21:17:59 +0200 Subject: [PATCH 243/416] refactor: make strings translate --- erpnext/regional/report/ksa_vat/ksa_vat.py | 4 ++-- .../saudi_arabia/wizard/operations/setup_ksa_vat_setting.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/regional/report/ksa_vat/ksa_vat.py b/erpnext/regional/report/ksa_vat/ksa_vat.py index 1697bbbd4d..a42ebc9f7e 100644 --- a/erpnext/regional/report/ksa_vat/ksa_vat.py +++ b/erpnext/regional/report/ksa_vat/ksa_vat.py @@ -150,7 +150,7 @@ def get_tax_data_for_each_vat_setting(vat_setting, filters, doctype): def append_data(data, title, amount, adjustment_amount, vat_amount): """Returns data with appended value.""" - data.append({"title":title, "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) + data.append({"title": _(title), "amount": amount, "adjustment_amount": adjustment_amount, "vat_amount": vat_amount}) def get_tax_amount(item_code, account_head, doctype, parent): if doctype == 'Sales Invoice': @@ -173,4 +173,4 @@ def get_tax_amount(item_code, account_head, doctype, parent): tax_amount = value[1] break - return tax_amount \ No newline at end of file + return tax_amount diff --git a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py index c30fcffc37..3c89edd37e 100644 --- a/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py +++ b/erpnext/regional/saudi_arabia/wizard/operations/setup_ksa_vat_setting.py @@ -16,7 +16,6 @@ def create_ksa_vat_setting(company): with open(file_path, 'r') as json_file: account_data = json.load(json_file) - # Creating KSA VAT Setting ksa_vat_setting = frappe.get_doc({ 'doctype': 'KSA VAT Setting', @@ -44,4 +43,4 @@ def create_ksa_vat_setting(company): 'account': f'{account} - {company.abbr}' }) - ksa_vat_setting.save() \ No newline at end of file + ksa_vat_setting.save() From 1b25e69af4f4069c5e8df713bc852aee06b8acb8 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 11 Oct 2021 10:20:23 +0530 Subject: [PATCH 244/416] fix: patch fixes- force reload doc, check for company --- .../v13_0/custom_fields_for_taxjar_integration.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index 1678434842..ae129f6b05 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -8,14 +8,17 @@ from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import def execute(): - frappe.reload_doctype("Taxjar Settings") + company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name']) + if not company: + return + + frappe.reload_doctype("Taxjar Settings", force=True) TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") - company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name']) - if not company or (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): + if (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): return frappe.reload_doctype("Product Tax Category") From ea2038489fd6b225154eb0d11bf9ce5eb5983e14 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 11 Oct 2021 11:11:01 +0530 Subject: [PATCH 245/416] fix: 'Taxjar' type fix --- erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index ae129f6b05..4f38af4523 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -12,7 +12,7 @@ def execute(): if not company: return - frappe.reload_doctype("Taxjar Settings", force=True) + frappe.reload_doctype("TaxJar Settings", force=True) TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") From 4dc17a856ef8a4ed5950e6e94406ef5d0daeddb9 Mon Sep 17 00:00:00 2001 From: ChillarAnand Date: Mon, 11 Oct 2021 10:37:59 +0530 Subject: [PATCH 246/416] fix(CI): Use bugbear instead of flake8-mutable --- .github/helper/.flake8_strict | 7 ++----- .pre-commit-config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/helper/.flake8_strict b/.github/helper/.flake8_strict index c8337a9c12..a79137d7c3 100644 --- a/.github/helper/.flake8_strict +++ b/.github/helper/.flake8_strict @@ -1,6 +1,8 @@ [flake8] ignore = B007, + B009, + B010, B950, E101, E111, @@ -65,11 +67,6 @@ ignore = E713, E712, -enable-extensions = - M90 - -select = - M511 max-line-length = 200 exclude=.github/helper/semgrep_rules,test_*.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e411f11301..b74d9a640d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,9 +21,9 @@ repos: hooks: - id: flake8 additional_dependencies: [ - 'flake8-mutable', + 'flake8-bugbear', ] - args: ['--select=M511', '--config', '.github/helper/.flake8_strict'] + args: ['--config', '.github/helper/.flake8_strict'] exclude: ".*setup.py$" - repo: https://github.com/timothycrosley/isort From f0c4ea14a989192def4e06b018f5de03f39f2207 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Mon, 11 Oct 2021 11:59:26 +0530 Subject: [PATCH 247/416] fix(hr): Update expense account after company is updated (#27843) --- .../hr/doctype/expense_claim/expense_claim.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 3c4c672816..218e97d7fc 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -10,6 +10,26 @@ frappe.ui.form.on('Expense Claim', { }, company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + var expenses = frm.doc.expenses; + for (var i = 0; i < expenses.length; i++) { + var expense = expenses[i]; + if (!expense.expense_type) { + continue; + } + frappe.call({ + method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account_and_cost_center", + args: { + "expense_claim_type": expense.expense_type, + "company": frm.doc.company + }, + callback: function(r) { + if (r.message) { + expense.default_account = r.message.account; + expense.cost_center = r.message.cost_center; + } + } + }); + } }, }); From 0a3dd3e954b3dbe19d01e0c77a33a8475567d494 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 11 Oct 2021 12:10:33 +0530 Subject: [PATCH 248/416] fix: bom item query #27890 fix: bom item query --- erpnext/manufacturing/doctype/bom/bom.py | 3 +-- erpnext/manufacturing/doctype/bom/test_bom.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 7cfec974fc..232e3a0b0f 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1133,8 +1133,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): query_filters["has_variants"] = 0 if filters and filters.get("is_stock_item"): - or_cond_filters["is_stock_item"] = 1 - or_cond_filters["has_variants"] = 1 + query_filters["is_stock_item"] = 1 return frappe.get_list("Item", fields = fields, filters=query_filters, diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 706ea268c6..4c032307d8 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -4,13 +4,14 @@ import unittest from collections import deque +from functools import partial import frappe from frappe.test_runner import make_test_records from frappe.utils import cstr, flt from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order -from erpnext.manufacturing.doctype.bom.bom import make_variant_bom +from erpnext.manufacturing.doctype.bom.bom import item_query, make_variant_bom from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( @@ -375,6 +376,16 @@ class TestBOM(unittest.TestCase): # FG Items in Scrap/Loss Table should have Is Process Loss set self.assertRaises(frappe.ValidationError, bom_doc.submit) + def test_bom_item_query(self): + query = partial(item_query, doctype="Item", txt="", searchfield="name", start=0, page_len=20, filters={"is_stock_item": 1}) + + test_items = query(txt="_Test") + filtered = query(txt="_Test Item 2") + + self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results") + self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results") + + def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) From 3337ae120cdf722c157b2048106399e2f4ca0b14 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 11 Oct 2021 13:12:02 +0530 Subject: [PATCH 249/416] fix: Status check for closed loans --- .../doctype/loan_disbursement/loan_disbursement.py | 5 ++++- .../loan_management/doctype/loan_repayment/loan_repayment.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py index 6d9d4f490d..99f0d25924 100644 --- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py +++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py @@ -198,7 +198,7 @@ def get_disbursal_amount(loan, on_current_security_price=0): security_value = get_total_pledged_security_value(loan) if loan_details.is_secured_loan and not on_current_security_price: - security_value = flt(loan_details.maximum_loan_amount) + security_value = get_maximum_amount_as_per_pledged_security(loan) if not security_value and not loan_details.is_secured_loan: security_value = flt(loan_details.loan_amount) @@ -209,3 +209,6 @@ def get_disbursal_amount(loan, on_current_security_price=0): disbursal_amount = loan_details.loan_amount - loan_details.disbursed_amount return disbursal_amount + +def get_maximum_amount_as_per_pledged_security(loan): + return flt(frappe.db.get_value('Loan Security Pledge', {'loan': loan}, 'sum(maximum_loan_value)')) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 13b7357327..40bb581165 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -411,7 +411,7 @@ def get_amounts(amounts, against_loan, posting_date): if due_date and not final_due_date: final_due_date = add_days(due_date, loan_type_details.grace_period_in_days) - if against_loan_doc.status in ('Disbursed', 'Loan Closure Requested', 'Closed'): + if against_loan_doc.status in ('Disbursed', 'Closed') or against_loan_doc.disbursed_amount >= against_loan_doc.loan_amount: pending_principal_amount = against_loan_doc.total_payment - against_loan_doc.total_principal_paid \ - against_loan_doc.total_interest_payable - against_loan_doc.written_off_amount else: From 7acdcc70ad4741fb0fd14750ab76630b15cb4440 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 11 Oct 2021 15:05:55 +0530 Subject: [PATCH 250/416] =?UTF-8?q?fix:=20v12=20migrate=20error=20-=20unkn?= =?UTF-8?q?own=20column=20=E2=80=98mandatory=5Fdepends=5Fon=E2=80=99=20(#2?= =?UTF-8?q?7897)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: v12 doesn't have mandatory_depends_on field * fix: move update_vehicle_no_reqd_condition to v13 * fix: move update_vehicle_no_reqd_condition to v13 * fix: file name missing .py * refactor!: add back empty line * fix: linters issue --- .../consolidated_financial_statement.py | 3 ++- erpnext/patches.txt | 4 ++-- .../{v12_0 => v13_0}/update_vehicle_no_reqd_condition.py | 0 3 files changed, 4 insertions(+), 3 deletions(-) rename erpnext/patches/{v12_0 => v13_0}/update_vehicle_no_reqd_condition.py (100%) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e093f9618b..a600ead9e5 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -3,12 +3,13 @@ from __future__ import unicode_literals +from collections import defaultdict + import frappe from frappe import _ from frappe.utils import cint, flt, getdate import erpnext -from collections import defaultdict from erpnext.accounts.report.balance_sheet.balance_sheet import ( get_chart_data, get_provisional_profit_loss, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 22a6313994..c55fb0abd1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -246,7 +246,7 @@ erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes -erpnext.patches.v12_0.update_vehicle_no_reqd_condition +erpnext.patches.v13_0.update_vehicle_no_reqd_condition erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae @@ -302,4 +302,4 @@ erpnext.patches.v13_0.create_custom_field_for_finance_book erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table -erpnext.patches.v13_0.add_default_interview_notification_templates \ No newline at end of file +erpnext.patches.v13_0.add_default_interview_notification_templates diff --git a/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py similarity index 100% rename from erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py rename to erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py From 530de12b079ff89a97b4270d85d471155d1f319b Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 11 Oct 2021 17:33:41 +0530 Subject: [PATCH 251/416] feat: HSN wise tax breakup check in GST Settings --- .../doctype/gst_settings/gst_settings.json | 39 ++++++++----------- erpnext/regional/india/utils.py | 15 +++++-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 95b930c4c8..1dc30e4e23 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -8,6 +8,7 @@ "gst_summary", "column_break_2", "round_off_gst_values", + "hsn_wise_tax_breakup", "gstin_email_sent_on", "section_break_4", "gst_accounts", @@ -17,37 +18,27 @@ { "fieldname": "gst_summary", "fieldtype": "HTML", - "label": "GST Summary", - "show_days": 1, - "show_seconds": 1 + "label": "GST Summary" }, { "fieldname": "column_break_2", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "gstin_email_sent_on", "fieldtype": "Date", "label": "GSTIN Email Sent On", - "read_only": 1, - "show_days": 1, - "show_seconds": 1 + "read_only": 1 }, { "fieldname": "section_break_4", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "gst_accounts", "fieldtype": "Table", "label": "GST Accounts", - "options": "GST Account", - "show_days": 1, - "show_seconds": 1 + "options": "GST Account" }, { "default": "250000", @@ -56,24 +47,26 @@ "fieldtype": "Data", "in_list_view": 1, "label": "B2C Limit", - "reqd": 1, - "show_days": 1, - "show_seconds": 1 + "reqd": 1 }, { "default": "0", "description": "Enabling this option will round off individual GST components in all the Invoices", "fieldname": "round_off_gst_values", "fieldtype": "Check", - "label": "Round Off GST Values", - "show_days": 1, - "show_seconds": 1 + "label": "Round Off GST Values" + }, + { + "default": "0", + "fieldname": "hsn_wise_tax_breakup", + "fieldtype": "Check", + "label": "HSN Wise Tax Breakup " } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-01-28 17:19:47.969260", + "modified": "2021-10-11 15:52:05.250159", "modified_by": "Administrator", "module": "Regional", "name": "GST Settings", @@ -83,4 +76,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 - } \ No newline at end of file +} \ No newline at end of file diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 94936143d8..0e4128024d 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -112,7 +112,11 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label)) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): - return [_("Item"), _("Taxable Amount")] + tax_accounts + hsn_wise_in_gst_settings = frappe.db.get_single_value('GST Settings','hsn_wise_tax_breakup') + if frappe.get_meta(item_doctype).has_field('gst_hsn_code') and hsn_wise_in_gst_settings: + return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts + else: + return [_("Item"), _("Taxable Amount")] + tax_accounts def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): itemised_tax = get_itemised_tax(doc.taxes, with_tax_account=account_wise) @@ -122,14 +126,17 @@ def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'): return itemised_tax, itemised_taxable_amount - if hsn_wise: + hsn_wise_in_gst_settings = frappe.db.get_single_value('GST Settings','hsn_wise_tax_breakup') + + tax_breakup_hsn_wise = hsn_wise or hsn_wise_in_gst_settings + if tax_breakup_hsn_wise: item_hsn_map = frappe._dict() for d in doc.items: item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code")) hsn_tax = {} for item, taxes in itemised_tax.items(): - item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + item_or_hsn = item if not tax_breakup_hsn_wise else item_hsn_map.get(item) hsn_tax.setdefault(item_or_hsn, frappe._dict()) for tax_desc, tax_detail in taxes.items(): key = tax_desc @@ -142,7 +149,7 @@ def get_itemised_tax_breakup_data(doc, account_wise=False, hsn_wise=False): # set taxable amount hsn_taxable_amount = frappe._dict() for item in itemised_taxable_amount: - item_or_hsn = item if not hsn_wise else item_hsn_map.get(item) + item_or_hsn = item if not tax_breakup_hsn_wise else item_hsn_map.get(item) hsn_taxable_amount.setdefault(item_or_hsn, 0) hsn_taxable_amount[item_or_hsn] += itemised_taxable_amount.get(item) From fc4facc5dc38103819d8ade40beb5a0824589b0e Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Mon, 11 Oct 2021 18:19:58 +0530 Subject: [PATCH 252/416] added new section in gst settings page --- .../doctype/gst_settings/gst_settings.json | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/erpnext/regional/doctype/gst_settings/gst_settings.json b/erpnext/regional/doctype/gst_settings/gst_settings.json index 1dc30e4e23..fc579d4b38 100644 --- a/erpnext/regional/doctype/gst_settings/gst_settings.json +++ b/erpnext/regional/doctype/gst_settings/gst_settings.json @@ -6,8 +6,9 @@ "engine": "InnoDB", "field_order": [ "gst_summary", - "column_break_2", + "gst_tax_settings_section", "round_off_gst_values", + "column_break_4", "hsn_wise_tax_breakup", "gstin_email_sent_on", "section_break_4", @@ -20,10 +21,6 @@ "fieldtype": "HTML", "label": "GST Summary" }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, { "fieldname": "gstin_email_sent_on", "fieldtype": "Date", @@ -60,13 +57,22 @@ "default": "0", "fieldname": "hsn_wise_tax_breakup", "fieldtype": "Check", - "label": "HSN Wise Tax Breakup " + "label": "Tax Breakup Table Based On HSN Code" + }, + { + "fieldname": "gst_tax_settings_section", + "fieldtype": "Section Break", + "label": "GST Tax Settings" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-10-11 15:52:05.250159", + "modified": "2021-10-11 18:10:14.242614", "modified_by": "Administrator", "module": "Regional", "name": "GST Settings", From ad444153cc899589b78e14168088dd6ff16d8f83 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 11 Oct 2021 22:14:28 +0530 Subject: [PATCH 253/416] fix: force reload custom field doctype (#27909) custom_field.json has the same modified key in both versions but not the same content. This can happen again if something is backported, safe solution is to force reload. --- erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py index 69bfaaa2cb..902707b4b6 100644 --- a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py +++ b/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py @@ -2,7 +2,7 @@ import frappe def execute(): - frappe.reload_doc('custom', 'doctype', 'custom_field') + frappe.reload_doc('custom', 'doctype', 'custom_field', force=True) company = frappe.get_all('Company', filters = {'country': 'India'}) if not company: return From d824a90fac68eb76d3f824249e83466201e96b02 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 11 Oct 2021 23:14:28 +0530 Subject: [PATCH 254/416] fix: Avoid automatic customer creation on website user login --- erpnext/shopping_cart/utils.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py index f412e61f06..6e32b61dce 100644 --- a/erpnext/shopping_cart/utils.py +++ b/erpnext/shopping_cart/utils.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - import frappe from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import ( @@ -18,10 +15,17 @@ def show_cart_count(): return False def set_cart_count(login_manager): - role, parties = check_customer_or_supplier() - if role == 'Supplier': return + # since this is run only on hooks login event + # make sure user is already a customer + # before trying to set cart count + user_is_customer = is_customer() + if not user_is_customer: return + if show_cart_count(): from erpnext.shopping_cart.cart import set_cart_count + # set_cart_count will try to fetch existing cart quotation + # or create one if non existent (and create a customer too) + # cart count is calculated from this quotation's items set_cart_count() def clear_cart_count(login_manager): @@ -32,13 +36,13 @@ def update_website_context(context): cart_enabled = is_cart_enabled() context["shopping_cart_enabled"] = cart_enabled -def check_customer_or_supplier(): - if frappe.session.user: +def is_customer(): + if frappe.session.user and frappe.session.user != "Guest": contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user}) if contact_name: contact = frappe.get_doc('Contact', contact_name) for link in contact.links: - if link.link_doctype in ('Customer', 'Supplier'): - return link.link_doctype, link.link_name + if link.link_doctype == 'Customer': + return True - return 'Customer', None + return False From d8aaf3d389366544b39c05d26e0bc29312f0e797 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 12 Oct 2021 01:07:11 +0530 Subject: [PATCH 255/416] fix: Add tests to validate Asset values --- erpnext/assets/doctype/asset/test_asset.py | 62 +++++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 45a1ae4a7c..6a283df9f4 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -32,6 +32,49 @@ class AssetSetup(unittest.TestCase): frappe.db.rollback() class TestAsset(AssetSetup): + def test_asset_category_is_fetched(self): + """Tests if the Item's Asset Category value is assigned to the Asset, if the field is empty.""" + + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.asset_category = None + asset.save() + + self.assertEqual(asset.asset_category, "Computers") + + def test_gross_purchase_amount_is_mandatory(self): + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.gross_purchase_amount = 0 + + self.assertRaises(frappe.MandatoryError, asset.save) + + def test_pr_or_pi_mandatory_if_not_existing_asset(self): + """Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0.""" + + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + enable_cwip_accounting(asset.asset_category) + asset.is_existing_asset=0 + + self.assertRaises(frappe.ValidationError, asset.save) + + enable_cwip_accounting(asset.asset_category, enable=0) + + def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.calculate_depreciation = 1 + + self.assertRaises(frappe.ValidationError, asset.save) + + # def test_available_for_use_date_is_after_purchase_date(self): + # pr = make_purchase_receipt(item_code="Macbook Pro", + # qty=1, rate=100000.0, location="Test Location", posting_date=add_days(nowdate(), -15)) + + # asset = create_asset(item_code="Macbook Pro", do_not_save=1) + # asset.is_existing_asset = 0 + # asset.purchase_receipt = pr + # asset.available_for_use_date = nowdate() + + # self.assertRaises(frappe.ValidationError, asset.save) + def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") @@ -950,20 +993,20 @@ def create_asset(**args): asset = frappe.get_doc({ "doctype": "Asset", "asset_name": args.asset_name or "Macbook Pro 1", - "asset_category": "Computers", + "asset_category": args.asset_category or "Computers", "item_code": args.item_code or "Macbook Pro", - "company": args.company or"_Test Company", - "purchase_date": "2015-01-01", + "company": args.company or "_Test Company", + "purchase_date": args.purchase_date or "2015-01-01", "calculate_depreciation": args.calculate_depreciation or 0, "opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0, "number_of_depreciations_booked": args.number_of_depreciations_booked or 0, - "gross_purchase_amount": 100000, - "purchase_receipt_amount": 100000, + "gross_purchase_amount": args.gross_purchase_amount or 100000, + "purchase_receipt_amount": args.purchase_receipt_amount or 100000, "warehouse": args.warehouse or "_Test Warehouse - _TC", "available_for_use_date": args.available_for_use_date or "2020-06-06", - "location": "Test Location", - "asset_owner": "Company", - "is_existing_asset": 1 + "location": args.location or "Test Location", + "asset_owner": args.asset_owner or "Company", + "is_existing_asset": args.is_existing_asset or 1 }) if asset.calculate_depreciation: @@ -1030,3 +1073,6 @@ def set_depreciation_settings_in_company(): # Enable booking asset depreciation entry automatically frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) + +def enable_cwip_accounting(asset_category, enable=1): + frappe.db.set_value("Asset Category", asset_category, "enable_cwip_accounting", enable) From a7ec007dcff77f99d1af9e8f8c473cba629f6efe Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 12 Oct 2021 01:33:46 +0530 Subject: [PATCH 256/416] fix: Add tests to validate item --- erpnext/assets/doctype/asset/test_asset.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 6a283df9f4..ce1d8d5194 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -75,6 +75,27 @@ class TestAsset(AssetSetup): # self.assertRaises(frappe.ValidationError, asset.save) + def test_item_exists(self): + asset = create_asset(item_code="MacBook", do_not_save=1) + + self.assertRaises(frappe.DoesNotExistError, asset.save) + + def test_validate_item(self): + asset = create_asset(item_code="MacBook Pro", do_not_save=1) + item = frappe.get_doc("Item", "MacBook Pro") + + item.disabled = 1 + item.save() + self.assertRaises(frappe.ValidationError, asset.save) + item.disabled = 0 + + item.is_fixed_asset = 0 + self.assertRaises(frappe.ValidationError, asset.save) + item.is_fixed_asset = 1 + + item.is_stock_item = 1 + self.assertRaises(frappe.ValidationError, asset.save) + def test_purchase_asset(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") From e1967870a9673cf8552c6de4a6a366df131672b3 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 12 Oct 2021 10:27:23 +0530 Subject: [PATCH 257/416] Merge pull request #27906 from Anuja-pawar/accounts-settings fix(Accounts Settings): Update label --- .../accounts/doctype/accounts_settings/accounts_settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 7d0ecfbafd..55ea571ebf 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -174,7 +174,7 @@ "default": "0", "fieldname": "automatically_fetch_payment_terms", "fieldtype": "Check", - "label": "Automatically Fetch Payment Terms" + "label": "Automatically Fetch Payment Terms from Order" }, { "description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ", @@ -282,7 +282,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2021-08-19 11:17:38.788054", + "modified": "2021-10-11 17:42:36.427699", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", From 401e22fb8d8a0bcf07e8318e9a97a3e8891976e9 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Tue, 12 Oct 2021 10:28:36 +0530 Subject: [PATCH 258/416] fix(accounts): Fix issue with fetching loyalty point entries (#27892) --- .../accounts/doctype/loyalty_point_entry/loyalty_point_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py index 0813926f5f..003389e0b5 100644 --- a/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py +++ b/erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py @@ -16,7 +16,7 @@ class LoyaltyPointEntry(Document): def get_loyalty_point_entries(customer, loyalty_program, company, expiry_date=None): if not expiry_date: - date = today() + expiry_date = today() return frappe.db.sql(''' select name, loyalty_points, expiry_date, loyalty_program_tier, invoice_type, invoice From 8355af6dcf6a6e770c354133e1f08012ad9bdfc9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Oct 2021 12:44:40 +0530 Subject: [PATCH 259/416] fix: Incorrect maximum loan amount update --- .../loan_management/doctype/loan/loan.json | 3 +-- erpnext/loan_management/doctype/loan/loan.py | 25 ++++++++++++------- .../loan_application/loan_application.py | 7 +++--- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json index c9f23ca4df..5979992bbe 100644 --- a/erpnext/loan_management/doctype/loan/loan.json +++ b/erpnext/loan_management/doctype/loan/loan.json @@ -334,7 +334,6 @@ }, { "depends_on": "eval:doc.is_secured_loan", - "fetch_from": "loan_application.maximum_loan_amount", "fieldname": "maximum_loan_amount", "fieldtype": "Currency", "label": "Maximum Loan Amount", @@ -360,7 +359,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:10:32.360818", + "modified": "2021-10-12 18:10:32.360818", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan", diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py index 7dbd42297e..0f2c3cfdfc 100644 --- a/erpnext/loan_management/doctype/loan/loan.py +++ b/erpnext/loan_management/doctype/loan/loan.py @@ -137,16 +137,23 @@ class Loan(AccountsController): frappe.throw(_("Loan amount is mandatory")) def link_loan_security_pledge(self): - if self.is_secured_loan: - loan_security_pledge = frappe.db.get_value('Loan Security Pledge', {'loan_application': self.loan_application}, - 'name') + if self.is_secured_loan and self.loan_application: + maximum_loan_value = frappe.db.get_value('Loan Security Pledge', + { + 'loan_application': self.loan_application, + 'status': 'Requested' + }, + 'sum(maximum_loan_value)' + ) - if loan_security_pledge: - frappe.db.set_value('Loan Security Pledge', loan_security_pledge, { - 'loan': self.name, - 'status': 'Pledged', - 'pledge_time': now_datetime() - }) + if maximum_loan_value: + frappe.db.sql(""" + UPDATE `tabLoan Security Pledge` + SET loan = %s, pledge_time = %s, status = 'Pledged' + WHERE status = 'Requested' and loan_application = %s + """, (self.name, now_datetime(), self.loan_application)) + + self.db_set('maximum_loan_amount', maximum_loan_value) def unlink_loan_security_pledge(self): pledges = frappe.get_all('Loan Security Pledge', fields=['name'], filters={'loan': self.name}) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index e492920abb..66a3be3513 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -130,10 +130,11 @@ class LoanApplication(Document): def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", - fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type} - )[0] + fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], + filters = {'name': source_doc.loan_type})[0] + if source_doc.is_secured_loan: + target_doc.maximum_loan_amount = 0 target_doc.mode_of_payment = account_details.mode_of_payment target_doc.payment_account = account_details.payment_account From af14ba43de755104bfab67b9e72ba838f30e355a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 12 Oct 2021 12:58:42 +0530 Subject: [PATCH 260/416] fix: Linting issues --- .../doctype/loan_application/loan_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.py b/erpnext/loan_management/doctype/loan_application/loan_application.py index 66a3be3513..ede0467b0e 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.py +++ b/erpnext/loan_management/doctype/loan_application/loan_application.py @@ -131,7 +131,7 @@ def create_loan(source_name, target_doc=None, submit=0): def update_accounts(source_doc, target_doc, source_parent): account_details = frappe.get_all("Loan Type", fields=["mode_of_payment", "payment_account","loan_account", "interest_income_account", "penalty_income_account"], - filters = {'name': source_doc.loan_type})[0] + filters = {'name': source_doc.loan_type})[0] if source_doc.is_secured_loan: target_doc.maximum_loan_amount = 0 From 5d4c919c5ce8ce0c112e368fb94b1fb862ea3c78 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 12 Oct 2021 13:04:25 +0530 Subject: [PATCH 261/416] fix: Update pacthes.txt --- .../patches/v13_0/custom_fields_for_taxjar_integration.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index 4f38af4523..49c2a3bc2e 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -7,13 +7,14 @@ from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import def execute(): + frappe.reload_doctype("TaxJar Settings", force=True) + frappe.reload_doctype("Product Tax Category", force=True) company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name']) if not company: return - frappe.reload_doctype("TaxJar Settings", force=True) - + TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") @@ -21,8 +22,6 @@ def execute(): if (not TAXJAR_CREATE_TRANSACTIONS and not TAXJAR_CALCULATE_TAX and not TAXJAR_SANDBOX_MODE): return - frappe.reload_doctype("Product Tax Category") - custom_fields = { 'Sales Invoice Item': [ dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category', From c103f72faddf9b7afd9d58d44703e98c6e687605 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:13:48 +0530 Subject: [PATCH 262/416] fix: rollback on exception --- erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py index ddf70aa814..3af7dac342 100644 --- a/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py +++ b/erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py @@ -52,7 +52,9 @@ def execute(): advance.db_set('exchange_gain_loss', 0, False) doc.docstatus = 1 doc.make_gl_entries() + frappe.db.commit() except Exception: + frappe.db.rollback() print(f'Failed to correct gl entries of {invoice.name}') if acc_frozen_upto: From 569dc5f6b17da4c7d59f040bea97fa684ec1b156 Mon Sep 17 00:00:00 2001 From: Afshan <33727827+AfshanKhan@users.noreply.github.com> Date: Tue, 12 Oct 2021 13:23:20 +0530 Subject: [PATCH 263/416] fix: add cost center in gl entry for advance payment entry (#27840) --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 8037ca16aa..0740ccd130 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -712,10 +712,14 @@ class PaymentEntry(AccountsController): dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" for d in self.get("references"): + cost_center = self.cost_center + if d.reference_doctype == "Sales Invoice" and not cost_center: + cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center") gle = party_gl_dict.copy() gle.update({ "against_voucher_type": d.reference_doctype, - "against_voucher": d.reference_name + "against_voucher": d.reference_name, + "cost_center": cost_center }) allocated_amount_in_company_currency = flt(flt(d.allocated_amount) * flt(d.exchange_rate), From a5002525704d5e9d4a33913143f49a355ac20f16 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:25:55 +0530 Subject: [PATCH 264/416] perf: skip get_pricing_rules if no pricing rule exists --- erpnext/accounts/doctype/pricing_rule/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 0637fdaef0..ef44b41476 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -29,6 +29,9 @@ def get_pricing_rules(args, doc=None): pricing_rules = [] values = {} + if not frappe.db.exists('Pricing Rule', {'disable': 0, args.transaction_type: 1}): + return + for apply_on in ['Item Code', 'Item Group', 'Brand']: pricing_rules.extend(_get_pricing_rules(apply_on, args, values)) if pricing_rules and not apply_multiple_pricing_rules(pricing_rules): From a780f78f38b247e5679053e15ee5113f9065a68e Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 12 Oct 2021 13:18:28 +0530 Subject: [PATCH 265/416] fix: Sider, Linter - Moved return to next line - Space between function import and body --- erpnext/shopping_cart/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/shopping_cart/utils.py b/erpnext/shopping_cart/utils.py index 6e32b61dce..5f0c792381 100644 --- a/erpnext/shopping_cart/utils.py +++ b/erpnext/shopping_cart/utils.py @@ -19,10 +19,12 @@ def set_cart_count(login_manager): # make sure user is already a customer # before trying to set cart count user_is_customer = is_customer() - if not user_is_customer: return + if not user_is_customer: + return if show_cart_count(): from erpnext.shopping_cart.cart import set_cart_count + # set_cart_count will try to fetch existing cart quotation # or create one if non existent (and create a customer too) # cart count is calculated from this quotation's items From 7b691beabb40cdf3156a5cf81355d8d4c6b8bd7b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:28:31 +0530 Subject: [PATCH 266/416] perf: fetch mode of payments data in single query --- .../doctype/sales_invoice/sales_invoice.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index d909814921..0c3eff2a03 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1951,22 +1951,23 @@ def update_multi_mode_option(doc, pos_profile): def append_payment(payment_mode): payment = doc.append('payments', {}) payment.default = payment_mode.default - payment.mode_of_payment = payment_mode.parent + payment.mode_of_payment = payment_mode.mop payment.account = payment_mode.default_account payment.type = payment_mode.type doc.set('payments', []) invalid_modes = [] - for pos_payment_method in pos_profile.get('payments'): - pos_payment_method = pos_payment_method.as_dict() + mode_of_payments = [d.mode_of_payment for d in pos_profile.get('payments')] + mode_of_payments_info = get_mode_of_payments_info(mode_of_payments, doc.company) - payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company) + for row in pos_profile.get('payments'): + payment_mode = mode_of_payments_info.get(row.mode_of_payment) if not payment_mode: - invalid_modes.append(get_link_to_form("Mode of Payment", pos_payment_method.mode_of_payment)) + invalid_modes.append(get_link_to_form("Mode of Payment", row.mode_of_payment)) continue - payment_mode[0].default = pos_payment_method.default - append_payment(payment_mode[0]) + payment_mode.default = row.default + append_payment(payment_mode) if invalid_modes: if invalid_modes == 1: @@ -1982,6 +1983,25 @@ def get_all_mode_of_payments(doc): where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", {'company': doc.company}, as_dict=1) +def get_mode_of_payments_info(mode_of_payments, company): + data = frappe.db.sql( + """ + select + mpa.default_account, mpa.parent as mop, mp.type as type + from + `tabMode of Payment Account` mpa,`tabMode of Payment` mp + where + mpa.parent = mp.name and + mpa.company = %s and + mp.enabled = 1 and + mp.name in (%s) + group by + mp.name + """, + (company, mode_of_payments), as_dict=1) + + return {row.get('mop'): row for row in data} + def get_mode_of_payment_info(mode_of_payment, company): return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type From eb3aae870f92e73b2b7babdb1bba01aaebf6e854 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:29:32 +0530 Subject: [PATCH 267/416] perf: get total company stock only for purchase order --- erpnext/stock/get_item_details.py | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index cbff2149d6..e0190b64a7 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -89,7 +89,13 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru out.update(get_bin_details(args.item_code, args.get("from_warehouse"))) elif out.get("warehouse"): - out.update(get_bin_details(args.item_code, out.warehouse, args.company)) + if doc and doc.get('doctype') == 'Purchase Order': + # calculate company_total_stock only for po + bin_details = get_bin_details(args.item_code, out.warehouse, args.company) + else: + bin_details = get_bin_details(args.item_code, out.warehouse) + + out.update(bin_details) # update args with out, if key or value not exists for key, value in iteritems(out): @@ -485,8 +491,9 @@ def get_item_tax_template(args, item, out): "item_tax_template": None } """ - item_tax_template = args.get("item_tax_template") - item_tax_template = _get_item_tax_template(args, item.taxes, out) + item_tax_template = None + if item.taxes: + item_tax_template = _get_item_tax_template(args, item.taxes, out) if not item_tax_template: item_group = item.item_group @@ -502,17 +509,17 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False): taxes_with_no_validity = [] for tax in taxes: - tax_company = frappe.get_value("Item Tax Template", tax.item_tax_template, 'company') - if (tax.valid_from or tax.maximum_net_rate) and tax_company == args['company']: - # In purchase Invoice first preference will be given to supplier invoice date - # if supplier date is not present then posting date - validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') + tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, 'company') + if tax_company == args['company']: + if (tax.valid_from or tax.maximum_net_rate): + # In purchase Invoice first preference will be given to supplier invoice date + # if supplier date is not present then posting date + validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date') - if getdate(tax.valid_from) <= getdate(validation_date) \ - and is_within_valid_range(args, tax): - taxes_with_validity.append(tax) - else: - if tax_company == args['company']: + if getdate(tax.valid_from) <= getdate(validation_date) \ + and is_within_valid_range(args, tax): + taxes_with_validity.append(tax) + else: taxes_with_no_validity.append(tax) if taxes_with_validity: @@ -890,8 +897,7 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa res[fieldname] = pos_profile.get(fieldname) if res.get("warehouse"): - res.actual_qty = get_bin_details(args.item_code, - res.warehouse).get("actual_qty") + res.actual_qty = get_bin_details(args.item_code, res.warehouse).get("actual_qty") return res From c7fc609236aeaada0375e90b664c041bdbc52570 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 12 Oct 2021 13:30:40 +0530 Subject: [PATCH 268/416] perf: skip insertion of stock ledger entry --- erpnext/stock/stock_ledger.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 1b5b792f94..bff7880fd6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -123,12 +123,11 @@ def set_as_cancel(voucher_type, voucher_no): (now(), frappe.session.user, voucher_type, voucher_no)) def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False): - args.update({"doctype": "Stock Ledger Entry"}) + args["doctype"] = "Stock Ledger Entry" sle = frappe.get_doc(args) sle.flags.ignore_permissions = 1 sle.allow_negative_stock=allow_negative_stock sle.via_landed_cost_voucher = via_landed_cost_voucher - sle.insert() sle.submit() return sle @@ -240,8 +239,7 @@ class update_entries_after(object): self.verbose = verbose self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher - self.allow_negative_stock = allow_negative_stock \ - or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) + self.allow_negative_stock = True self.args = frappe._dict(args) self.item_code = args.get("item_code") From 2bc1ca993a37270758b63c931e368a5074b30423 Mon Sep 17 00:00:00 2001 From: Anuja Pawar Date: Tue, 12 Oct 2021 14:52:40 +0530 Subject: [PATCH 269/416] fix: keeping sections consistent across sales & purchase invoice --- .../purchase_invoice/purchase_invoice.json | 41 ++++++++++++------- .../doctype/sales_invoice/sales_invoice.json | 22 +++++----- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 55e288eeef..d55a97ffde 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -149,16 +149,18 @@ "cb_17", "hold_comment", "more_info", + "status", + "inter_company_invoice_reference", + "represents_company", + "column_break_147", + "is_internal_supplier", + "accounting_details_section", "credit_to", "party_account_currency", "is_opening", "against_expense_account", "column_break_63", "unrealized_profit_loss_account", - "status", - "inter_company_invoice_reference", - "is_internal_supplier", - "represents_company", "remarks", "subscription_section", "from_date", @@ -1171,6 +1173,15 @@ "options": "fa fa-file-text", "print_hide": 1 }, + { + "default": "0", + "fetch_from": "supplier.is_internal_supplier", + "fieldname": "is_internal_supplier", + "fieldtype": "Check", + "ignore_user_permissions": 1, + "label": "Is Internal Supplier", + "read_only": 1 + }, { "fieldname": "credit_to", "fieldtype": "Link", @@ -1196,7 +1207,7 @@ "default": "No", "fieldname": "is_opening", "fieldtype": "Select", - "label": "Is Opening", + "label": "Is Opening Entry", "oldfieldname": "is_opening", "oldfieldtype": "Select", "options": "No\nYes", @@ -1298,15 +1309,6 @@ "fieldname": "dimension_col_break", "fieldtype": "Column Break" }, - { - "default": "0", - "fetch_from": "supplier.is_internal_supplier", - "fieldname": "is_internal_supplier", - "fieldtype": "Check", - "ignore_user_permissions": 1, - "label": "Is Internal Supplier", - "read_only": 1 - }, { "fieldname": "tax_withholding_category", "fieldtype": "Link", @@ -1395,13 +1397,22 @@ "hidden": 1, "label": "Ignore Default Payment Terms Template", "read_only": 1 + }, + { + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "column_break_147", + "fieldtype": "Column Break" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-09-28 13:10:28.351810", + "modified": "2021-10-11 20:08:21.822856", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index f3adb898aa..93e32f1a18 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -124,6 +124,13 @@ "total_advance", "outstanding_amount", "disable_rounded_total", + "column_break4", + "write_off_amount", + "base_write_off_amount", + "write_off_outstanding_amount_automatically", + "column_break_74", + "write_off_account", + "write_off_cost_center", "advances_section", "allocate_advances_automatically", "get_advances", @@ -144,13 +151,6 @@ "column_break_90", "change_amount", "account_for_change_amount", - "column_break4", - "write_off_amount", - "base_write_off_amount", - "write_off_outstanding_amount_automatically", - "column_break_74", - "write_off_account", - "write_off_cost_center", "terms_section_break", "tc_name", "terms", @@ -161,14 +161,14 @@ "column_break_84", "language", "more_information", + "status", "inter_company_invoice_reference", - "is_internal_customer", "represents_company", "customer_group", "campaign", - "is_discounted", "col_break23", - "status", + "is_internal_customer", + "is_discounted", "source", "more_info", "debit_to", @@ -2031,7 +2031,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-10-02 03:36:10.251715", + "modified": "2021-10-11 20:19:38.667508", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", From 06fa35a9c132c387bdb99d7c6340d0dbe16ad5fe Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 14 Sep 2021 20:05:16 +0530 Subject: [PATCH 270/416] test: add custom TestCase class and use in stock --- erpnext/stock/doctype/batch/test_batch.py | 6 ++---- .../doctype/delivery_note/test_delivery_note.py | 4 ++-- .../doctype/delivery_trip/test_delivery_trip.py | 6 ++++-- erpnext/stock/doctype/item/test_item.py | 6 +++--- .../item_alternative/test_item_alternative.py | 5 +++-- .../doctype/item_attribute/test_item_attribute.py | 6 +++--- .../stock/doctype/item_price/test_item_price.py | 6 +++--- .../test_landed_cost_voucher.py | 5 ++--- .../material_request/test_material_request.py | 5 ++--- .../doctype/packing_slip/test_packing_slip.py | 2 ++ erpnext/stock/doctype/pick_list/test_pick_list.py | 5 ++--- .../purchase_receipt/test_purchase_receipt.py | 3 ++- .../doctype/putaway_rule/test_putaway_rule.py | 5 ++--- .../quality_inspection/test_quality_inspection.py | 6 +++--- erpnext/stock/doctype/serial_no/test_serial_no.py | 5 ++--- erpnext/stock/doctype/shipment/test_shipment.py | 4 ++-- .../stock_ledger_entry/test_stock_ledger_entry.py | 5 ++--- .../test_stock_reconciliation.py | 6 ++---- .../doctype/stock_settings/test_stock_settings.py | 5 ++++- erpnext/stock/doctype/warehouse/test_warehouse.py | 6 +++--- .../stock_analytics/test_stock_analytics.py | 3 ++- erpnext/tests/utils.py | 15 +++++++++++++++ 22 files changed, 67 insertions(+), 52 deletions(-) diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py index 79989307ef..0a663c2a18 100644 --- a/erpnext/stock/doctype/batch/test_batch.py +++ b/erpnext/stock/doctype/batch/test_batch.py @@ -1,8 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - -import unittest import frappe from frappe.exceptions import ValidationError @@ -11,9 +8,10 @@ from frappe.utils import cint, flt from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty from erpnext.stock.get_item_details import get_item_details +from erpnext.tests.utils import ERPNextTestCase -class TestBatch(unittest.TestCase): +class TestBatch(ERPNextTestCase): def test_item_has_batch_enabled(self): self.assertRaises(ValidationError, frappe.get_doc({ "doctype": "Batch", diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 7fda94b269..f58b586ab2 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.utils import cstr, flt, nowdate, nowtime @@ -37,9 +36,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ) from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.tests.utils import ERPNextTestCase -class TestDeliveryNote(unittest.TestCase): +class TestDeliveryNote(ERPNextTestCase): def test_over_billing_against_dn(self): frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py index c9081c908f..c6ff73e633 100644 --- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py @@ -14,11 +14,12 @@ from erpnext.stock.doctype.delivery_trip.delivery_trip import ( make_expense_claim, notify_customers, ) -from erpnext.tests.utils import create_test_contact_and_address +from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address -class TestDeliveryTrip(unittest.TestCase): +class TestDeliveryTrip(ERPNextTestCase): def setUp(self): + super().setUp() driver = create_driver() create_vehicle() create_delivery_notification() @@ -32,6 +33,7 @@ class TestDeliveryTrip(unittest.TestCase): frappe.db.sql("delete from `tabVehicle`") frappe.db.sql("delete from `tabEmail Template`") frappe.db.sql("delete from `tabDelivery Trip`") + return super().tearDown() def test_expense_claim_fields_are_fetched_properly(self): expense_claim = make_expense_claim(self.delivery_trip.name) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index e911d35db3..9198272513 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.test_runner import make_test_objects @@ -25,7 +24,7 @@ from erpnext.stock.doctype.item.item import ( ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details -from erpnext.tests.utils import change_settings +from erpnext.tests.utils import ERPNextTestCase, change_settings test_ignore = ["BOM"] test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"] @@ -53,8 +52,9 @@ def make_item(item_code, properties=None): return item -class TestItem(unittest.TestCase): +class TestItem(ERPNextTestCase): def setUp(self): + super().setUp() frappe.flags.attribute_values = None def get_item(self, idx): diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py index 2be8ef740a..af6cc472e3 100644 --- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import json -import unittest import frappe from frappe.utils import flt @@ -21,10 +20,12 @@ from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( create_stock_reconciliation, ) +from erpnext.tests.utils import ERPNextTestCase -class TestItemAlternative(unittest.TestCase): +class TestItemAlternative(ERPNextTestCase): def setUp(self): + super().setUp() make_items() def test_alternative_item_for_subcontract_rm(self): diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py index fc809f443e..2cd711bbb1 100644 --- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py @@ -3,17 +3,17 @@ from __future__ import unicode_literals -import unittest - import frappe test_records = frappe.get_test_records('Item Attribute') from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError +from erpnext.tests.utils import ERPNextTestCase -class TestItemAttribute(unittest.TestCase): +class TestItemAttribute(ERPNextTestCase): def setUp(self): + super().setUp() if frappe.db.exists("Item Attribute", "_Test_Length"): frappe.delete_doc("Item Attribute", "_Test_Length") diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index 5ed8092166..3a51fbbe17 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -3,17 +3,17 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.test_runner import make_test_records_for_doctype from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem from erpnext.stock.get_item_details import get_price_list_rate_for, process_args +from erpnext.tests.utils import ERPNextTestCase -class TestItemPrice(unittest.TestCase): +class TestItemPrice(ERPNextTestCase): def setUp(self): + super().setUp() frappe.db.sql("delete from `tabItem Price`") make_test_records_for_doctype("Item Price", force=True) diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 58a72f72dd..339eaaaf7a 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -4,8 +4,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import flt @@ -16,9 +14,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import ( get_gl_entries, make_purchase_receipt, ) +from erpnext.tests.utils import ERPNextTestCase -class TestLandedCostVoucher(unittest.TestCase): +class TestLandedCostVoucher(ERPNextTestCase): def test_landed_cost_voucher(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 5c2ac2584f..f66a228e35 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import flt, today @@ -18,9 +16,10 @@ from erpnext.stock.doctype.material_request.material_request import ( make_supplier_quotation, raise_work_orders, ) +from erpnext.tests.utils import ERPNextTestCase -class TestMaterialRequest(unittest.TestCase): +class TestMaterialRequest(ERPNextTestCase): def test_make_purchase_order(self): mr = frappe.copy_doc(test_records[0]).insert() diff --git a/erpnext/stock/doctype/packing_slip/test_packing_slip.py b/erpnext/stock/doctype/packing_slip/test_packing_slip.py index 193adfcf1c..c70cba67f2 100644 --- a/erpnext/stock/doctype/packing_slip/test_packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/test_packing_slip.py @@ -6,6 +6,8 @@ from __future__ import unicode_literals import unittest # test_records = frappe.get_test_records('Packing Slip') +from erpnext.tests.utils import ERPNextTestCase + class TestPackingSlip(unittest.TestCase): pass diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index aa710ad0e9..fd0b3680df 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] @@ -15,9 +13,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( EmptyStockReconciliationItemsError, ) +from erpnext.tests.utils import ERPNextTestCase -class TestPickList(unittest.TestCase): +class TestPickList(ERPNextTestCase): def test_pick_list_picks_warehouse_for_each_item(self): try: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 044856cca9..de17744428 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -17,9 +17,10 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction +from erpnext.tests.utils import ERPNextTestCase -class TestPurchaseReceipt(unittest.TestCase): +class TestPurchaseReceipt(ERPNextTestCase): def setUp(self): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py index 0aa7610575..c25bca94db 100644 --- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py +++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe from erpnext.stock.doctype.batch.test_batch import make_new_batch @@ -13,9 +11,10 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.tests.utils import ERPNextTestCase -class TestPutawayRule(unittest.TestCase): +class TestPutawayRule(ERPNextTestCase): def setUp(self): if not frappe.db.exists("Item", "_Rice"): make_item("_Rice", { diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py index f5d076a077..308c62875d 100644 --- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py @@ -1,8 +1,6 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # See license.txt -import unittest - import frappe from frappe.utils import nowdate @@ -15,12 +13,14 @@ from erpnext.controllers.stock_controller import ( from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase # test_records = frappe.get_test_records('Quality Inspection') -class TestQualityInspection(unittest.TestCase): +class TestQualityInspection(ERPNextTestCase): def setUp(self): + super().setUp() create_item("_Test Item with QA") frappe.db.set_value( "Item", "_Test Item with QA", "inspection_required_before_delivery", 1 diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 818c163c68..546e21bde0 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note @@ -20,9 +18,10 @@ test_dependencies = ["Item"] test_records = frappe.get_test_records('Serial No') from erpnext.stock.doctype.serial_no.serial_no import * +from erpnext.tests.utils import ERPNextTestCase -class TestSerialNo(unittest.TestCase): +class TestSerialNo(ERPNextTestCase): def test_cannot_create_direct(self): frappe.delete_doc_if_exists("Serial No", "_TCSER0001") diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 9914cf8015..288d874800 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -3,15 +3,15 @@ # See license.txt from __future__ import unicode_literals -import unittest from datetime import date, timedelta import frappe from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment +from erpnext.tests.utils import ERPNextTestCase -class TestShipment(unittest.TestCase): +class TestShipment(ERPNextTestCase): def test_shipment_from_delivery_note(self): delivery_note = create_test_delivery_note() delivery_note.submit() diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py index 61bae49b0b..ff33c2789b 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py @@ -3,8 +3,6 @@ # See license.txt from __future__ import unicode_literals -import unittest - import frappe from frappe.core.page.permission_manager.permission_manager import reset from frappe.utils import add_days, today @@ -21,9 +19,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation, ) from erpnext.stock.stock_ledger import get_previous_sle +from erpnext.tests.utils import ERPNextTestCase -class TestStockLedgerEntry(unittest.TestCase): +class TestStockLedgerEntry(ERPNextTestCase): def setUp(self): items = create_items() reset('Stock Entry') diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 8647bee40e..b0a15379e6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -6,8 +6,6 @@ from __future__ import unicode_literals -import unittest - import frappe from frappe.utils import add_days, flt, nowdate, nowtime, random_string @@ -22,10 +20,10 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import ( from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method -from erpnext.tests.utils import change_settings +from erpnext.tests.utils import ERPNextTestCase, change_settings -class TestStockReconciliation(unittest.TestCase): +class TestStockReconciliation(ERPNextTestCase): @classmethod def setUpClass(self): create_batch_or_serial_no_items() diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py index 7e8090499f..bf8ac5dc79 100644 --- a/erpnext/stock/doctype/stock_settings/test_stock_settings.py +++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py @@ -7,9 +7,12 @@ import unittest import frappe +from erpnext.tests.utils import ERPNextTestCase -class TestStockSettings(unittest.TestCase): + +class TestStockSettings(ERPNextTestCase): def setUp(self): + super().setUp() frappe.db.set_value("Stock Settings", None, "clean_description_html", 0) def test_settings(self): diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index 1ca7181f27..98317ec9c5 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -2,8 +2,6 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import unittest - import frappe from frappe.test_runner import make_test_records from frappe.utils import cint @@ -12,11 +10,13 @@ import erpnext from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.tests.utils import ERPNextTestCase test_records = frappe.get_test_records('Warehouse') -class TestWarehouse(unittest.TestCase): +class TestWarehouse(ERPNextTestCase): def setUp(self): + super().setUp() if not frappe.get_value('Item', '_Test Item'): make_test_records('Item') diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py index 21e1205bfc..32df585937 100644 --- a/erpnext/stock/report/stock_analytics/test_stock_analytics.py +++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py @@ -5,9 +5,10 @@ from frappe import _dict from erpnext.accounts.utils import get_fiscal_year from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges +from erpnext.tests.utils import ERPNextTestCase -class TestStockAnalyticsReport(unittest.TestCase): +class TestStockAnalyticsReport(ERPNextTestCase): def test_get_period_date_ranges(self): filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06") diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index a3cab4b59d..95e56683e1 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -2,6 +2,7 @@ # License: GNU General Public License v3. See license.txt import copy +import unittest from contextlib import contextmanager from typing import Any, Dict, NewType, Optional @@ -12,6 +13,20 @@ ReportFilters = Dict[str, Any] ReportName = NewType("ReportName", str) +class ERPNextTestCase(unittest.TestCase): + """A sane default test class for ERPNext tests.""" + + def setUp(self) -> None: + frappe.db.commit() + return super().setUp() + + + def tearDown(self) -> None: + frappe.db.rollback() + return super().tearDown() + + + def create_test_contact_and_address(): frappe.db.sql('delete from tabContact') frappe.db.sql('delete from `tabContact Email`') From acdb26a4bb45787fad3ee56366cd7e6100c85c41 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 14:45:29 +0530 Subject: [PATCH 271/416] refactor: rollback after full test --- .../test_stock_reconciliation.py | 1 + erpnext/tests/utils.py | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index b0a15379e6..e82401d073 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -26,6 +26,7 @@ from erpnext.tests.utils import ERPNextTestCase, change_settings class TestStockReconciliation(ERPNextTestCase): @classmethod def setUpClass(self): + super().setUpClass() create_batch_or_serial_no_items() frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py index 95e56683e1..91df5480e3 100644 --- a/erpnext/tests/utils.py +++ b/erpnext/tests/utils.py @@ -16,15 +16,16 @@ ReportName = NewType("ReportName", str) class ERPNextTestCase(unittest.TestCase): """A sane default test class for ERPNext tests.""" - def setUp(self) -> None: + + @classmethod + def setUpClass(cls) -> None: frappe.db.commit() - return super().setUp() + return super().setUpClass() - - def tearDown(self) -> None: + @classmethod + def tearDownClass(cls) -> None: frappe.db.rollback() - return super().tearDown() - + return super().tearDownClass() def create_test_contact_and_address(): From 8d69ec72a64eae0908aa0fb80a7325545db9a0cf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 15:26:37 +0530 Subject: [PATCH 272/416] fix: remove transaction commit from tests --- erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py | 2 -- .../report/procurement_tracker/test_procurement_tracker.py | 1 - .../hr/doctype/daily_work_summary/test_daily_work_summary.py | 1 - erpnext/selling/doctype/sales_order/test_sales_order.py | 1 - .../shopping_cart_settings/test_shopping_cart_settings.py | 1 - erpnext/stock/doctype/shipment/test_shipment.py | 2 -- .../stock_reconciliation/test_stock_reconciliation.py | 5 ----- 7 files changed, 13 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index f492a03daf..e11fe13383 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1087,8 +1087,6 @@ class TestSalesInvoice(unittest.TestCase): actual_qty_1 = get_qty_after_transaction(item_code = "_Test Item", warehouse = "Stores - TCP1") - frappe.db.commit() - self.assertEqual(actual_qty_0 - 5, actual_qty_1) # outgoing_rate diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py index a5b09473a0..fd23795287 100644 --- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py +++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py @@ -45,7 +45,6 @@ class TestProcurementTracker(unittest.TestCase): pr = make_purchase_receipt(po.name) pr.get("items")[0].cost_center = "Main - _TPC" pr.submit() - frappe.db.commit() date_obj = datetime.date(datetime.now()) po.load_from_db() diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py index bed12e31ea..8a23682ad4 100644 --- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py +++ b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.py @@ -74,7 +74,6 @@ class TestDailyWorkSummary(unittest.TestCase): from `tabEmail Queue` as q, `tabEmail Queue Recipient` as r \ where q.name = r.parent""", as_dict=1) - frappe.db.commit() def setup_groups(self, hour=None): # setup email to trigger at this hour diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index bbfe7c06d8..222e74ee6c 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1382,7 +1382,6 @@ def make_sales_order_workflow(): frappe.get_doc(dict(doctype='Role', role_name='Test Junior Approver')).insert(ignore_if_duplicate=True) frappe.get_doc(dict(doctype='Role', role_name='Test Approver')).insert(ignore_if_duplicate=True) - frappe.db.commit() frappe.cache().hdel('roles', frappe.session.user) workflow = frappe.get_doc({ diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py index f8a22b0e02..1164a5d394 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py @@ -44,7 +44,6 @@ class TestShoppingCartSettings(unittest.TestCase): def test_tax_rule_validation(self): frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0") - frappe.db.commit() cart_settings = self.get_cart_settings() cart_settings.enabled = 1 diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 288d874800..dcd0b7c17c 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -47,7 +47,6 @@ def create_test_delivery_note(): } ) delivery_note.insert() - frappe.db.commit() return delivery_note @@ -91,7 +90,6 @@ def create_test_shipment(delivery_notes = None): } ) shipment.insert() - frappe.db.commit() return shipment diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e82401d073..415ac5eb26 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -371,7 +371,6 @@ class TestStockReconciliation(ERPNextTestCase): """ from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note from erpnext.stock.stock_ledger import NegativeStockError - frappe.db.commit() item_code = "Backdated-Reco-Cancellation-Item" warehouse = "_Test Warehouse - _TC" @@ -394,10 +393,6 @@ class TestStockReconciliation(ERPNextTestCase): repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name})) self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation") - # teardown - frappe.db.rollback() - - def test_valid_batch(self): create_batch_item_with_batch("Testing Batch Item 1", "001") create_batch_item_with_batch("Testing Batch Item 2", "002") From 6f107da16570c5b406e923ecfb4ae99d836905b0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 12 Oct 2021 20:15:55 +0530 Subject: [PATCH 273/416] perf: Add indexes in stock queries and speed up bin updation #27758 perf: Add indexes in stock queries and speed up bin updation --- erpnext/controllers/stock_controller.py | 2 +- erpnext/stock/doctype/bin/bin.py | 109 ++++++++++-------- .../stock_ledger_entry.json | 2 +- .../stock_ledger_entry/stock_ledger_entry.py | 1 + erpnext/stock/stock_ledger.py | 13 +-- erpnext/stock/utils.py | 21 +++- 6 files changed, 91 insertions(+), 57 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 78a6e52e4d..4697205d72 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -591,7 +591,7 @@ def future_sle_exists(args, sl_entries=None): data = frappe.db.sql(""" select item_code, warehouse, count(name) as total_row - from `tabStock Ledger Entry` + from `tabStock Ledger Entry` force index (item_warehouse) where ({}) and timestamp(posting_date, posting_time) diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 5fbc2d8dee..4be0415564 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -14,51 +14,6 @@ class Bin(Document): self.stock_uom = frappe.get_cached_value('Item', self.item_code, 'stock_uom') self.set_projected_qty() - def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False): - '''Called from erpnext.stock.utils.update_bin''' - self.update_qty(args) - - if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": - from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle - - if not args.get("posting_date"): - args["posting_date"] = nowdate() - - if args.get("is_cancelled") and via_landed_cost_voucher: - return - - # Reposts only current voucher SL Entries - # Updates valuation rate, stock value, stock queue for current transaction - update_entries_after({ - "item_code": self.item_code, - "warehouse": self.warehouse, - "posting_date": args.get("posting_date"), - "posting_time": args.get("posting_time"), - "voucher_type": args.get("voucher_type"), - "voucher_no": args.get("voucher_no"), - "sle_id": args.name, - "creation": args.creation - }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - - # update qty in future ale and Validate negative qty - update_qty_in_future_sle(args, allow_negative_stock) - - - def update_qty(self, args): - # update the stock values (for current quantities) - if args.get("voucher_type")=="Stock Reconciliation": - self.actual_qty = args.get("qty_after_transaction") - else: - self.actual_qty = flt(self.actual_qty) + flt(args.get("actual_qty")) - - self.ordered_qty = flt(self.ordered_qty) + flt(args.get("ordered_qty")) - self.reserved_qty = flt(self.reserved_qty) + flt(args.get("reserved_qty")) - self.indented_qty = flt(self.indented_qty) + flt(args.get("indented_qty")) - self.planned_qty = flt(self.planned_qty) + flt(args.get("planned_qty")) - - self.set_projected_qty() - self.db_update() - def set_projected_qty(self): self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty) + flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty) @@ -143,3 +98,67 @@ class Bin(Document): def on_doctype_update(): frappe.db.add_index("Bin", ["item_code", "warehouse"]) + + +def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False): + '''Called from erpnext.stock.utils.update_bin''' + update_qty(bin_name, args) + + if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": + from erpnext.stock.stock_ledger import update_entries_after, update_qty_in_future_sle + + if not args.get("posting_date"): + args["posting_date"] = nowdate() + + if args.get("is_cancelled") and via_landed_cost_voucher: + return + + # Reposts only current voucher SL Entries + # Updates valuation rate, stock value, stock queue for current transaction + update_entries_after({ + "item_code": args.get('item_code'), + "warehouse": args.get('warehouse'), + "posting_date": args.get("posting_date"), + "posting_time": args.get("posting_time"), + "voucher_type": args.get("voucher_type"), + "voucher_no": args.get("voucher_no"), + "sle_id": args.get('name'), + "creation": args.get('creation') + }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) + + # update qty in future sle and Validate negative qty + update_qty_in_future_sle(args, allow_negative_stock) + +def get_bin_details(bin_name): + return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty', + 'reserved_qty', 'indented_qty', 'planned_qty', 'reserved_qty_for_production', + 'reserved_qty_for_sub_contract'], as_dict=1) + +def update_qty(bin_name, args): + bin_details = get_bin_details(bin_name) + + # update the stock values (for current quantities) + if args.get("voucher_type")=="Stock Reconciliation": + actual_qty = args.get('qty_after_transaction') + else: + actual_qty = bin_details.actual_qty + flt(args.get("actual_qty")) + + ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty")) + reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty")) + indented_qty = flt(bin_details.indented_qty) + flt(args.get("indented_qty")) + planned_qty = flt(bin_details.planned_qty) + flt(args.get("planned_qty")) + + + # compute projected qty + projected_qty = (flt(actual_qty) + flt(ordered_qty) + + flt(indented_qty) + flt(planned_qty) - flt(reserved_qty) + - flt(bin_details.reserved_qty_for_production) - flt(bin_details.reserved_qty_for_sub_contract)) + + frappe.db.set_value('Bin', bin_name, { + 'actual_qty': actual_qty, + 'ordered_qty': ordered_qty, + 'reserved_qty': reserved_qty, + 'indented_qty': indented_qty, + 'planned_qty': planned_qty, + 'projected_qty': projected_qty + }) \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 40ae340bfe..2651407d16 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -317,7 +317,7 @@ "in_create": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2021-10-08 12:42:51.857631", + "modified": "2021-10-08 13:42:51.857631", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 382fdfa4bf..2cf71accf8 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -181,3 +181,4 @@ def on_doctype_update(): frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"]) frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"]) + frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 1b5b792f94..e9d5b6ae09 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -13,8 +13,8 @@ from six import iteritems import erpnext from erpnext.stock.utils import ( - get_bin, get_incoming_outgoing_rate_for_cancel, + get_or_make_bin, get_valuation_method, ) @@ -805,14 +805,13 @@ class update_entries_after(object): def update_bin(self): # update bin for each warehouse for warehouse, data in iteritems(self.data): - bin_doc = get_bin(self.item_code, warehouse) - bin_doc.update({ + bin_record = get_or_make_bin(self.item_code, warehouse) + + frappe.db.set_value('Bin', bin_record, { "valuation_rate": data.valuation_rate, "actual_qty": data.qty_after_transaction, "stock_value": data.stock_value }) - bin_doc.flags.via_stock_ledger_entry = True - bin_doc.save(ignore_permissions=True) def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False): @@ -918,7 +917,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, company = erpnext.get_default_company() last_valuation_rate = frappe.db.sql("""select valuation_rate - from `tabStock Ledger Entry` + from `tabStock Ledger Entry` force index (item_warehouse) where item_code = %s AND warehouse = %s @@ -929,7 +928,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, if not last_valuation_rate: # Get valuation rate from last sle for the item against any warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate - from `tabStock Ledger Entry` + from `tabStock Ledger Entry` force index (item_code) where item_code = %s AND valuation_rate > 0 diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index aeb06e987f..c4a0497b74 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -180,12 +180,27 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = True return bin_obj +def get_or_make_bin(item_code, warehouse) -> str: + bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse}) + + if not bin_record: + bin_obj = frappe.get_doc({ + "doctype": "Bin", + "item_code": item_code, + "warehouse": warehouse, + }) + bin_obj.flags.ignore_permissions = 1 + bin_obj.insert() + bin_record = bin_obj.name + + return bin_record + def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False): + from erpnext.stock.doctype.bin.bin import update_stock is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item') if is_stock_item: - bin = get_bin(args.get("item_code"), args.get("warehouse")) - bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher) - return bin + bin_record = get_or_make_bin(args.get("item_code"), args.get("warehouse")) + update_stock(bin_record, args, allow_negative_stock, via_landed_cost_voucher) else: frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code"))) From 7bafa11d5758c40496898cd0151efde4ff358946 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 20:39:10 +0530 Subject: [PATCH 274/416] fix: undo changes to allow negative stock flag --- erpnext/stock/stock_ledger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bff7880fd6..2b6eefb5cc 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -239,7 +239,8 @@ class update_entries_after(object): self.verbose = verbose self.allow_zero_rate = allow_zero_rate self.via_landed_cost_voucher = via_landed_cost_voucher - self.allow_negative_stock = True + self.allow_negative_stock = allow_negative_stock \ + or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock")) self.args = frappe._dict(args) self.item_code = args.get("item_code") From d181cc42a1e7027d431bc47c46aa5aac8eda2c53 Mon Sep 17 00:00:00 2001 From: Anuja Pawar Date: Tue, 12 Oct 2021 20:57:08 +0530 Subject: [PATCH 275/416] fix: set collapsible & print hide --- .../accounts/doctype/purchase_invoice/purchase_invoice.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index d55a97ffde..03cbc4acbc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1399,9 +1399,11 @@ "read_only": 1 }, { + "collapsible": 1, "fieldname": "accounting_details_section", "fieldtype": "Section Break", - "label": "Accounting Details" + "label": "Accounting Details", + "print_hide": 1 }, { "fieldname": "column_break_147", @@ -1412,7 +1414,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2021-10-11 20:08:21.822856", + "modified": "2021-10-12 20:55:16.145651", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", From 3b9514d6e17bb9afab988e203268a3ac34356628 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 12 Oct 2021 21:49:51 +0530 Subject: [PATCH 276/416] fix: Update message string --- erpnext/accounts/doctype/payment_entry/payment_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 094e7a2bbe..1526374374 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -506,7 +506,7 @@ class PaymentEntry(AccountsController): def validate_received_amount(self): if self.paid_from_account_currency == self.paid_to_account_currency: if self.paid_amount < self.received_amount: - frappe.throw(_("Received Amount cannot be greater tha Paid Amount")) + frappe.throw(_("Received Amount cannot be greater than Paid Amount")) def set_received_amount(self): self.base_received_amount = self.base_paid_amount From 06b426e9c3d898242ffb3c44b7f223ba0e3d496d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 12 Oct 2021 23:01:37 +0530 Subject: [PATCH 277/416] ci: rule to fail PRs that add a new manual commit (#27928) Manual commits are frequent source of bugs, confusions or undefined behaviour. All new manual commits should be explcitly ignored with explanation on why it's added. This will only fail for new additions. Existing ones need to be cleaned up manually. --- .../semgrep_rules/frappe_correctness.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index d9603e89aa..166e98a8a2 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -131,3 +131,21 @@ rules: key `$X` is uselessly assigned twice. This could be a potential bug. languages: [python] severity: ERROR + + +- id: frappe-manual-commit + patterns: + - pattern: frappe.db.commit() + - pattern-not-inside: | + try: + ... + except ...: + ... + message: | + Manually commiting a transaction is highly discouraged. Read about the transaction model implemented by Frappe Framework before adding manual commits: https://frappeframework.com/docs/user/en/api/database#database-transaction-model If you think manual commit is required then add a comment explaining why and `// nosemgrep` on the same line. + paths: + exclude: + - "**/patches/**" + - "**/demo/**" + languages: [python] + severity: ERROR From ca0067212dd153566df2965c12aace641ff68720 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Oct 2021 11:58:42 +0530 Subject: [PATCH 278/416] fix: TDS round off not working from second transaction --- .../tax_withholding_category/tax_withholding_category.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 16ef5fc974..cc8ef58e20 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -191,6 +191,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 + + if cint(tax_details.round_off_tax_amount): + tax_amount = round(tax_amount) else: tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) From 2d19e2d54b3d03f70996ac104508a14b21bc2911 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Oct 2021 13:24:19 +0530 Subject: [PATCH 279/416] fix: Patch --- erpnext/patches.txt | 2 ++ erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0f57b50307..a580fd386c 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -298,6 +298,8 @@ erpnext.patches.v13_0.shopify_deprecation_warning erpnext.patches.v13_0.migrate_stripe_api erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries erpnext.patches.v13_0.einvoicing_deprecation_warning +execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings") +execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category") erpnext.patches.v13_0.custom_fields_for_taxjar_integration erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v13_0.set_operation_time_based_on_operating_cost diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py index 49c2a3bc2e..e136d64bb5 100644 --- a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py +++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py @@ -7,14 +7,10 @@ from erpnext.erpnext_integrations.doctype.taxjar_settings.taxjar_settings import def execute(): - frappe.reload_doctype("TaxJar Settings", force=True) - frappe.reload_doctype("Product Tax Category", force=True) - company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name']) if not company: return - TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions") TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax") TAXJAR_SANDBOX_MODE = frappe.db.get_single_value("TaxJar Settings", "is_sandbox") From bd8cfb2e3079012f4e9c0732f3d14db93917bd31 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 13 Oct 2021 13:40:53 +0530 Subject: [PATCH 280/416] fix: Move product tax category folder to taxjar settings --- .../doctype/taxjar_settings}/product_tax_category_data.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename erpnext/{regional/united_states => erpnext_integrations/doctype/taxjar_settings}/product_tax_category_data.json (100%) diff --git a/erpnext/regional/united_states/product_tax_category_data.json b/erpnext/erpnext_integrations/doctype/taxjar_settings/product_tax_category_data.json similarity index 100% rename from erpnext/regional/united_states/product_tax_category_data.json rename to erpnext/erpnext_integrations/doctype/taxjar_settings/product_tax_category_data.json From 790c1cda6f52ad74e169edc689014c67892c16ef Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:18:20 +0530 Subject: [PATCH 281/416] fix: Removing unused fields from workspace JSON files #27612 fix: Removing unused fields from workspace JSON files --- .../accounts/workspace/accounting/accounting.json | 12 +----------- .../workspace/agriculture/agriculture.json | 12 +----------- erpnext/assets/workspace/assets/assets.json | 12 +----------- erpnext/buying/workspace/buying/buying.json | 15 +-------------- erpnext/crm/workspace/crm/crm.json | 12 +----------- .../education/workspace/education/education.json | 12 +----------- .../erpnext_integrations.json | 12 +----------- .../erpnext_integrations_settings.json | 12 +----------- erpnext/hr/workspace/hr/hr.json | 12 +----------- .../loan_management/loan_management.json | 12 +----------- .../workspace/manufacturing/manufacturing.json | 12 +----------- .../workspace/non_profit/non_profit.json | 12 +----------- erpnext/payroll/workspace/payroll/payroll.json | 12 +----------- erpnext/projects/workspace/projects/projects.json | 12 +----------- .../workspace/quality/quality.json | 12 +----------- erpnext/selling/workspace/retail/retail.json | 12 +----------- erpnext/selling/workspace/selling/selling.json | 14 +------------- .../erpnext_settings/erpnext_settings.json | 12 +----------- erpnext/setup/workspace/home/home.json | 14 ++------------ erpnext/stock/workspace/stock/stock.json | 14 +------------- erpnext/support/workspace/support/support.json | 12 +----------- .../utilities/workspace/utilities/utilities.json | 12 +----------- 22 files changed, 23 insertions(+), 250 deletions(-) diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json index 30ed58b37b..33d1748825 100644 --- a/erpnext/accounts/workspace/accounting/accounting.json +++ b/erpnext/accounts/workspace/accounting/accounting.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Profit and Loss", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Accounts\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Profit and Loss\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chart of Accounts\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Invoice\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Journal Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payment Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Trial Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounting Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"General Ledger\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Receivable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Accounts Payable\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Financial Statements\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Multi Currency\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bank Statement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Subscription Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goods and Services Tax (GST India)\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Share Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Cost Center and Budgeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening and Closing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Profitability\", \"col\": 4}}]", "creation": "2020-03-02 15:41:59.515192", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "accounting", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Accounting", "links": [ { @@ -1227,15 +1220,12 @@ "type": "Link" } ], - "modified": "2021-08-26 13:15:52.872470", + "modified": "2021-08-27 12:15:52.872471", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting", - "onboarding": "Accounts", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/agriculture/workspace/agriculture/agriculture.json b/erpnext/agriculture/workspace/agriculture/agriculture.json index 633777eeb7..6714de6d38 100644 --- a/erpnext/agriculture/workspace/agriculture/agriculture.json +++ b/erpnext/agriculture/workspace/agriculture/agriculture.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Crops & Lands\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Analytics\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Diseases & Fertilizers\", \"col\": 4}}]", "creation": "2020-03-02 17:23:34.339274", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "agriculture", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Agriculture", "links": [ { @@ -163,15 +156,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:54.595197", + "modified": "2021-08-05 12:15:54.595198", "modified_by": "Administrator", "module": "Agriculture", "name": "Agriculture", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Agriculture", "roles": [], diff --git a/erpnext/assets/workspace/assets/assets.json b/erpnext/assets/workspace/assets/assets.json index dfbf1a378e..495de46e41 100644 --- a/erpnext/assets/workspace/assets/assets.json +++ b/erpnext/assets/workspace/assets/assets.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Asset Value Analytics", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Assets\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Asset Value Analytics\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Asset Category\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fixed Asset Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assets\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:43:27.634865", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "assets", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Assets", "links": [ { @@ -179,15 +172,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:54.839452", + "modified": "2021-08-05 12:15:54.839453", "modified_by": "Administrator", "module": "Assets", "name": "Assets", - "onboarding": "Assets", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/buying/workspace/buying/buying.json b/erpnext/buying/workspace/buying/buying.json index 6c91e81695..380ef3639f 100644 --- a/erpnext/buying/workspace/buying/buying.json +++ b/erpnext/buying/workspace/buying/buying.json @@ -1,27 +1,18 @@ { - "cards_label": "", - "category": "", "charts": [ { "chart_name": "Purchase Order Trends", "label": "Purchase Order Trends" } ], - "charts_label": "", "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Buying\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Purchase Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Buying\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items & Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Supplier Scorecard\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Regional\", \"col\": 4}}]", "creation": "2020-01-28 11:50:26.195467", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "buying", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Buying", "links": [ { @@ -518,15 +509,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:56.218427", + "modified": "2021-08-05 12:15:56.218428", "modified_by": "Administrator", "module": "Buying", "name": "Buying", - "onboarding": "Buying", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], @@ -572,6 +560,5 @@ "type": "Dashboard" } ], - "shortcuts_label": "", "title": "Buying" } \ No newline at end of file diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json index a661b62379..5a63dc18d0 100644 --- a/erpnext/crm/workspace/crm/crm.json +++ b/erpnext/crm/workspace/crm/crm.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Territory Wise Sales" @@ -7,18 +6,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"CRM\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Lead\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Opportunity\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Customer\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Sales Pipeline\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Campaign\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-01-23 14:48:30.183272", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "crm", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "CRM", "links": [ { @@ -421,15 +414,12 @@ "type": "Link" } ], - "modified": "2021-08-19 19:08:08.728876", + "modified": "2021-08-20 12:15:56.913092", "modified_by": "Administrator", "module": "CRM", "name": "CRM", - "onboarding": "CRM", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/education/workspace/education/education.json b/erpnext/education/workspace/education/education.json index c58ddd63cf..1465295658 100644 --- a/erpnext/education/workspace/education/education.json +++ b/erpnext/education/workspace/education/education.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Program Enrollments", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Education\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Program Enrollments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Instructor\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Program\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Fees\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Course Scheduling Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Student Attendance Tool\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Student and Instructor\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Content Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Admission\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fees\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Schedule\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"LMS Activity\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Assessment Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-03-02 17:22:57.066401", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "education", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Education", "links": [ { @@ -699,15 +692,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:57.929275", + "modified": "2021-08-05 12:15:57.929276", "modified_by": "Administrator", "module": "Education", "name": "Education", - "onboarding": "Education", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Education", "roles": [], diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json index 9f9204a78d..8e4f92791a 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Marketplace\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payments\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-08-20 19:30:48.138801", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "integration", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "ERPNext Integrations", "links": [ { @@ -119,15 +112,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:58.740246", + "modified": "2021-08-05 12:15:58.740247", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json index fd4afb85fd..5fe5afa2c4 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Integrations Settings\", \"col\": 4}}]", "creation": "2020-07-31 10:38:54.021237", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "setting", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "ERPNext Integrations Settings", "links": [ { @@ -81,15 +74,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:15:58.951704", + "modified": "2021-08-05 12:15:58.951705", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "ERPNext Integrations Settings", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json index 9c5d0c1b0e..7408d63eee 100644 --- a/erpnext/hr/workspace/hr/hr.json +++ b/erpnext/hr/workspace/hr/hr.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Outgoing Salary", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Human Resource\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Employee\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Leave Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Job Applicant\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Monthly Attendance Sheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Employee Lifecycle\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Shift Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Leaves\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Attendance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Expense Claims\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Fleet Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Recruitment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loans\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Training\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Performance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:48:58.322521", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "hr", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "HR", "links": [ { @@ -942,15 +935,12 @@ "type": "Link" } ], - "modified": "2021-08-31 12:18:59.842918", + "modified": "2021-08-31 12:18:59.842919", "modified_by": "Administrator", "module": "HR", "name": "HR", - "onboarding": "Human Resource", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/loan_management/workspace/loan_management/loan_management.json b/erpnext/loan_management/workspace/loan_management/loan_management.json index ca528ec6bd..7deee0d461 100644 --- a/erpnext/loan_management/workspace/loan_management/loan_management.json +++ b/erpnext/loan_management/workspace/loan_management/loan_management.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan Application\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Loan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Processes\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Disbursement and Repayment\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Security\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-12 16:35:55.299820", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "loan", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Loans", "links": [ { @@ -245,15 +238,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:18:13.350904", + "modified": "2021-08-05 12:18:13.350905", "modified_by": "Administrator", "module": "Loan Management", "name": "Loans", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json index 84eabcd2bd..cfa80f8e9f 100644 --- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json +++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Produced Quantity" @@ -7,18 +6,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]", "creation": "2020-03-02 17:11:37.032604", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "organization", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Manufacturing", "links": [ { @@ -304,15 +297,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:00.825741", + "modified": "2021-08-05 12:16:00.825742", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", - "onboarding": "Manufacturing", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Manufacturing", "roles": [], diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json index e6d4445945..ba2f919d01 100644 --- a/erpnext/non_profit/workspace/non_profit/non_profit.json +++ b/erpnext/non_profit/workspace/non_profit/non_profit.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Member\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Profit Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Membership\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Chapter Member\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loan Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Grant Application\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Membership\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Volunteer\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Chapter\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Donation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tax Exemption Certification (India)\", \"col\": 4}}]", "creation": "2020-03-02 17:23:47.811421", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "non-profit", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Non Profit", "links": [ { @@ -238,15 +231,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.146206", + "modified": "2021-08-05 12:16:01.146207", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Non Profit", "roles": [], diff --git a/erpnext/payroll/workspace/payroll/payroll.json b/erpnext/payroll/workspace/payroll/payroll.json index b55bdc7711..7246dae5bc 100644 --- a/erpnext/payroll/workspace/payroll/payroll.json +++ b/erpnext/payroll/workspace/payroll/payroll.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Outgoing Salary", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Payroll\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Outgoing Salary\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Structure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Payroll Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Slip\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Income Tax Slab\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Salary Register\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Payroll\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Taxation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Compensations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-05-27 19:54:23.405607", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "money-coins-1", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Payroll", "links": [ { @@ -319,15 +312,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.335324", + "modified": "2021-08-05 12:16:01.335325", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll", - "onboarding": "Payroll", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/projects/workspace/projects/projects.json b/erpnext/projects/workspace/projects/projects.json index 065f1eda1f..1df2b08983 100644 --- a/erpnext/projects/workspace/projects/projects.json +++ b/erpnext/projects/workspace/projects/projects.json @@ -1,5 +1,4 @@ { - "category": "", "charts": [ { "chart_name": "Project Summary", @@ -8,18 +7,12 @@ ], "content": "[{\"type\": \"chart\", \"data\": {\"chart_name\": \"Open Projects\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Task\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Timesheet\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Project Billing Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Projects\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Time Tracking\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:46:04.874669", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "project", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Projects", "links": [ { @@ -201,15 +194,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.540145", + "modified": "2021-08-05 12:16:01.540147", "modified_by": "Administrator", "module": "Projects", "name": "Projects", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/quality_management/workspace/quality/quality.json b/erpnext/quality_management/workspace/quality/quality.json index 4dc8129d89..ae28470182 100644 --- a/erpnext/quality_management/workspace/quality/quality.json +++ b/erpnext/quality_management/workspace/quality/quality.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Goal\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Procedure\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Inspection\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Review\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Quality Action\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Non Conformance\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Goal and Procedure\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Feedback\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Meeting\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Review and Action\", \"col\": 4}}]", "creation": "2020-03-02 15:49:28.632014", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "quality", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Quality", "links": [ { @@ -149,15 +142,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.699912", + "modified": "2021-08-05 12:16:01.699913", "modified_by": "Administrator", "module": "Quality Management", "name": "Quality", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/selling/workspace/retail/retail.json b/erpnext/selling/workspace/retail/retail.json index 9d2e6cabbc..a851ace738 100644 --- a/erpnext/selling/workspace/retail/retail.json +++ b/erpnext/selling/workspace/retail/retail.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Point Of Sale\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings & Configurations\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Loyalty Program\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Opening & Closing\", \"col\": 4}}]", "creation": "2020-03-02 17:18:32.505616", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "retail", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Retail", "links": [ { @@ -108,15 +101,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.840988", + "modified": "2021-08-05 12:16:01.840989", "modified_by": "Administrator", "module": "Selling", "name": "Retail", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "Retail", "roles": [], diff --git a/erpnext/selling/workspace/selling/selling.json b/erpnext/selling/workspace/selling/selling.json index 345187f93c..db2e6bafd5 100644 --- a/erpnext/selling/workspace/selling/selling.json +++ b/erpnext/selling/workspace/selling/selling.json @@ -1,26 +1,18 @@ { - "category": "", "charts": [ { "chart_name": "Sales Order Trends", "label": "Sales Order Trends" } ], - "charts_label": "Selling ", "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Selling\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Sales Order Trends\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Analytics\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Sales Order Analysis\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Selling\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}]", "creation": "2020-01-28 11:49:12.092882", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "sell", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Selling", "links": [ { @@ -570,15 +562,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:01.990702", + "modified": "2021-08-05 12:16:01.990703", "modified_by": "Administrator", "module": "Selling", "name": "Selling", - "onboarding": "Selling", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], @@ -619,6 +608,5 @@ "type": "Dashboard" } ], - "shortcuts_label": "Quick Access", "title": "Selling" } \ No newline at end of file diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index ef4b050ceb..320cb7ba84 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,31 +1,21 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]", "creation": "2020-03-12 14:47:51.166455", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "setting", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "ERPNext Settings", "links": [], - "modified": "2021-08-05 12:15:59.052327", + "modified": "2021-08-05 12:15:59.052328", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json index a4e7ad863b..4e1ccf9b94 100644 --- a/erpnext/setup/workspace/home/home.json +++ b/erpnext/setup/workspace/home/home.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], - "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]", "creation": "2020-01-23 13:46:38.833076", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "getting-started", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Home", "links": [ { @@ -278,15 +271,12 @@ "type": "Link" } ], - "modified": "2021-08-10 15:33:20.704740", + "modified": "2021-08-10 15:33:20.704741", "modified_by": "Administrator", "module": "Setup", "name": "Home", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/stock/workspace/stock/stock.json b/erpnext/stock/workspace/stock/stock.json index 26d10ce703..9c805150f1 100644 --- a/erpnext/stock/workspace/stock/stock.json +++ b/erpnext/stock/workspace/stock/stock.json @@ -1,6 +1,4 @@ { - "cards_label": "Masters & Reports", - "category": "", "charts": [ { "chart_name": "Warehouse wise Stock Value" @@ -8,18 +6,12 @@ ], "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Stock\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Quick Access\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Material Request\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Entry\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Purchase Receipt\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Delivery Note\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Ledger\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Balance\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Masters & Reports\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Items and Pricing\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Transactions\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Stock Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Serial No and Batch\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Key Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Other Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Incorrect Data Report\", \"col\": 4}}]", "creation": "2020-03-02 15:43:10.096528", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "stock", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Stock", "links": [ { @@ -764,15 +756,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:02.361509", + "modified": "2021-08-05 12:16:02.361519", "modified_by": "Administrator", "module": "Stock", "name": "Stock", - "onboarding": "Stock", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], @@ -831,6 +820,5 @@ "type": "Dashboard" } ], - "shortcuts_label": "Quick Access", "title": "Stock" } \ No newline at end of file diff --git a/erpnext/support/workspace/support/support.json b/erpnext/support/workspace/support/support.json index 4c5829d7a0..d68c7c70cf 100644 --- a/erpnext/support/workspace/support/support.json +++ b/erpnext/support/workspace/support/support.json @@ -1,20 +1,13 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Issue\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Maintenance Visit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Issues\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Maintenance\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Service Level Agreement\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Warranty\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]", "creation": "2020-03-02 15:48:23.224699", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "icon": "support", "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Support", "links": [ { @@ -176,15 +169,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:02.699923", + "modified": "2021-08-05 12:16:02.699924", "modified_by": "Administrator", "module": "Support", "name": "Support", - "onboarding": "", "owner": "Administrator", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], diff --git a/erpnext/utilities/workspace/utilities/utilities.json b/erpnext/utilities/workspace/utilities/utilities.json index 4ad4afb8f4..02a8af5d6c 100644 --- a/erpnext/utilities/workspace/utilities/utilities.json +++ b/erpnext/utilities/workspace/utilities/utilities.json @@ -1,19 +1,12 @@ { - "category": "", "charts": [], "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Video\", \"col\": 4}}]", "creation": "2020-09-10 12:21:22.335307", - "developer_mode_only": 0, - "disable_user_customization": 0, "docstatus": 0, "doctype": "Workspace", - "extends": "", - "extends_another_page": 0, "for_user": "", "hide_custom": 0, "idx": 0, - "is_default": 0, - "is_standard": 0, "label": "Utilities", "links": [ { @@ -47,15 +40,12 @@ "type": "Link" } ], - "modified": "2021-08-05 12:16:03.350804", + "modified": "2021-08-05 12:16:03.350805", "modified_by": "Administrator", "module": "Utilities", "name": "Utilities", - "onboarding": "", "owner": "user@erpnext.com", "parent_page": "", - "pin_to_bottom": 0, - "pin_to_top": 0, "public": 1, "restrict_to_domain": "", "roles": [], From 2bb383b178314e096488764c04182f35fca17d64 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 13 Oct 2021 16:20:08 +0530 Subject: [PATCH 282/416] fix: not authorized to update entries after freezing accounts (#27937) * fix: not authorized to update entries after freezing accounts * fix: Add test case * fix(patch): patched to requeue failed reposts(check_freezing_date) * chore: misc fixes Co-authored-by: Deepesh Garg Co-authored-by: Ankush Menat --- .../doctype/sales_invoice/test_sales_invoice.py | 12 ++++++++++++ erpnext/accounts/general_ledger.py | 2 +- erpnext/patches.txt | 1 + erpnext/patches/v13_0/requeue_failed_reposts.py | 13 +++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v13_0/requeue_failed_reposts.py diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e11fe13383..56de3c6292 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2355,6 +2355,18 @@ class TestSalesInvoice(unittest.TestCase): si.reload() self.assertEqual(si.status, "Paid") + def test_sales_invoice_submission_post_account_freezing_date(self): + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1)) + si = create_sales_invoice(do_not_save=True) + si.posting_date = add_days(getdate(), 1) + si.save() + + self.assertRaises(frappe.ValidationError, si.submit) + si.posting_date = getdate() + si.submit() + + frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 0cee6f5b3a..0cae16bc51 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -293,7 +293,7 @@ def check_freezing_date(posting_date, adv_adj=False): if acc_frozen_upto: frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier') if getdate(posting_date) <= getdate(acc_frozen_upto) \ - and not frozen_accounts_modifier in frappe.get_roles() or frappe.session.user == 'Administrator': + and (frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == 'Administrator'): frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto))) def set_as_cancel(voucher_type, voucher_no): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 86480bee34..1228b66fbf 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -305,3 +305,4 @@ erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2 erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates +erpnext.patches.v13_0.requeue_failed_reposts diff --git a/erpnext/patches/v13_0/requeue_failed_reposts.py b/erpnext/patches/v13_0/requeue_failed_reposts.py new file mode 100644 index 0000000000..213cb9e26e --- /dev/null +++ b/erpnext/patches/v13_0/requeue_failed_reposts.py @@ -0,0 +1,13 @@ +import frappe +from frappe.utils import cstr + + +def execute(): + + reposts = frappe.get_all("Repost Item Valuation", + {"status": "Failed", "modified": [">", "2021-10-05"] }, + ["name", "modified", "error_log"]) + + for repost in reposts: + if "check_freezing_date" in cstr(repost.error_log): + frappe.db.set_value("Repost Item Valuation", repost.name, "status", "Queued") From 646acb6b467fcaa20c1f2c80c33d4aaf84e723f8 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Oct 2021 16:33:19 +0530 Subject: [PATCH 283/416] fix: Improve error message for Serial No mismatch between SI and DN --- .../doctype/sales_invoice/sales_invoice.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index d909814921..9b702f58e4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1284,12 +1284,20 @@ class SalesInvoice(SellingController): serial_nos = item.serial_no or "" si_serial_nos = set(get_serial_nos(serial_nos)) + serial_no_diff = si_serial_nos - dn_serial_nos - if si_serial_nos - dn_serial_nos: - frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note").format(item.idx)) + if serial_no_diff: + dn_link = frappe.utils.get_link_to_form("Delivery Note", item.delivery_note) + serial_no_msg = ", ".join(frappe.bold(d) for d in serial_no_diff) + + msg = _("Row #{0}: The following Serial Nos are not present in \ + Delivery Note {1}: ").format(item.idx, dn_link) + msg += serial_no_msg + + frappe.throw(msg=msg, title=_("Serial Nos Mismatch")) if item.serial_no and cint(item.qty) != len(si_serial_nos): - frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format( + frappe.throw(_("Row #{0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format( item.idx, item.qty, item.item_code, len(si_serial_nos))) def update_project(self): From 60f35ad8a2d5f7af0a2f5b52a2beb6f559f12dc0 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Oct 2021 16:50:10 +0530 Subject: [PATCH 284/416] fix: Remove trailing space and line break in translatable string --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9b702f58e4..dafae3128a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1290,9 +1290,9 @@ class SalesInvoice(SellingController): dn_link = frappe.utils.get_link_to_form("Delivery Note", item.delivery_note) serial_no_msg = ", ".join(frappe.bold(d) for d in serial_no_diff) - msg = _("Row #{0}: The following Serial Nos are not present in \ - Delivery Note {1}: ").format(item.idx, dn_link) - msg += serial_no_msg + msg = _("Row #{0}: The following Serial Nos are not present in Delivery Note {1}:").format( + item.idx, dn_link) + msg += " " + serial_no_msg frappe.throw(msg=msg, title=_("Serial Nos Mismatch")) From 904381295ed0e6dc731c4b4806062d37b1f64a15 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 13 Oct 2021 20:07:27 +0530 Subject: [PATCH 285/416] chore: Add Try on Frappe Cloud button to README (#27950) --- .github/try-on-f-cloud-button.svg | 32 +++++++++++++++++++++++++++++++ README.md | 6 ++++++ 2 files changed, 38 insertions(+) create mode 100644 .github/try-on-f-cloud-button.svg diff --git a/.github/try-on-f-cloud-button.svg b/.github/try-on-f-cloud-button.svg new file mode 100644 index 0000000000..fe0bb2c52d --- /dev/null +++ b/.github/try-on-f-cloud-button.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 87d7d73d5a..6fad8f4fcd 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a --- + + ### Containerized Installation Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details. From 8ea1ad9232ceac388c3fc4117752af3fa6880276 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 20:47:39 +0530 Subject: [PATCH 286/416] fix: Only validate against JV if it's not a reverse depreciation entry --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 6 ++++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index e568a82761..d04e4a5d8b 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -58,7 +58,10 @@ class JournalEntry(AccountsController): if not frappe.flags.in_import: self.validate_total_debit_and_credit() - self.validate_against_jv() + if not self.flags.is_reverse_depr_entry: + self.validate_against_jv() + self.validate_stock_accounts() + self.validate_reference_doc() if self.docstatus == 0: self.set_against_account() @@ -69,7 +72,6 @@ class JournalEntry(AccountsController): self.validate_empty_accounts_table() self.set_account_and_party_balance() self.validate_inter_company_accounts() - self.validate_stock_accounts() if self.docstatus == 0: self.apply_tax_withholding() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 44a5fae6fb..2744e0d310 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1063,6 +1063,7 @@ class SalesInvoice(SellingController): reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() + reverse_journal_entry.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() asset.flags.ignore_validate_update_after_submit = True From 749d1b6ee68603984c99045c3d4115678523893e Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 20:49:07 +0530 Subject: [PATCH 287/416] fix: Enable cwip accounting --- erpnext/assets/doctype/asset/test_asset.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index ce1d8d5194..878a31ef93 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -25,6 +25,7 @@ class AssetSetup(unittest.TestCase): def setUpClass(cls): set_depreciation_settings_in_company() create_asset_data() + enable_cwip_accounting("Computers") frappe.db.sql("delete from `tabTax Rule`") @classmethod @@ -51,13 +52,10 @@ class TestAsset(AssetSetup): """Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0.""" asset = create_asset(item_code="Macbook Pro", do_not_save=1) - enable_cwip_accounting(asset.asset_category) asset.is_existing_asset=0 self.assertRaises(frappe.ValidationError, asset.save) - enable_cwip_accounting(asset.asset_category, enable=0) - def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): asset = create_asset(item_code="Macbook Pro", do_not_save=1) asset.calculate_depreciation = 1 @@ -330,7 +328,6 @@ class TestAsset(AssetSetup): def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location") - doc = make_invoice(pr.name) self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) From 83ec9879ee963c3e8b3b7551df93679eb3643689 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 21:21:50 +0530 Subject: [PATCH 288/416] fix: Add test to validate available_for_use_date --- erpnext/assets/doctype/asset/test_asset.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 878a31ef93..964451dc2f 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -62,16 +62,17 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) - # def test_available_for_use_date_is_after_purchase_date(self): - # pr = make_purchase_receipt(item_code="Macbook Pro", - # qty=1, rate=100000.0, location="Test Location", posting_date=add_days(nowdate(), -15)) + def test_available_for_use_date_is_after_purchase_date(self): + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, + location="Test Location", posting_date=getdate("2021-10-10")) - # asset = create_asset(item_code="Macbook Pro", do_not_save=1) - # asset.is_existing_asset = 0 - # asset.purchase_receipt = pr - # asset.available_for_use_date = nowdate() + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, do_not_save=1) + asset.is_existing_asset = 0 + asset.purchase_receipt = pr.name + asset.purchase_date = getdate("2021-10-10") + asset.available_for_use_date = getdate("2021-10-1") - # self.assertRaises(frappe.ValidationError, asset.save) + self.assertRaises(frappe.ValidationError, asset.save) def test_item_exists(self): asset = create_asset(item_code="MacBook", do_not_save=1) From e8986df3cafa49d1b317429036f4c145ca4f8d86 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 13 Oct 2021 21:36:10 +0530 Subject: [PATCH 289/416] fix: Move test for Finance Books to Depreciation test suite --- erpnext/assets/doctype/asset/test_asset.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 964451dc2f..cb984a70bc 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -56,12 +56,6 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) - def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): - asset = create_asset(item_code="Macbook Pro", do_not_save=1) - asset.calculate_depreciation = 1 - - self.assertRaises(frappe.ValidationError, asset.save) - def test_available_for_use_date_is_after_purchase_date(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location", posting_date=getdate("2021-10-10")) @@ -852,6 +846,12 @@ class TestDepreciationBasics(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) + def test_finance_books_are_present_if_calculate_depreciation_is_enabled(self): + asset = create_asset(item_code="Macbook Pro", do_not_save=1) + asset.calculate_depreciation = 1 + + self.assertRaises(frappe.ValidationError, asset.save) + def test_post_depreciation_entries(self): """Tests if post_depreciation_entries() works as expected.""" From efc60ec2b5d4019560599ba56ffcf5eb7612347b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 14 Oct 2021 16:01:08 +0530 Subject: [PATCH 290/416] fix: patch to enable scheduled job for reposting --- erpnext/patches.txt | 1 + .../v13_0/enable_scheduler_job_for_item_reposting.py | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1228b66fbf..bd166928bc 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -305,4 +305,5 @@ erpnext.patches.v13_0.modify_invalid_gain_loss_gl_entries #2 erpnext.patches.v13_0.fix_additional_cost_in_mfg_stock_entry erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates +erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts diff --git a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py new file mode 100644 index 0000000000..d18bcd7b22 --- /dev/null +++ b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py @@ -0,0 +1,7 @@ +import frappe + +def execute(): + frappe.reload_doc('core', 'doctype', 'scheduled_job_type') + if frappe.db.exists('Scheduled Job Type', 'repost_item_valuation.repost_entries'): + frappe.db.set_value('Scheduled Job Type', + 'repost_item_valuation.repost_entries', 'stopped', 0) \ No newline at end of file From 1f70dd6e9865604d9f16b91cddcf989729898ccb Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 14 Oct 2021 16:23:12 +0530 Subject: [PATCH 291/416] fix: value_after_depreciation calculation (#27954) --- erpnext/assets/doctype/asset/asset.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 21 +++++++++++++++++++ .../doctype/asset_repair/test_asset_repair.py | 10 ++++----- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 7e135be30b..99a6cc35db 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -194,7 +194,7 @@ class Asset(AccountsController): start = self.clear_depreciation_schedule() # value_after_depreciation - current Asset value - if d.value_after_depreciation: + if self.docstatus == 1 and d.value_after_depreciation: value_after_depreciation = (flt(d.value_after_depreciation) - flt(self.opening_accumulated_depreciation)) else: diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 7183ee7e36..cf4581b4a1 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -682,6 +682,27 @@ class TestAsset(unittest.TestCase): # reset indian company frappe.flags.company = company_flag + def test_expected_value_change(self): + """ + tests if changing `expected_value_after_useful_life` + affects `value_after_depreciation` + """ + + asset = create_asset(calculate_depreciation=1) + asset.opening_accumulated_depreciation = 2000 + asset.number_of_depreciations_booked = 1 + + asset.finance_books[0].expected_value_after_useful_life = 100 + asset.save() + asset.reload() + self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0) + + # changing expected_value_after_useful_life shouldn't affect value_after_depreciation + asset.finance_books[0].expected_value_after_useful_life = 200 + asset.save() + asset.reload() + self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0) + def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): create_asset_category() diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py index 9945a328cf..30e3a5296e 100644 --- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py +++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py @@ -22,7 +22,7 @@ class TestAssetRepair(unittest.TestCase): frappe.db.sql("delete from `tabTax Rule`") def test_update_status(self): - asset = create_asset() + asset = create_asset(submit=1) initial_status = asset.status asset_repair = create_asset_repair(asset = asset) @@ -76,7 +76,7 @@ class TestAssetRepair(unittest.TestCase): self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) def test_increase_in_asset_value_due_to_stock_consumption(self): - asset = create_asset(calculate_depreciation = 1) + asset = create_asset(calculate_depreciation = 1, submit=1) initial_asset_value = get_asset_value(asset) asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1) asset.reload() @@ -85,7 +85,7 @@ class TestAssetRepair(unittest.TestCase): self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value) def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self): - asset = create_asset(calculate_depreciation = 1) + asset = create_asset(calculate_depreciation = 1, submit=1) initial_asset_value = get_asset_value(asset) asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1) asset.reload() @@ -103,7 +103,7 @@ class TestAssetRepair(unittest.TestCase): self.assertEqual(asset_repair.name, gl_entry.voucher_no) def test_increase_in_asset_life(self): - asset = create_asset(calculate_depreciation = 1) + asset = create_asset(calculate_depreciation = 1, submit=1) initial_num_of_depreciations = num_of_depreciations(asset) create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1) asset.reload() @@ -126,7 +126,7 @@ def create_asset_repair(**args): if args.asset: asset = args.asset else: - asset = create_asset(is_existing_asset = 1) + asset = create_asset(is_existing_asset = 1, submit=1) asset_repair = frappe.new_doc("Asset Repair") asset_repair.update({ "asset": asset.name, From 3f97413814aca3815273a51b2abca84180806b99 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 14 Oct 2021 16:45:27 +0530 Subject: [PATCH 292/416] chore: formatting --- erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py index d18bcd7b22..cc10413183 100644 --- a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py +++ b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc('core', 'doctype', 'scheduled_job_type') if frappe.db.exists('Scheduled Job Type', 'repost_item_valuation.repost_entries'): From 230a5d4b399a127bd6cb33315f410900d1553b9c Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Oct 2021 16:57:54 +0530 Subject: [PATCH 293/416] Update enable_scheduler_job_for_item_reposting.py --- .../patches/v13_0/enable_scheduler_job_for_item_reposting.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py index cc10413183..e1fae8428f 100644 --- a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py +++ b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py @@ -1,8 +1,7 @@ import frappe - def execute(): frappe.reload_doc('core', 'doctype', 'scheduled_job_type') if frappe.db.exists('Scheduled Job Type', 'repost_item_valuation.repost_entries'): frappe.db.set_value('Scheduled Job Type', - 'repost_item_valuation.repost_entries', 'stopped', 0) \ No newline at end of file + 'repost_item_valuation.repost_entries', 'stopped', 0) From 9c1d828b1b6c93abdb0208c41824aacbe88e9f12 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 14 Oct 2021 17:18:24 +0530 Subject: [PATCH 294/416] chore: formatting --- erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py index e1fae8428f..7a51b43211 100644 --- a/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py +++ b/erpnext/patches/v13_0/enable_scheduler_job_for_item_reposting.py @@ -1,5 +1,6 @@ import frappe + def execute(): frappe.reload_doc('core', 'doctype', 'scheduled_job_type') if frappe.db.exists('Scheduled Job Type', 'repost_item_valuation.repost_entries'): From 41035b033047dfe00a4a881a7255fd677e78fd90 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 14 Oct 2021 18:29:46 +0530 Subject: [PATCH 295/416] fix: Retain space inside Serial no string while cleaning serial nos --- erpnext/controllers/stock_controller.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4697205d72..dfb53c7543 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -79,8 +79,15 @@ class StockController(AccountsController): def clean_serial_nos(self): for row in self.get("items"): if hasattr(row, "serial_no") and row.serial_no: - # replace commas by linefeed and remove all spaces in string - row.serial_no = row.serial_no.replace(",", "\n").replace(" ", "") + # replace commas by linefeed + row.serial_no = row.serial_no.replace(",", "\n") + + # strip preceeding and succeeding spaces for each SN + # (SN could have valid spaces in between e.g. SN - 123 - 2021) + serial_no_list = row.serial_no.split("\n") + serial_no_list = [sn.lstrip().rstrip() for sn in serial_no_list] + + row.serial_no = "\n".join(serial_no_list) def get_gl_entries(self, warehouse_account=None, default_expense_account=None, default_cost_center=None): From 8cf188d9c0f195d9caca5caa698ebc9b675e5906 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 14 Oct 2021 19:21:41 +0530 Subject: [PATCH 296/416] fix: Use strip instead of lstrip and rstrip Co-authored-by: Ankush Menat --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index dfb53c7543..08d422d3bc 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -85,7 +85,7 @@ class StockController(AccountsController): # strip preceeding and succeeding spaces for each SN # (SN could have valid spaces in between e.g. SN - 123 - 2021) serial_no_list = row.serial_no.split("\n") - serial_no_list = [sn.lstrip().rstrip() for sn in serial_no_list] + serial_no_list = [sn.strip() for sn in serial_no_list] row.serial_no = "\n".join(serial_no_list) From a9341672cf6c33d0d7df67dfe828dc8a7a26a940 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 14 Oct 2021 19:25:14 +0530 Subject: [PATCH 297/416] test: Include serial no with spaces in it in sanitation test --- erpnext/stock/doctype/serial_no/test_serial_no.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py index 546e21bde0..570f22e6af 100644 --- a/erpnext/stock/doctype/serial_no/test_serial_no.py +++ b/erpnext/stock/doctype/serial_no/test_serial_no.py @@ -184,14 +184,14 @@ class TestSerialNo(ERPNextTestCase): se = frappe.copy_doc(test_records[0]) se.get("items")[0].item_code = item_code - se.get("items")[0].qty = 3 - se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 " - se.get("items")[0].transfer_qty = 3 + se.get("items")[0].qty = 4 + se.get("items")[0].serial_no = " _TS1, _TS2 , _TS3 , _TS4 - 2021" + se.get("items")[0].transfer_qty = 4 se.set_stock_entry_type() se.insert() se.submit() - self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3") + self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021") frappe.db.rollback() From 4437eb0c4b0bdacb6569d5067e2e3c1aba1191ac Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 14 Oct 2021 20:03:37 +0530 Subject: [PATCH 298/416] fix: remove bad description (#27963) --- .../support/doctype/support_settings/support_settings.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/support_settings/support_settings.json b/erpnext/support/doctype/support_settings/support_settings.json index 5d3d3ace59..bf1daa16f8 100644 --- a/erpnext/support/doctype/support_settings/support_settings.json +++ b/erpnext/support/doctype/support_settings/support_settings.json @@ -37,7 +37,6 @@ }, { "default": "7", - "description": "Auto close Issue after 7 days", "fieldname": "close_issue_after_days", "fieldtype": "Int", "label": "Close Issue After Days" @@ -164,7 +163,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-06-11 13:08:38.473616", + "modified": "2021-10-14 13:08:38.473616", "modified_by": "Administrator", "module": "Support", "name": "Support Settings", @@ -185,4 +184,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 4bf01bb4b7b6770b5f957824d912397b1556f168 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 14 Oct 2021 20:28:57 +0530 Subject: [PATCH 299/416] fix: Move Purchase Receipt creation to setUpClass --- erpnext/assets/doctype/asset/test_asset.py | 56 ++++++---------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 773ecc7841..a94f84f969 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -26,6 +26,7 @@ class AssetSetup(unittest.TestCase): set_depreciation_settings_in_company() create_asset_data() enable_cwip_accounting("Computers") + make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location") frappe.db.sql("delete from `tabTax Rule`") @classmethod @@ -57,12 +58,8 @@ class TestAsset(AssetSetup): self.assertRaises(frappe.ValidationError, asset.save) def test_available_for_use_date_is_after_purchase_date(self): - pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=100000.0, - location="Test Location", posting_date=getdate("2021-10-10")) - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, do_not_save=1) asset.is_existing_asset = 0 - asset.purchase_receipt = pr.name asset.purchase_date = getdate("2021-10-10") asset.available_for_use_date = getdate("2021-10-1") @@ -152,21 +149,9 @@ class TestAsset(AssetSetup): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-01-01' - asset.purchase_date = '2020-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 10, - "frequency_of_depreciation": 1 - }) - asset.submit() + asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-01-01', + purchase_date = '2020-01-01', expected_value_after_useful_life=10000, + total_number_of_depreciations=10, frequency_of_depreciation=1, submit=1) post_depreciation_entries(date=add_months('2020-01-01', 4)) @@ -194,22 +179,11 @@ class TestAsset(AssetSetup): self.assertEqual(asset.status, "Partially Depreciated") def test_gle_made_by_asset_sale(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-06-06', + purchase_date = '2020-01-01', expected_value_after_useful_life=10000, + total_number_of_depreciations=3, frequency_of_depreciation=10, + depreciation_start_date='2020-12-31', submit=1) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() post_depreciation_entries(date="2021-01-01") si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company") @@ -237,6 +211,13 @@ class TestAsset(AssetSetup): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_expense_head(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=2, rate=200000.0, location="Test Location") + doc = make_invoice(pr.name) + + self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + # CWIP: Capital Work In Progress def test_cwip_accounting(self): pr = make_purchase_receipt(item_code="Macbook Pro", @@ -320,13 +301,6 @@ class TestAsset(AssetSetup): self.assertEqual(gle, expected_gle) - def test_expense_head(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=2, rate=200000.0, location="Test Location") - doc = make_invoice(pr.name) - - self.assertEqual('Asset Received But Not Billed - _TC', doc.items[0].expense_account) - def test_asset_cwip_toggling_cases(self): cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]) From 09215a9781379e78b78b3d7e5a5c6546558256a6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 14 Oct 2021 22:15:10 +0530 Subject: [PATCH 300/416] fix: Remove PR creation from all tests for Depreciation Methods --- erpnext/assets/doctype/asset/test_asset.py | 194 ++++++--------------- 1 file changed, 54 insertions(+), 140 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index a94f84f969..8795b0223e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -371,23 +371,10 @@ class TestAsset(AssetSetup): class TestDepreciationMethods(AssetSetup): def test_schedule_for_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", purchase_date="2030-01-01", + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -402,21 +389,13 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_straight_line_method_for_existing_asset(self): - create_asset(is_existing_asset=1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 40000 - asset.available_for_use_date = "2030-06-06" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-06-06", is_existing_asset=1, + number_of_depreciations_booked = 1, opening_accumulated_depreciation=40000, + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) + self.assertEqual(asset.status, "Draft") - asset.save() expected_schedules = [ ["2030-12-31", 14246.58, 54246.58], ["2031-12-31", 25000.00, 79246.58], @@ -428,22 +407,12 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", purchase_date="2030-01-01", + depreciation_method="Double Declining Balance", + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": '2030-12-31' - }) - asset.save() self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -458,22 +427,13 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method_for_existing_asset(self): - create_asset(is_existing_asset = 1) - asset = frappe.get_doc("Asset", {"asset_name": "Macbook Pro 1"}) - asset.calculate_depreciation = 1 - asset.is_existing_asset = 1 - asset.number_of_depreciations_booked = 1 - asset.opening_accumulated_depreciation = 50000 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2029-11-30' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Double Declining Balance", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save() + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", is_existing_asset=1, + depreciation_method="Double Declining Balance", + number_of_depreciations_booked = 1, opening_accumulated_depreciation=50000, + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) + self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -487,24 +447,11 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_prorated_straight_line_method(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2030-01-30' - asset.is_existing_asset = 0 - asset.available_for_use_date = "2030-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - - asset.save() + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-30", purchase_date="2030-01-30", + depreciation_method="Straight Line", + expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) expected_schedules = [ ["2030-12-31", 27534.25, 27534.25], @@ -520,29 +467,18 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_depreciation_entry_for_wdv_without_pro_rata(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-01-01' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-01-01", purchase_date="2030-01-01", + depreciation_method="Written Down Value", + expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 4000.00, 4000.00], - ["2031-12-31", 2000.00, 6000.00], - ["2032-12-31", 1000.00, 7000.0], + ["2030-12-31", 50000.0, 50000.0], + ["2031-12-31", 25000.0, 75000.0], + ["2032-12-31", 12500.0, 87500.0], ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -552,30 +488,19 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_pro_rata_depreciation_entry_for_wdv(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-06-06", purchase_date="2030-01-01", + depreciation_method="Written Down Value", + expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 2279.45, 2279.45], - ["2031-12-31", 2860.28, 5139.73], - ["2032-12-31", 1430.14, 6569.87], - ["2033-06-06", 430.13, 7000.0], + ["2030-12-31", 28493.15, 28493.15], + ["2031-12-31", 35753.43, 64246.58], + ["2032-12-31", 17876.71, 82123.29], + ["2033-06-06", 5376.71, 87500.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -588,30 +513,19 @@ class TestDepreciationMethods(AssetSetup): company_flag = frappe.flags.company frappe.flags.company = "_Test Company" - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=8000.0, location="Test Location") - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-07-12' - asset.purchase_date = '2030-01-01' - asset.append("finance_books", { - "expected_value_after_useful_life": 1000, - "depreciation_method": "Written Down Value", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 12, - "depreciation_start_date": "2030-12-31" - }) - asset.save(ignore_permissions=True) + asset = create_asset(calculate_depreciation=1, + available_for_use_date="2030-07-12", purchase_date="2030-01-01", + depreciation_method="Written Down Value", + expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", + total_number_of_depreciations=3, frequency_of_depreciation=12) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 942.47, 942.47], - ["2031-12-31", 3528.77, 4471.24], - ["2032-12-31", 1764.38, 6235.62], - ["2033-07-12", 764.38, 7000.00] + ["2030-12-31", 11780.82, 11780.82], + ["2031-12-31", 44109.59, 55890.41], + ["2032-12-31", 22054.8, 77945.21], + ["2033-07-12", 9554.79, 87500.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -1009,7 +923,7 @@ def create_asset(**args): if asset.calculate_depreciation: asset.append("finance_books", { - "depreciation_method": "Straight Line", + "depreciation_method": args.depreciation_method or "Straight Line", "frequency_of_depreciation": args.frequency_of_depreciation or 12, "total_number_of_depreciations": args.total_number_of_depreciations or 5, "expected_value_after_useful_life": args.expected_value_after_useful_life or 0, From 968be70bd1c0011792ea2c3887dc2f794b15d300 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 14 Oct 2021 22:32:27 +0530 Subject: [PATCH 301/416] fix: Remove PR creation from all tests in TestDepreciationBasics --- erpnext/assets/doctype/asset/test_asset.py | 92 +++++----------------- 1 file changed, 20 insertions(+), 72 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 8795b0223e..cbfadc0470 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -744,17 +744,9 @@ class TestDepreciationBasics(AssetSetup): """Tests if post_depreciation_entries() works as expected.""" asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - asset.submit() + available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", + frequency_of_depreciation=12, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -767,17 +759,9 @@ class TestDepreciationBasics(AssetSetup): """Tests if clear_depreciation_schedule() works as expected.""" asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - asset.submit() + available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", + frequency_of_depreciation=12, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -787,22 +771,12 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(len(asset.schedules), 1) def test_depreciation_entry_cancellation(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(item_code="Macbook Pro", + calculate_depreciation=1, purchase_date="2020-06-06", + available_for_use_date="2020-06-06", depreciation_start_date="2020-12-31", + frequency_of_depreciation=10, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() post_depreciation_entries(date="2021-01-01") asset.load_from_db() @@ -817,21 +791,11 @@ class TestDepreciationBasics(AssetSetup): self.assertFalse(depr_entry) def test_asset_expected_value_after_useful_life(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, + available_for_use_date="2020-06-06", purchase_date="2020-06-06", + frequency_of_depreciation=10, total_number_of_depreciations=3, + expected_value_after_useful_life=10000) - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10 - }) - asset.save() accumulated_depreciation_after_full_schedule = \ max(d.accumulated_depreciation_amount for d in asset.get("schedules")) @@ -841,28 +805,12 @@ class TestDepreciationBasics(AssetSetup): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) def test_gle_made_by_depreciation_entries(self): - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=100000.0, location="Test Location") + asset = create_asset(item_code="Macbook Pro", + calculate_depreciation=1, purchase_date="2020-01-30", + available_for_use_date="2020-01-30", depreciation_start_date="2020-12-31", + frequency_of_depreciation=10, total_number_of_depreciations=3, + expected_value_after_useful_life=10000, submit=1) - finance_book = frappe.new_doc('Finance Book') - finance_book.finance_book_name = 'Income Tax' - finance_book.for_income_tax = 1 - finance_book.insert(ignore_if_duplicate=1) - - asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') - asset = frappe.get_doc('Asset', asset_name) - asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' - asset.available_for_use_date = "2020-01-30" - asset.append("finance_books", { - "expected_value_after_useful_life": 10000, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" - }) - asset.submit() - asset.load_from_db() self.assertEqual(asset.status, "Submitted") frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-") From e9d310a13e902d7208a925bb577faba2604cd684 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 01:45:53 +0530 Subject: [PATCH 302/416] fix: Format tests --- erpnext/assets/doctype/asset/test_asset.py | 406 ++++++++++++++------- 1 file changed, 269 insertions(+), 137 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 070a3e9f55..523c8195ae 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -149,9 +149,15 @@ class TestAsset(AssetSetup): self.assertEqual(doc.items[0].is_fixed_asset, 1) def test_scrap_asset(self): - asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-01-01', - purchase_date = '2020-01-01', expected_value_after_useful_life=10000, - total_number_of_depreciations=10, frequency_of_depreciation=1, submit=1) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = '2020-01-01', + purchase_date = '2020-01-01', + expected_value_after_useful_life = 10000, + total_number_of_depreciations = 10, + frequency_of_depreciation = 1, + submit = 1 + ) post_depreciation_entries(date=add_months('2020-01-01', 4)) @@ -179,10 +185,16 @@ class TestAsset(AssetSetup): self.assertEqual(asset.status, "Partially Depreciated") def test_gle_made_by_asset_sale(self): - asset = create_asset(calculate_depreciation=1, available_for_use_date='2020-06-06', - purchase_date = '2020-01-01', expected_value_after_useful_life=10000, - total_number_of_depreciations=3, frequency_of_depreciation=10, - depreciation_start_date='2020-12-31', submit=1) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = '2020-06-06', + purchase_date = '2020-01-01', + expected_value_after_useful_life = 10000, + total_number_of_depreciations = 3, + frequency_of_depreciation = 10, + depreciation_start_date = '2020-12-31', + submit = 1 + ) post_depreciation_entries(date="2021-01-01") @@ -371,10 +383,15 @@ class TestAsset(AssetSetup): class TestDepreciationMethods(AssetSetup): def test_schedule_for_straight_line_method(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", purchase_date="2030-01-01", - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + purchase_date = "2030-01-01", + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -389,11 +406,17 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_straight_line_method_for_existing_asset(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-06-06", is_existing_asset=1, - number_of_depreciations_booked = 1, opening_accumulated_depreciation=40000, - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-06-06", + is_existing_asset = 1, + number_of_depreciations_booked = 1, + opening_accumulated_depreciation = 40000, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") expected_schedules = [ @@ -407,11 +430,16 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", purchase_date="2030-01-01", - depreciation_method="Double Declining Balance", - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + purchase_date = "2030-01-01", + depreciation_method = "Double Declining Balance", + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") @@ -427,12 +455,18 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_double_declining_method_for_existing_asset(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", is_existing_asset=1, - depreciation_method="Double Declining Balance", - number_of_depreciations_booked = 1, opening_accumulated_depreciation=50000, - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + is_existing_asset = 1, + depreciation_method = "Double Declining Balance", + number_of_depreciations_booked = 1, + opening_accumulated_depreciation = 50000, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.status, "Draft") @@ -447,11 +481,16 @@ class TestDepreciationMethods(AssetSetup): self.assertEqual(schedules, expected_schedules) def test_schedule_for_prorated_straight_line_method(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-30", purchase_date="2030-01-30", - depreciation_method="Straight Line", - expected_value_after_useful_life=10000, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-30", + purchase_date = "2030-01-30", + depreciation_method = "Straight Line", + expected_value_after_useful_life = 10000, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) expected_schedules = [ ["2030-12-31", 27534.25, 27534.25], @@ -467,11 +506,16 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_depreciation_entry_for_wdv_without_pro_rata(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-01-01", purchase_date="2030-01-01", - depreciation_method="Written Down Value", - expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-01-01", + purchase_date = "2030-01-01", + depreciation_method = "Written Down Value", + expected_value_after_useful_life = 12500, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) @@ -488,11 +532,16 @@ class TestDepreciationMethods(AssetSetup): # WDV: Written Down Value method def test_pro_rata_depreciation_entry_for_wdv(self): - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-06-06", purchase_date="2030-01-01", - depreciation_method="Written Down Value", - expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-06-06", + purchase_date = "2030-01-01", + depreciation_method = "Written Down Value", + expected_value_after_useful_life = 12500, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) @@ -513,11 +562,16 @@ class TestDepreciationMethods(AssetSetup): company_flag = frappe.flags.company frappe.flags.company = "_Test Company" - asset = create_asset(calculate_depreciation=1, - available_for_use_date="2030-07-12", purchase_date="2030-01-01", - depreciation_method="Written Down Value", - expected_value_after_useful_life=12500, depreciation_start_date="2030-12-31", - total_number_of_depreciations=3, frequency_of_depreciation=12) + asset = create_asset( + calculate_depreciation = 1, + available_for_use_date = "2030-07-12", + purchase_date = "2030-01-01", + depreciation_method = "Written Down Value", + expected_value_after_useful_life = 12500, + depreciation_start_date = "2030-12-31", + total_number_of_depreciations = 3, + frequency_of_depreciation = 12 + ) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) @@ -538,9 +592,15 @@ class TestDepreciationMethods(AssetSetup): class TestDepreciationBasics(AssetSetup): def test_depreciation_without_pro_rata(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-12-31"), submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = getdate("2019-12-31"), + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = getdate("2020-12-31"), + submit = 1 + ) expected_values = [ ["2020-12-31", 30000, 30000], @@ -554,9 +614,15 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) def test_depreciation_with_pro_rata(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = getdate("2019-12-31"), + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = getdate("2020-07-01"), + submit = 1 + ) expected_values = [ ["2020-07-01", 15000, 15000], @@ -575,16 +641,18 @@ class TestDepreciationBasics(AssetSetup): from erpnext.assets.doctype.asset.asset import get_depreciation_amount - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31")) + asset = create_asset( + item_code = "Macbook Pro", + available_for_use_date = "2019-12-31" + ) - asset.finance_books = [] + asset.calculate_depreciation = 1 asset.append("finance_books", { "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") + "depreciation_start_date": "2020-12-31" }) depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0]) @@ -593,19 +661,16 @@ class TestDepreciationBasics(AssetSetup): def test_make_depreciation_schedule(self): """Tests if make_depreciation_schedule() returns the right values.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - - asset.make_depreciation_schedule(date_of_sale=None) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_method = "Straight Line", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 1000, + depreciation_start_date = "2020-12-31" + ) expected_values = [ ['2020-12-31', 30000.0], @@ -620,20 +685,16 @@ class TestDepreciationBasics(AssetSetup): def test_set_accumulated_depreciation(self): """Tests if set_accumulated_depreciation() returns the right values.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) - - asset.finance_books = [] - asset.append("finance_books", { - "depreciation_method": "Straight Line", - "frequency_of_depreciation": 12, - "total_number_of_depreciations": 3, - "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") - }) - - asset.make_depreciation_schedule(date_of_sale=None) - asset.set_accumulated_depreciation() + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_method = "Straight Line", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 1000, + depreciation_start_date = "2020-12-31" + ) expected_values = [30000.0, 60000.0, 90000.0] @@ -643,16 +704,19 @@ class TestDepreciationBasics(AssetSetup): def test_check_is_pro_rata(self): """Tests if check_is_pro_rata() returns the right value(i.e. checks if has_pro_rata is accurate).""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + available_for_use_date = "2019-12-31", + do_not_save = 1 + ) - asset.finance_books = [] + asset.calculate_depreciation = 1 asset.append("finance_books", { "depreciation_method": "Straight Line", "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-12-31") + "depreciation_start_date": "2020-12-31" }) has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) @@ -664,7 +728,7 @@ class TestDepreciationBasics(AssetSetup): "frequency_of_depreciation": 12, "total_number_of_depreciations": 3, "expected_value_after_useful_life": 10000, - "depreciation_start_date": getdate("2020-07-01") + "depreciation_start_date": "2020-07-01" }) has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0]) @@ -673,64 +737,103 @@ class TestDepreciationBasics(AssetSetup): def test_expected_value_after_useful_life_greater_than_purchase_amount(self): """Tests if an error is raised when expected_value_after_useful_life(110,000) > gross_purchase_amount(100,000).""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=110000, depreciation_start_date=getdate("2020-07-01"), do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 110000, + depreciation_start_date = "2020-07-01", + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_depreciation_start_date(self): """Tests if an error is raised when neither depreciation_start_date nor available_for_use_date are specified.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - total_number_of_depreciations=3, expected_value_after_useful_life=110000, do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 110000, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_opening_accumulated_depreciation(self): """Tests if an error is raised when opening_accumulated_depreciation > (gross_purchase_amount - expected_value_after_useful_life).""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), - opening_accumulated_depreciation=100000, do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2020-07-01", + opening_accumulated_depreciation = 100000, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_number_of_depreciations_booked(self): """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), - opening_accumulated_depreciation=10000, do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2020-07-01", + opening_accumulated_depreciation = 10000, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_number_of_depreciations(self): """Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2020-07-01"), - opening_accumulated_depreciation=10000, number_of_depreciations_booked=5, - do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2020-07-01", + opening_accumulated_depreciation = 10000, + number_of_depreciations_booked = 5, + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_depreciation_start_date_is_before_purchase_date(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2014-07-01"), - do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2014-07-01", + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) def test_depreciation_start_date_is_before_available_for_use_date(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date=getdate("2019-12-31"), total_number_of_depreciations=3, - expected_value_after_useful_life=10000, depreciation_start_date=getdate("2018-07-01"), - do_not_save=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + depreciation_start_date = "2018-07-01", + do_not_save = 1 + ) self.assertRaises(frappe.ValidationError, asset.save) @@ -743,10 +846,16 @@ class TestDepreciationBasics(AssetSetup): def test_post_depreciation_entries(self): """Tests if post_depreciation_entries() works as expected.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", - frequency_of_depreciation=12, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -758,10 +867,16 @@ class TestDepreciationBasics(AssetSetup): def test_clear_depreciation_schedule(self): """Tests if clear_depreciation_schedule() works as expected.""" - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date="2019-12-31", depreciation_start_date="2020-12-31", - frequency_of_depreciation=12, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2019-12-31", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 12, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) post_depreciation_entries(date="2021-06-01") asset.load_from_db() @@ -771,11 +886,17 @@ class TestDepreciationBasics(AssetSetup): self.assertEqual(len(asset.schedules), 1) def test_depreciation_entry_cancellation(self): - asset = create_asset(item_code="Macbook Pro", - calculate_depreciation=1, purchase_date="2020-06-06", - available_for_use_date="2020-06-06", depreciation_start_date="2020-12-31", - frequency_of_depreciation=10, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + purchase_date = "2020-06-06", + available_for_use_date = "2020-06-06", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 10, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) post_depreciation_entries(date="2021-01-01") @@ -791,10 +912,15 @@ class TestDepreciationBasics(AssetSetup): self.assertFalse(depr_entry) def test_asset_expected_value_after_useful_life(self): - asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, - available_for_use_date="2020-06-06", purchase_date="2020-06-06", - frequency_of_depreciation=10, total_number_of_depreciations=3, - expected_value_after_useful_life=10000) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + available_for_use_date = "2020-06-06", + purchase_date = "2020-06-06", + frequency_of_depreciation = 10, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000 + ) accumulated_depreciation_after_full_schedule = \ max(d.accumulated_depreciation_amount for d in asset.get("schedules")) @@ -805,11 +931,17 @@ class TestDepreciationBasics(AssetSetup): self.assertTrue(asset.finance_books[0].expected_value_after_useful_life >= asset_value_after_full_schedule) def test_gle_made_by_depreciation_entries(self): - asset = create_asset(item_code="Macbook Pro", - calculate_depreciation=1, purchase_date="2020-01-30", - available_for_use_date="2020-01-30", depreciation_start_date="2020-12-31", - frequency_of_depreciation=10, total_number_of_depreciations=3, - expected_value_after_useful_life=10000, submit=1) + asset = create_asset( + item_code = "Macbook Pro", + calculate_depreciation = 1, + purchase_date = "2020-01-30", + available_for_use_date = "2020-01-30", + depreciation_start_date = "2020-12-31", + frequency_of_depreciation = 10, + total_number_of_depreciations = 3, + expected_value_after_useful_life = 10000, + submit = 1 + ) self.assertEqual(asset.status, "Submitted") From 371b62136454f720a02b12644d6e57e7d9840a34 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:02:27 +0530 Subject: [PATCH 303/416] fix: Compare date strings --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 523c8195ae..c694677d8d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -679,7 +679,7 @@ class TestDepreciationBasics(AssetSetup): ] for i, schedule in enumerate(asset.schedules): - self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][0], schedule.schedule_date) self.assertEqual(expected_values[i][1], schedule.depreciation_amount) def test_set_accumulated_depreciation(self): From 60aae4423dcf9e26557f0a44c2f7c206d54eb07b Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:03:30 +0530 Subject: [PATCH 304/416] fix: Add missing digit --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c694677d8d..c28add8065 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -692,7 +692,7 @@ class TestDepreciationBasics(AssetSetup): depreciation_method = "Straight Line", frequency_of_depreciation = 12, total_number_of_depreciations = 3, - expected_value_after_useful_life = 1000, + expected_value_after_useful_life = 10000, depreciation_start_date = "2020-12-31" ) From fdeb273fa06670d802847199d57ecd40dbecd476 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:53:23 +0530 Subject: [PATCH 305/416] fix: Sider issues --- erpnext/assets/doctype/asset/test_asset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c28add8065..6dd7a5be71 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -410,7 +410,7 @@ class TestDepreciationMethods(AssetSetup): calculate_depreciation = 1, available_for_use_date = "2030-06-06", is_existing_asset = 1, - number_of_depreciations_booked = 1, + number_of_depreciations_booked = 1, opening_accumulated_depreciation = 40000, expected_value_after_useful_life = 10000, depreciation_start_date = "2030-12-31", @@ -460,7 +460,7 @@ class TestDepreciationMethods(AssetSetup): available_for_use_date = "2030-01-01", is_existing_asset = 1, depreciation_method = "Double Declining Balance", - number_of_depreciations_booked = 1, + number_of_depreciations_booked = 1, opening_accumulated_depreciation = 50000, expected_value_after_useful_life = 10000, depreciation_start_date = "2030-12-31", From 0b8cb5dd47816b87cfea2176ffa76c889bb9d383 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Fri, 15 Oct 2021 04:56:00 +0530 Subject: [PATCH 306/416] fix: Add missing digit --- erpnext/assets/doctype/asset/test_asset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 6dd7a5be71..c44c63ae9e 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -668,7 +668,7 @@ class TestDepreciationBasics(AssetSetup): depreciation_method = "Straight Line", frequency_of_depreciation = 12, total_number_of_depreciations = 3, - expected_value_after_useful_life = 1000, + expected_value_after_useful_life = 10000, depreciation_start_date = "2020-12-31" ) From f3cf36f6130983456bce96d1fbcf8c953bff8f1a Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 15 Oct 2021 12:39:52 +0530 Subject: [PATCH 307/416] fix: POS Profile payment methods table (#27956) Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/accounts/doctype/pos_profile/pos_profile.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 8afa0abd36..9c9f37bba2 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -120,6 +120,7 @@ { "fieldname": "payments", "fieldtype": "Table", + "label": "Payment Methods", "options": "POS Payment Method", "reqd": 1 }, @@ -377,7 +378,7 @@ "link_fieldname": "pos_profile" } ], - "modified": "2021-02-01 13:52:51.081311", + "modified": "2021-10-14 14:17:00.469298", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", From 17a8649500dee2989b2f1231434a207288c7e3a7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 15 Oct 2021 21:19:41 +0530 Subject: [PATCH 308/416] fix: Account number and name incorrectly import using COA importer --- .../account/chart_of_accounts/chart_of_accounts.py | 13 ++++++++----- .../chart_of_accounts_importer.py | 7 +++++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index d6ccd16936..05caafe1c4 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -12,7 +12,7 @@ from six import iteritems from unidecode import unidecode -def create_charts(company, chart_template=None, existing_company=None, custom_chart=None): +def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None): chart = custom_chart or get_chart(chart_template, existing_company) if chart: accounts = [] @@ -22,7 +22,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch if root_account: root_type = child.get("root_type") - if account_name not in ["account_number", "account_type", + if account_name not in ["account_name", "account_number", "account_type", "root_type", "is_group", "tax_rate"]: account_number = cstr(child.get("account_number")).strip() @@ -35,7 +35,7 @@ def create_charts(company, chart_template=None, existing_company=None, custom_ch account = frappe.get_doc({ "doctype": "Account", - "account_name": account_name, + "account_name": child.get('account_name') if from_coa_importer else account_name, "company": company, "parent_account": parent, "is_group": is_group, @@ -213,7 +213,7 @@ def validate_bank_account(coa, bank_account): return (bank_account in accounts) @frappe.whitelist() -def build_tree_from_json(chart_template, chart_data=None): +def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False): ''' get chart template from its folder and parse the json to be rendered as tree ''' chart = chart_data or get_chart(chart_template) @@ -226,9 +226,12 @@ def build_tree_from_json(chart_template, chart_data=None): ''' recursively called to form a parent-child based list of dict from chart template ''' for account_name, child in iteritems(children): account = {} - if account_name in ["account_number", "account_type",\ + if account_name in ["account_name", "account_number", "account_type",\ "root_type", "is_group", "tax_rate"]: continue + if from_coa_importer: + account_name = child['account_name'] + account['parent_account'] = parent account['expandable'] = True if identify_is_group(child) else False account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \ diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 5e596f8677..eabe408d64 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -69,7 +69,7 @@ def import_coa(file_name, company): frappe.local.flags.ignore_root_company_validation = True forest = build_forest(data) - create_charts(company, custom_chart=forest) + create_charts(company, custom_chart=forest, from_coa_importer=True) # trigger on_update for company to reset default accounts set_default_accounts(company) @@ -148,7 +148,7 @@ def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0): if not for_validate: forest = build_forest(data) - accounts = build_tree_from_json("", chart_data=forest) # returns a list of dict in a tree render-able form + accounts = build_tree_from_json("", chart_data=forest, from_coa_importer=True) # returns a list of dict in a tree render-able form # filter out to show data for the selected node only accounts = [d for d in accounts if d['parent_account']==parent] @@ -212,11 +212,14 @@ def build_forest(data): if not account_name: error_messages.append("Row {0}: Please enter Account Name".format(line_no)) + name = account_name if account_number: account_number = cstr(account_number).strip() account_name = "{} - {}".format(account_number, account_name) charts_map[account_name] = {} + charts_map[account_name]['account_name'] = name + if account_number: charts_map[account_name]["account_number"] = account_number if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group if account_type: charts_map[account_name]["account_type"] = account_type if root_type: charts_map[account_name]["root_type"] = root_type From d9d42b13ab70f4677a5e8ba45af1cbaf484dedc4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 16 Oct 2021 17:09:37 +0530 Subject: [PATCH 309/416] fix: Interstate internal transfer invoices not visible in GSTR-1 --- erpnext/regional/report/gstr_1/gstr_1.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 23924c5fb6..7d401bab66 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -172,13 +172,6 @@ class Gstr1Report(object): self.invoices = frappe._dict() conditions = self.get_conditions() - company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True) - - if company_gstins: - self.filters.update({ - 'company_gstins': company_gstins - }) - invoice_data = frappe.db.sql(""" select {select_columns} @@ -242,7 +235,7 @@ class Gstr1Report(object): elif self.filters.get("type_of_business") == "EXPORT": conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ - conditions += " AND IFNULL(billing_address_gstin, '') NOT IN %(company_gstins)s" + conditions += " AND IFNULL(billing_address_gstin, '') != company_gstin" return conditions From b7a08535b5b4e47f89a1ea642f8e71953f357cff Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 16 Oct 2021 20:58:24 +0530 Subject: [PATCH 310/416] fix: TDS round off not working from second transaction --- .../tax_withholding_category/tax_withholding_category.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index cc8ef58e20..c3cb8396d0 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -191,9 +191,6 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N tax_amount = get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total) else: tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0 - - if cint(tax_details.round_off_tax_amount): - tax_amount = round(tax_amount) else: tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers) @@ -206,6 +203,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N # then chargeable value is "prev invoices + advances" value which cross the threshold tax_amount = get_tcs_amount(parties, inv, tax_details, vouchers, advance_vouchers) + if cint(tax_details.round_off_tax_amount): + tax_amount = round(tax_amount) + return tax_amount, tax_deducted def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): @@ -325,9 +325,6 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): else: tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0 - if cint(tax_details.round_off_tax_amount): - tds_amount = round(tds_amount) - return tds_amount def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers): From ac381d21fe94b99406b4d7d332a6356a41bd8e26 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 18 Oct 2021 10:43:15 +0530 Subject: [PATCH 311/416] fix: sider --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0c3eff2a03..290c7fe60b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1997,8 +1997,7 @@ def get_mode_of_payments_info(mode_of_payments, company): mp.name in (%s) group by mp.name - """, - (company, mode_of_payments), as_dict=1) + """, (company, mode_of_payments), as_dict=1) return {row.get('mop'): row for row in data} From 8eacaddde73f29f8b9c6de43547a56f6be5a6687 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 18 Oct 2021 11:06:16 +0530 Subject: [PATCH 312/416] fix: flaky Org Chart Test (#27971) --- .../integration/test_organizational_chart_desktop.js | 2 +- .../integration/test_organizational_chart_mobile.js | 2 +- .../js/hierarchy_chart/hierarchy_chart_desktop.js | 10 ++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js index 79e08b3bba..464cce48d0 100644 --- a/cypress/integration/test_organizational_chart_desktop.js +++ b/cypress/integration/test_organizational_chart_desktop.js @@ -24,7 +24,7 @@ context('Organizational Chart', () => { cy.get('.frappe-control[data-fieldname=company] input').focus().as('input'); cy.get('@input') .clear({ force: true }) - .type('Test Org Chart{enter}', { force: true }) + .type('Test Org Chart{downarrow}{enter}', { force: true }) .blur({ force: true }); }); }); diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js index 161fae098a..971ac6d3ef 100644 --- a/cypress/integration/test_organizational_chart_mobile.js +++ b/cypress/integration/test_organizational_chart_mobile.js @@ -25,7 +25,7 @@ context('Organizational Chart Mobile', () => { cy.get('.frappe-control[data-fieldname=company] input').focus().as('input'); cy.get('@input') .clear({ force: true }) - .type('Test Org Chart{enter}', { force: true }) + .type('Test Org Chart{downarrow}{enter}', { force: true }) .blur({ force: true }); }); }); diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js index 7b358195c3..831626aa91 100644 --- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js +++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js @@ -334,10 +334,12 @@ erpnext.HierarchyChart = class { if (child_nodes) { $.each(child_nodes, (_i, data) => { - this.add_node(node, data); - setTimeout(() => { - this.add_connector(node.id, data.id); - }, 250); + if (!$(`[id="${data.id}"]`).length) { + this.add_node(node, data); + setTimeout(() => { + this.add_connector(node.id, data.id); + }, 250); + } }); } } From 1aa34d178051c9b1ec20bf45171bd12d6df4d151 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Mon, 18 Oct 2021 18:46:45 +0530 Subject: [PATCH 313/416] fix: incorrect VAT Amount in UAT VAT 201 report --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index f4c049d162..8507b0effb 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, sum(s.total_taxes_and_charges) + s.vat_emirate as emirate, sum(i.base_amount) as total, s.total_taxes_and_charges from `tabSales Invoice Item` i inner join `tabSales Invoice` s on From 7717b99edbae6a6d85c5582cf11c1a25eb9c3a5e Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 19 Oct 2021 05:49:40 +0100 Subject: [PATCH 314/416] feat: add enabled field in UOM (#27993) --- erpnext/setup/doctype/uom/uom.json | 126 +++++------------------------ 1 file changed, 22 insertions(+), 104 deletions(-) diff --git a/erpnext/setup/doctype/uom/uom.json b/erpnext/setup/doctype/uom/uom.json index 3a4e7f6dc4..844a11f139 100644 --- a/erpnext/setup/doctype/uom/uom.json +++ b/erpnext/setup/doctype/uom/uom.json @@ -1,164 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:uom_name", - "beta": 0, "creation": "2013-01-10 16:34:24", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "enabled", + "uom_name", + "must_be_whole_number" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "uom_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "UOM Name", - "length": 0, - "no_copy": 0, "oldfieldname": "uom_name", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "Check this to disallow fractions. (for Nos)", "fieldname": "must_be_whole_number", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Must be Whole Number", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Must be Whole Number" + }, + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-compass", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 06:35:56.143361", + "links": [], + "modified": "2021-10-18 14:07:43.722144", "modified_by": "Administrator", "module": "Setup", "name": "UOM", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Item Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file From af1b9e100e558cdb3b751d15666ec0bd65e5a1cb Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Mon, 18 Oct 2021 13:57:23 +0530 Subject: [PATCH 315/416] fix: changes in schedules gets overwritten on save --- .../doctype/maintenance_schedule/maintenance_schedule.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py index a1df9cfd0e..adb57f9f39 100644 --- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py +++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py @@ -199,12 +199,16 @@ class MaintenanceSchedule(TransactionBase): if chk: throw(_("Maintenance Schedule {0} exists against {1}").format(chk[0][0], d.sales_order)) + def validate_no_of_visits(self): + return len(self.schedules) != sum(d.no_of_visits for d in self.items) + def validate(self): self.validate_end_date_visits() self.validate_maintenance_detail() self.validate_dates_with_periodicity() self.validate_sales_order() - self.generate_schedule() + if not self.schedules or self.validate_no_of_visits(): + self.generate_schedule() def on_update(self): frappe.db.set(self, 'status', 'Draft') From 4c499e804a29ac6639de6df9cfd6704f338a58c3 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Tue, 19 Oct 2021 14:12:59 +0530 Subject: [PATCH 316/416] fix: wrong vat amount --- erpnext/regional/report/uae_vat_201/uae_vat_201.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 8507b0effb..2b5ecc3b18 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -122,7 +122,7 @@ def get_total_emiratewise(filters): try: return frappe.db.sql(""" select - s.vat_emirate as emirate, sum(i.base_amount) as total, s.total_taxes_and_charges + s.vat_emirate as emirate, sum(i.base_amount) as total, sum(i.tax_amount) from `tabSales Invoice Item` i inner join `tabSales Invoice` s on From 393749a61132ea3948b85ddf5fa491b16b100199 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 19 Oct 2021 14:05:44 +0530 Subject: [PATCH 317/416] fix: dont recompute item wise taxes from front end --- erpnext/public/js/controllers/taxes_and_totals.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 702064fe55..b5a6d8fdf6 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -137,7 +137,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var me = this; $.each(this.frm.doc["taxes"] || [], function(i, tax) { - tax.item_wise_tax_detail = {}; + if (!tax.dont_recompute_tax) { + tax.item_wise_tax_detail = {}; + } var tax_fields = ["total", "tax_amount_after_discount_amount", "tax_amount_for_current_item", "grand_total_for_current_item", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]; @@ -421,7 +423,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { current_tax_amount = tax_rate * item.qty; } - this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); + if (!tax.dont_recompute_tax) { + this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount); + } return current_tax_amount; } @@ -589,7 +593,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { delete tax[fieldname]; }); - tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); + if (!tax.dont_recompute_tax) { + tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail); + } }); } } From 9916b877676477d546d1afe15e56acc2cfdfbdb3 Mon Sep 17 00:00:00 2001 From: gavin Date: Tue, 19 Oct 2021 14:20:09 +0530 Subject: [PATCH 318/416] ci: Rule Added for using frappe.qb over db.sql* (#28000) ERPNext port of https://github.com/frappe/frappe/pull/14481 Co-authored-by: Ankush Menat Co-authored-by: abhishek --- .../helper/semgrep_rules/frappe_correctness.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml index 166e98a8a2..0cf4e78b81 100644 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ b/.github/helper/semgrep_rules/frappe_correctness.yml @@ -132,7 +132,6 @@ rules: languages: [python] severity: ERROR - - id: frappe-manual-commit patterns: - pattern: frappe.db.commit() @@ -149,3 +148,16 @@ rules: - "**/demo/**" languages: [python] severity: ERROR + +- id: frappe-using-db-sql + pattern-either: + - pattern: frappe.db.sql(...) + - pattern: frappe.db.sql_ddl(...) + - pattern: frappe.db.sql_list(...) + paths: + exclude: + - "test_*.py" + message: | + The PR contains a SQL query that may be re-written with frappe.qb (https://frappeframework.com/docs/user/en/api/query-builder) or the Database API (https://frappeframework.com/docs/user/en/api/database) + languages: [python] + severity: ERROR \ No newline at end of file From 21ba9ac744d2c7cf06d575e28c737b0ae6f03d2c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 19 Oct 2021 15:08:05 +0530 Subject: [PATCH 319/416] fix: Totals row incorrect value in GL Entry (#27867) (cherry picked from commit ebe68c1a7a47f5e711cc9fc1d40c07e140b76888) --- erpnext/accounts/report/general_ledger/general_ledger.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 5bd6e583db..0094bc2eeb 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -421,8 +421,6 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): update_value_in_dict(totals, 'closing', gle) elif gle.posting_date <= to_date: - update_value_in_dict(gle_map[gle.get(group_by)].totals, 'total', gle) - update_value_in_dict(totals, 'total', gle) if filters.get("group_by") != 'Group by Voucher (Consolidated)': gle_map[gle.get(group_by)].entries.append(gle) elif filters.get("group_by") == 'Group by Voucher (Consolidated)': @@ -436,10 +434,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map): else: update_value_in_dict(consolidated_gle, key, gle) - update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle) - update_value_in_dict(totals, 'closing', gle) - for key, value in consolidated_gle.items(): + update_value_in_dict(gle_map[value.get(group_by)].totals, 'total', value) + update_value_in_dict(totals, 'total', value) + update_value_in_dict(gle_map[value.get(group_by)].totals, 'closing', value) + update_value_in_dict(totals, 'closing', value) entries.append(value) return totals, entries From 73ae5047211504646e6b3a2afc067cdfaf5711e5 Mon Sep 17 00:00:00 2001 From: Chillar Anand Date: Tue, 19 Oct 2021 18:59:23 +0530 Subject: [PATCH 320/416] fix(healthcare): Add patch for removing healthcare doctypes (#27995) * fix(healthcare): Add patch for healthcare doctypes * fix: Update healthcare patch * fix: Reload stale doctypes * fix: Reload doc fix --- erpnext/patches.txt | 2 + .../v14_0/delete_healthcare_doctypes.py | 49 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 erpnext/patches/v14_0/delete_healthcare_doctypes.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index bd166928bc..351d729c82 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -307,3 +307,5 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts +erpnext.patches.v13_0.healthcare_deprecation_warning +erpnext.patches.v14_0.delete_healthcare_doctypes diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py new file mode 100644 index 0000000000..28fc01beab --- /dev/null +++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py @@ -0,0 +1,49 @@ +import frappe + + +def execute(): + if "healthcare" in frappe.get_installed_apps(): + return + + frappe.delete_doc("Workspace", "Healthcare", ignore_missing=True, force=True) + + pages = frappe.get_all("Page", {"module": "healthcare"}, pluck='name') + for page in pages: + frappe.delete_doc("Page", page, ignore_missing=True, force=True) + + reports = frappe.get_all("Report", {"module": "healthcare", "is_standard": "Yes"}, pluck='name') + for report in reports: + frappe.delete_doc("Report", report, ignore_missing=True, force=True) + + print_formats = frappe.get_all("Print Format", {"module": "healthcare", "standard": "Yes"}, pluck='name') + for print_format in print_formats: + frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True) + + frappe.reload_doc("website", "doctype", "website_settings") + forms = frappe.get_all("Web Form", {"module": "healthcare", "is_standard": 1}, pluck='name') + for form in forms: + frappe.delete_doc("Web Form", form, ignore_missing=True, force=True) + + dashboards = frappe.get_all("Dashboard", {"module": "healthcare", "is_standard": 1}, pluck='name') + for dashboard in dashboards: + frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True) + + dashboards = frappe.get_all("Dashboard Chart", {"module": "healthcare", "is_standard": 1}, pluck='name') + for dashboard in dashboards: + frappe.delete_doc("Dashboard Chart", dashboard, ignore_missing=True, force=True) + + frappe.reload_doc("desk", "doctype", "number_card") + cards = frappe.get_all("Number Card", {"module": "healthcare", "is_standard": 1}, pluck='name') + for card in cards: + frappe.delete_doc("Number Card", card, ignore_missing=True, force=True) + + titles = ['Lab Test', 'Prescription', 'Patient Appointment'] + items = frappe.get_all('Portal Menu Item', filters=[['title', 'in', titles]], pluck='name') + for item in items: + frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True) + + doctypes = frappe.get_all("DocType", {"module": "healthcare", "custom": 0}, pluck='name') + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + frappe.delete_doc("Module Def", "Healthcare", ignore_missing=True, force=True) From d81f81134955783d632b7d4005999da2092e82e7 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Wed, 20 Oct 2021 10:53:39 +0530 Subject: [PATCH 321/416] fix: map missing fields in opportunity (#27904) --- erpnext/crm/doctype/opportunity/opportunity.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index be843a3386..55e0efaab1 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -33,6 +33,7 @@ class Opportunity(TransactionBase): self.validate_item_details() self.validate_uom_is_integer("uom", "qty") self.validate_cust_name() + self.map_fields() if not self.title: self.title = self.customer_name @@ -43,6 +44,15 @@ class Opportunity(TransactionBase): else: self.calculate_totals() + def map_fields(self): + for field in self.meta.fields: + if not self.get(field.fieldname): + try: + value = frappe.db.get_value(self.opportunity_from, self.party_name, field.fieldname) + frappe.db.set(self, field.fieldname, value) + except Exception: + continue + def calculate_totals(self): total = base_total = 0 for item in self.get('items'): From 4f2cf43db703b83698289ac7a9d7132118d1b12e Mon Sep 17 00:00:00 2001 From: Govind S Menokee Date: Tue, 19 Oct 2021 12:49:00 +0530 Subject: [PATCH 322/416] YTD and MTD Messed up in Salary Slip The filter for YTD, MTD etc are based on employee name. This seems like an amateur mistake. It should be based on employee id. (cherry picked from commit efc292a5ddc00e433d75b87d6b3378455bac4438) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index d113e7e569..86a0807f93 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1244,7 +1244,7 @@ class SalarySlip(TransactionBase): salary_slip_sum = frappe.get_list('Salary Slip', fields = ['sum(net_pay) as net_sum', 'sum(gross_pay) as gross_sum'], - filters = {'employee_name' : self.employee_name, + filters = {'employee' : self.employee, 'start_date' : ['>=', period_start_date], 'end_date' : ['<', period_end_date], 'name': ['!=', self.name], @@ -1264,7 +1264,7 @@ class SalarySlip(TransactionBase): first_day_of_the_month = get_first_day(self.start_date) salary_slip_sum = frappe.get_list('Salary Slip', fields = ['sum(net_pay) as sum'], - filters = {'employee_name' : self.employee_name, + filters = {'employee' : self.employee, 'start_date' : ['>=', first_day_of_the_month], 'end_date' : ['<', self.start_date], 'name': ['!=', self.name], @@ -1288,13 +1288,13 @@ class SalarySlip(TransactionBase): INNER JOIN `tabSalary Slip` as salary_slip ON detail.parent = salary_slip.name WHERE - salary_slip.employee_name = %(employee_name)s + salary_slip.employee = %(employee)s AND detail.salary_component = %(component)s AND salary_slip.start_date >= %(period_start_date)s AND salary_slip.end_date < %(period_end_date)s AND salary_slip.name != %(docname)s AND salary_slip.docstatus = 1""", - {'employee_name': self.employee_name, 'component': component.salary_component, 'period_start_date': period_start_date, + {'employee': self.employee, 'component': component.salary_component, 'period_start_date': period_start_date, 'period_end_date': period_end_date, 'docname': self.name} ) From 2ef4844a3ca56d261cbc61085fd030d969e62b28 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 20 Oct 2021 15:50:30 +0530 Subject: [PATCH 323/416] feat: Tax for recurring additional salary (#27459) * fix: Logic for tax calculation on recurring additional salary * fix: Get actual amount always in case of overwritten additional salary even if based on payment days * feat: Test case added for recurring additional salary * fix: use query builder to get additional salaries instead of raw SQL * fix: query formatting and remove trailing spaces Co-authored-by: Rucha Mahabal --- erpnext/hr/doctype/employee/test_employee.py | 1 + .../additional_salary/additional_salary.py | 41 +++++------ .../doctype/salary_detail/salary_detail.json | 13 +++- .../doctype/salary_slip/salary_slip.py | 51 ++++++++++---- .../doctype/salary_slip/test_salary_slip.py | 69 +++++++++++++++++++ 5 files changed, 138 insertions(+), 37 deletions(-) diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 8d6dfa2c1d..8a2da0866e 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -55,6 +55,7 @@ def make_employee(user, company=None, **kwargs): "email": user, "first_name": user, "new_password": "password", + "send_welcome_email": 0, "roles": [{"doctype": "Has Role", "role": "Employee"}] }).insert() diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py index 7c0a8eac99..b6377f4006 100644 --- a/erpnext/payroll/doctype/additional_salary/additional_salary.py +++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py @@ -125,27 +125,28 @@ class AdditionalSalary(Document): no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1 return amount_per_day * no_of_days +@frappe.whitelist() def get_additional_salaries(employee, start_date, end_date, component_type): - additional_salary_list = frappe.db.sql(""" - select name, salary_component as component, type, amount, - overwrite_salary_structure_amount as overwrite, - deduct_full_tax_on_selected_payroll_date - from `tabAdditional Salary` - where employee=%(employee)s - and docstatus = 1 - and ( - payroll_date between %(from_date)s and %(to_date)s - or - from_date <= %(to_date)s and to_date >= %(to_date)s - ) - and type = %(component_type)s - order by salary_component, overwrite ASC - """, { - 'employee': employee, - 'from_date': start_date, - 'to_date': end_date, - 'component_type': "Earning" if component_type == "earnings" else "Deduction" - }, as_dict=1) + comp_type = 'Earning' if component_type == 'earnings' else 'Deduction' + + additional_sal = frappe.qb.DocType('Additional Salary') + component_field = additional_sal.salary_component.as_('component') + overwrite_field = additional_sal.overwrite_salary_structure_amount.as_('overwrite') + + additional_salary_list = frappe.qb.from_( + additional_sal + ).select( + additional_sal.name, component_field, additional_sal.type, + additional_sal.amount, additional_sal.is_recurring, overwrite_field, + additional_sal.deduct_full_tax_on_selected_payroll_date + ).where( + (additional_sal.employee == employee) + & (additional_sal.docstatus == 1) + & (additional_sal.type == comp_type) + ).where( + additional_sal.payroll_date[start_date: end_date] + | ((additional_sal.from_date <= end_date) & (additional_sal.to_date >= end_date)) + ).run(as_dict=True) additional_salaries = [] components_to_overwrite = [] diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index 393f647cc8..665f0a8297 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -12,6 +12,7 @@ "year_to_date", "section_break_5", "additional_salary", + "is_recurring_additional_salary", "statistical_component", "depends_on_payment_days", "exempted_from_income_tax", @@ -235,11 +236,19 @@ "label": "Year To Date", "options": "currency", "read_only": 1 - } + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.additional_salary", + "fieldname": "is_recurring_additional_salary", + "fieldtype": "Check", + "label": "Is Recurring Additional Salary", + "read_only": 1 + } ], "istable": 1, "links": [], - "modified": "2021-01-14 13:39:15.847158", + "modified": "2021-08-30 13:39:15.847158", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 86a0807f93..3bc709ea86 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -630,7 +630,8 @@ class SalarySlip(TransactionBase): get_salary_component_data(additional_salary.component), additional_salary.amount, component_type, - additional_salary + additional_salary, + is_recurring = additional_salary.is_recurring ) def add_tax_components(self, payroll_period): @@ -651,7 +652,7 @@ class SalarySlip(TransactionBase): tax_row = get_salary_component_data(d) self.update_component_row(tax_row, tax_amount, "deductions") - def update_component_row(self, component_data, amount, component_type, additional_salary=None): + def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0): component_row = None for d in self.get(component_type): if d.salary_component != component_data.salary_component: @@ -698,6 +699,8 @@ class SalarySlip(TransactionBase): else: component_row.default_amount = 0 component_row.additional_amount = amount + + component_row.is_recurring_additional_salary = is_recurring component_row.additional_salary = additional_salary.name component_row.deduct_full_tax_on_selected_payroll_date = \ additional_salary.deduct_full_tax_on_selected_payroll_date @@ -894,25 +897,33 @@ class SalarySlip(TransactionBase): amount, additional_amount = earning.default_amount, earning.additional_amount if earning.is_tax_applicable: - if additional_amount: - taxable_earnings += (amount - additional_amount) - additional_income += additional_amount - if earning.deduct_full_tax_on_selected_payroll_date: - additional_income_with_full_tax += additional_amount - continue - if earning.is_flexible_benefit: flexi_benefits += amount else: - taxable_earnings += amount + taxable_earnings += (amount - additional_amount) + additional_income += additional_amount + + # Get additional amount based on future recurring additional salary + if additional_amount and earning.is_recurring_additional_salary: + additional_income += self.get_future_recurring_additional_amount(earning.additional_salary, + earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month + + if earning.deduct_full_tax_on_selected_payroll_date: + additional_income_with_full_tax += additional_amount if allow_tax_exemption: for ded in self.deductions: if ded.exempted_from_income_tax: - amount = ded.amount + amount, additional_amount = ded.amount, ded.additional_amount if based_on_payment_days: - amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date)[0] - taxable_earnings -= flt(amount) + amount, additional_amount = self.get_amount_based_on_payment_days(ded, joining_date, relieving_date) + + taxable_earnings -= flt(amount - additional_amount) + additional_income -= additional_amount + + if additional_amount and ded.is_recurring_additional_salary: + additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary, + ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month return frappe._dict({ "taxable_earnings": taxable_earnings, @@ -921,11 +932,21 @@ class SalarySlip(TransactionBase): "flexi_benefits": flexi_benefits }) + def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount): + future_recurring_additional_amount = 0 + to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date') + # future month count excluding current + future_recurring_period = (getdate(to_date).month - getdate(self.start_date).month) + if future_recurring_period > 0: + future_recurring_additional_amount = monthly_additional_amount * future_recurring_period # Used earning.additional_amount to consider the amount for the full month + return future_recurring_additional_amount + def get_amount_based_on_payment_days(self, row, joining_date, relieving_date): amount, additional_amount = row.amount, row.additional_amount if (self.salary_structure and - cint(row.depends_on_payment_days) and cint(self.total_working_days) and - (not self.salary_slip_based_on_timesheet or + cint(row.depends_on_payment_days) and cint(self.total_working_days) + and not (row.additional_salary and row.default_amount) # to identify overwritten additional salary + and (not self.salary_slip_based_on_timesheet or getdate(self.start_date) < joining_date or (relieving_date and getdate(self.end_date) > relieving_date) )): diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 178cd5c9d0..c4b6a38c4e 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -536,6 +536,61 @@ class TestSalarySlip(unittest.TestCase): # undelete fixture data frappe.db.rollback() + def test_tax_for_recurring_additional_salary(self): + frappe.db.sql("""delete from `tabPayroll Period`""") + frappe.db.sql("""delete from `tabSalary Component`""") + + payroll_period = create_payroll_period() + + create_tax_slab(payroll_period, allow_tax_exemption=True) + + employee = make_employee("test_tax@salary.slip") + delete_docs = [ + "Salary Slip", + "Additional Salary", + "Employee Tax Exemption Declaration", + "Employee Tax Exemption Proof Submission", + "Employee Benefit Claim", + "Salary Structure Assignment" + ] + for doc in delete_docs: + frappe.db.sql("delete from `tab%s` where employee='%s'" % (doc, employee)) + + from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure + + salary_structure = make_salary_structure("Stucture to test tax", "Monthly", + other_details={"max_benefits": 100000}, test_tax=True, + employee=employee, payroll_period=payroll_period) + + + create_salary_slips_for_payroll_period(employee, salary_structure.name, + payroll_period, deduct_random=False, num=3) + + tax_paid = get_tax_paid_in_period(employee) + + annual_tax = 23196.0 + self.assertEqual(tax_paid, annual_tax) + + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + #------------------------------------ + # Recurring additional salary + start_date = add_months(payroll_period.start_date, 3) + end_date = add_months(payroll_period.start_date, 5) + create_recurring_additional_salary(employee, "Performance Bonus", 20000, start_date, end_date) + + frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) + + create_salary_slips_for_payroll_period(employee, salary_structure.name, + payroll_period, deduct_random=False, num=4) + + tax_paid = get_tax_paid_in_period(employee) + + annual_tax = 32315.0 + self.assertEqual(tax_paid, annual_tax) + + frappe.db.rollback() + def make_activity_for_employee(self): activity_type = frappe.get_doc("Activity Type", "_Test Activity Type") activity_type.billing_rate = 50 @@ -1007,3 +1062,17 @@ def make_salary_slip_for_payment_days_dependency_test(employee, salary_structure salary_slip = frappe.get_doc("Salary Slip", salary_slip_name) return salary_slip + +def create_recurring_additional_salary(employee, salary_component, amount, from_date, to_date, company=None): + frappe.get_doc({ + "doctype": "Additional Salary", + "employee": employee, + "company": company or erpnext.get_default_company(), + "salary_component": salary_component, + "is_recurring": 1, + "from_date": from_date, + "to_date": to_date, + "amount": amount, + "type": "Earning", + "currency": erpnext.get_default_currency() + }).submit() From 261f80c5ca65c6abda1620717df54d3fc4548858 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 20 Oct 2021 16:58:32 +0530 Subject: [PATCH 324/416] fix: avoid resetting employee on amending timesheets (#28025) --- erpnext/projects/doctype/timesheet/timesheet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 1655b76b98..65a8566a27 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -32,12 +32,12 @@ frappe.ui.form.on("Timesheet", { }; }, - onload: function(frm){ + onload: function(frm) { if (frm.doc.__islocal && frm.doc.time_logs) { calculate_time_and_amount(frm); } - if (frm.is_new()) { + if (frm.is_new() && !frm.doc.employee) { set_employee_and_company(frm); } }, From 92c0dcc3eafab2bf7521548a4a8fa4b1f03a4566 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 14:43:39 +0530 Subject: [PATCH 325/416] ci: install dev requirements in CI --- .github/helper/install.sh | 3 +++ .github/workflows/server-tests.yml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/.github/helper/install.sh b/.github/helper/install.sh index ac623e947d..85f146d351 100644 --- a/.github/helper/install.sh +++ b/.github/helper/install.sh @@ -37,6 +37,9 @@ sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile bench get-app erpnext "${GITHUB_WORKSPACE}" + +if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi + bench start &> bench_run_logs.txt & bench --site test_site reinstall --yes bench build --app frappe diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index 4f84b860af..77c0aee195 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -91,6 +91,8 @@ jobs: - name: Install run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh + env: + TYPE: server - name: Run Tests run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage From 5ada11b1afa4113c26f02fb9c5ae529f60beefad Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 14:53:49 +0530 Subject: [PATCH 326/416] ci: run semgrep after basic linters --- .github/workflows/linters.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 16e490a460..9389eaabaa 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -10,13 +10,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: returntocorp/semgrep-action@v1 - env: - SEMGREP_TIMEOUT: 120 - with: - config: >- - r/python.lang.correctness - .github/helper/semgrep_rules - name: Set up Python 3.8 uses: actions/setup-python@v2 @@ -25,3 +18,11 @@ jobs: - name: Install and Run Pre-commit uses: pre-commit/action@v2.0.3 + + - uses: returntocorp/semgrep-action@v1 + env: + SEMGREP_TIMEOUT: 120 + with: + config: >- + r/python.lang.correctness + .github/helper/semgrep_rules From 8d9d0987fe67689696742cad3898870d085f24f2 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Wed, 20 Oct 2021 19:15:35 +0530 Subject: [PATCH 327/416] fix: incorrect status being set in Invoices (#28019) Co-authored-by: Pruthvi Patel --- .../purchase_invoice/purchase_invoice.py | 6 +- .../doctype/sales_invoice/sales_invoice.py | 50 +++++--- erpnext/controllers/accounts_controller.py | 57 +++++++-- erpnext/patches.txt | 1 + erpnext/patches/v13_0/fix_invoice_statuses.py | 113 ++++++++++++++++++ 5 files changed, 200 insertions(+), 27 deletions(-) create mode 100644 erpnext/patches/v13_0/fix_invoice_statuses.py diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1c9943fd22..508f728b72 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -15,6 +15,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( check_if_return_invoice_linked_with_payment_entry, + get_total_in_party_account_currency, is_overdue, unlink_inter_company_doc, update_linked_doc, @@ -1183,6 +1184,7 @@ class PurchaseInvoice(BuyingController): return outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) + total = get_total_in_party_account_currency(self) if not status: if self.docstatus == 2: @@ -1190,9 +1192,9 @@ class PurchaseInvoice(BuyingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif is_overdue(self): + elif is_overdue(self, total): self.status = "Overdue" - elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + elif 0 < outstanding_amount < total: self.status = "Partly Paid" elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index dafae3128a..40ad7b7b5c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1427,6 +1427,7 @@ class SalesInvoice(SellingController): return outstanding_amount = flt(self.outstanding_amount, self.precision("outstanding_amount")) + total = get_total_in_party_account_currency(self) if not status: if self.docstatus == 2: @@ -1434,9 +1435,9 @@ class SalesInvoice(SellingController): elif self.docstatus == 1: if self.is_internal_transfer(): self.status = 'Internal Transfer' - elif is_overdue(self): + elif is_overdue(self, total): self.status = "Overdue" - elif 0 < outstanding_amount < flt(self.grand_total, self.precision("grand_total")): + elif 0 < outstanding_amount < total: self.status = "Partly Paid" elif outstanding_amount > 0 and getdate(self.due_date) >= getdate(): self.status = "Unpaid" @@ -1463,27 +1464,42 @@ class SalesInvoice(SellingController): if update: self.db_set('status', self.status, update_modified = update_modified) -def is_overdue(doc): - outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount")) +def get_total_in_party_account_currency(doc): + total_fieldname = ( + "grand_total" + if doc.disable_rounded_total + else "rounded_total" + ) + if doc.party_account_currency != doc.currency: + total_fieldname = "base_" + total_fieldname + + return flt(doc.get(total_fieldname), doc.precision(total_fieldname)) + +def is_overdue(doc, total): + outstanding_amount = flt(doc.outstanding_amount, doc.precision("outstanding_amount")) if outstanding_amount <= 0: return - grand_total = flt(doc.grand_total, doc.precision("grand_total")) - nowdate = getdate() - if doc.payment_schedule: - # calculate payable amount till date - payable_amount = sum( - payment.payment_amount - for payment in doc.payment_schedule - if getdate(payment.due_date) < nowdate - ) + today = getdate() + if doc.get('is_pos') or not doc.get('payment_schedule'): + return getdate(doc.due_date) < today - if (grand_total - outstanding_amount) < payable_amount: - return True + # calculate payable amount till date + payment_amount_field = ( + "base_payment_amount" + if doc.party_account_currency != doc.currency + else "payment_amount" + ) + + payable_amount = sum( + payment.get(payment_amount_field) + for payment in doc.payment_schedule + if getdate(payment.due_date) < today + ) + + return (total - outstanding_amount) < payable_amount - elif getdate(doc.due_date) < nowdate: - return True def get_discounting_status(sales_invoice): status = None diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index e9b531ecb8..88c439b4f6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1686,17 +1686,58 @@ def get_advance_payment_entries(party_type, party, party_account, order_doctype, def update_invoice_status(): """Updates status as Overdue for applicable invoices. Runs daily.""" + today = getdate() for doctype in ("Sales Invoice", "Purchase Invoice"): frappe.db.sql(""" - update `tab{}` as dt set dt.status = 'Overdue' - where dt.docstatus = 1 - and dt.status != 'Overdue' - and dt.outstanding_amount > 0 - and (dt.grand_total - dt.outstanding_amount) < - (select sum(payment_amount) from `tabPayment Schedule` as ps - where ps.parent = dt.name and ps.due_date < %s) - """.format(doctype), getdate()) + UPDATE `tab{doctype}` invoice SET invoice.status = 'Overdue' + WHERE invoice.docstatus = 1 + AND invoice.status REGEXP '^Unpaid|^Partly Paid' + AND invoice.outstanding_amount > 0 + AND ( + {or_condition} + ( + ( + CASE + WHEN invoice.party_account_currency = invoice.currency + THEN ( + CASE + WHEN invoice.disable_rounded_total + THEN invoice.grand_total + ELSE invoice.rounded_total + END + ) + ELSE ( + CASE + WHEN invoice.disable_rounded_total + THEN invoice.base_grand_total + ELSE invoice.base_rounded_total + END + ) + END + ) - invoice.outstanding_amount + ) < ( + SELECT SUM( + CASE + WHEN invoice.party_account_currency = invoice.currency + THEN ps.payment_amount + ELSE ps.base_payment_amount + END + ) + FROM `tabPayment Schedule` ps + WHERE ps.parent = invoice.name + AND ps.due_date < %(today)s + ) + ) + """.format( + doctype=doctype, + or_condition=( + "invoice.is_pos AND invoice.due_date < %(today)s OR" + if doctype == "Sales Invoice" + else "" + ) + ), {"today": today} + ) @frappe.whitelist() def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 351d729c82..e446d6be42 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -294,6 +294,7 @@ erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.create_gst_payment_entry_fields erpnext.patches.v14_0.delete_shopify_doctypes +erpnext.patches.v13_0.fix_invoice_statuses erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.update_dates_in_tax_withholding_category erpnext.patches.v14_0.update_opportunity_currency_fields diff --git a/erpnext/patches/v13_0/fix_invoice_statuses.py b/erpnext/patches/v13_0/fix_invoice_statuses.py new file mode 100644 index 0000000000..4395757159 --- /dev/null +++ b/erpnext/patches/v13_0/fix_invoice_statuses.py @@ -0,0 +1,113 @@ +import frappe +from frappe.utils import flt, getdate + +from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + get_total_in_party_account_currency, + is_overdue, +) + +TODAY = getdate() + +def execute(): + # This fix is not related to Party Specific Item, + # but it is needed for code introduced after Party Specific Item was + # If your DB doesn't have this doctype yet, you should be fine + if not frappe.db.exists("DocType", "Party Specific Item"): + return + + for doctype in ("Purchase Invoice", "Sales Invoice"): + fields = [ + "name", + "status", + "due_date", + "outstanding_amount", + "grand_total", + "base_grand_total", + "rounded_total", + "base_rounded_total", + "disable_rounded_total", + ] + if doctype == "Sales Invoice": + fields.append("is_pos") + + invoices_to_update = frappe.get_all( + doctype, + fields=fields, + filters={ + "docstatus": 1, + "status": ("in", ( + "Overdue", + "Overdue and Discounted", + "Partly Paid", + "Partly Paid and Discounted" + )), + "outstanding_amount": (">", 0), + "modified": (">", "2021-01-01") + # an assumption is being made that only invoices modified + # after 2021 got affected as incorrectly overdue. + # required for performance reasons. + } + ) + + invoices_to_update = { + invoice.name: invoice for invoice in invoices_to_update + } + + payment_schedule_items = frappe.get_all( + "Payment Schedule", + fields=( + "due_date", + "payment_amount", + "base_payment_amount", + "parent" + ), + filters={"parent": ("in", invoices_to_update)} + ) + + for item in payment_schedule_items: + invoices_to_update[item.parent].setdefault( + "payment_schedule", [] + ).append(item) + + status_map = {} + + for invoice in invoices_to_update.values(): + invoice.doctype = doctype + doc = frappe.get_doc(invoice) + correct_status = get_correct_status(doc) + if not correct_status or doc.status == correct_status: + continue + + status_map.setdefault(correct_status, []).append(doc.name) + + for status, docs in status_map.items(): + frappe.db.set_value( + doctype, {"name": ("in", docs)}, + "status", + status, + update_modified=False + ) + + + +def get_correct_status(doc): + outstanding_amount = flt( + doc.outstanding_amount, doc.precision("outstanding_amount") + ) + total = get_total_in_party_account_currency(doc) + + status = "" + if is_overdue(doc, total): + status = "Overdue" + elif 0 < outstanding_amount < total: + status = "Partly Paid" + elif outstanding_amount > 0 and getdate(doc.due_date) >= TODAY: + status = "Unpaid" + + if not status: + return + + if doc.status.endswith(" and Discounted"): + status += " and Discounted" + + return status From 8221e7e01ff2fda65953c2454be0aa9fde3b8ac1 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 19:29:31 +0530 Subject: [PATCH 328/416] fix: remove employee_name from job card summary This field doesn't exist and it's moved on individual line level logs. --- .../report/job_card_summary/job_card_summary.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index a7aec315ff..0d5181e95b 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -24,7 +24,7 @@ def get_data(filters): } fields = ["name", "status", "work_order", "production_item", "item_name", "posting_date", - "total_completed_qty", "workstation", "operation", "employee_name", "total_time_in_mins"] + "total_completed_qty", "workstation", "operation", "total_time_in_mins"] for field in ["work_order", "workstation", "operation", "company"]: if filters.get(field): @@ -172,12 +172,6 @@ def get_columns(filters): "options": "Operation", "width": 110 }, - { - "label": _("Employee Name"), - "fieldname": "employee_name", - "fieldtype": "Data", - "width": 110 - }, { "label": _("Total Completed Qty"), "fieldname": "total_completed_qty", From 126ba1674089609adacafaa9a4a380361fdd3a50 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 19:42:37 +0530 Subject: [PATCH 329/416] fix: remove debug from query --- .../manufacturing/report/job_card_summary/job_card_summary.py | 2 +- erpnext/stock/report/process_loss_report/process_loss_report.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 0d5181e95b..74bd685b79 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -45,7 +45,7 @@ def get_data(filters): job_card_time_details = {} for job_card_data in frappe.get_all("Job Card Time Log", fields=["min(from_time) as from_time", "max(to_time) as to_time", "parent"], - filters=job_card_time_filter, group_by="parent", debug=1): + filters=job_card_time_filter, group_by="parent"): job_card_time_details[job_card_data.parent] = job_card_data res = [] diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/stock/report/process_loss_report/process_loss_report.py index 499c49f893..9b544dafa5 100644 --- a/erpnext/stock/report/process_loss_report/process_loss_report.py +++ b/erpnext/stock/report/process_loss_report/process_loss_report.py @@ -111,7 +111,7 @@ def run_query(query_args: QueryArgs) -> Data: {work_order_filter} GROUP BY se.work_order - """.format(**query_args), query_args, as_dict=1, debug=1) + """.format(**query_args), query_args, as_dict=1) def update_data_with_total_pl_value(data: Data) -> None: for row in data: From da3635b94f950d7498a259a4babb4b27b3865b04 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Oct 2021 19:42:56 +0530 Subject: [PATCH 330/416] test: execute manufacturing reports --- erpnext/manufacturing/report/test_reports.py | 63 ++++++++++++++++++++ erpnext/stock/report/test_reports.py | 1 + 2 files changed, 64 insertions(+) create mode 100644 erpnext/manufacturing/report/test_reports.py diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py new file mode 100644 index 0000000000..fa7f91a81b --- /dev/null +++ b/erpnext/manufacturing/report/test_reports.py @@ -0,0 +1,63 @@ +import unittest +from typing import List, Tuple + +import frappe + +from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report + +DEFAULT_FILTERS = { + "company": "_Test Company", + "from_date": "2010-01-01", + "to_date": "2030-01-01", + "warehouse": "_Test Warehouse - _TC", +} + + +REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ + ("BOM Explorer", {"bom": frappe.get_last_doc("BOM").name}), + ("BOM Operations Time", {}), + ("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}), + ("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}), + ("Cost of Poor Quality Report", {}), + ("Downtime Analysis", {}), + ( + "Exponential Smoothing Forecasting", + { + "based_on_document": "Sales Order", + "based_on_field": "Qty", + "no_of_years": 3, + "periodicity": "Yearly", + "smoothing_constant": 0.3, + }, + ), + ("Job Card Summary", {"fiscal_year": "2021-2022"}), + ("Production Analytics", {"range": "Monthly"}), + ("Quality Inspection Summary", {}), + ("Work Order Stock Report", {}), + ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), +] + + +if frappe.db.a_row_exists("Production Plan"): + REPORT_FILTER_TEST_CASES.append( + ("Production Plan Summary", {"production_plan": frappe.get_last_doc("Production Plan").name}) + ) + +OPTIONAL_FILTERS = { + "warehouse": "_Test Warehouse - _TC", + "item": "_Test Item", + "item_group": "_Test Item Group", +} + + +class TestManufacturingReports(unittest.TestCase): + def test_execute_all_manufacturing_reports(self): + """Test that all script report in manufacturing modules are executable with supported filters""" + for report, filter in REPORT_FILTER_TEST_CASES: + execute_script_report( + report_name=report, + module="Manufacturing", + filters=filter, + default_filters=DEFAULT_FILTERS, + optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, + ) diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index d7fb5b2bf3..9802f600ad 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -40,6 +40,7 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Item Variant Details", {"item": "_Test Variant Item",}), ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), + ("Process Loss Report", {}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), ] From 311bafb23bdfdb38ccdd82d3d3c95ba7dfed809c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 20 Oct 2021 20:28:29 +0530 Subject: [PATCH 331/416] fix: incorrect fieldname --- .../manufacturing/doctype/production_plan/production_plan.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index b9efe9b41e..7e6fc3c4a6 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -424,7 +424,7 @@ class ProductionPlan(Document): po = frappe.new_doc('Purchase Order') po.supplier = supplier po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate() - po.is_subcontracted_item = 'Yes' + po.is_subcontracted = 'Yes' for row in po_list: args = { 'item_code': row.production_item, From 871cb1157fe2ff10e8027e86cc37400fa47688ce Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 20 Oct 2021 21:08:15 +0530 Subject: [PATCH 332/416] fix: consolidated report issue #28035 fix: consolidated report issue --- .../consolidated_financial_statement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index a600ead9e5..0de2a9854d 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -115,7 +115,7 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, # opening_value = Aseet - liability - equity for data in [asset_data, liability_data, equity_data]: account_name = get_root_account_name(data[0].root_type, company) - opening_value += get_opening_balance(account_name, data, company) + opening_value += (get_opening_balance(account_name, data, company) or 0.0) opening_balance[company] = opening_value From 03bfc7794036663261b5e136c9bdf2be53d0b247 Mon Sep 17 00:00:00 2001 From: Mohammed Yusuf Shaikh <49878143+mohammedyusufshaikh@users.noreply.github.com> Date: Thu, 21 Oct 2021 10:15:09 +0530 Subject: [PATCH 333/416] feat: employee initial work history updated when transfer is performed (#27768) * feat: employee initial work history updated when transfer is performed * fix: sider * fix: remove commit statement * fix: tests and code formatting * fix: tests Co-authored-by: Rucha Mahabal --- .../employee_promotion/employee_promotion.py | 6 +- .../employee_transfer/employee_transfer.py | 8 +- .../test_employee_transfer.py | 82 ++++++++++++++++++- erpnext/hr/utils.py | 34 +++++++- 4 files changed, 121 insertions(+), 9 deletions(-) diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py index 164d48b895..b05175200e 100644 --- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py +++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee, validate_active_employee +from erpnext.hr.utils import update_employee_work_history, validate_active_employee class EmployeePromotion(Document): @@ -23,10 +23,10 @@ class EmployeePromotion(Document): def on_submit(self): employee = frappe.get_doc("Employee", self.employee) - employee = update_employee(employee, self.promotion_details, date=self.promotion_date) + employee = update_employee_work_history(employee, self.promotion_details, date=self.promotion_date) employee.save() def on_cancel(self): employee = frappe.get_doc("Employee", self.employee) - employee = update_employee(employee, self.promotion_details, cancel=True) + employee = update_employee_work_history(employee, self.promotion_details, cancel=True) employee.save() diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py index b1f66098f0..29d93f348c 100644 --- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.document import Document from frappe.utils import getdate -from erpnext.hr.utils import update_employee +from erpnext.hr.utils import update_employee_work_history class EmployeeTransfer(Document): @@ -24,7 +24,7 @@ class EmployeeTransfer(Document): new_employee = frappe.copy_doc(employee) new_employee.name = None new_employee.employee_number = None - new_employee = update_employee(new_employee, self.transfer_details, date=self.transfer_date) + new_employee = update_employee_work_history(new_employee, self.transfer_details, date=self.transfer_date) if self.new_company and self.company != self.new_company: new_employee.internal_work_history = [] new_employee.date_of_joining = self.transfer_date @@ -39,7 +39,7 @@ class EmployeeTransfer(Document): employee.db_set("relieving_date", self.transfer_date) employee.db_set("status", "Left") else: - employee = update_employee(employee, self.transfer_details, date=self.transfer_date) + employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date) if self.new_company and self.company != self.new_company: employee.company = self.new_company employee.date_of_joining = self.transfer_date @@ -56,7 +56,7 @@ class EmployeeTransfer(Document): employee.status = "Active" employee.relieving_date = '' else: - employee = update_employee(employee, self.transfer_details, cancel=True) + employee = update_employee_work_history(employee, self.transfer_details, date=self.transfer_date, cancel=True) if self.new_company != self.company: employee.company = self.company employee.save() diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py index ad2f3ade05..c0440d09e7 100644 --- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py +++ b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import unittest +from datetime import date import frappe from frappe.utils import add_days, getdate @@ -15,7 +16,12 @@ class TestEmployeeTransfer(unittest.TestCase): def setUp(self): make_employee("employee2@transfers.com") make_employee("employee3@transfers.com") - frappe.db.sql("""delete from `tabEmployee Transfer`""") + create_company() + create_employee() + create_employee_transfer() + + def tearDown(self): + frappe.db.rollback() def test_submit_before_transfer_date(self): transfer_obj = frappe.get_doc({ @@ -57,3 +63,77 @@ class TestEmployeeTransfer(unittest.TestCase): self.assertTrue(transfer.new_employee_id) self.assertEqual(frappe.get_value("Employee", transfer.new_employee_id, "status"), "Active") self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left") + + def test_employee_history(self): + name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name") + doc = frappe.get_doc("Employee",name) + count = 0 + department = ["Accounts - TC", "Management - TC"] + designation = ["Accountant", "Manager"] + dt = [getdate("01-10-2021"), date.today()] + + for data in doc.internal_work_history: + self.assertEqual(data.department, department[count]) + self.assertEqual(data.designation, designation[count]) + self.assertEqual(data.from_date, dt[count]) + count = count + 1 + + data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"]) + doc = frappe.get_doc("Employee Transfer", data[0]["name"]) + doc.cancel() + employee_doc = frappe.get_doc("Employee",name) + + for data in employee_doc.internal_work_history: + self.assertEqual(data.designation, designation[0]) + self.assertEqual(data.department, department[0]) + self.assertEqual(data.from_date, dt[0]) + +def create_employee(): + doc = frappe.get_doc({ + "doctype": "Employee", + "first_name": "John", + "company": "Test Company", + "gender": "Male", + "date_of_birth": getdate("30-09-1980"), + "date_of_joining": getdate("01-10-2021"), + "department": "Accounts - TC", + "designation": "Accountant" + }) + + doc.save() + +def create_company(): + exists = frappe.db.exists("Company", "Test Company") + if not exists: + doc = frappe.get_doc({ + "doctype": "Company", + "company_name": "Test Company", + "default_currency": "INR", + "country": "India" + }) + + doc.save() + +def create_employee_transfer(): + doc = frappe.get_doc({ + "doctype": "Employee Transfer", + "employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"), + "transfer_date": date.today(), + "transfer_details": [ + { + "property": "Designation", + "current": "Accountant", + "new": "Manager", + "fieldname": "designation" + }, + { + "property": "Department", + "current": "Accounts - TC", + "new": "Management - TC", + "fieldname": "department" + } + ] + }) + + doc.save() + doc.submit() \ No newline at end of file diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index b6f4cadcc9..0febce1610 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -29,7 +29,15 @@ def set_employee_name(doc): if doc.employee and not doc.employee_name: doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name") -def update_employee(employee, details, date=None, cancel=False): +def update_employee_work_history(employee, details, date=None, cancel=False): + if not employee.internal_work_history and not cancel: + employee.append("internal_work_history", { + "branch": employee.branch, + "designation": employee.designation, + "department": employee.department, + "from_date": employee.date_of_joining + }) + internal_work_history = {} for item in details: field = frappe.get_meta("Employee").get_field(item.fieldname) @@ -44,11 +52,35 @@ def update_employee(employee, details, date=None, cancel=False): setattr(employee, item.fieldname, new_data) if item.fieldname in ["department", "designation", "branch"]: internal_work_history[item.fieldname] = item.new + if internal_work_history and not cancel: internal_work_history["from_date"] = date employee.append("internal_work_history", internal_work_history) + + if cancel: + delete_employee_work_history(details, employee, date) + return employee +def delete_employee_work_history(details, employee, date): + filters = {} + for d in details: + for history in employee.internal_work_history: + if d.property == "Department" and history.department == d.new: + department = d.new + filters["department"] = department + if d.property == "Designation" and history.designation == d.new: + designation = d.new + filters["designation"] = designation + if d.property == "Branch" and history.branch == d.new: + branch = d.new + filters["branch"] = branch + if date and date == history.from_date: + filters["from_date"] = date + if filters: + frappe.db.delete("Employee Internal Work History", filters) + + @frappe.whitelist() def get_employee_fields_label(): fields = [] From 1a31d5856fab7a0256609ae8164ef2f71fefa99f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 21 Oct 2021 11:05:50 +0530 Subject: [PATCH 334/416] fix: useless validation message (#28029) (#28046) Co-authored-by: Rucha Mahabal (cherry picked from commit 152f9b0a432361f1c801364d1c85c42a16691a8a) Co-authored-by: Devin Slauenwhite --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 3bc709ea86..bee96b6430 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -172,7 +172,6 @@ class SalarySlip(TransactionBase): and employee = %s and name != %s {0}""".format(cond), (self.start_date, self.end_date, self.employee, self.name)) if ret_exist: - self.employee = '' frappe.throw(_("Salary Slip of employee {0} already created for this period").format(self.employee)) else: for data in self.timesheets: From 2849297471ece8c829e8701134c1150eb3efc4a3 Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Thu, 21 Oct 2021 11:07:47 +0530 Subject: [PATCH 335/416] refactor: move process loss report to manufacturing (#28043) * refactor: move process loss report to manufacturing * test: fix process loss report test Co-authored-by: Ankush Menat --- .../report/process_loss_report/__init__.py | 0 .../report/process_loss_report/process_loss_report.js | 0 .../report/process_loss_report/process_loss_report.json | 7 ++----- .../report/process_loss_report/process_loss_report.py | 0 erpnext/manufacturing/report/test_reports.py | 1 + erpnext/stock/report/test_reports.py | 1 - 6 files changed, 3 insertions(+), 6 deletions(-) rename erpnext/{stock => manufacturing}/report/process_loss_report/__init__.py (100%) rename erpnext/{stock => manufacturing}/report/process_loss_report/process_loss_report.js (100%) rename erpnext/{stock => manufacturing}/report/process_loss_report/process_loss_report.json (83%) rename erpnext/{stock => manufacturing}/report/process_loss_report/process_loss_report.py (100%) diff --git a/erpnext/stock/report/process_loss_report/__init__.py b/erpnext/manufacturing/report/process_loss_report/__init__.py similarity index 100% rename from erpnext/stock/report/process_loss_report/__init__.py rename to erpnext/manufacturing/report/process_loss_report/__init__.py diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.js b/erpnext/manufacturing/report/process_loss_report/process_loss_report.js similarity index 100% rename from erpnext/stock/report/process_loss_report/process_loss_report.js rename to erpnext/manufacturing/report/process_loss_report/process_loss_report.js diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.json b/erpnext/manufacturing/report/process_loss_report/process_loss_report.json similarity index 83% rename from erpnext/stock/report/process_loss_report/process_loss_report.json rename to erpnext/manufacturing/report/process_loss_report/process_loss_report.json index afe4aff7f1..7d3d13d98c 100644 --- a/erpnext/stock/report/process_loss_report/process_loss_report.json +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.json @@ -9,9 +9,9 @@ "filters": [], "idx": 0, "is_standard": "Yes", - "modified": "2021-08-24 16:38:15.233395", + "modified": "2021-10-20 22:03:57.606612", "modified_by": "Administrator", - "module": "Stock", + "module": "Manufacturing", "name": "Process Loss Report", "owner": "Administrator", "prepared_report": 0, @@ -21,9 +21,6 @@ "roles": [ { "role": "Manufacturing User" - }, - { - "role": "Stock User" } ] } \ No newline at end of file diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py similarity index 100% rename from erpnext/stock/report/process_loss_report/process_loss_report.py rename to erpnext/manufacturing/report/process_loss_report/process_loss_report.py diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py index fa7f91a81b..1de472659e 100644 --- a/erpnext/manufacturing/report/test_reports.py +++ b/erpnext/manufacturing/report/test_reports.py @@ -33,6 +33,7 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Job Card Summary", {"fiscal_year": "2021-2022"}), ("Production Analytics", {"range": "Monthly"}), ("Quality Inspection Summary", {}), + ("Process Loss Report", {}), ("Work Order Stock Report", {}), ("Work Order Summary", {"fiscal_year": "2021-2022", "age": 0}), ] diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py index 9802f600ad..d7fb5b2bf3 100644 --- a/erpnext/stock/report/test_reports.py +++ b/erpnext/stock/report/test_reports.py @@ -40,7 +40,6 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [ ("Item Variant Details", {"item": "_Test Variant Item",}), ("Total Stock Summary", {"group_by": "warehouse",}), ("Batch Item Expiry Status", {}), - ("Process Loss Report", {}), ("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}), ] From 8b547e39a872f3ce5c785d1e905bff58496db8d6 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 21 Oct 2021 12:06:02 +0530 Subject: [PATCH 336/416] feat: Taxes template for selling/buying doctypes Depends on the new Print Format Builder frappe/frappe#14134 --- .../print_format_field_template/__init__.py | 0 .../purchase_invoice_taxes/__init__.py | 0 .../purchase_invoice_taxes.json | 16 +++++++++ .../sales_invoice_taxes/__init__.py | 0 .../sales_invoice_taxes.json | 16 +++++++++ .../print_format_field_template/__init__.py | 0 .../purchase_order_taxes/__init__.py | 0 .../purchase_order_taxes.json | 16 +++++++++ .../supplier_quotation_taxes/__init__.py | 0 .../supplier_quotation_taxes.json | 16 +++++++++ .../print_format_field_template/__init__.py | 0 .../quotation_taxes/__init__.py | 0 .../quotation_taxes/quotation_taxes.json | 16 +++++++++ .../sales_order_taxes/__init__.py | 0 .../sales_order_taxes/sales_order_taxes.json | 16 +++++++++ .../includes/taxes_and_charges.html | 34 +++++++++++++++++++ 16 files changed, 130 insertions(+) create mode 100644 erpnext/accounts/print_format_field_template/__init__.py create mode 100644 erpnext/accounts/print_format_field_template/purchase_invoice_taxes/__init__.py create mode 100644 erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json create mode 100644 erpnext/accounts/print_format_field_template/sales_invoice_taxes/__init__.py create mode 100644 erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json create mode 100644 erpnext/buying/print_format_field_template/__init__.py create mode 100644 erpnext/buying/print_format_field_template/purchase_order_taxes/__init__.py create mode 100644 erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json create mode 100644 erpnext/buying/print_format_field_template/supplier_quotation_taxes/__init__.py create mode 100644 erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json create mode 100644 erpnext/selling/print_format_field_template/__init__.py create mode 100644 erpnext/selling/print_format_field_template/quotation_taxes/__init__.py create mode 100644 erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json create mode 100644 erpnext/selling/print_format_field_template/sales_order_taxes/__init__.py create mode 100644 erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json create mode 100644 erpnext/templates/print_formats/includes/taxes_and_charges.html diff --git a/erpnext/accounts/print_format_field_template/__init__.py b/erpnext/accounts/print_format_field_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/__init__.py b/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json b/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json new file mode 100644 index 0000000000..f525f7b8d4 --- /dev/null +++ b/erpnext/accounts/print_format_field_template/purchase_invoice_taxes/purchase_invoice_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 18:06:53.083133", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Purchase Invoice", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:06:53.083133", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Purchase Invoice Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/accounts/print_format_field_template/sales_invoice_taxes/__init__.py b/erpnext/accounts/print_format_field_template/sales_invoice_taxes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json b/erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json new file mode 100644 index 0000000000..8ce62a8b26 --- /dev/null +++ b/erpnext/accounts/print_format_field_template/sales_invoice_taxes/sales_invoice_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 17:50:00.152759", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Sales Invoice", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:13:20.894207", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Invoice Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/buying/print_format_field_template/__init__.py b/erpnext/buying/print_format_field_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/print_format_field_template/purchase_order_taxes/__init__.py b/erpnext/buying/print_format_field_template/purchase_order_taxes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json b/erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json new file mode 100644 index 0000000000..73b7730894 --- /dev/null +++ b/erpnext/buying/print_format_field_template/purchase_order_taxes/purchase_order_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 18:07:19.253457", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Purchase Order", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:07:19.253457", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/buying/print_format_field_template/supplier_quotation_taxes/__init__.py b/erpnext/buying/print_format_field_template/supplier_quotation_taxes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json b/erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json new file mode 100644 index 0000000000..2be17a1b01 --- /dev/null +++ b/erpnext/buying/print_format_field_template/supplier_quotation_taxes/supplier_quotation_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 18:09:08.103919", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Supplier Quotation", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:09:08.103919", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Quotation Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/selling/print_format_field_template/__init__.py b/erpnext/selling/print_format_field_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format_field_template/quotation_taxes/__init__.py b/erpnext/selling/print_format_field_template/quotation_taxes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json b/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json new file mode 100644 index 0000000000..eaa17cedf0 --- /dev/null +++ b/erpnext/selling/print_format_field_template/quotation_taxes/quotation_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 15:48:56.416449", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Quotation", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:11:33.553722", + "modified_by": "Administrator", + "module": "Selling", + "name": "Quotation Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/selling/print_format_field_template/sales_order_taxes/__init__.py b/erpnext/selling/print_format_field_template/sales_order_taxes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json b/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json new file mode 100644 index 0000000000..2aacb440ff --- /dev/null +++ b/erpnext/selling/print_format_field_template/sales_order_taxes/sales_order_taxes.json @@ -0,0 +1,16 @@ +{ + "creation": "2021-10-19 18:04:24.443076", + "docstatus": 0, + "doctype": "Print Format Field Template", + "document_type": "Sales Order", + "field": "taxes", + "idx": 0, + "modified": "2021-10-19 18:04:24.443076", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order Taxes", + "owner": "Administrator", + "standard": 1, + "template": "", + "template_file": "templates/print_formats/includes/taxes_and_charges.html" +} \ No newline at end of file diff --git a/erpnext/templates/print_formats/includes/taxes_and_charges.html b/erpnext/templates/print_formats/includes/taxes_and_charges.html new file mode 100644 index 0000000000..0d8e3834d8 --- /dev/null +++ b/erpnext/templates/print_formats/includes/taxes_and_charges.html @@ -0,0 +1,34 @@ +{% macro render_row(label, value) %} +
    +
    +
    {{ label }}
    +
    +
    + {{ value }} +
    +
    +{% endmacro %} + +{%- macro render_discount_amount(doc) -%} + {%- if doc.discount_amount -%} + {{ render_row(_(doc.meta.get_label('discount_amount')), '- ' + doc.get_formatted("discount_amount", doc)) }} + {%- endif -%} +{%- endmacro -%} + +
    +
    +
    + {%- if doc.apply_discount_on == "Net Total" -%} + {{ render_discount_amount(doc) }} + {%- endif -%} + {%- for charge in doc.taxes -%} + {%- if (charge.tax_amount or print_settings.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%} + {{ render_row(charge.get_formatted("description"), charge.get_formatted('tax_amount', doc)) }} + {%- endif -%} + {%- endfor -%} + {%- if doc.apply_discount_on == "Grand Total" -%} + {{ render_discount_amount(doc) }} + {%- endif -%} +
    +
    + From c6d06816cfbafef05d79c26df166cc6cd7c18b48 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 21 Oct 2021 15:37:59 +0530 Subject: [PATCH 337/416] fix: Auto update company address check on linking with company --- erpnext/accounts/custom/address.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py index a6d08d8ff6..551048e50b 100644 --- a/erpnext/accounts/custom/address.py +++ b/erpnext/accounts/custom/address.py @@ -10,6 +10,7 @@ from frappe.contacts.doctype.address.address import ( class ERPNextAddress(Address): def validate(self): self.validate_reference() + self.update_compnay_address() super(ERPNextAddress, self).validate() def link_address(self): @@ -19,6 +20,11 @@ class ERPNextAddress(Address): return super(ERPNextAddress, self).link_address() + def update_compnay_address(self): + for link in self.get('links'): + if link.link_doctype == 'Company': + self.is_your_company_address = 1 + def validate_reference(self): if self.is_your_company_address and not [ row for row in self.links if row.link_doctype == "Company" From 7733afc0ae7e5e3a67b8e498a282ac5fb54afabf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 21 Oct 2021 16:13:00 +0530 Subject: [PATCH 338/416] fix(minor): Filters and fixes in Tax Withholding Category --- .../tax_withholding_category/tax_withholding_category.js | 3 ++- .../tax_withholding_category/tax_withholding_category.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.js b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.js index b8d6c9af3a..7b47974946 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.js +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.js @@ -8,7 +8,8 @@ frappe.ui.form.on('Tax Withholding Category', { if (child.company) { return { filters: { - 'company': child.company + 'company': child.company, + 'root_type': ['in', ['Asset', 'Liability']] } }; } diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index c3cb8396d0..c36f3cb201 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -13,6 +13,7 @@ from frappe.utils import cint, getdate class TaxWithholdingCategory(Document): def validate(self): self.validate_dates() + self.validate_accounts() self.validate_thresholds() def validate_dates(self): @@ -25,6 +26,14 @@ class TaxWithholdingCategory(Document): if last_date and getdate(d.to_date) < getdate(last_date): frappe.throw(_("Row #{0}: Dates overlapping with other row").format(d.idx)) + def validate_accounts(self): + existing_accounts = [] + for d in self.get('accounts'): + if d.get('account') in existing_accounts: + frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get('account')))) + + existing_accounts.append(d.get('account')) + def validate_thresholds(self): for d in self.get('rates'): if d.cumulative_threshold and d.cumulative_threshold < d.single_threshold: From a20058a3436f63afb4c537916aeffacdac14c786 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 21 Oct 2021 17:52:13 +0530 Subject: [PATCH 339/416] fix(minor): Add mandatory depends on condition for reference no and date fields --- .../doctype/payment_entry/payment_entry.json | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 6f362c1fbb..ee2e319a6f 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -27,10 +27,12 @@ "payment_accounts_section", "party_balance", "paid_from", + "paid_from_account_type", "paid_from_account_currency", "paid_from_account_balance", "column_break_18", "paid_to", + "paid_to_account_type", "paid_to_account_currency", "paid_to_account_balance", "payment_amounts_section", @@ -440,7 +442,8 @@ "depends_on": "eval:(doc.paid_from && doc.paid_to)", "fieldname": "reference_no", "fieldtype": "Data", - "label": "Cheque/Reference No" + "label": "Cheque/Reference No", + "mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')" }, { "fieldname": "column_break_23", @@ -452,6 +455,7 @@ "fieldname": "reference_date", "fieldtype": "Date", "label": "Cheque/Reference Date", + "mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')", "search_index": 1 }, { @@ -707,15 +711,30 @@ "label": "Received Amount After Tax (Company Currency)", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fetch_from": "paid_from.account_type", + "fieldname": "paid_from_account_type", + "fieldtype": "Data", + "hidden": 1, + "label": "Paid From Account Type" + }, + { + "fetch_from": "paid_to.account_type", + "fieldname": "paid_to_account_type", + "fieldtype": "Data", + "hidden": 1, + "label": "Paid To Account Type" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-07-09 08:58:15.008761", + "modified": "2021-10-22 17:50:24.632806", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { From 65025fb628d55a97d3c2281760d7d5954ac44912 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 21 Oct 2021 19:29:18 +0530 Subject: [PATCH 340/416] fix: Update receivable/payable account on company change --- .../accounts/doctype/purchase_invoice/purchase_invoice.js | 6 ++++++ erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 6c74d2b438..3526e488e1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -590,5 +590,11 @@ frappe.ui.form.on("Purchase Invoice", { company: function(frm) { erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); + + if (frm.doc.company) { + frappe.db.get_value('Company', frm.doc.company, 'default_payable_account', (r) => { + frm.set_value('credit_to', r.default_payable_account); + }); + } }, }) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 73e1284304..bee153b7b8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -12,6 +12,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e } company() { erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype); + + let me = this; + if (this.frm.doc.company) { + frappe.db.get_value('Company', this.frm.doc.company, 'default_receivable_account', (r) => { + me.frm.set_value('debit_to', r.default_receivable_account); + }); + } } onload() { var me = this; From 2bdaf7bb2313cf8fff48732abf99d9781e9b3f75 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Thu, 21 Oct 2021 20:23:04 +0200 Subject: [PATCH 341/416] fix: don't reset rates in Timesheet Detail when Activity Type is cleared (#28056) * fix: don't reset rates when activity type is cleared * refactor: suggestions from review Co-authored-by: Sagar Vora * refactor: suggestions from review (fix) * style: fix sider * fix: sider issue Co-authored-by: Sagar Vora --- erpnext/projects/doctype/timesheet/timesheet.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 65a8566a27..f615f051f0 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -283,7 +283,9 @@ frappe.ui.form.on("Timesheet Detail", { calculate_time_and_amount(frm); }, - activity_type: function(frm, cdt, cdn) { + activity_type: function (frm, cdt, cdn) { + if (!frappe.get_doc(cdt, cdn).activity_type) return; + frappe.call({ method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost", args: { @@ -291,10 +293,10 @@ frappe.ui.form.on("Timesheet Detail", { activity_type: frm.selected_doc.activity_type, currency: frm.doc.currency }, - callback: function(r){ - if(r.message){ - frappe.model.set_value(cdt, cdn, 'billing_rate', r.message['billing_rate']); - frappe.model.set_value(cdt, cdn, 'costing_rate', r.message['costing_rate']); + callback: function (r) { + if (r.message) { + frappe.model.set_value(cdt, cdn, "billing_rate", r.message["billing_rate"]); + frappe.model.set_value(cdt, cdn, "costing_rate", r.message["costing_rate"]); calculate_billing_costing_amount(frm, cdt, cdn); } } From cc1baae5eb738a821e4e3c14987acc71b5f6318f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Oct 2021 21:43:50 +0530 Subject: [PATCH 342/416] ci: move semgrep rules out of repo (#28067) Moving semgrep rules out of repos as it's unnecessary to maintain same ruleset for different repos and different branches. --- .github/helper/semgrep_rules/README.md | 38 ---- .../semgrep_rules/frappe_correctness.py | 64 ------- .../semgrep_rules/frappe_correctness.yml | 163 ------------------ .github/helper/semgrep_rules/report.py | 15 -- .github/helper/semgrep_rules/report.yml | 34 ---- .github/helper/semgrep_rules/security.py | 6 - .github/helper/semgrep_rules/security.yml | 10 -- .github/helper/semgrep_rules/translate.js | 44 ----- .github/helper/semgrep_rules/translate.py | 61 ------- .github/helper/semgrep_rules/translate.yml | 64 ------- .github/helper/semgrep_rules/ux.js | 9 - .github/helper/semgrep_rules/ux.py | 31 ---- .github/helper/semgrep_rules/ux.yml | 30 ---- .github/workflows/linters.yml | 5 +- 14 files changed, 4 insertions(+), 570 deletions(-) delete mode 100644 .github/helper/semgrep_rules/README.md delete mode 100644 .github/helper/semgrep_rules/frappe_correctness.py delete mode 100644 .github/helper/semgrep_rules/frappe_correctness.yml delete mode 100644 .github/helper/semgrep_rules/report.py delete mode 100644 .github/helper/semgrep_rules/report.yml delete mode 100644 .github/helper/semgrep_rules/security.py delete mode 100644 .github/helper/semgrep_rules/security.yml delete mode 100644 .github/helper/semgrep_rules/translate.js delete mode 100644 .github/helper/semgrep_rules/translate.py delete mode 100644 .github/helper/semgrep_rules/translate.yml delete mode 100644 .github/helper/semgrep_rules/ux.js delete mode 100644 .github/helper/semgrep_rules/ux.py delete mode 100644 .github/helper/semgrep_rules/ux.yml diff --git a/.github/helper/semgrep_rules/README.md b/.github/helper/semgrep_rules/README.md deleted file mode 100644 index 670d8d280f..0000000000 --- a/.github/helper/semgrep_rules/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Semgrep linting - -## What is semgrep? -Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc. - -Example: - -To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc. - -You can read more such examples in `.github/helper/semgrep_rules` directory. - -# Why/when to use this? -We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us. - -## Running locally - -Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`. - -To run locally use following command: - -`semgrep --config=.github/helper/semgrep_rules [file/folder names]` - -## Testing -semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/ - -When writing new rules you should write few positive and few negative cases as shown in the guide and current tests. - -To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules` - - -## Reference - -If you are new to Semgrep read following pages to get started on writing/modifying rules: - -- https://semgrep.dev/docs/getting-started/ -- https://semgrep.dev/docs/writing-rules/rule-syntax -- https://semgrep.dev/docs/writing-rules/pattern-examples/ -- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases diff --git a/.github/helper/semgrep_rules/frappe_correctness.py b/.github/helper/semgrep_rules/frappe_correctness.py deleted file mode 100644 index 83d4acfe4a..0000000000 --- a/.github/helper/semgrep_rules/frappe_correctness.py +++ /dev/null @@ -1,64 +0,0 @@ -import frappe -from frappe import _ - -from frappe.model.document import Document - - -# ruleid: frappe-modifying-but-not-comitting -def on_submit(self): - if self.value_of_goods == 0: - frappe.throw(_('Value of goods cannot be 0')) - self.status = 'Submitted' - - -# ok: frappe-modifying-but-not-comitting -def on_submit(self): - if self.value_of_goods == 0: - frappe.throw(_('Value of goods cannot be 0')) - self.status = 'Submitted' - self.db_set('status', 'Submitted') - -# ok: frappe-modifying-but-not-comitting -def on_submit(self): - if self.value_of_goods == 0: - frappe.throw(_('Value of goods cannot be 0')) - x = "y" - self.status = x - self.db_set('status', x) - - -# ok: frappe-modifying-but-not-comitting -def on_submit(self): - x = "y" - self.status = x - self.save() - -# ruleid: frappe-modifying-but-not-comitting-other-method -class DoctypeClass(Document): - def on_submit(self): - self.good_method() - self.tainted_method() - - def tainted_method(self): - self.status = "uptate" - - -# ok: frappe-modifying-but-not-comitting-other-method -class DoctypeClass(Document): - def on_submit(self): - self.good_method() - self.tainted_method() - - def tainted_method(self): - self.status = "update" - self.db_set("status", "update") - -# ok: frappe-modifying-but-not-comitting-other-method -class DoctypeClass(Document): - def on_submit(self): - self.good_method() - self.tainted_method() - self.save() - - def tainted_method(self): - self.status = "uptate" diff --git a/.github/helper/semgrep_rules/frappe_correctness.yml b/.github/helper/semgrep_rules/frappe_correctness.yml deleted file mode 100644 index 0cf4e78b81..0000000000 --- a/.github/helper/semgrep_rules/frappe_correctness.yml +++ /dev/null @@ -1,163 +0,0 @@ -# This file specifies rules for correctness according to how frappe doctype data model works. - -rules: -- id: frappe-modifying-but-not-comitting - patterns: - - pattern: | - def $METHOD(self, ...): - ... - self.$ATTR = ... - - pattern-not: | - def $METHOD(self, ...): - ... - self.$ATTR = ... - ... - self.db_set(..., self.$ATTR, ...) - - pattern-not: | - def $METHOD(self, ...): - ... - self.$ATTR = $SOME_VAR - ... - self.db_set(..., $SOME_VAR, ...) - - pattern-not: | - def $METHOD(self, ...): - ... - self.$ATTR = $SOME_VAR - ... - self.save() - - metavariable-regex: - metavariable: '$ATTR' - # this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me) - regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$' - - metavariable-regex: - metavariable: "$METHOD" - regex: "(on_submit|on_cancel)" - message: | - DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database. - languages: [python] - severity: ERROR - -- id: frappe-modifying-but-not-comitting-other-method - patterns: - - pattern: | - class $DOCTYPE(...): - def $METHOD(self, ...): - ... - self.$ANOTHER_METHOD() - ... - - def $ANOTHER_METHOD(self, ...): - ... - self.$ATTR = ... - - pattern-not: | - class $DOCTYPE(...): - def $METHOD(self, ...): - ... - self.$ANOTHER_METHOD() - ... - - def $ANOTHER_METHOD(self, ...): - ... - self.$ATTR = ... - ... - self.db_set(..., self.$ATTR, ...) - - pattern-not: | - class $DOCTYPE(...): - def $METHOD(self, ...): - ... - self.$ANOTHER_METHOD() - ... - - def $ANOTHER_METHOD(self, ...): - ... - self.$ATTR = $SOME_VAR - ... - self.db_set(..., $SOME_VAR, ...) - - pattern-not: | - class $DOCTYPE(...): - def $METHOD(self, ...): - ... - self.$ANOTHER_METHOD() - ... - self.save() - def $ANOTHER_METHOD(self, ...): - ... - self.$ATTR = ... - - metavariable-regex: - metavariable: "$METHOD" - regex: "(on_submit|on_cancel)" - message: | - self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database. - languages: [python] - severity: ERROR - -- id: frappe-print-function-in-doctypes - pattern: print(...) - message: | - Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement. - languages: [python] - severity: WARNING - paths: - include: - - "*/**/doctype/*" - -- id: frappe-modifying-child-tables-while-iterating - pattern-either: - - pattern: | - for $ROW in self.$TABLE: - ... - self.remove(...) - - pattern: | - for $ROW in self.$TABLE: - ... - self.append(...) - message: | - Child table being modified while iterating on it. - languages: [python] - severity: ERROR - paths: - include: - - "*/**/doctype/*" - -- id: frappe-same-key-assigned-twice - pattern-either: - - pattern: | - {..., $X: $A, ..., $X: $B, ...} - - pattern: | - dict(..., ($X, $A), ..., ($X, $B), ...) - - pattern: | - _dict(..., ($X, $A), ..., ($X, $B), ...) - message: | - key `$X` is uselessly assigned twice. This could be a potential bug. - languages: [python] - severity: ERROR - -- id: frappe-manual-commit - patterns: - - pattern: frappe.db.commit() - - pattern-not-inside: | - try: - ... - except ...: - ... - message: | - Manually commiting a transaction is highly discouraged. Read about the transaction model implemented by Frappe Framework before adding manual commits: https://frappeframework.com/docs/user/en/api/database#database-transaction-model If you think manual commit is required then add a comment explaining why and `// nosemgrep` on the same line. - paths: - exclude: - - "**/patches/**" - - "**/demo/**" - languages: [python] - severity: ERROR - -- id: frappe-using-db-sql - pattern-either: - - pattern: frappe.db.sql(...) - - pattern: frappe.db.sql_ddl(...) - - pattern: frappe.db.sql_list(...) - paths: - exclude: - - "test_*.py" - message: | - The PR contains a SQL query that may be re-written with frappe.qb (https://frappeframework.com/docs/user/en/api/query-builder) or the Database API (https://frappeframework.com/docs/user/en/api/database) - languages: [python] - severity: ERROR \ No newline at end of file diff --git a/.github/helper/semgrep_rules/report.py b/.github/helper/semgrep_rules/report.py deleted file mode 100644 index ff278408e1..0000000000 --- a/.github/helper/semgrep_rules/report.py +++ /dev/null @@ -1,15 +0,0 @@ -from frappe import _ - - -# ruleid: frappe-missing-translate-function-in-report-python -{"label": "Field Label"} - -# ruleid: frappe-missing-translate-function-in-report-python -dict(label="Field Label") - - -# ok: frappe-missing-translate-function-in-report-python -{"label": _("Field Label")} - -# ok: frappe-missing-translate-function-in-report-python -dict(label=_("Field Label")) diff --git a/.github/helper/semgrep_rules/report.yml b/.github/helper/semgrep_rules/report.yml deleted file mode 100644 index f2a9b16739..0000000000 --- a/.github/helper/semgrep_rules/report.yml +++ /dev/null @@ -1,34 +0,0 @@ -rules: -- id: frappe-missing-translate-function-in-report-python - paths: - include: - - "**/report" - exclude: - - "**/regional" - pattern-either: - - patterns: - - pattern: | - {..., "label": "...", ...} - - pattern-not: | - {..., "label": _("..."), ...} - - patterns: - - pattern: dict(..., label="...", ...) - - pattern-not: dict(..., label=_("..."), ...) - message: | - All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations - languages: [python] - severity: ERROR - -- id: frappe-translated-values-in-business-logic - paths: - include: - - "**/report" - patterns: - - pattern-inside: | - {..., filters: [...], ...} - - pattern: | - {..., options: [..., __("..."), ...], ...} - message: | - Using translated values in options field will require you to translate the values while comparing in business logic. Instead of passing translated labels provide objects that contain both label and value. e.g. { label: __("Option value"), value: "Option value"} - languages: [javascript] - severity: ERROR diff --git a/.github/helper/semgrep_rules/security.py b/.github/helper/semgrep_rules/security.py deleted file mode 100644 index f477d7c176..0000000000 --- a/.github/helper/semgrep_rules/security.py +++ /dev/null @@ -1,6 +0,0 @@ -def function_name(input): - # ruleid: frappe-codeinjection-eval - eval(input) - -# ok: frappe-codeinjection-eval -eval("1 + 1") diff --git a/.github/helper/semgrep_rules/security.yml b/.github/helper/semgrep_rules/security.yml deleted file mode 100644 index 8b21979208..0000000000 --- a/.github/helper/semgrep_rules/security.yml +++ /dev/null @@ -1,10 +0,0 @@ -rules: -- id: frappe-codeinjection-eval - patterns: - - pattern-not: eval("...") - - pattern: eval(...) - message: | - Detected the use of eval(). eval() can be dangerous if used to evaluate - dynamic content. Avoid it or use safe_eval(). - languages: [python] - severity: ERROR diff --git a/.github/helper/semgrep_rules/translate.js b/.github/helper/semgrep_rules/translate.js deleted file mode 100644 index 9cdfb75d0b..0000000000 --- a/.github/helper/semgrep_rules/translate.js +++ /dev/null @@ -1,44 +0,0 @@ -// ruleid: frappe-translation-empty-string -__("") -// ruleid: frappe-translation-empty-string -__('') - -// ok: frappe-translation-js-formatting -__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]); - -// ruleid: frappe-translation-js-formatting -__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`); - -// ok: frappe-translation-js-formatting -__('This is fine'); - - -// ok: frappe-translation-trailing-spaces -__('This is fine'); - -// ruleid: frappe-translation-trailing-spaces -__(' this is not ok '); -// ruleid: frappe-translation-trailing-spaces -__('this is not ok '); -// ruleid: frappe-translation-trailing-spaces -__(' this is not ok'); - -// ok: frappe-translation-js-splitting -__('You have {0} subscribers in your mailing list.', [subscribers.length]) - -// todoruleid: frappe-translation-js-splitting -__('You have') + subscribers.length + __('subscribers in your mailing list.') - -// ruleid: frappe-translation-js-splitting -__('You have' + 'subscribers in your mailing list.') - -// ruleid: frappe-translation-js-splitting -__('You have {0} subscribers' + - 'in your mailing list', [subscribers.length]) - -// ok: frappe-translation-js-splitting -__("Ctrl+Enter to add comment") - -// ruleid: frappe-translation-js-splitting -__('You have {0} subscribers \ - in your mailing list', [subscribers.length]) diff --git a/.github/helper/semgrep_rules/translate.py b/.github/helper/semgrep_rules/translate.py deleted file mode 100644 index 9de6aa94f0..0000000000 --- a/.github/helper/semgrep_rules/translate.py +++ /dev/null @@ -1,61 +0,0 @@ -# Examples taken from https://frappeframework.com/docs/user/en/translations -# This file is used for testing the tests. - -from frappe import _ - -full_name = "Jon Doe" -# ok: frappe-translation-python-formatting -_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name) - -# ruleid: frappe-translation-python-formatting -_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name) -# ruleid: frappe-translation-python-formatting -_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name}) - -# ruleid: frappe-translation-python-formatting -_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name)) - - -subscribers = ["Jon", "Doe"] -# ok: frappe-translation-python-formatting -_('You have {0} subscribers in your mailing list.').format(len(subscribers)) - -# ruleid: frappe-translation-python-splitting -_('You have') + len(subscribers) + _('subscribers in your mailing list.') - -# ruleid: frappe-translation-python-splitting -_('You have {0} subscribers \ - in your mailing list').format(len(subscribers)) - -# ok: frappe-translation-python-splitting -_('You have {0} subscribers') \ - + 'in your mailing list' - -# ruleid: frappe-translation-trailing-spaces -msg = _(" You have {0} pending invoice ") -# ruleid: frappe-translation-trailing-spaces -msg = _("You have {0} pending invoice ") -# ruleid: frappe-translation-trailing-spaces -msg = _(" You have {0} pending invoice") - -# ok: frappe-translation-trailing-spaces -msg = ' ' + _("You have {0} pending invoices") + ' ' - -# ruleid: frappe-translation-python-formatting -_(f"can not format like this - {subscribers}") -# ruleid: frappe-translation-python-splitting -_(f"what" + f"this is also not cool") - - -# ruleid: frappe-translation-empty-string -_("") -# ruleid: frappe-translation-empty-string -_('') - - -class Test: - # ok: frappe-translation-python-splitting - def __init__( - args - ): - pass diff --git a/.github/helper/semgrep_rules/translate.yml b/.github/helper/semgrep_rules/translate.yml deleted file mode 100644 index 5f03fb9fd0..0000000000 --- a/.github/helper/semgrep_rules/translate.yml +++ /dev/null @@ -1,64 +0,0 @@ -rules: -- id: frappe-translation-empty-string - pattern-either: - - pattern: _("") - - pattern: __("") - message: | - Empty string is useless for translation. - Please refer: https://frappeframework.com/docs/user/en/translations - languages: [python, javascript, json] - severity: ERROR - -- id: frappe-translation-trailing-spaces - pattern-either: - - pattern: _("=~/(^[ \t]+|[ \t]+$)/") - - pattern: __("=~/(^[ \t]+|[ \t]+$)/") - message: | - Trailing or leading whitespace not allowed in translate strings. - Please refer: https://frappeframework.com/docs/user/en/translations - languages: [python, javascript, json] - severity: ERROR - -- id: frappe-translation-python-formatting - pattern-either: - - pattern: _("..." % ...) - - pattern: _("...".format(...)) - - pattern: _(f"...") - message: | - Only positional formatters are allowed and formatting should not be done before translating. - Please refer: https://frappeframework.com/docs/user/en/translations - languages: [python] - severity: ERROR - -- id: frappe-translation-js-formatting - patterns: - - pattern: __(`...`) - - pattern-not: __("...") - message: | - Template strings are not allowed for text formatting. - Please refer: https://frappeframework.com/docs/user/en/translations - languages: [javascript, json] - severity: ERROR - -- id: frappe-translation-python-splitting - pattern-either: - - pattern: _(...) + _(...) - - pattern: _("..." + "...") - - pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\` - - pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( ) - message: | - Do not split strings inside translate function. Do not concatenate using translate functions. - Please refer: https://frappeframework.com/docs/user/en/translations - languages: [python] - severity: ERROR - -- id: frappe-translation-js-splitting - pattern-either: - - pattern-regex: '__\([^\)]*[\\]\s+' - - pattern: __('...' + '...', ...) - - pattern: __('...') + __('...') - message: | - Do not split strings inside translate function. Do not concatenate using translate functions. - Please refer: https://frappeframework.com/docs/user/en/translations - languages: [javascript, json] - severity: ERROR diff --git a/.github/helper/semgrep_rules/ux.js b/.github/helper/semgrep_rules/ux.js deleted file mode 100644 index ae73f9cc60..0000000000 --- a/.github/helper/semgrep_rules/ux.js +++ /dev/null @@ -1,9 +0,0 @@ - -// ok: frappe-missing-translate-function-js -frappe.msgprint('{{ _("Both login and password required") }}'); - -// ruleid: frappe-missing-translate-function-js -frappe.msgprint('What'); - -// ok: frappe-missing-translate-function-js -frappe.throw(' {{ _("Both login and password required") }}. '); diff --git a/.github/helper/semgrep_rules/ux.py b/.github/helper/semgrep_rules/ux.py deleted file mode 100644 index a00d3cd8ae..0000000000 --- a/.github/helper/semgrep_rules/ux.py +++ /dev/null @@ -1,31 +0,0 @@ -import frappe -from frappe import msgprint, throw, _ - - -# ruleid: frappe-missing-translate-function-python -throw("Error Occured") - -# ruleid: frappe-missing-translate-function-python -frappe.throw("Error Occured") - -# ruleid: frappe-missing-translate-function-python -frappe.msgprint("Useful message") - -# ruleid: frappe-missing-translate-function-python -msgprint("Useful message") - - -# ok: frappe-missing-translate-function-python -translatedmessage = _("Hello") - -# ok: frappe-missing-translate-function-python -throw(translatedmessage) - -# ok: frappe-missing-translate-function-python -msgprint(translatedmessage) - -# ok: frappe-missing-translate-function-python -msgprint(_("Helpful message")) - -# ok: frappe-missing-translate-function-python -frappe.throw(_("Error occured")) diff --git a/.github/helper/semgrep_rules/ux.yml b/.github/helper/semgrep_rules/ux.yml deleted file mode 100644 index dd667f36c0..0000000000 --- a/.github/helper/semgrep_rules/ux.yml +++ /dev/null @@ -1,30 +0,0 @@ -rules: -- id: frappe-missing-translate-function-python - pattern-either: - - patterns: - - pattern: frappe.msgprint("...", ...) - - pattern-not: frappe.msgprint(_("..."), ...) - - patterns: - - pattern: frappe.throw("...", ...) - - pattern-not: frappe.throw(_("..."), ...) - message: | - All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations - languages: [python] - severity: ERROR - -- id: frappe-missing-translate-function-js - pattern-either: - - patterns: - - pattern: frappe.msgprint("...", ...) - - pattern-not: frappe.msgprint(__("..."), ...) - # ignore microtemplating e.g. msgprint("{{ _("server side translation") }}") - - pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...) - - patterns: - - pattern: frappe.throw("...", ...) - - pattern-not: frappe.throw(__("..."), ...) - # ignore microtemplating - - pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...) - message: | - All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations - languages: [javascript] - severity: ERROR diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 9389eaabaa..c59b50cb42 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -19,10 +19,13 @@ jobs: - name: Install and Run Pre-commit uses: pre-commit/action@v2.0.3 + - name: Download Semgrep rules + run: git clone --depth 1 https://github.com/frappe/frappe-semgrep-rules.git + - uses: returntocorp/semgrep-action@v1 env: SEMGREP_TIMEOUT: 120 with: config: >- r/python.lang.correctness - .github/helper/semgrep_rules + ./frappe-semgrep-rules/rules From 4ad2b851c48d126dae07b80f756f88bb3d141b60 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 22 Oct 2021 22:38:44 +0530 Subject: [PATCH 343/416] chore: change semgrep rules repo name [skip ci] --- .github/workflows/linters.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index c59b50cb42..ebb88c9eda 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -20,7 +20,7 @@ jobs: uses: pre-commit/action@v2.0.3 - name: Download Semgrep rules - run: git clone --depth 1 https://github.com/frappe/frappe-semgrep-rules.git + run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules - uses: returntocorp/semgrep-action@v1 env: From 2ea4c95f86a9e409b274e7a6a5a0d3266e00694b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 12:07:22 +0530 Subject: [PATCH 344/416] fix: Error in TDS computation summary (cherry picked from commit f12deae24b7ab164edd0868a1254bdf28bfeac09) --- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 621b697aca..4a25bcdee3 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -44,7 +44,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): if rate and tds_deducted: row = { - 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier).pan, + 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'), 'supplier': supplier_map.get(supplier).name } From 881e091b8597d0ebc8782c6764fb56ebd953adac Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 12:16:22 +0530 Subject: [PATCH 345/416] fix: Check for other properties (cherry picked from commit b7befe49dc83b938b74b7a63d31787734d7857f8) --- .../report/tds_payable_monthly/tds_payable_monthly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 4a25bcdee3..5c47514cc3 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -45,7 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): if rate and tds_deducted: row = { 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'), - 'supplier': supplier_map.get(supplier).name + 'supplier': supplier_map.get(supplier, {}).get('name') } if filters.naming_series == 'Naming Series': @@ -53,7 +53,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): row.update({ 'section_code': tax_withholding_category, - 'entity_type': supplier_map.get(supplier).supplier_type, + 'entity_type': supplier_map.get(supplier, {}).get('supplier_type'), 'tds_rate': rate, 'total_amount_credited': total_amount_credited, 'tds_deducted': tds_deducted, From 7f2dde7d9451a1fb3ccd2757c0b3e8647508909d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 12:17:13 +0530 Subject: [PATCH 346/416] fix: Check for supplier name (cherry picked from commit 944e3d467c5ab0a2a4a76a8b532e53bca77f8e61) --- .../accounts/report/tds_payable_monthly/tds_payable_monthly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py index 5c47514cc3..6a7f2e5b53 100644 --- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py +++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py @@ -49,7 +49,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map): } if filters.naming_series == 'Naming Series': - row.update({'supplier_name': supplier_map.get(supplier).supplier_name}) + row.update({'supplier_name': supplier_map.get(supplier, {}).get('supplier_name')}) row.update({ 'section_code': tax_withholding_category, From e6e804e7d783a43d13dafe7b16f6e5d3b750f1d3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 23 Oct 2021 18:58:41 +0530 Subject: [PATCH 347/416] fix: Get LTDS based on tax withholding category --- .../doctype/tax_withholding_category/tax_withholding_category.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index c3cb8396d0..54042fbe74 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -165,6 +165,7 @@ def get_lower_deduction_certificate(tax_details, pan_no): ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, + 'tax_withholding_category': tax_details.tax_withholding_category, 'valid_from': ('>=', tax_details.from_date), 'valid_upto': ('<=', tax_details.to_date) }, 'name') From bf13d183d8066219379a5e00c4ace4ca2f6c155b Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 23 Oct 2021 19:00:03 +0530 Subject: [PATCH 348/416] fix: Replace section code with tax withholding category in LDC --- erpnext/patches.txt | 1 + .../update_category_in_ltds_certificate.py | 10 ++++++++++ .../lower_deduction_certificate.json | 20 ++++++++++--------- .../lower_deduction_certificate.py | 12 ++++++----- 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 erpnext/patches/v13_0/update_category_in_ltds_certificate.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e446d6be42..daea70cf62 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -310,3 +310,4 @@ erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v14_0.delete_healthcare_doctypes +erpnext.patches.v13_0.update_category_in_ltds_certificate diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py new file mode 100644 index 0000000000..f8a0646fcb --- /dev/null +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + + frappe.db.sql(""" + UPDATE `tabLower Deduction Certificate` l, `tabSupplier` s + SET l.tax_withholding_category = s.tax_withholding_category + WHERE l.supplier = s.name + """) \ No newline at end of file diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json index f48fe6f476..c32ab6bec2 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json @@ -7,7 +7,7 @@ "engine": "InnoDB", "field_order": [ "certificate_details_section", - "section_code", + "tax_withholding_category", "fiscal_year", "column_break_3", "certificate_no", @@ -33,13 +33,6 @@ "reqd": 1, "unique": 1 }, - { - "fieldname": "section_code", - "fieldtype": "Select", - "label": "Section Code", - "options": "192\n193\n194\n194A\n194C\n194D\n194H\n194I\n194J\n194LA\n194LBB\n194LBC\n195", - "reqd": 1 - }, { "fieldname": "section_break_3", "fieldtype": "Section Break", @@ -123,13 +116,22 @@ "label": "Fiscal Year", "options": "Fiscal Year", "reqd": 1 + }, + { + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "label": "Tax Withholding Category", + "options": "Tax Withholding Category", + "reqd": 1 } ], + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-04-23 23:04:41.203721", + "modified": "2021-10-23 18:33:38.962622", "modified_by": "Administrator", "module": "Regional", "name": "Lower Deduction Certificate", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index d8553f1d91..7afbc00980 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -15,7 +15,7 @@ from erpnext.accounts.utils import get_fiscal_year class LowerDeductionCertificate(Document): def validate(self): self.validate_dates() - self.validate_supplier_against_section_code() + self.validate_supplier_against_tax_category() def validate_dates(self): if getdate(self.valid_upto) < getdate(self.valid_from): @@ -31,12 +31,14 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def validate_supplier_against_section_code(self): - duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'section_code': self.section_code}, ['name', 'valid_from', 'valid_upto'], as_dict=True) + def tax_withholding_category(self): + duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', + {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, + ['name', 'valid_from', 'valid_upto'], as_dict=True) if duplicate_certificate and self.are_dates_overlapping(duplicate_certificate): certificate_link = get_link_to_form('Lower Deduction Certificate', duplicate_certificate.name) - frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against Section Code {2} for this time period.") - .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.section_code))) + frappe.throw(_("There is already a valid Lower Deduction Certificate {0} for Supplier {1} against category {2} for this time period.") + .format(certificate_link, frappe.bold(self.supplier), frappe.bold(self.tax_withholding_category))) def are_dates_overlapping(self,duplicate_certificate): valid_from = duplicate_certificate.valid_from From fdaf93f76ca12ad7cc3d5e386b1ec5e2497cba75 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Sat, 23 Oct 2021 21:04:42 +0530 Subject: [PATCH 349/416] refactor: shows opening balance from filtered from_date (#26877) * refactor: shows opening balance from filtered from_date * refactor: opening balance considered from filtered from_date in stock ledger * fix: check if stock reco is opening and misc cleanups --- erpnext/stock/report/stock_balance/stock_balance.py | 4 +++- erpnext/stock/report/stock_ledger/stock_ledger.py | 13 +++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index fc5d5c12da..bb53c55737 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -202,7 +202,9 @@ def get_item_warehouse_map(filters, sle): value_diff = flt(d.stock_value_difference) - if d.posting_date < from_date: + if d.posting_date < from_date or (d.posting_date == from_date + and d.voucher_type == "Stock Reconciliation" and + frappe.db.get_value("Stock Reconciliation", d.voucher_no, "purpose") == "Opening Stock"): qty_dict.opening_qty += qty_diff qty_dict.opening_val += value_diff diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index 1ea58fed19..4e20b47261 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -21,7 +21,7 @@ def execute(filters=None): items = get_items(filters) sl_entries = get_stock_ledger_entries(filters, items) item_details = get_item_details(items, sl_entries, include_uom) - opening_row = get_opening_balance(filters, columns) + opening_row = get_opening_balance(filters, columns, sl_entries) precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) data = [] @@ -218,7 +218,7 @@ def get_sle_conditions(filters): return "and {}".format(" and ".join(conditions)) if conditions else "" -def get_opening_balance(filters, columns): +def get_opening_balance(filters, columns, sl_entries): if not (filters.item_code and filters.warehouse and filters.from_date): return @@ -230,6 +230,15 @@ def get_opening_balance(filters, columns): "posting_time": "00:00:00" }) + # check if any SLEs are actually Opening Stock Reconciliation + for sle in sl_entries: + if (sle.get("voucher_type") == "Stock Reconciliation" + and sle.get("date").split()[0] == filters.from_date + and frappe.db.get_value("Stock Reconciliation", sle.voucher_no, "purpose") == "Opening Stock" + ): + last_entry = sle + sl_entries.remove(sle) + row = { "item_code": _("'Opening'"), "qty_after_transaction": last_entry.get("qty_after_transaction", 0), From e3ae8d5a1e6bc1251feae63f51215e9ce3c2365a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 20 Oct 2021 22:10:21 +0530 Subject: [PATCH 350/416] fix: Move PAN field from standard doctype to fixtures for India --- erpnext/buying/doctype/supplier/supplier.json | 9 ++----- erpnext/patches.txt | 1 + .../v13_0/create_pan_field_for_india.py | 25 +++++++++++++++++++ erpnext/regional/india/setup.py | 16 ++++++++++-- .../selling/doctype/customer/customer.json | 12 +++------ 5 files changed, 45 insertions(+), 18 deletions(-) create mode 100644 erpnext/patches/v13_0/create_pan_field_for_india.py diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 12a09cdd0e..a57d9a92bb 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -25,7 +25,6 @@ "column_break0", "supplier_group", "supplier_type", - "pan", "allow_purchase_invoice_creation_without_purchase_order", "allow_purchase_invoice_creation_without_purchase_receipt", "disabled", @@ -176,11 +175,6 @@ "options": "Company\nIndividual", "reqd": 1 }, - { - "fieldname": "pan", - "fieldtype": "Data", - "label": "PAN" - }, { "fieldname": "language", "fieldtype": "Link", @@ -438,11 +432,12 @@ "link_fieldname": "party" } ], - "modified": "2021-09-06 17:37:56.522233", + "modified": "2021-10-20 22:03:33.147249", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e446d6be42..9bcab5e74a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -310,3 +310,4 @@ erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v14_0.delete_healthcare_doctypes +erpnext.patches.v13_0.create_pan_field_for_india diff --git a/erpnext/patches/v13_0/create_pan_field_for_india.py b/erpnext/patches/v13_0/create_pan_field_for_india.py new file mode 100644 index 0000000000..e94a9d93b3 --- /dev/null +++ b/erpnext/patches/v13_0/create_pan_field_for_india.py @@ -0,0 +1,25 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + custom_fields = { + 'Supplier': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'supplier_type' + } + ], + 'Customer': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'customer_type' + } + ] + } + + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index afb1b07ccc..1cbb154125 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -614,11 +614,17 @@ def get_custom_fields(): fieldtype='Currency', insert_after='monthly_hra_exemption', read_only=1, depends_on='house_rent_payment_amount') ], 'Supplier': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'supplier_type' + }, { 'fieldname': 'gst_transporter_id', 'label': 'GST Transporter ID', 'fieldtype': 'Data', - 'insert_after': 'supplier_type', + 'insert_after': 'pan', 'depends_on': 'eval:doc.is_transporter' }, { @@ -640,11 +646,17 @@ def get_custom_fields(): } ], 'Customer': [ + { + 'fieldname': 'pan', + 'label': 'PAN', + 'fieldtype': 'Data', + 'insert_after': 'customer_type' + }, { 'fieldname': 'gst_category', 'label': 'GST Category', 'fieldtype': 'Select', - 'insert_after': 'customer_type', + 'insert_after': 'pan', 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', 'default': 'Unregistered' }, diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index e811435e66..fa6e932d9e 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -16,7 +16,6 @@ "customer_name", "gender", "customer_type", - "pan", "tax_withholding_category", "default_bank_account", "lead_name", @@ -213,8 +212,7 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company", - "unique": 1 + "options": "Company" }, { "depends_on": "represents_company", @@ -486,11 +484,6 @@ "fieldtype": "Check", "label": "Allow Sales Invoice Creation Without Delivery Note" }, - { - "fieldname": "pan", - "fieldtype": "Data", - "label": "PAN" - }, { "fieldname": "tax_withholding_category", "fieldtype": "Link", @@ -517,11 +510,12 @@ "link_fieldname": "party" } ], - "modified": "2021-09-06 17:38:54.196663", + "modified": "2021-10-20 22:07:52.485809", "modified_by": "Administrator", "module": "Selling", "name": "Customer", "name_case": "Title Case", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { From 9e7022830e058c3ba95f034942cad054e045e8f6 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Mon, 25 Oct 2021 01:34:42 +0530 Subject: [PATCH 351/416] fix: Only add additional depreciation schedule row on sale if depreciation_amount > 0 --- erpnext/assets/doctype/asset/asset.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index f6c2f4caa2..cf62f496ea 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -232,13 +232,15 @@ class Asset(AccountsController): depreciation_amount, days, months = self.get_pro_rata_amt(d, depreciation_amount, from_date, date_of_sale) - self.append("schedules", { - "schedule_date": date_of_sale, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + if depreciation_amount > 0: + self.append("schedules", { + "schedule_date": date_of_sale, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + break # For first row From cd4b20313e69affe3a5074a0c514f1a96ece0f6f Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 25 Oct 2021 11:21:55 +0530 Subject: [PATCH 352/416] fix: Test case fixes and linting issues --- erpnext/patches/v13_0/create_pan_field_for_india.py | 1 - erpnext/regional/india/utils.py | 2 +- erpnext/selling/doctype/customer/customer.json | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v13_0/create_pan_field_for_india.py b/erpnext/patches/v13_0/create_pan_field_for_india.py index e94a9d93b3..01d4305bc4 100644 --- a/erpnext/patches/v13_0/create_pan_field_for_india.py +++ b/erpnext/patches/v13_0/create_pan_field_for_india.py @@ -1,4 +1,3 @@ -import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 0e4128024d..1733220c0a 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -62,7 +62,7 @@ def validate_gstin_for_india(doc, method): .format(doc.gst_state_number), title=_("Invalid GSTIN")) def validate_pan_for_india(doc, method): - if doc.get('country') != 'India' or not doc.pan: + if doc.get('country') != 'India' or not doc.get('pan'): return if not PAN_NUMBER_FORMAT.match(doc.pan): diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index fa6e932d9e..ae40630617 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -212,7 +212,8 @@ "fieldtype": "Link", "ignore_user_permissions": 1, "label": "Represents Company", - "options": "Company" + "options": "Company", + "unique": 1 }, { "depends_on": "represents_company", From fc8307621c091f228ba3d513826f7cddcc06ea9e Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Mon, 25 Oct 2021 13:18:08 +0530 Subject: [PATCH 353/416] fix: POS Closing Entry without linked invoices (#28042) --- .../doctype/pos_closing_entry/pos_closing_entry.json | 5 ++--- .../doctype/pos_invoice_merge_log/pos_invoice_merge_log.py | 5 ++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json index 4d6e4a2ba0..d6e35c6a50 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json @@ -180,8 +180,7 @@ "fieldname": "pos_transactions", "fieldtype": "Table", "label": "POS Transactions", - "options": "POS Invoice Reference", - "reqd": 1 + "options": "POS Invoice Reference" }, { "fieldname": "pos_opening_entry", @@ -229,7 +228,7 @@ "link_fieldname": "pos_closing_entry" } ], - "modified": "2021-05-05 16:59:49.723261", + "modified": "2021-10-20 16:19:25.340565", "modified_by": "Administrator", "module": "Accounts", "name": "POS Closing Entry", diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 9dae3a7b75..4f26ed43db 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -246,7 +246,10 @@ def get_invoice_customer_map(pos_invoices): return pos_invoice_customer_map def consolidate_pos_invoices(pos_invoices=None, closing_entry=None): - invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) or get_all_unconsolidated_invoices() + invoices = pos_invoices or (closing_entry and closing_entry.get('pos_transactions')) + if frappe.flags.in_test and not invoices: + invoices = get_all_unconsolidated_invoices() + invoice_by_customer = get_invoice_customer_map(invoices) if len(invoices) >= 10 and closing_entry: From 9c1705205f4597639c800b04eaf421bb7e987844 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 25 Oct 2021 20:06:24 +0530 Subject: [PATCH 354/416] fix: Payment Terms validation precision --- erpnext/controllers/accounts_controller.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 88c439b4f6..b05d1b6f6d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1354,8 +1354,8 @@ class AccountsController(TransactionBase): total = 0 base_total = 0 for d in self.get("payment_schedule"): - total += flt(d.payment_amount) - base_total += flt(d.base_payment_amount) + total += flt(d.payment_amount, d.precision("payment_amount")) + base_total += flt(d.base_payment_amount, d.precision("base_payment_amount")) base_grand_total = self.get("base_rounded_total") or self.base_grand_total grand_total = self.get("rounded_total") or self.grand_total @@ -1371,8 +1371,9 @@ class AccountsController(TransactionBase): else: grand_total -= self.get("total_advance") base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total")) - if total != flt(grand_total, self.precision("grand_total")) or \ - base_total != flt(base_grand_total, self.precision("base_grand_total")): + + if flt(total, self.precision("grand_total")) != flt(grand_total, self.precision("grand_total")) or \ + flt(base_total, self.precision("base_grand_total")) != flt(base_grand_total, self.precision("base_grand_total")): frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total")) def is_rounded_total_disabled(self): From 07a3be6f6124ddc9db97a0f22d9c1c886f1782ab Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira <33246109+kennethsequeira@users.noreply.github.com> Date: Mon, 25 Oct 2021 20:10:52 +0530 Subject: [PATCH 355/416] fix: remove VM from installation (#28075) --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 6fad8f4fcd..1105a97005 100644 --- a/README.md +++ b/README.md @@ -55,14 +55,6 @@ The Easy Way: our install script for bench will install all dependencies (e.g. M New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt). -### Virtual Image - -You can download a virtual image to run ERPNext in a virtual machine on your local system. - -- [ERPNext Download](http://erpnext.com/download) - -System and user credentials are listed on the download page. - --- ## License From 89d5e494ddbe82912a65400ebf7f7b4ff565a8b0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 26 Oct 2021 19:51:03 +0530 Subject: [PATCH 356/416] fix: Reload customer and supplier doctype --- erpnext/patches.txt | 2 +- erpnext/patches/v13_0/create_pan_field_for_india.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9bcab5e74a..20e54e08e6 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -310,4 +310,4 @@ erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v14_0.delete_healthcare_doctypes -erpnext.patches.v13_0.create_pan_field_for_india +erpnext.patches.v13_0.create_pan_field_for_india #2 diff --git a/erpnext/patches/v13_0/create_pan_field_for_india.py b/erpnext/patches/v13_0/create_pan_field_for_india.py index 01d4305bc4..c37651aaa3 100644 --- a/erpnext/patches/v13_0/create_pan_field_for_india.py +++ b/erpnext/patches/v13_0/create_pan_field_for_india.py @@ -1,7 +1,11 @@ +import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def execute(): + frappe.reload_doc('buying', 'doctype', 'supplier', force=True) + frappe.reload_doc('selling', 'doctype', 'customer', force=True) + custom_fields = { 'Supplier': [ { From 82bf5e55393520f7681615e1addf4ceee4319ee5 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 20:53:47 +0530 Subject: [PATCH 357/416] fix: Replace post_depreciation_entries() with make_depreciation_entry() --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 379bbbe911..1f0b3c244b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -37,7 +37,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - post_depreciation_entries, + make_depreciation_entry ) from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data @@ -1001,7 +1001,7 @@ class SalesInvoice(SellingController): asset.prepare_depreciation_data(date_of_sale=self.posting_date) asset.save() - post_depreciation_entries(self.posting_date) + make_depreciation_entry(asset.name, self.posting_date) def reset_depreciation_schedule(self, asset): asset.flags.ignore_validate_update_after_submit = True From cde0dae98733563c02c7e364de2b97d9831a3e97 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 26 Oct 2021 21:04:06 +0530 Subject: [PATCH 358/416] fix: Add flag for reverse depreciation entries --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 ++- erpnext/accounts/doctype/journal_entry/journal_entry.py | 2 +- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 1e983b1d42..60015f6ec8 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -58,7 +58,8 @@ class GLEntry(Document): # Update outstanding amt on against voucher if (self.against_voucher_type in ['Journal Entry', 'Sales Invoice', 'Purchase Invoice', 'Fees'] - and self.against_voucher and self.flags.update_outstanding == 'Yes'): + and self.against_voucher and self.flags.update_outstanding == 'Yes' + and not frappe.flags.is_reverse_depr_entry): update_outstanding_amt(self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index d04e4a5d8b..f3a0bdbec4 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -58,7 +58,7 @@ class JournalEntry(AccountsController): if not frappe.flags.in_import: self.validate_total_debit_and_credit() - if not self.flags.is_reverse_depr_entry: + if not frappe.flags.is_reverse_depr_entry: self.validate_against_jv() self.validate_stock_accounts() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1f0b3c244b..ef72decaa2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1045,7 +1045,7 @@ class SalesInvoice(SellingController): reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry) reverse_journal_entry.posting_date = nowdate() - reverse_journal_entry.flags.is_reverse_depr_entry = True + frappe.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() asset.flags.ignore_validate_update_after_submit = True From 06c505ddc2c3529703a35a986896112de7107ae3 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Wed, 27 Oct 2021 05:23:01 +0530 Subject: [PATCH 359/416] fix: Linters --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 2 +- erpnext/assets/doctype/asset/test_asset.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ef72decaa2..1c287d3f8c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -37,7 +37,7 @@ from erpnext.assets.doctype.asset.depreciation import ( get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain, - make_depreciation_entry + make_depreciation_entry, ) from erpnext.controllers.selling_controller import SellingController from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index c44c63ae9e..81c679f851 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -20,6 +20,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( ) from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt + class AssetSetup(unittest.TestCase): @classmethod def setUpClass(cls): From 6ec047cba9418815712efbe71abc147677ebfc0e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 10:30:05 +0530 Subject: [PATCH 360/416] fix(ux): overbiling message in SO->SI, PO->PI (#28088) --- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/status_updater.py | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 88c439b4f6..904f221718 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1032,7 +1032,7 @@ class AccountsController(TransactionBase): if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1: frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") - .format(total_overbilled_amt, role_allowed_to_over_bill), title=_("Warning"), indicator="orange") + .format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True) def throw_overbill_exception(self, item, max_allowed_amt): frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 8738204ce0..49a76da697 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -216,11 +216,14 @@ class StatusUpdater(Document): overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / item[args['target_ref_field']]) * 100 - if overflow_percent - allowance > 0.01 and role not in frappe.get_roles(): + if overflow_percent - allowance > 0.01: item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100) item['reduce_by'] = item[args['target_field']] - item['max_allowed'] - self.limits_crossed_error(args, item, qty_or_amount) + if role not in frappe.get_roles(): + self.limits_crossed_error(args, item, qty_or_amount) + else: + self.warn_about_bypassing_with_role(item, qty_or_amount, role) def limits_crossed_error(self, args, item, qty_or_amount): '''Raise exception for limits crossed''' @@ -238,6 +241,19 @@ class StatusUpdater(Document): frappe.bold(item.get('item_code')) ) + '

    ' + action_msg, OverAllowanceError, title = _('Limit Crossed')) + def warn_about_bypassing_with_role(self, item, qty_or_amount, role): + action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling") + + msg = (_("{} of {} {} ignored for item {} because you have {} role.") + .format( + action, + _(item["target_ref_field"].title()), + frappe.bold(item["reduce_by"]), + frappe.bold(item.get('item_code')), + role) + ) + frappe.msgprint(msg, indicator="orange", alert=True) + def update_qty(self, update_modified=True): """Updates qty or amount at row level From f2d136e57475421694c12c97b9eba6ccb8e45f76 Mon Sep 17 00:00:00 2001 From: Diksha Jadhav Date: Tue, 5 Oct 2021 14:50:29 +0530 Subject: [PATCH 361/416] feat(pick list): group items based on item code and warehouse before printing picklist --- .../stock/doctype/pick_list/pick_list.json | 22 +++++++++++++--- erpnext/stock/doctype/pick_list/pick_list.py | 26 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json index 2146793537..c604c711ef 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.json +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -18,7 +18,9 @@ "get_item_locations", "section_break_6", "locations", - "amended_from" + "amended_from", + "print_settings_section", + "group_same_items" ], "fields": [ { @@ -110,14 +112,28 @@ "options": "STO-PICK-.YYYY.-", "reqd": 1, "set_only_once": 1 + }, + { + "fieldname": "print_settings_section", + "fieldtype": "Section Break", + "label": "Print Settings" + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "group_same_items", + "fieldtype": "Check", + "label": "Group Same Items", + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-03-17 11:38:41.932875", + "modified": "2021-10-05 15:08:40.369957", "modified_by": "Administrator", "module": "Stock", "name": "Pick List", + "naming_rule": "By \"Naming Series\" field", "owner": "Administrator", "permissions": [ { @@ -184,4 +200,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index dffbe80fa3..7b2f44b084 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -121,6 +121,32 @@ class PickList(Document): and (self.for_qty is None or self.for_qty == 0): frappe.throw(_("Qty of Finished Goods Item should be greater than 0.")) + def before_print(self, settings=None): + if self.get("group_same_items"): + self.group_similar_items() + + def group_similar_items(self): + group_item_qty = {} + group_picked_qty = {} + count = 0 + + for item in self.locations: + group_item_qty[(item.item_code, item.warehouse)] = group_item_qty.get((item.item_code,item.warehouse), 0) + item.qty + group_picked_qty[(item.item_code, item.warehouse)] = group_picked_qty.get((item.item_code,item.warehouse), 0) + item.picked_qty + + duplicate_list = [] + for item in self.locations: + if (item.item_code, item.warehouse) in group_item_qty: + count += 1 + item.qty = group_item_qty[(item.item_code, item.warehouse)] + item.picked_qty = group_picked_qty[(item.item_code, item.warehouse)] + item.stock_qty = group_item_qty[(item.item_code, item.warehouse)] + item.idx = count + del group_item_qty[(item.item_code, item.warehouse)] + else: + duplicate_list.append(item) + for item in duplicate_list: + self.remove(item) def validate_item_locations(pick_list): if not pick_list.locations: From 69429005551161e63cda5c2b2fcda1a685280775 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 10:36:06 +0530 Subject: [PATCH 362/416] refactor: use defaultdict and enumeration --- erpnext/stock/doctype/pick_list/pick_list.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 7b2f44b084..4c02f3db43 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -2,10 +2,8 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt -from __future__ import unicode_literals - import json -from collections import OrderedDict +from collections import OrderedDict, defaultdict import frappe from frappe import _ @@ -126,28 +124,30 @@ class PickList(Document): self.group_similar_items() def group_similar_items(self): - group_item_qty = {} - group_picked_qty = {} - count = 0 + group_item_qty = defaultdict(float) + group_picked_qty = defaultdict(float) for item in self.locations: - group_item_qty[(item.item_code, item.warehouse)] = group_item_qty.get((item.item_code,item.warehouse), 0) + item.qty - group_picked_qty[(item.item_code, item.warehouse)] = group_picked_qty.get((item.item_code,item.warehouse), 0) + item.picked_qty + group_item_qty[(item.item_code, item.warehouse)] += item.qty + group_picked_qty[(item.item_code, item.warehouse)] += item.picked_qty duplicate_list = [] for item in self.locations: if (item.item_code, item.warehouse) in group_item_qty: - count += 1 item.qty = group_item_qty[(item.item_code, item.warehouse)] item.picked_qty = group_picked_qty[(item.item_code, item.warehouse)] item.stock_qty = group_item_qty[(item.item_code, item.warehouse)] - item.idx = count del group_item_qty[(item.item_code, item.warehouse)] else: duplicate_list.append(item) + for item in duplicate_list: self.remove(item) + for idx, item in enumerate(self.locations, start=1): + item.idx = idx + + def validate_item_locations(pick_list): if not pick_list.locations: frappe.throw(_("Add items in the Item Locations table")) From 479ecb8de0c77beafddd243c1b5fe38793ea32de Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 10:51:47 +0530 Subject: [PATCH 363/416] test: picklist item grouping --- .../stock/doctype/pick_list/test_pick_list.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index fd0b3680df..58b46e1eef 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe import _dict test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] @@ -356,6 +357,39 @@ class TestPickList(ERPNextTestCase): sales_order.cancel() purchase_receipt.cancel() + def test_pick_list_grouping_before_print(self): + def _compare_dicts(a, b): + "compare dicts but ignore missing keys in `a`" + for key, value in a.items(): + self.assertEqual(b.get(key), value, msg=f"{key} doesn't match") + + # nothing should be grouped + pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[ + _dict(item_code="A", warehouse="X", qty=1, picked_qty=2), + _dict(item_code="B", warehouse="X", qty=1, picked_qty=2), + _dict(item_code="A", warehouse="Y", qty=1, picked_qty=2), + _dict(item_code="B", warehouse="Y", qty=1, picked_qty=2), + ]) + pl.before_print() + self.assertEqual(len(pl.locations), 4) + + # grouping should halve the number of items + pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[ + _dict(item_code="A", warehouse="X", qty=5, picked_qty=1), + _dict(item_code="B", warehouse="Y", qty=4, picked_qty=2), + _dict(item_code="A", warehouse="X", qty=3, picked_qty=2), + _dict(item_code="B", warehouse="Y", qty=2, picked_qty=2), + ]) + pl.before_print() + self.assertEqual(len(pl.locations), 2) + + expected_items = [ + _dict(item_code="A", warehouse="X", qty=8, picked_qty=3), + _dict(item_code="B", warehouse="Y", qty=6, picked_qty=4), + ] + for expected_item, created_item in zip(expected_items, pl.locations): + _compare_dicts(expected_item, created_item) + # def test_pick_list_skips_items_in_expired_batch(self): # pass From 2920f2f61479448e6467c97b7ba696bb90d9c092 Mon Sep 17 00:00:00 2001 From: Dany Robert Date: Wed, 27 Oct 2021 11:17:48 +0530 Subject: [PATCH 364/416] fix: change modified timestamp to apply changes on migrate #28095 fix: change modified timestamp to apply changes on migrate --- .../expense_taxes_and_charges/expense_taxes_and_charges.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json index 4a1064b66b..2f7b8fcf67 100644 --- a/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json +++ b/erpnext/hr/doctype/expense_taxes_and_charges/expense_taxes_and_charges.json @@ -100,7 +100,7 @@ ], "istable": 1, "links": [], - "modified": "2020-09-23 20:27:36.027728", + "modified": "2021-10-26 20:27:36.027728", "modified_by": "Administrator", "module": "HR", "name": "Expense Taxes and Charges", From 25d1c1ce86ec9d66b50bdf12d4954631d22d50c5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 27 Oct 2021 11:21:26 +0530 Subject: [PATCH 365/416] fix: add filter to query to avoid send reminder for zero years (#28092) (#28096) Co-authored-by: Rucha Mahabal (cherry picked from commit 8cad23b8fb4d830a5703e107c371ae9319bc003d) Co-authored-by: gsi-maruiz <62341390+gsi-maruiz@users.noreply.github.com> --- erpnext/hr/doctype/employee/employee_reminders.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py index 216d8f6bb3..559bd393e6 100644 --- a/erpnext/hr/doctype/employee/employee_reminders.py +++ b/erpnext/hr/doctype/employee/employee_reminders.py @@ -156,6 +156,8 @@ def get_employees_having_an_event_today(event_type): DAY({condition_column}) = DAY(%(today)s) AND MONTH({condition_column}) = MONTH(%(today)s) + AND + YEAR({condition_column}) < YEAR(%(today)s) AND `status` = 'Active' """, @@ -166,6 +168,8 @@ def get_employees_having_an_event_today(event_type): DATE_PART('day', {condition_column}) = date_part('day', %(today)s) AND DATE_PART('month', {condition_column}) = date_part('month', %(today)s) + AND + DATE_PART('year', {condition_column}) < date_part('year', %(today)s) AND "status" = 'Active' """, From 05831b18ad08ffcbc8a332ed74697efae1bd870a Mon Sep 17 00:00:00 2001 From: Alan <2.alan.tom@gmail.com> Date: Wed, 27 Oct 2021 11:36:37 +0530 Subject: [PATCH 366/416] fix: update production plan status #27567 fix: update production plan status --- .../production_plan/production_plan.py | 11 ++++++- erpnext/patches.txt | 1 + .../v12_0/update_production_plan_status.py | 31 +++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/update_production_plan_status.py diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 7e6fc3c4a6..2424ef9a71 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -311,7 +311,7 @@ class ProductionPlan(Document): if self.total_produced_qty > 0: self.status = "In Process" - if self.total_produced_qty >= self.total_planned_qty: + if self.check_have_work_orders_completed(): self.status = "Completed" if self.status != 'Completed': @@ -575,6 +575,15 @@ class ProductionPlan(Document): self.append("sub_assembly_items", data) + def check_have_work_orders_completed(self): + wo_status = frappe.db.get_list( + "Work Order", + filters={"production_plan": self.name}, + fields="status", + pluck="status" + ) + return all(s == "Completed" for s in wo_status) + @frappe.whitelist() def download_raw_materials(doc, warehouses=None): if isinstance(doc, str): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 20e54e08e6..1dac50c6e1 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -308,6 +308,7 @@ erpnext.patches.v13_0.set_status_in_maintenance_schedule_table erpnext.patches.v13_0.add_default_interview_notification_templates erpnext.patches.v13_0.enable_scheduler_job_for_item_reposting erpnext.patches.v13_0.requeue_failed_reposts +erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v14_0.delete_healthcare_doctypes erpnext.patches.v13_0.create_pan_field_for_india #2 diff --git a/erpnext/patches/v12_0/update_production_plan_status.py b/erpnext/patches/v12_0/update_production_plan_status.py new file mode 100644 index 0000000000..06fc503a33 --- /dev/null +++ b/erpnext/patches/v12_0/update_production_plan_status.py @@ -0,0 +1,31 @@ +# Copyright (c) 2021, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe + + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "production_plan") + frappe.db.sql(""" + UPDATE `tabProduction Plan` ppl + SET status = "Completed" + WHERE ppl.name IN ( + SELECT ss.name FROM ( + SELECT + ( + count(wo.status = "Completed") = + count(pp.name) + ) = + ( + pp.status != "Completed" + AND pp.total_produced_qty >= pp.total_planned_qty + ) AS should_set, + pp.name AS name + FROM + `tabWork Order` wo INNER JOIN`tabProduction Plan` pp + ON wo.production_plan = pp.name + GROUP BY pp.name + HAVING should_set = 1 + ) ss + ) + """) From 0806e32049fee67b731cb8c742fc6654faae6143 Mon Sep 17 00:00:00 2001 From: Summayya Hashmani <58825865+sumaiya2908@users.noreply.github.com> Date: Wed, 27 Oct 2021 11:46:10 +0530 Subject: [PATCH 367/416] fix(ux): add naming series to ERPNext setting workspace (#28090) * fix(ux): add naming series to setting workspace * fix: doctype link to naming series Co-authored-by: Ankush Menat Co-authored-by: Summayya Co-authored-by: Ankush Menat --- .../workspace/erpnext_settings/erpnext_settings.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json index 320cb7ba84..1412acfcea 100644 --- a/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json +++ b/erpnext/setup/workspace/erpnext_settings/erpnext_settings.json @@ -1,6 +1,6 @@ { "charts": [], - "content": "[{\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Projects Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Accounts Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Stock Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"HR Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Selling Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Buying Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Support Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Shopping Cart Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Portal Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Manufacturing Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Education Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Hotel Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Domain Settings\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Products Settings\", \"col\": 4}}]", + "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":4}}]", "creation": "2020-03-12 14:47:51.166455", "docstatus": 0, "doctype": "Workspace", @@ -10,7 +10,7 @@ "idx": 0, "label": "ERPNext Settings", "links": [], - "modified": "2021-08-05 12:15:59.052328", + "modified": "2021-10-26 21:32:55.323591", "modified_by": "Administrator", "module": "Setup", "name": "ERPNext Settings", @@ -27,6 +27,14 @@ "link_to": "Projects Settings", "type": "DocType" }, + { + "color": "Grey", + "doc_view": "", + "icon": "dot-horizontal", + "label": "Naming Series", + "link_to": "Naming Series", + "type": "DocType" + }, { "icon": "accounting", "label": "Accounts Settings", From f24ed6723ebc16282b3968e9ef5d58e629f7deb1 Mon Sep 17 00:00:00 2001 From: hendrik Date: Wed, 27 Oct 2021 16:08:20 +0700 Subject: [PATCH 368/416] fix(general_ledger): Order by in case Group by Account (#28093) * Update general_ledger.py Fix order_by_statement if filter group by: Group by Account * chore: whitespace Co-authored-by: Ankush Menat Co-authored-by: Afshan <33727827+AfshanKhan@users.noreply.github.com> --- erpnext/accounts/report/general_ledger/general_ledger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 0094bc2eeb..31416da4ac 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -155,6 +155,8 @@ def get_gl_entries(filters, accounting_dimensions): if filters.get("group_by") == "Group by Voucher": order_by_statement = "order by posting_date, voucher_type, voucher_no" + if filters.get("group_by") == "Group by Account": + order_by_statement = "order by account, posting_date, creation" if filters.get("include_default_book_entries"): filters['company_fb'] = frappe.db.get_value("Company", From c9f3ea5fea6ee1d07897d8ae6496d9a26289f900 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Wed, 27 Oct 2021 15:16:38 +0530 Subject: [PATCH 369/416] fix: Remove pointless buttons from Payment Order fix: Remove pointless buttons from Payment Order --- erpnext/accounts/doctype/payment_order/payment_order.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index aa373bc2fc..9074defa57 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -10,6 +10,9 @@ frappe.ui.form.on('Payment Order', { } } }); + + frm.set_df_property('references', 'cannot_add_rows', true); + frm.set_df_property('references', 'cannot_delete_rows', true); }, refresh: function(frm) { if (frm.doc.docstatus == 0) { From e0cf45e7ec60649a35c591e3da44df4b47fb0cdd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 15:27:33 +0530 Subject: [PATCH 370/416] fix(ux): misleading label for image fields (#28107) --- erpnext/manufacturing/doctype/bom/bom.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 7e539183b0..62187077f3 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -436,7 +436,7 @@ "description": "Item Image (if not slideshow)", "fieldname": "website_image", "fieldtype": "Attach Image", - "label": "Image" + "label": "Website Image" }, { "allow_on_submit": 1, @@ -539,7 +539,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-05-16 12:25:09.081968", + "modified": "2021-10-27 14:52:04.500251", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", From a261d08dd868ff6c306a2c8392dddd73d006ab85 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 27 Oct 2021 18:30:37 +0530 Subject: [PATCH 371/416] fix: reset temporary flag after use --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0ca11fd451..cd204ba523 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1048,6 +1048,7 @@ class SalesInvoice(SellingController): frappe.flags.is_reverse_depr_entry = True reverse_journal_entry.submit() + frappe.flags.is_reverse_depr_entry = False asset.flags.ignore_validate_update_after_submit = True schedule.journal_entry = None asset.save() From 3a6894fb9c2803cdd411a577f05f1cbf76ac2b06 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 27 Oct 2021 19:39:18 +0530 Subject: [PATCH 372/416] fix: Autoemail report not showing dynamic report filters --- erpnext/public/js/financial_statements.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 0d79b10c04..1a309ba015 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -113,15 +113,15 @@ function get_filters() { "fieldname":"period_start_date", "label": __("Start Date"), "fieldtype": "Date", - "hidden": 1, - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Date Range'" }, { "fieldname":"period_end_date", "label": __("End Date"), "fieldtype": "Date", - "hidden": 1, - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Date Range'" }, { "fieldname":"from_fiscal_year", @@ -129,7 +129,8 @@ function get_filters() { "fieldtype": "Link", "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, { "fieldname":"to_fiscal_year", @@ -137,7 +138,8 @@ function get_filters() { "fieldtype": "Link", "options": "Fiscal Year", "default": frappe.defaults.get_user_default("fiscal_year"), - "reqd": 1 + "reqd": 1, + "depends_on": "eval:doc.filter_based_on == 'Fiscal Year'" }, { "fieldname": "periodicity", From 5f9bd9b8e9f0dca849f6ad78269cf4151181d5a5 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 18:39:07 +0530 Subject: [PATCH 373/416] fix: remove bad hardcoded max value --- erpnext/manufacturing/doctype/work_order/work_order.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index e282dd3ecb..f881e1bf16 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -685,9 +685,7 @@ class WorkOrder(Document): if not d.operation: d.operation = operation else: - # Attribute a big number (999) to idx for sorting putpose in case idx is NULL - # For instance in BOM Explosion Item child table, the items coming from sub assembly items - for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999): + for item in sorted(item_dict.values(), key=lambda d: d['idx'] or float('inf')): self.append('required_items', { 'rate': item.rate, 'amount': item.rate * item.qty, From 5902762ec8e243c17116732d616426a17f49bc1c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 18:55:54 +0530 Subject: [PATCH 374/416] fix(ux): alternative item two way validation --- .../item_alternative/item_alternative.py | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py index 6080fb4a5f..6f2a389b70 100644 --- a/erpnext/stock/doctype/item_alternative/item_alternative.py +++ b/erpnext/stock/doctype/item_alternative/item_alternative.py @@ -25,19 +25,29 @@ class ItemAlternative(Document): frappe.throw(_("Alternative item must not be same as item code")) item_meta = frappe.get_meta("Item") - fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no","has_batch_no"] - item_data = frappe.db.get_values("Item", self.item_code, fields, as_dict=1) - alternative_item_data = frappe.db.get_values("Item", self.alternative_item_code, fields, as_dict=1) + fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no", "has_batch_no", "allow_alternative_item"] + item_data = frappe.db.get_value("Item", self.item_code, fields, as_dict=1) + alternative_item_data = frappe.db.get_value("Item", self.alternative_item_code, fields, as_dict=1) for field in fields: - if item_data[0].get(field) != alternative_item_data[0].get(field): + if item_data.get(field) != alternative_item_data.get(field): raise_exception, alert = [1, False] if field == "is_stock_item" else [0, True] frappe.msgprint(_("The value of {0} differs between Items {1} and {2}") \ .format(frappe.bold(item_meta.get_label(field)), frappe.bold(self.alternative_item_code), frappe.bold(self.item_code)), - alert=alert, raise_exception=raise_exception) + alert=alert, raise_exception=raise_exception, indicator="Orange") + + alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {}") + + if not item_data.allow_alternative_item: + frappe.throw(alternate_item_check_msg.format(self.item_code)) + if self.two_way and not alternative_item_data.allow_alternative_item: + frappe.throw(alternate_item_check_msg.format(self.item_code)) + + + def validate_duplicate(self): if frappe.db.get_value("Item Alternative", {'item_code': self.item_code, From 2221c9ed89467ee7db7289da7b15778aed0c68a0 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 19:15:44 +0530 Subject: [PATCH 375/416] fix: don't show blocked supplier in autocomplete --- erpnext/controllers/queries.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 7b4566a2fa..eeb659dee5 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -132,7 +132,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select {field} from `tabSupplier` where docstatus < 2 and ({key} like %(txt)s - or supplier_name like %(txt)s) and disabled=0 + or supplier_name like %(txt)s) and disabled=0 + and (on_hold = 0 or (on_hold = 1 and CURDATE() > release_date)) {mcond} order by if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), From d81b87d9b3c83413adf4e1a87fccb95486b50931 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 27 Oct 2021 19:22:11 +0530 Subject: [PATCH 376/416] fix(ux): make qty 1 by default in WO --- erpnext/manufacturing/doctype/work_order/work_order.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 913fc85af6..7f8e816a22 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -182,6 +182,7 @@ "reqd": 1 }, { + "default": "1.0", "fieldname": "qty", "fieldtype": "Float", "label": "Qty To Manufacture", @@ -572,10 +573,11 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-08-24 15:14:03.844937", + "modified": "2021-10-27 19:21:35.139888", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", + "naming_rule": "By \"Naming Series\" field", "nsm_parent_field": "parent_work_order", "owner": "Administrator", "permissions": [ From 4787a7520847456a510577b708acfc22b8e4840b Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Thu, 28 Oct 2021 11:13:11 +0530 Subject: [PATCH 377/416] fix: opportunity link is missign from customer (#28110) --- erpnext/crm/doctype/opportunity/opportunity.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 55e0efaab1..0e469ac642 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -314,6 +314,8 @@ def make_request_for_quotation(source_name, target_doc=None): @frappe.whitelist() def make_customer(source_name, target_doc=None): def set_missing_values(source, target): + target.opportunity_name = source.name + if source.opportunity_from == "Lead": target.lead_name = source.party_name From d786855d94a8d60be34ddd34e4b7a013bc7d4480 Mon Sep 17 00:00:00 2001 From: Subin Tom Date: Thu, 28 Oct 2021 13:19:19 +0530 Subject: [PATCH 378/416] fix: Consolidated Financial Report throws error for empty equity data list --- .../consolidated_financial_statement.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 0de2a9854d..0475231a93 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -114,8 +114,9 @@ def prepare_companywise_opening_balance(asset_data, liability_data, equity_data, # opening_value = Aseet - liability - equity for data in [asset_data, liability_data, equity_data]: - account_name = get_root_account_name(data[0].root_type, company) - opening_value += (get_opening_balance(account_name, data, company) or 0.0) + if data: + account_name = get_root_account_name(data[0].root_type, company) + opening_value += (get_opening_balance(account_name, data, company) or 0.0) opening_balance[company] = opening_value From b01635e1da0353d554b0de39d48a0fc275efc7d7 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 28 Oct 2021 14:07:15 +0530 Subject: [PATCH 379/416] refactor!: remove hub #28117 refactor!: remove hub --- .../doctype/pos_settings/pos_settings.js | 4 +- erpnext/demo/data/drug_list.json | 63 --- erpnext/hub/__init__.py | 0 erpnext/hub_node/__init__.py | 19 - erpnext/hub_node/api.py | 233 ---------- .../company_to_hub_company.json | 50 --- .../hub_message_to_lead.json | 31 -- .../item_to_hub_item/item_to_hub_item.json | 55 --- .../hub_sync/hub_sync.json | 19 - erpnext/hub_node/doctype/__init__.py | 0 .../doctype/hub_tracked_item/__init__.py | 0 .../hub_tracked_item/hub_tracked_item.js | 8 - .../hub_tracked_item/hub_tracked_item.json | 210 --------- .../hub_tracked_item/hub_tracked_item.py | 11 - .../hub_tracked_item/test_hub_tracked_item.py | 10 - erpnext/hub_node/doctype/hub_user/__init__.py | 0 .../hub_node/doctype/hub_user/hub_user.json | 140 ------ erpnext/hub_node/doctype/hub_user/hub_user.py | 11 - .../hub_node/doctype/hub_users/__init__.py | 0 .../hub_node/doctype/hub_users/hub_users.json | 72 --- .../hub_node/doctype/hub_users/hub_users.py | 11 - .../doctype/marketplace_settings/__init__.py | 0 .../marketplace_settings.js | 8 - .../marketplace_settings.json | 410 ------------------ .../marketplace_settings.py | 93 ---- .../test_marketplace_settings.py | 10 - erpnext/hub_node/legacy.py | 148 ------- erpnext/modules.txt | 3 +- erpnext/patches.txt | 6 +- erpnext/patches/v10_0/delete_hub_documents.py | 19 - .../reset_publish_in_hub_for_all_items.py | 8 - erpnext/patches/v11_0/update_hub_url.py | 8 - .../set_published_in_hub_tracked_item.py | 14 - erpnext/patches/v14_0/delete_hub_doctypes.py | 10 + erpnext/public/build.json | 8 - erpnext/public/images/hub_logo.svg | 112 ----- erpnext/public/js/erpnext.bundle.js | 1 - erpnext/public/js/hub/PageContainer.vue | 119 ----- erpnext/public/js/hub/Sidebar.vue | 110 ----- .../public/js/hub/components/CommentInput.vue | 39 -- .../js/hub/components/DetailHeaderItem.vue | 26 -- .../public/js/hub/components/DetailView.vue | 86 ---- .../public/js/hub/components/EmptyState.vue | 50 --- erpnext/public/js/hub/components/Image.vue | 40 -- erpnext/public/js/hub/components/ItemCard.vue | 142 ------ .../js/hub/components/ItemCardsContainer.vue | 62 --- .../public/js/hub/components/ItemListCard.vue | 21 - .../js/hub/components/NotificationMessage.vue | 38 -- erpnext/public/js/hub/components/Rating.vue | 16 - .../public/js/hub/components/ReviewArea.vue | 140 ------ .../js/hub/components/ReviewTimelineItem.vue | 53 --- .../public/js/hub/components/SearchInput.vue | 26 -- .../js/hub/components/SectionHeader.vue | 3 - .../public/js/hub/components/TimelineItem.vue | 9 - .../js/hub/components/edit_details_dialog.js | 41 -- .../js/hub/components/item_publish_dialog.js | 39 -- .../js/hub/components/profile_dialog.js | 56 --- erpnext/public/js/hub/components/reviews.js | 80 ---- erpnext/public/js/hub/hub_call.js | 68 --- erpnext/public/js/hub/hub_factory.js | 34 -- erpnext/public/js/hub/marketplace.bundle.js | 225 ---------- erpnext/public/js/hub/pages/Buying.vue | 56 --- erpnext/public/js/hub/pages/Category.vue | 76 ---- erpnext/public/js/hub/pages/FeaturedItems.vue | 116 ----- erpnext/public/js/hub/pages/Home.vue | 114 ----- erpnext/public/js/hub/pages/Item.vue | 356 --------------- erpnext/public/js/hub/pages/Messages.vue | 104 ----- erpnext/public/js/hub/pages/NotFound.vue | 36 -- erpnext/public/js/hub/pages/Publish.vue | 212 --------- .../public/js/hub/pages/PublishedItems.vue | 74 ---- erpnext/public/js/hub/pages/SavedItems.vue | 116 ----- erpnext/public/js/hub/pages/Search.vue | 81 ---- erpnext/public/js/hub/pages/Seller.vue | 201 --------- erpnext/public/js/hub/pages/SellerItems.vue | 57 --- erpnext/public/js/hub/pages/Selling.vue | 66 --- erpnext/public/js/hub/vue-plugins.js | 58 --- .../operations/install_fixtures.py | 1 - erpnext/stock/doctype/item/item.json | 54 +-- erpnext/stock/doctype/item/item.py | 1 - 79 files changed, 17 insertions(+), 5090 deletions(-) delete mode 100644 erpnext/hub/__init__.py delete mode 100644 erpnext/hub_node/__init__.py delete mode 100644 erpnext/hub_node/api.py delete mode 100644 erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json delete mode 100644 erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json delete mode 100644 erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json delete mode 100644 erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json delete mode 100644 erpnext/hub_node/doctype/__init__.py delete mode 100644 erpnext/hub_node/doctype/hub_tracked_item/__init__.py delete mode 100644 erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js delete mode 100644 erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json delete mode 100644 erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py delete mode 100644 erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py delete mode 100644 erpnext/hub_node/doctype/hub_user/__init__.py delete mode 100644 erpnext/hub_node/doctype/hub_user/hub_user.json delete mode 100644 erpnext/hub_node/doctype/hub_user/hub_user.py delete mode 100644 erpnext/hub_node/doctype/hub_users/__init__.py delete mode 100644 erpnext/hub_node/doctype/hub_users/hub_users.json delete mode 100644 erpnext/hub_node/doctype/hub_users/hub_users.py delete mode 100644 erpnext/hub_node/doctype/marketplace_settings/__init__.py delete mode 100644 erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js delete mode 100644 erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json delete mode 100644 erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py delete mode 100644 erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py delete mode 100644 erpnext/hub_node/legacy.py delete mode 100644 erpnext/patches/v10_0/delete_hub_documents.py delete mode 100644 erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py delete mode 100644 erpnext/patches/v11_0/update_hub_url.py delete mode 100644 erpnext/patches/v12_0/set_published_in_hub_tracked_item.py create mode 100644 erpnext/patches/v14_0/delete_hub_doctypes.py delete mode 100644 erpnext/public/images/hub_logo.svg delete mode 100644 erpnext/public/js/hub/PageContainer.vue delete mode 100644 erpnext/public/js/hub/Sidebar.vue delete mode 100644 erpnext/public/js/hub/components/CommentInput.vue delete mode 100644 erpnext/public/js/hub/components/DetailHeaderItem.vue delete mode 100644 erpnext/public/js/hub/components/DetailView.vue delete mode 100644 erpnext/public/js/hub/components/EmptyState.vue delete mode 100644 erpnext/public/js/hub/components/Image.vue delete mode 100644 erpnext/public/js/hub/components/ItemCard.vue delete mode 100644 erpnext/public/js/hub/components/ItemCardsContainer.vue delete mode 100644 erpnext/public/js/hub/components/ItemListCard.vue delete mode 100644 erpnext/public/js/hub/components/NotificationMessage.vue delete mode 100644 erpnext/public/js/hub/components/Rating.vue delete mode 100644 erpnext/public/js/hub/components/ReviewArea.vue delete mode 100644 erpnext/public/js/hub/components/ReviewTimelineItem.vue delete mode 100644 erpnext/public/js/hub/components/SearchInput.vue delete mode 100644 erpnext/public/js/hub/components/SectionHeader.vue delete mode 100644 erpnext/public/js/hub/components/TimelineItem.vue delete mode 100644 erpnext/public/js/hub/components/edit_details_dialog.js delete mode 100644 erpnext/public/js/hub/components/item_publish_dialog.js delete mode 100644 erpnext/public/js/hub/components/profile_dialog.js delete mode 100644 erpnext/public/js/hub/components/reviews.js delete mode 100644 erpnext/public/js/hub/hub_call.js delete mode 100644 erpnext/public/js/hub/hub_factory.js delete mode 100644 erpnext/public/js/hub/marketplace.bundle.js delete mode 100644 erpnext/public/js/hub/pages/Buying.vue delete mode 100644 erpnext/public/js/hub/pages/Category.vue delete mode 100644 erpnext/public/js/hub/pages/FeaturedItems.vue delete mode 100644 erpnext/public/js/hub/pages/Home.vue delete mode 100644 erpnext/public/js/hub/pages/Item.vue delete mode 100644 erpnext/public/js/hub/pages/Messages.vue delete mode 100644 erpnext/public/js/hub/pages/NotFound.vue delete mode 100644 erpnext/public/js/hub/pages/Publish.vue delete mode 100644 erpnext/public/js/hub/pages/PublishedItems.vue delete mode 100644 erpnext/public/js/hub/pages/SavedItems.vue delete mode 100644 erpnext/public/js/hub/pages/Search.vue delete mode 100644 erpnext/public/js/hub/pages/Seller.vue delete mode 100644 erpnext/public/js/hub/pages/SellerItems.vue delete mode 100644 erpnext/public/js/hub/pages/Selling.vue delete mode 100644 erpnext/public/js/hub/vue-plugins.js diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js index 9003af56a5..7d8f3562c8 100644 --- a/erpnext/accounts/doctype/pos_settings/pos_settings.js +++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js @@ -2,11 +2,11 @@ // For license information, please see license.txt let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor']; -let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series", +let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "asset_naming_series", "default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series", "serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account", "deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail", - "web_long_description", "hub_sync_id"] + "web_long_description"] frappe.ui.form.on('POS Settings', { onload: function(frm) { diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json index e91c30d199..3069042843 100644 --- a/erpnext/demo/data/drug_list.json +++ b/erpnext/demo/data/drug_list.json @@ -60,7 +60,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -144,7 +143,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -226,7 +224,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -308,7 +305,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -390,7 +386,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -472,7 +467,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -554,7 +548,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -636,7 +629,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -718,7 +710,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -800,7 +791,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -882,7 +872,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -964,7 +953,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1046,7 +1034,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1128,7 +1115,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1210,7 +1196,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1292,7 +1277,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1374,7 +1358,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1456,7 +1439,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1538,7 +1520,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1620,7 +1601,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1702,7 +1682,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1784,7 +1763,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1866,7 +1844,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -1948,7 +1925,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2030,7 +2006,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2112,7 +2087,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2194,7 +2168,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2276,7 +2249,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2358,7 +2330,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2440,7 +2411,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2522,7 +2492,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2604,7 +2573,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2686,7 +2654,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2768,7 +2735,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2850,7 +2816,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -2932,7 +2897,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3014,7 +2978,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3098,7 +3061,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3180,7 +3142,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3262,7 +3223,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3344,7 +3304,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3426,7 +3385,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3508,7 +3466,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3590,7 +3547,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3672,7 +3628,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3754,7 +3709,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3836,7 +3790,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -3918,7 +3871,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4000,7 +3952,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4082,7 +4033,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4164,7 +4114,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4246,7 +4195,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4328,7 +4276,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4410,7 +4357,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4492,7 +4438,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4574,7 +4519,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4656,7 +4600,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4738,7 +4681,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4820,7 +4762,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4902,7 +4843,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -4984,7 +4924,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -5066,7 +5005,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, @@ -5148,7 +5086,6 @@ "standard_rate": 0.0, "stock_uom": "Nos", "supplier_items": [], - "synced_with_hub": 0, "taxes": [], "thumbnail": null, "tolerance": 0.0, diff --git a/erpnext/hub/__init__.py b/erpnext/hub/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py deleted file mode 100644 index 6ac3255c12..0000000000 --- a/erpnext/hub_node/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import frappe - - -@frappe.whitelist() -def enable_hub(): - hub_settings = frappe.get_doc('Marketplace Settings') - hub_settings.register() - frappe.db.commit() - return hub_settings - -@frappe.whitelist() -def sync(): - hub_settings = frappe.get_doc('Marketplace Settings') - hub_settings.sync() diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py deleted file mode 100644 index 5530491775..0000000000 --- a/erpnext/hub_node/api.py +++ /dev/null @@ -1,233 +0,0 @@ -from __future__ import unicode_literals - -import json - -import frappe -from frappe import _ -from frappe.desk.form.load import get_attachments -from frappe.frappeclient import FrappeClient -from six import string_types - -current_user = frappe.session.user - - -@frappe.whitelist() -def register_marketplace(company, company_description): - validate_registerer() - - settings = frappe.get_single('Marketplace Settings') - message = settings.register_seller(company, company_description) - - if message.get('hub_seller_name'): - settings.registered = 1 - settings.hub_seller_name = message.get('hub_seller_name') - settings.save() - - settings.add_hub_user(frappe.session.user) - - return { 'ok': 1 } - - -@frappe.whitelist() -def register_users(user_list): - user_list = json.loads(user_list) - - settings = frappe.get_single('Marketplace Settings') - - for user in user_list: - settings.add_hub_user(user) - - return user_list - - -def validate_registerer(): - if current_user == 'Administrator': - frappe.throw(_('Please login as another user to register on Marketplace')) - - valid_roles = ['System Manager', 'Item Manager'] - - if not frappe.utils.is_subset(valid_roles, frappe.get_roles()): - frappe.throw(_('Only users with {0} role can register on Marketplace').format(', '.join(valid_roles)), - frappe.PermissionError) - - -@frappe.whitelist() -def call_hub_method(method, params=None): - connection = get_hub_connection() - - if isinstance(params, string_types): - params = json.loads(params) - - params.update({ - 'cmd': 'hub.hub.api.' + method - }) - - response = connection.post_request(params) - return response - - -def map_fields(items): - field_mappings = get_field_mappings() - table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()] - - hub_seller_name = frappe.db.get_value('Marketplace Settings', 'Marketplace Settings', 'hub_seller_name') - - for item in items: - for fieldname in table_fields: - item.pop(fieldname, None) - - for mapping in field_mappings: - local_fieldname = mapping.get('local_fieldname') - remote_fieldname = mapping.get('remote_fieldname') - - value = item.get(local_fieldname) - item.pop(local_fieldname, None) - item[remote_fieldname] = value - - item['doctype'] = 'Hub Item' - item['hub_seller'] = hub_seller_name - item.pop('attachments', None) - - return items - - -@frappe.whitelist() -def get_valid_items(search_value=''): - items = frappe.get_list( - 'Item', - fields=["*"], - filters={ - 'disabled': 0, - 'item_name': ['like', '%' + search_value + '%'], - 'publish_in_hub': 0 - }, - order_by="modified desc" - ) - - valid_items = filter(lambda x: x.image and x.description, items) - - def prepare_item(item): - item.source_type = "local" - item.attachments = get_attachments('Item', item.item_code) - return item - - valid_items = map(prepare_item, valid_items) - - return valid_items - -@frappe.whitelist() -def update_item(ref_doc, data): - data = json.loads(data) - - data.update(dict(doctype='Hub Item', name=ref_doc)) - try: - connection = get_hub_connection() - connection.update(data) - except Exception as e: - frappe.log_error(message=e, title='Hub Sync Error') - -@frappe.whitelist() -def publish_selected_items(items_to_publish): - items_to_publish = json.loads(items_to_publish) - items_to_update = [] - if not len(items_to_publish): - frappe.throw(_('No items to publish')) - - for item in items_to_publish: - item_code = item.get('item_code') - frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - - hub_dict = { - 'doctype': 'Hub Tracked Item', - 'item_code': item_code, - 'published': 1, - 'hub_category': item.get('hub_category'), - 'image_list': item.get('image_list') - } - frappe.get_doc(hub_dict).insert(ignore_if_duplicate=True) - - items = map_fields(items_to_publish) - - try: - item_sync_preprocess(len(items)) - convert_relative_image_urls_to_absolute(items) - - # TODO: Publish Progress - connection = get_hub_connection() - connection.insert_many(items) - - item_sync_postprocess() - except Exception as e: - frappe.log_error(message=e, title='Hub Sync Error') - -@frappe.whitelist() -def unpublish_item(item_code, hub_item_name): - ''' Remove item listing from the marketplace ''' - - response = call_hub_method('unpublish_item', { - 'hub_item_name': hub_item_name - }) - - if response: - frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) - frappe.delete_doc('Hub Tracked Item', item_code) - else: - frappe.throw(_('Unable to update remote activity')) - -@frappe.whitelist() -def get_unregistered_users(): - settings = frappe.get_single('Marketplace Settings') - registered_users = [user.user for user in settings.users] + ['Administrator', 'Guest'] - all_users = [user.name for user in frappe.db.get_all('User', filters={'enabled': 1})] - unregistered_users = [user for user in all_users if user not in registered_users] - return unregistered_users - - -def item_sync_preprocess(intended_item_publish_count): - response = call_hub_method('pre_items_publish', { - 'intended_item_publish_count': intended_item_publish_count - }) - - if response: - frappe.db.set_value("Marketplace Settings", "Marketplace Settings", "sync_in_progress", 1) - return response - else: - frappe.throw(_('Unable to update remote activity')) - - -def item_sync_postprocess(): - response = call_hub_method('post_items_publish', {}) - if response: - frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'last_sync_datetime', frappe.utils.now()) - else: - frappe.throw(_('Unable to update remote activity')) - - frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'sync_in_progress', 0) - - -def convert_relative_image_urls_to_absolute(items): - from six.moves.urllib.parse import urljoin - - for item in items: - file_path = item['image'] - - if file_path.startswith('/files/'): - item['image'] = urljoin(frappe.utils.get_url(), file_path) - - -def get_hub_connection(): - settings = frappe.get_single('Marketplace Settings') - marketplace_url = settings.marketplace_url - hub_user = settings.get_hub_user(frappe.session.user) - - if hub_user: - password = hub_user.get_password() - hub_connection = FrappeClient(marketplace_url, hub_user.user, password) - return hub_connection - else: - read_only_hub_connection = FrappeClient(marketplace_url) - return read_only_hub_connection - - -def get_field_mappings(): - return [] diff --git a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json b/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json deleted file mode 100644 index b1e421dada..0000000000 --- a/erpnext/hub_node/data_migration_mapping/company_to_hub_company/company_to_hub_company.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "condition": "{'name': ('=', frappe.db.get_single_value('Hub Settings', 'company'))}", - "creation": "2017-09-07 11:38:43.169065", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "name", - "remote_fieldname": "company_name" - }, - { - "is_child_table": 0, - "local_fieldname": "country", - "remote_fieldname": "country" - }, - { - "is_child_table": 0, - "local_fieldname": "\"city\"", - "remote_fieldname": "seller_city" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.local.site", - "remote_fieldname": "site_name" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.session.user", - "remote_fieldname": "user" - }, - { - "is_child_table": 0, - "local_fieldname": "company_logo", - "remote_fieldname": "company_logo" - } - ], - "idx": 2, - "local_doctype": "Company", - "mapping_name": "Company to Hub Company", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "name": "Company to Hub Company", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Company", - "remote_primary_key": "name" -} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json b/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json deleted file mode 100644 index d11abeb4b3..0000000000 --- a/erpnext/hub_node/data_migration_mapping/hub_message_to_lead/hub_message_to_lead.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "condition": "{'reference_doctype': 'Lead', 'user': frappe.db.get_single_value('Hub Settings', 'user'), 'status': 'Pending'}", - "creation": "2017-09-20 15:06:40.279930", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "email_id", - "remote_fieldname": "email_id" - }, - { - "is_child_table": 0, - "local_fieldname": "lead_name", - "remote_fieldname": "lead_name" - } - ], - "idx": 0, - "local_doctype": "Lead", - "local_primary_key": "email_id", - "mapping_name": "Hub Message to Lead", - "mapping_type": "Pull", - "migration_id_field": "hub_sync_id", - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "name": "Hub Message to Lead", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Message", - "remote_primary_key": "name" -} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json b/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json deleted file mode 100644 index bcece69b38..0000000000 --- a/erpnext/hub_node/data_migration_mapping/item_to_hub_item/item_to_hub_item.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "condition": "{\"publish_in_hub\": 1}", - "creation": "2017-09-07 13:27:52.726350", - "docstatus": 0, - "doctype": "Data Migration Mapping", - "fields": [ - { - "is_child_table": 0, - "local_fieldname": "item_code", - "remote_fieldname": "item_code" - }, - { - "is_child_table": 0, - "local_fieldname": "item_name", - "remote_fieldname": "item_name" - }, - { - "is_child_table": 0, - "local_fieldname": "eval:frappe.db.get_value('Hub Settings' , 'Hub Settings', 'company_email')", - "remote_fieldname": "hub_seller" - }, - { - "is_child_table": 0, - "local_fieldname": "image", - "remote_fieldname": "image" - }, - { - "is_child_table": 0, - "local_fieldname": "image_list", - "remote_fieldname": "image_list" - }, - { - "is_child_table": 0, - "local_fieldname": "item_group", - "remote_fieldname": "item_group" - }, - { - "is_child_table": 0, - "local_fieldname": "hub_category", - "remote_fieldname": "hub_category" - } - ], - "idx": 1, - "local_doctype": "Item", - "mapping_name": "Item to Hub Item", - "mapping_type": "Push", - "migration_id_field": "hub_sync_id", - "modified": "2018-08-19 22:20:25.727581", - "modified_by": "Administrator", - "name": "Item to Hub Item", - "owner": "Administrator", - "page_length": 10, - "remote_objectname": "Hub Item", - "remote_primary_key": "item_code" -} \ No newline at end of file diff --git a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json b/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json deleted file mode 100644 index e90b1dd1e8..0000000000 --- a/erpnext/hub_node/data_migration_plan/hub_sync/hub_sync.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "creation": "2017-09-07 11:39:38.445902", - "docstatus": 0, - "doctype": "Data Migration Plan", - "idx": 1, - "mappings": [ - { - "enabled": 1, - "mapping": "Item to Hub Item" - } - ], - "modified": "2018-08-19 22:20:25.644602", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub Sync", - "owner": "Administrator", - "plan_name": "Hub Sync", - "postprocess_method": "erpnext.hub_node.api.item_sync_postprocess" -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/__init__.py b/erpnext/hub_node/doctype/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hub_node/doctype/hub_tracked_item/__init__.py b/erpnext/hub_node/doctype/hub_tracked_item/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js deleted file mode 100644 index 660532d13d..0000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Hub Tracked Item', { - refresh: function(frm) { - - } -}); diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json deleted file mode 100644 index 7d07ba4093..0000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:item_code", - "beta": 0, - "creation": "2018-03-18 09:33:50.267762", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hub_category", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hub Category", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "published", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Published", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image_list", - "fieldtype": "Long Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Image List", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-12-10 11:37:35.951019", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub Tracked Item", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Item Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "read_only": 1, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py deleted file mode 100644 index 823c79eb72..0000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class HubTrackedItem(Document): - pass diff --git a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py b/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py deleted file mode 100644 index c403f902a2..0000000000 --- a/erpnext/hub_node/doctype/hub_tracked_item/test_hub_tracked_item.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestHubTrackedItem(unittest.TestCase): - pass diff --git a/erpnext/hub_node/doctype/hub_user/__init__.py b/erpnext/hub_node/doctype/hub_user/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.json b/erpnext/hub_node/doctype/hub_user/hub_user.json deleted file mode 100644 index f51ffb4387..0000000000 --- a/erpnext/hub_node/doctype/hub_user/hub_user.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2018-08-31 12:36:45.627531", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hub_user_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hub User", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "password", - "fieldtype": "Password", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Hub Password", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub User", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_user/hub_user.py b/erpnext/hub_node/doctype/hub_user/hub_user.py deleted file mode 100644 index 1f7c8fc3f2..0000000000 --- a/erpnext/hub_node/doctype/hub_user/hub_user.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class HubUser(Document): - pass diff --git a/erpnext/hub_node/doctype/hub_users/__init__.py b/erpnext/hub_node/doctype/hub_users/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.json b/erpnext/hub_node/doctype/hub_users/hub_users.json deleted file mode 100644 index d42f3fdf1b..0000000000 --- a/erpnext/hub_node/doctype/hub_users/hub_users.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-03-06 04:38:49.891787", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Hub Users", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/hub_users/hub_users.py b/erpnext/hub_node/doctype/hub_users/hub_users.py deleted file mode 100644 index e08ed68ed8..0000000000 --- a/erpnext/hub_node/doctype/hub_users/hub_users.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -from frappe.model.document import Document - - -class HubUsers(Document): - pass diff --git a/erpnext/hub_node/doctype/marketplace_settings/__init__.py b/erpnext/hub_node/doctype/marketplace_settings/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js deleted file mode 100644 index 36da832c7c..0000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Marketplace Settings', { - refresh: function(frm) { - $('#toolbar-user .marketplace-link').toggle(!frm.doc.disable_marketplace); - }, -}); diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json deleted file mode 100644 index e784f68fcf..0000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.json +++ /dev/null @@ -1,410 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 1, - "creation": "2018-08-31 15:54:38.795263", - "custom": 0, - "description": "", - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 0, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disable_marketplace", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable Marketplace", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.disable_marketplace", - "fieldname": "marketplace_settings_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Marketplace Settings", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "https://hubmarket.org", - "fieldname": "marketplace_url", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Marketplace URL (to hide and update label)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "registered", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Registered", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sync_in_progress", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sync in Progress", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hub_seller_name", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Hub Seller Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "users", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Users", - "length": 0, - "no_copy": 0, - "options": "Hub User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "last_sync_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Last Sync On", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "depends_on": "eval:1", - "fieldname": "custom_data", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Custom Data", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2020-09-18 17:26:09.703215", - "modified_by": "Administrator", - "module": "Hub Node", - "name": "Marketplace Settings", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "All", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py b/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py deleted file mode 100644 index 33d23f6eae..0000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/marketplace_settings.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals - -import json - -import frappe -from frappe.frappeclient import FrappeClient -from frappe.model.document import Document -from frappe.utils import cint - - -class MarketplaceSettings(Document): - - def register_seller(self, company, company_description): - - country, currency, company_logo = frappe.db.get_value('Company', company, - ['country', 'default_currency', 'company_logo']) - - company_details = { - 'company': company, - 'country': country, - 'currency': currency, - 'company_description': company_description, - 'company_logo': company_logo, - 'site_name': frappe.utils.get_url() - } - - hub_connection = self.get_connection() - - response = hub_connection.post_request({ - 'cmd': 'hub.hub.api.add_hub_seller', - 'company_details': json.dumps(company_details) - }) - - return response - - - def add_hub_user(self, user_email): - '''Create a Hub User and User record on hub server - and if successfull append it to Hub User table - ''' - - if not self.registered: - return - - hub_connection = self.get_connection() - - first_name, last_name = frappe.db.get_value('User', user_email, ['first_name', 'last_name']) - - hub_user = hub_connection.post_request({ - 'cmd': 'hub.hub.api.add_hub_user', - 'user_email': user_email, - 'first_name': first_name, - 'last_name': last_name, - 'hub_seller': self.hub_seller_name - }) - - self.append('users', { - 'user': hub_user.get('user_email'), - 'hub_user_name': hub_user.get('hub_user_name'), - 'password': hub_user.get('password') - }) - - self.save() - - def get_hub_user(self, user): - '''Return the Hub User doc from the `users` table if password is set''' - - filtered_users = list(filter( - lambda x: x.user == user and x.password, - self.users - )) - - if filtered_users: - return filtered_users[0] - - - def get_connection(self): - return FrappeClient(self.marketplace_url) - - - def unregister(self): - """Disable the User on hubmarket.org""" - -@frappe.whitelist() -def is_marketplace_enabled(): - if not hasattr(frappe.local, 'is_marketplace_enabled'): - frappe.local.is_marketplace_enabled = cint(frappe.db.get_single_value('Marketplace Settings', - 'disable_marketplace')) - - return frappe.local.is_marketplace_enabled diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py deleted file mode 100644 index 7922f45ab5..0000000000 --- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - - -class TestMarketplaceSettings(unittest.TestCase): - pass diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py deleted file mode 100644 index 2e4c266843..0000000000 --- a/erpnext/hub_node/legacy.py +++ /dev/null @@ -1,148 +0,0 @@ -from __future__ import unicode_literals - -import json - -import frappe -from frappe.contacts.doctype.contact.contact import get_default_contact -from frappe.frappeclient import FrappeClient -from frappe.utils import nowdate -from frappe.utils.nestedset import get_root_of - - -def get_list(doctype, start, limit, fields, filters, order_by): - pass - -def get_hub_connection(): - if frappe.db.exists('Data Migration Connector', 'Hub Connector'): - hub_connector = frappe.get_doc('Data Migration Connector', 'Hub Connector') - hub_connection = hub_connector.get_connection() - return hub_connection.connection - - # read-only connection - hub_connection = FrappeClient(frappe.conf.hub_url) - return hub_connection - -def make_opportunity(buyer_name, email_id): - buyer_name = "HUB-" + buyer_name - - if not frappe.db.exists('Lead', {'email_id': email_id}): - lead = frappe.new_doc("Lead") - lead.lead_name = buyer_name - lead.email_id = email_id - lead.save(ignore_permissions=True) - - o = frappe.new_doc("Opportunity") - o.opportunity_from = "Lead" - o.lead = frappe.get_all("Lead", filters={"email_id": email_id}, fields = ["name"])[0]["name"] - o.save(ignore_permissions=True) - -@frappe.whitelist() -def make_rfq_and_send_opportunity(item, supplier): - supplier = make_supplier(supplier) - contact = make_contact(supplier) - item = make_item(item) - rfq = make_rfq(item, supplier, contact) - status = send_opportunity(contact) - - return { - 'rfq': rfq, - 'hub_document_created': status - } - -def make_supplier(supplier): - # make supplier if not already exists - supplier = frappe._dict(json.loads(supplier)) - - if not frappe.db.exists('Supplier', {'supplier_name': supplier.supplier_name}): - supplier_doc = frappe.get_doc({ - 'doctype': 'Supplier', - 'supplier_name': supplier.supplier_name, - 'supplier_group': supplier.supplier_group, - 'supplier_email': supplier.supplier_email - }).insert() - else: - supplier_doc = frappe.get_doc('Supplier', supplier.supplier_name) - - return supplier_doc - -def make_contact(supplier): - contact_name = get_default_contact('Supplier', supplier.supplier_name) - # make contact if not already exists - if not contact_name: - contact = frappe.get_doc({ - 'doctype': 'Contact', - 'first_name': supplier.supplier_name, - 'is_primary_contact': 1, - 'links': [ - {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} - ] - }) - contact.add_email(supplier.supplier_email, is_primary=True) - contact.insert() - else: - contact = frappe.get_doc('Contact', contact_name) - - return contact - -def make_item(item): - # make item if not already exists - item = frappe._dict(json.loads(item)) - - if not frappe.db.exists('Item', {'item_code': item.item_code}): - item_doc = frappe.get_doc({ - 'doctype': 'Item', - 'item_code': item.item_code, - 'item_group': item.item_group, - 'is_item_from_hub': 1 - }).insert() - else: - item_doc = frappe.get_doc('Item', item.item_code) - - return item_doc - -def make_rfq(item, supplier, contact): - # make rfq - rfq = frappe.get_doc({ - 'doctype': 'Request for Quotation', - 'transaction_date': nowdate(), - 'status': 'Draft', - 'company': frappe.db.get_single_value('Marketplace Settings', 'company'), - 'message_for_supplier': 'Please supply the specified items at the best possible rates', - 'suppliers': [ - { 'supplier': supplier.name, 'contact': contact.name } - ], - 'items': [ - { - 'item_code': item.item_code, - 'qty': 1, - 'schedule_date': nowdate(), - 'warehouse': item.default_warehouse or get_root_of("Warehouse"), - 'description': item.description, - 'uom': item.stock_uom - } - ] - }).insert() - - rfq.save() - rfq.submit() - return rfq - -def send_opportunity(contact): - # Make Hub Message on Hub with lead data - doc = { - 'doctype': 'Lead', - 'lead_name': frappe.db.get_single_value('Marketplace Settings', 'company'), - 'email_id': frappe.db.get_single_value('Marketplace Settings', 'user') - } - - args = frappe._dict(dict( - doctype='Hub Message', - reference_doctype='Lead', - data=json.dumps(doc), - user=contact.email_id - )) - - connection = get_hub_connection() - response = connection.insert('Hub Message', args) - - return response.ok diff --git a/erpnext/modules.txt b/erpnext/modules.txt index a9f94ce133..15a24a746f 100644 --- a/erpnext/modules.txt +++ b/erpnext/modules.txt @@ -20,9 +20,8 @@ Agriculture ERPNext Integrations Non Profit Hotels -Hub Node Quality Management Communication Loan Management Payroll -Telephony +Telephony \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 1dac50c6e1..c533d480f8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -58,11 +58,7 @@ erpnext.patches.v11_0.set_department_for_doctypes erpnext.patches.v11_0.update_allow_transfer_for_manufacture erpnext.patches.v11_0.add_item_group_defaults erpnext.patches.v11_0.add_expense_claim_default_account -execute:frappe.delete_doc("Page", "hub") -erpnext.patches.v11_0.reset_publish_in_hub_for_all_items -erpnext.patches.v11_0.update_hub_url # 2018-08-31 # 2018-09-03 erpnext.patches.v11_0.make_job_card -erpnext.patches.v10_0.delete_hub_documents # 12-08-2018 erpnext.patches.v11_0.add_default_dispatch_notification_template erpnext.patches.v11_0.add_market_segments erpnext.patches.v11_0.add_sales_stages @@ -153,7 +149,6 @@ erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim erpnext.patches.v12_0.add_eway_bill_in_delivery_note erpnext.patches.v12_0.set_lead_title_field erpnext.patches.v12_0.set_permission_einvoicing -erpnext.patches.v12_0.set_published_in_hub_tracked_item erpnext.patches.v12_0.set_job_offer_applicant_email erpnext.patches.v12_0.create_irs_1099_field_united_states erpnext.patches.v12_0.move_bank_account_swift_number_to_bank @@ -312,3 +307,4 @@ erpnext.patches.v12_0.update_production_plan_status erpnext.patches.v13_0.healthcare_deprecation_warning erpnext.patches.v14_0.delete_healthcare_doctypes erpnext.patches.v13_0.create_pan_field_for_india #2 +erpnext.patches.v14_0.delete_hub_doctypes diff --git a/erpnext/patches/v10_0/delete_hub_documents.py b/erpnext/patches/v10_0/delete_hub_documents.py deleted file mode 100644 index 16c7abfc97..0000000000 --- a/erpnext/patches/v10_0/delete_hub_documents.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def execute(): - for dt, dn in (("Page", "Hub"), ("DocType", "Hub Settings"), ("DocType", "Hub Category")): - frappe.delete_doc(dt, dn, ignore_missing=True) - - if frappe.db.exists("DocType", "Data Migration Plan"): - data_migration_plans = frappe.get_all("Data Migration Plan", filters={"module": 'Hub Node'}) - for plan in data_migration_plans: - plan_doc = frappe.get_doc("Data Migration Plan", plan.name) - for m in plan_doc.get("mappings"): - frappe.delete_doc("Data Migration Mapping", m.mapping, force=True) - docs = frappe.get_all("Data Migration Run", filters={"data_migration_plan": plan.name}) - for doc in docs: - frappe.delete_doc("Data Migration Run", doc.name) - frappe.delete_doc("Data Migration Plan", plan.name) diff --git a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py b/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py deleted file mode 100644 index a664baf6dd..0000000000 --- a/erpnext/patches/v11_0/reset_publish_in_hub_for_all_items.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def execute(): - frappe.reload_doc('stock', 'doctype', 'item') - frappe.db.sql("""update `tabItem` set publish_in_hub = 0""") diff --git a/erpnext/patches/v11_0/update_hub_url.py b/erpnext/patches/v11_0/update_hub_url.py deleted file mode 100644 index c89b9b5060..0000000000 --- a/erpnext/patches/v11_0/update_hub_url.py +++ /dev/null @@ -1,8 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def execute(): - frappe.reload_doc('hub_node', 'doctype', 'Marketplace Settings') - frappe.db.set_value('Marketplace Settings', 'Marketplace Settings', 'marketplace_url', 'https://hubmarket.org') diff --git a/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py b/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py deleted file mode 100644 index 73c6ce8220..0000000000 --- a/erpnext/patches/v12_0/set_published_in_hub_tracked_item.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import unicode_literals - -import frappe - - -def execute(): - frappe.reload_doc("Hub Node", "doctype", "Hub Tracked Item") - if not frappe.db.a_row_exists("Hub Tracked Item"): - return - - frappe.db.sql(''' - Update `tabHub Tracked Item` - SET published = 1 - ''') diff --git a/erpnext/patches/v14_0/delete_hub_doctypes.py b/erpnext/patches/v14_0/delete_hub_doctypes.py new file mode 100644 index 0000000000..d1e9e31f0c --- /dev/null +++ b/erpnext/patches/v14_0/delete_hub_doctypes.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + + doctypes = frappe.get_all("DocType", {"module": "Hub Node", "custom": 0}, pluck='name') + for doctype in doctypes: + frappe.delete_doc("DocType", doctype, ignore_missing=True) + + frappe.delete_doc("Module Def", "Hub Node", ignore_missing=True, force=True) diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 6b70dab803..f8e817770d 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,14 +1,10 @@ { "css/erpnext.css": [ "public/less/erpnext.less", - "public/less/hub.less", "public/scss/call_popup.scss", "public/scss/point-of-sale.scss", "public/scss/hierarchy_chart.scss" ], - "css/marketplace.css": [ - "public/less/hub.less" - ], "js/erpnext-web.min.js": [ "public/js/website_utils.js", "public/js/shopping_cart.js" @@ -17,9 +13,6 @@ "public/scss/website.scss", "public/scss/shopping_cart.scss" ], - "js/marketplace.min.js": [ - "public/js/hub/marketplace.js" - ], "js/erpnext.min.js": [ "public/js/conf.js", "public/js/utils.js", @@ -41,7 +34,6 @@ "public/js/utils/supplier_quick_entry.js", "public/js/education/student_button.html", "public/js/education/assessment_result_tool.html", - "public/js/hub/hub_factory.js", "public/js/call_popup/call_popup.js", "public/js/utils/dimension_tree_filter.js", "public/js/telephony.js", diff --git a/erpnext/public/images/hub_logo.svg b/erpnext/public/images/hub_logo.svg deleted file mode 100644 index 4af482176e..0000000000 --- a/erpnext/public/images/hub_logo.svg +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js index febdb24da3..5259bdcc76 100644 --- a/erpnext/public/js/erpnext.bundle.js +++ b/erpnext/public/js/erpnext.bundle.js @@ -18,7 +18,6 @@ import "./utils/customer_quick_entry"; import "./utils/supplier_quick_entry"; import "./education/student_button.html"; import "./education/assessment_result_tool.html"; -import "./hub/hub_factory"; import "./call_popup/call_popup"; import "./utils/dimension_tree_filter"; import "./telephony"; diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue deleted file mode 100644 index 54c359766d..0000000000 --- a/erpnext/public/js/hub/PageContainer.vue +++ /dev/null @@ -1,119 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/Sidebar.vue b/erpnext/public/js/hub/Sidebar.vue deleted file mode 100644 index 66c291ec52..0000000000 --- a/erpnext/public/js/hub/Sidebar.vue +++ /dev/null @@ -1,110 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/CommentInput.vue b/erpnext/public/js/hub/components/CommentInput.vue deleted file mode 100644 index 31562c7a28..0000000000 --- a/erpnext/public/js/hub/components/CommentInput.vue +++ /dev/null @@ -1,39 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/DetailHeaderItem.vue b/erpnext/public/js/hub/components/DetailHeaderItem.vue deleted file mode 100644 index a6c5f066f2..0000000000 --- a/erpnext/public/js/hub/components/DetailHeaderItem.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/DetailView.vue b/erpnext/public/js/hub/components/DetailView.vue deleted file mode 100644 index 942c1ebdb3..0000000000 --- a/erpnext/public/js/hub/components/DetailView.vue +++ /dev/null @@ -1,86 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/EmptyState.vue b/erpnext/public/js/hub/components/EmptyState.vue deleted file mode 100644 index e3a33a0830..0000000000 --- a/erpnext/public/js/hub/components/EmptyState.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/Image.vue b/erpnext/public/js/hub/components/Image.vue deleted file mode 100644 index 9acf421032..0000000000 --- a/erpnext/public/js/hub/components/Image.vue +++ /dev/null @@ -1,40 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/ItemCard.vue b/erpnext/public/js/hub/components/ItemCard.vue deleted file mode 100644 index 675ad86645..0000000000 --- a/erpnext/public/js/hub/components/ItemCard.vue +++ /dev/null @@ -1,142 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/ItemCardsContainer.vue b/erpnext/public/js/hub/components/ItemCardsContainer.vue deleted file mode 100644 index 0a20bcdc63..0000000000 --- a/erpnext/public/js/hub/components/ItemCardsContainer.vue +++ /dev/null @@ -1,62 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/ItemListCard.vue b/erpnext/public/js/hub/components/ItemListCard.vue deleted file mode 100644 index 7f6fb77d76..0000000000 --- a/erpnext/public/js/hub/components/ItemListCard.vue +++ /dev/null @@ -1,21 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/NotificationMessage.vue b/erpnext/public/js/hub/components/NotificationMessage.vue deleted file mode 100644 index c26672635c..0000000000 --- a/erpnext/public/js/hub/components/NotificationMessage.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/components/Rating.vue b/erpnext/public/js/hub/components/Rating.vue deleted file mode 100644 index 33290b8e20..0000000000 --- a/erpnext/public/js/hub/components/Rating.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/ReviewArea.vue b/erpnext/public/js/hub/components/ReviewArea.vue deleted file mode 100644 index aa83bb0e46..0000000000 --- a/erpnext/public/js/hub/components/ReviewArea.vue +++ /dev/null @@ -1,140 +0,0 @@ - - diff --git a/erpnext/public/js/hub/components/ReviewTimelineItem.vue b/erpnext/public/js/hub/components/ReviewTimelineItem.vue deleted file mode 100644 index d0e83f3b1c..0000000000 --- a/erpnext/public/js/hub/components/ReviewTimelineItem.vue +++ /dev/null @@ -1,53 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/SearchInput.vue b/erpnext/public/js/hub/components/SearchInput.vue deleted file mode 100644 index 4b1ce6e4ef..0000000000 --- a/erpnext/public/js/hub/components/SearchInput.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/erpnext/public/js/hub/components/SectionHeader.vue b/erpnext/public/js/hub/components/SectionHeader.vue deleted file mode 100644 index 05a2f838a0..0000000000 --- a/erpnext/public/js/hub/components/SectionHeader.vue +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/erpnext/public/js/hub/components/TimelineItem.vue b/erpnext/public/js/hub/components/TimelineItem.vue deleted file mode 100644 index d13c842beb..0000000000 --- a/erpnext/public/js/hub/components/TimelineItem.vue +++ /dev/null @@ -1,9 +0,0 @@ -/* Saving this for later */ - diff --git a/erpnext/public/js/hub/components/edit_details_dialog.js b/erpnext/public/js/hub/components/edit_details_dialog.js deleted file mode 100644 index 97c5f83a13..0000000000 --- a/erpnext/public/js/hub/components/edit_details_dialog.js +++ /dev/null @@ -1,41 +0,0 @@ -function edit_details_dialog(params) { - let dialog = new frappe.ui.Dialog({ - title: __('Update Details'), - fields: [ - { - label: 'Item Name', - fieldname: 'item_name', - fieldtype: 'Data', - default: params.defaults.item_name, - reqd: 1 - }, - { - label: 'Hub Category', - fieldname: 'hub_category', - fieldtype: 'Autocomplete', - default: params.defaults.hub_category, - options: [], - reqd: 1 - }, - { - label: 'Description', - fieldname: 'description', - fieldtype: 'Text', - default: params.defaults.description, - options: [], - reqd: 1 - } - ], - primary_action_label: params.primary_action.label || __('Update Details'), - primary_action: params.primary_action.fn - }); - - hub.call('get_categories').then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.set_data(categories); - }); - - return dialog; -} - -export { edit_details_dialog }; diff --git a/erpnext/public/js/hub/components/item_publish_dialog.js b/erpnext/public/js/hub/components/item_publish_dialog.js deleted file mode 100644 index 08de5b30b6..0000000000 --- a/erpnext/public/js/hub/components/item_publish_dialog.js +++ /dev/null @@ -1,39 +0,0 @@ -function ItemPublishDialog(primary_action, secondary_action) { - let dialog = new frappe.ui.Dialog({ - title: __('Edit Publishing Details'), - fields: [ - { - label: __('Item Code'), - fieldname: 'item_code', - fieldtype: 'Data', - read_only: 1 - }, - { - label: __('Hub Category'), - fieldname: 'hub_category', - fieldtype: 'Autocomplete', - options: [], - reqd: 1 - }, - { - label: __('Images'), - fieldname: 'image_list', - fieldtype: 'MultiSelect', - options: [], - reqd: 1 - } - ], - primary_action_label: primary_action.label || __('Set Details'), - primary_action: primary_action.fn, - secondary_action: secondary_action.fn - }); - - hub.call('get_categories').then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.set_data(categories); - }); - - return dialog; -} - -export { ItemPublishDialog }; diff --git a/erpnext/public/js/hub/components/profile_dialog.js b/erpnext/public/js/hub/components/profile_dialog.js deleted file mode 100644 index 8e3abc37eb..0000000000 --- a/erpnext/public/js/hub/components/profile_dialog.js +++ /dev/null @@ -1,56 +0,0 @@ -const ProfileDialog = (title = __('Edit Profile'), action={}) => { - const fields = [ - { - fieldtype: 'Link', - fieldname: 'company', - label: __('Company'), - options: 'Company' - }, - { - fieldtype: 'Read Only', - fieldname: 'email', - label: __('Email') - }, - { - label: __('About your company'), - fieldname: 'company_description', - fieldtype: 'Text' - } - ]; - - let dialog = new frappe.ui.Dialog({ - title: title, - fields: fields, - primary_action_label: action.label || __('Update'), - primary_action: () => { - const form_values = dialog.get_values(); - let values_filled = true; - - // TODO: Say "we notice that the company description and logo isn't set. Please set them in master." - // Only then allow to register - - const mandatory_fields = ['company', 'company_description']; - mandatory_fields.forEach(field => { - const value = form_values[field]; - if (!value) { - dialog.set_df_property(field, 'reqd', 1); - values_filled = false; - } - }); - if (!values_filled) return; - - action.on_submit(form_values); - } - }); - - // Post create - const default_company = frappe.defaults.get_default('company'); - dialog.set_value('company', default_company); - dialog.set_value('email', frappe.session.user); - - return dialog; -} - -export { - ProfileDialog -} diff --git a/erpnext/public/js/hub/components/reviews.js b/erpnext/public/js/hub/components/reviews.js deleted file mode 100644 index e34d68038f..0000000000 --- a/erpnext/public/js/hub/components/reviews.js +++ /dev/null @@ -1,80 +0,0 @@ -function get_review_html(review) { - let username = review.username || review.user || __("Anonymous"); - - let image_html = review.user_image - ? `
    ` - : `
    ${frappe.get_abbr(username)}
    ` - - let edit_html = review.own - ? ` -
    - - ${'data.edit'} - -
    ` - : ''; - - let rating_html = get_rating_html(review.rating); - - return get_timeline_item(review, image_html, edit_html, rating_html); -} - -function get_timeline_item(data, image_html, edit_html, rating_html) { - return `
    - -
    -
    -
    ${edit_html}
    - -
    - - ${image_html} - - -
    - - - ${data.username} - - - - - -
    -
    -
    -
    -

    - ${rating_html} -

    -
    ${data.subject}
    -

    - ${data.content} -

    -
    -
    -
    -
    -
    `; -} - -function get_rating_html(rating) { - let rating_html = ``; - for (var i = 0; i < 5; i++) { - let star_class = 'fa-star'; - if (i >= rating) star_class = 'fa-star-o'; - rating_html += ``; - } - return rating_html; -} - -export { - get_review_html, - get_rating_html -} diff --git a/erpnext/public/js/hub/hub_call.js b/erpnext/public/js/hub/hub_call.js deleted file mode 100644 index 5545a4935b..0000000000 --- a/erpnext/public/js/hub/hub_call.js +++ /dev/null @@ -1,68 +0,0 @@ -frappe.provide('hub'); -frappe.provide('erpnext.hub'); - -erpnext.hub.cache = {}; -hub.call = function call_hub_method(method, args={}, clear_cache_on_event) { // eslint-disable-line - return new Promise((resolve, reject) => { - - // cache - const key = method + JSON.stringify(args); - if (erpnext.hub.cache[key]) { - resolve(erpnext.hub.cache[key]); - } - - // cache invalidation - const clear_cache = () => delete erpnext.hub.cache[key]; - - if (!clear_cache_on_event) { - invalidate_after_5_mins(clear_cache); - } else { - erpnext.hub.on(clear_cache_on_event, () => { - clear_cache(key); - }); - } - - let res; - if (hub.is_server) { - res = frappe.call({ - method: 'hub.hub.api.' + method, - args - }); - } else { - res = frappe.call({ - method: 'erpnext.hub_node.api.call_hub_method', - args: { - method, - params: args - } - }); - } - - res.then(r => { - if (r.message) { - const response = r.message; - if (response.error) { - frappe.throw({ - title: __('Marketplace Error'), - message: response.error - }); - } - - erpnext.hub.cache[key] = response; - erpnext.hub.trigger(`response:${key}`, { response }); - resolve(response); - } - reject(r); - - }).fail(reject); - }); -}; - -function invalidate_after_5_mins(clear_cache) { - // cache invalidation after 5 minutes - const timeout = 5 * 60 * 1000; - - setTimeout(() => { - clear_cache(); - }, timeout); -} diff --git a/erpnext/public/js/hub/hub_factory.js b/erpnext/public/js/hub/hub_factory.js deleted file mode 100644 index 9c67c1cf9f..0000000000 --- a/erpnext/public/js/hub/hub_factory.js +++ /dev/null @@ -1,34 +0,0 @@ -frappe.provide('erpnext.hub'); - -frappe.views.MarketplaceFactory = class MarketplaceFactory extends frappe.views.Factory { - show() { - is_marketplace_disabled() - .then(disabled => { - if (disabled) { - frappe.show_not_found('Marketplace'); - return; - } - - if (frappe.pages.marketplace) { - frappe.container.change_to('marketplace'); - erpnext.hub.marketplace.refresh(); - } else { - this.make('marketplace'); - } - }); - } - - make(page_name) { - frappe.require('marketplace.bundle.js', () => { - erpnext.hub.marketplace = new erpnext.hub.Marketplace({ - parent: this.make_page(true, page_name) - }); - }); - } -}; - -function is_marketplace_disabled() { - return frappe.call({ - method: "erpnext.hub_node.doctype.marketplace_settings.marketplace_settings.is_marketplace_enabled" - }).then(r => r.message) -} diff --git a/erpnext/public/js/hub/marketplace.bundle.js b/erpnext/public/js/hub/marketplace.bundle.js deleted file mode 100644 index a1596e0043..0000000000 --- a/erpnext/public/js/hub/marketplace.bundle.js +++ /dev/null @@ -1,225 +0,0 @@ -import Vue from 'vue/dist/vue.js'; -import './vue-plugins'; - -// components -import PageContainer from './PageContainer.vue'; -import Sidebar from './Sidebar.vue'; -import { ProfileDialog } from './components/profile_dialog'; - -// helpers -import './hub_call'; - -frappe.provide('hub'); -frappe.provide('erpnext.hub'); -frappe.provide('frappe.route'); - -frappe.utils.make_event_emitter(frappe.route); -frappe.utils.make_event_emitter(erpnext.hub); - -erpnext.hub.Marketplace = class Marketplace { - constructor({ parent }) { - this.$parent = $(parent); - this.page = parent.page; - - this.update_hub_settings().then(() => { - - this.setup_header(); - this.make_sidebar(); - this.make_body(); - this.setup_events(); - this.refresh(); - - if (!hub.is_server) { - if (!hub.is_seller_registered()) { - this.page.set_primary_action('Become a Seller', this.show_register_dialog.bind(this)) - } else { - this.page.set_secondary_action('Add Users', this.show_add_user_dialog.bind(this)); - } - } - }); - } - - setup_header() { - if (hub.is_server) return; - this.page.set_title(__('Marketplace')); - } - - setup_events() { - this.$parent.on('click', '[data-route]', (e) => { - const $target = $(e.currentTarget); - const route = $target.data().route; - frappe.set_route(route); - }); - - // generic action handler - this.$parent.on('click', '[data-action]', e => { - const $target = $(e.currentTarget); - const action = $target.data().action; - - if (action && this[action]) { - this[action].apply(this, $target); - } - }) - } - - make_sidebar() { - this.$sidebar = this.$parent.find('.layout-side-section').addClass('hidden-xs'); - - new Vue({ - el: $('
    ').appendTo(this.$sidebar)[0], - render: h => h(Sidebar) - }); - } - - make_body() { - this.$body = this.$parent.find('.layout-main-section'); - this.$page_container = $('
    ').appendTo(this.$body); - - new Vue({ - el: '.hub-page-container', - render: h => h(PageContainer) - }); - - if (!hub.is_server) { - erpnext.hub.on('seller-registered', () => { - this.page.clear_primary_action(); - }); - } - } - - refresh() { - - } - - show_register_dialog() { - if(frappe.session.user === 'Administrator') { - frappe.msgprint(__('You need to be a user other than Administrator with System Manager and Item Manager roles to register on Marketplace.')); - return; - } - - if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) { - frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to register on Marketplace.')); - return; - } - - this.register_dialog = ProfileDialog( - __('Become a Seller'), - { - label: __('Register'), - on_submit: this.register_marketplace.bind(this) - } - ); - - this.register_dialog.show(); - } - - register_marketplace({company, company_description}) { - frappe.call({ - method: 'erpnext.hub_node.api.register_marketplace', - args: { - company, - company_description - } - }).then((r) => { - if (r.message && r.message.ok) { - this.register_dialog.hide(); - - this.update_hub_settings() - .then(() => { - frappe.set_route('marketplace', 'publish'); - erpnext.hub.trigger('seller-registered'); - }); - } - }); - } - - show_add_user_dialog() { - if (!is_subset(['System Manager', 'Item Manager'], frappe.user_roles)) { - frappe.msgprint(__('You need to be a user with System Manager and Item Manager roles to add users to Marketplace.')); - return; - } - - this.get_unregistered_users() - .then(r => { - const user_list = r.message; - - const d = new frappe.ui.Dialog({ - title: __('Add Users to Marketplace'), - fields: [ - { - label: __('Users'), - fieldname: 'users', - fieldtype: 'MultiSelect', - reqd: 1, - get_data() { - return user_list; - } - } - ], - primary_action({ users }) { - const selected_users = users.split(',').map(d => d.trim()).filter(Boolean); - - if (!selected_users.every(user => user_list.includes(user))) { - d.set_df_property('users', 'description', __('Some emails are invalid')); - return; - } else { - d.set_df_property('users', 'description', ''); - } - - frappe.call('erpnext.hub_node.api.register_users', { - user_list: selected_users - }) - .then(r => { - d.hide(); - - if (r.message && r.message.length) { - frappe.show_alert(__('Added {0} users', [r.message.length])); - } - }); - } - }); - - d.show(); - }); - } - - get_unregistered_users() { - return frappe.call('erpnext.hub_node.api.get_unregistered_users') - } - - update_hub_settings() { - return hub.get_settings().then(doc => { - hub.settings = doc; - }); - } -} - -Object.assign(hub, { - is_seller_registered() { - return hub.settings.registered; - }, - - is_user_registered() { - return this.is_seller_registered() && hub.settings.users - .filter(hub_user => hub_user.user === frappe.session.user) - .length === 1; - }, - - get_settings() { - if (frappe.session.user === 'Guest') { - return Promise.resolve({ - registered: 0 - }); - } - return frappe.db.get_doc('Marketplace Settings'); - } -}); - -/** - * Returns true if list_a is subset of list_b - * @param {Array} list_a - * @param {Array} list_b - */ -function is_subset(list_a, list_b) { - return list_a.every(item => list_b.includes(item)); -} diff --git a/erpnext/public/js/hub/pages/Buying.vue b/erpnext/public/js/hub/pages/Buying.vue deleted file mode 100644 index ebf593aca4..0000000000 --- a/erpnext/public/js/hub/pages/Buying.vue +++ /dev/null @@ -1,56 +0,0 @@ - - diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue deleted file mode 100644 index 16d06018ff..0000000000 --- a/erpnext/public/js/hub/pages/Category.vue +++ /dev/null @@ -1,76 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/FeaturedItems.vue b/erpnext/public/js/hub/pages/FeaturedItems.vue deleted file mode 100644 index 8380b2b2c0..0000000000 --- a/erpnext/public/js/hub/pages/FeaturedItems.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue deleted file mode 100644 index 8fe824566d..0000000000 --- a/erpnext/public/js/hub/pages/Home.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue deleted file mode 100644 index 93002a7b27..0000000000 --- a/erpnext/public/js/hub/pages/Item.vue +++ /dev/null @@ -1,356 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Messages.vue b/erpnext/public/js/hub/pages/Messages.vue deleted file mode 100644 index 73506e9926..0000000000 --- a/erpnext/public/js/hub/pages/Messages.vue +++ /dev/null @@ -1,104 +0,0 @@ - - diff --git a/erpnext/public/js/hub/pages/NotFound.vue b/erpnext/public/js/hub/pages/NotFound.vue deleted file mode 100644 index 8901b97802..0000000000 --- a/erpnext/public/js/hub/pages/NotFound.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Publish.vue b/erpnext/public/js/hub/pages/Publish.vue deleted file mode 100644 index ecba4b1e5a..0000000000 --- a/erpnext/public/js/hub/pages/Publish.vue +++ /dev/null @@ -1,212 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/PublishedItems.vue b/erpnext/public/js/hub/pages/PublishedItems.vue deleted file mode 100644 index cbb22164e6..0000000000 --- a/erpnext/public/js/hub/pages/PublishedItems.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/SavedItems.vue b/erpnext/public/js/hub/pages/SavedItems.vue deleted file mode 100644 index 7007ddcf8e..0000000000 --- a/erpnext/public/js/hub/pages/SavedItems.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue deleted file mode 100644 index c10841e984..0000000000 --- a/erpnext/public/js/hub/pages/Search.vue +++ /dev/null @@ -1,81 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Seller.vue b/erpnext/public/js/hub/pages/Seller.vue deleted file mode 100644 index 3c9b800f4a..0000000000 --- a/erpnext/public/js/hub/pages/Seller.vue +++ /dev/null @@ -1,201 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/SellerItems.vue b/erpnext/public/js/hub/pages/SellerItems.vue deleted file mode 100644 index 852fbaee3f..0000000000 --- a/erpnext/public/js/hub/pages/SellerItems.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - - - diff --git a/erpnext/public/js/hub/pages/Selling.vue b/erpnext/public/js/hub/pages/Selling.vue deleted file mode 100644 index 8743027885..0000000000 --- a/erpnext/public/js/hub/pages/Selling.vue +++ /dev/null @@ -1,66 +0,0 @@ - - diff --git a/erpnext/public/js/hub/vue-plugins.js b/erpnext/public/js/hub/vue-plugins.js deleted file mode 100644 index 4912d68499..0000000000 --- a/erpnext/public/js/hub/vue-plugins.js +++ /dev/null @@ -1,58 +0,0 @@ -import Vue from 'vue/dist/vue.js'; - -// Global components -import ItemCardsContainer from './components/ItemCardsContainer.vue'; -import SectionHeader from './components/SectionHeader.vue'; -import SearchInput from './components/SearchInput.vue'; -import DetailView from './components/DetailView.vue'; -import DetailHeaderItem from './components/DetailHeaderItem.vue'; -import EmptyState from './components/EmptyState.vue'; -import Image from './components/Image.vue'; - -Vue.prototype.__ = window.__; -Vue.prototype.frappe = window.frappe; - -Vue.component('item-cards-container', ItemCardsContainer); -Vue.component('section-header', SectionHeader); -Vue.component('search-input', SearchInput); -Vue.component('detail-view', DetailView); -Vue.component('detail-header-item', DetailHeaderItem); -Vue.component('empty-state', EmptyState); -Vue.component('base-image', Image); - -Vue.directive('route', { - bind(el, binding) { - const route = binding.value; - if (!route) return; - el.classList.add('cursor-pointer'); - el.dataset.route = route; - el.addEventListener('click', () => frappe.set_route(route)); - }, - unbind(el) { - el.classList.remove('cursor-pointer'); - } -}); - -const handleImage = (el, src) => { - let img = new Image(); - // add loading class - el.src = ''; - el.classList.add('img-loading'); - - img.onload = () => { - // image loaded, remove loading class - el.classList.remove('img-loading'); - // set src - el.src = src; - } - img.onerror = () => { - el.classList.remove('img-loading'); - el.classList.add('no-image'); - el.src = null; - } - img.src = src; -} - -Vue.filter('striphtml', function (text) { - return strip_html(text || ''); -}); diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index c473395a9a..d61d94c3c6 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -202,7 +202,6 @@ def install(country=None): {'doctype': "Party Type", "party_type": "Student", "account_type": "Receivable"}, {'doctype': "Party Type", "party_type": "Donor", "account_type": "Receivable"}, - {'doctype': "Opportunity Type", "name": "Hub"}, {'doctype': "Opportunity Type", "name": _("Sales")}, {'doctype': "Opportunity Type", "name": _("Support")}, {'doctype': "Opportunity Type", "name": _("Maintenance")}, diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index db5caf9164..4b314a00a4 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -17,7 +17,6 @@ "variant_of", "item_name", "item_group", - "is_item_from_hub", "stock_uom", "column_break0", "disabled", @@ -134,12 +133,7 @@ "website_specifications", "web_long_description", "website_content", - "total_projected_qty", - "hub_publishing_sb", - "publish_in_hub", - "hub_category_to_publish", - "hub_warehouse", - "synced_with_hub" + "total_projected_qty" ], "fields": [ { @@ -202,14 +196,6 @@ "reqd": 1, "search_index": 1 }, - { - "default": "0", - "depends_on": "eval:!doc.is_fixed_asset", - "fieldname": "is_item_from_hub", - "fieldtype": "Check", - "label": "Is Item from Hub", - "read_only": 1 - }, { "fieldname": "stock_uom", "fieldtype": "Link", @@ -996,41 +982,6 @@ "print_hide": 1, "read_only": 1 }, - { - "collapsible": 1, - "depends_on": "eval:(!doc.is_item_from_hub && !doc.is_fixed_asset)", - "fieldname": "hub_publishing_sb", - "fieldtype": "Section Break", - "label": "Hub Publishing Details" - }, - { - "default": "0", - "description": "Publish Item to hub.erpnext.com", - "fieldname": "publish_in_hub", - "fieldtype": "Check", - "label": "Publish in Hub" - }, - { - "fieldname": "hub_category_to_publish", - "fieldtype": "Data", - "label": "Hub Category to Publish", - "read_only": 1 - }, - { - "description": "Publish \"In Stock\" or \"Not in Stock\" on Hub based on stock available in this warehouse.", - "fieldname": "hub_warehouse", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Hub Warehouse", - "options": "Warehouse" - }, - { - "default": "0", - "fieldname": "synced_with_hub", - "fieldtype": "Check", - "label": "Synced With Hub", - "read_only": 1 - }, { "depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset", "fieldname": "over_delivery_receipt_allowance", @@ -1078,10 +1029,11 @@ "index_web_pages_for_search": 1, "links": [], "max_attachments": 1, - "modified": "2021-08-26 12:23:07.277077", + "modified": "2021-10-27 21:04:00.324786", "modified_by": "Administrator", "module": "Stock", "name": "Item", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 8cc9f74a42..04e4653d93 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -123,7 +123,6 @@ class Item(WebsiteGenerator): self.validate_barcode() self.validate_warehouse_for_reorder() self.update_bom_item_desc() - self.synced_with_hub = 0 self.validate_has_variants() self.validate_attributes_in_variants() From 09a5616e2dce7d0d9962326781cd7c0a4bcaebbc Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 28 Oct 2021 19:01:24 +0530 Subject: [PATCH 380/416] fix: Rewrite patch using query builder --- .../update_category_in_ltds_certificate.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py index f8a0646fcb..4d4645269c 100644 --- a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -2,9 +2,17 @@ import frappe def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return - frappe.db.sql(""" - UPDATE `tabLower Deduction Certificate` l, `tabSupplier` s - SET l.tax_withholding_category = s.tax_withholding_category - WHERE l.supplier = s.name - """) \ No newline at end of file + ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc") + supplier = frappe.qb.DocType("Supplier") + + frappe.qb.update(ldc).inner_join(supplier).on( + ldc.supplier == supplier.name + ).set( + ldc.tax_withholding_category, supplier.tax_withholding_category + ).where( + ldc.tax_withholding_category.isnull() + ).run() \ No newline at end of file From 8844fdbf230ef167c0051935a8f922c5c6bb13de Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 28 Oct 2021 19:25:55 +0530 Subject: [PATCH 381/416] fix: Remove print statements --- erpnext/controllers/accounts_controller.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index bfbbf4d140..9267eb5b8e 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -820,14 +820,10 @@ class AccountsController(TransactionBase): self.unlink_ref_doc_from_po() def unlink_ref_doc_from_po(self): - print("\n"*5, "*"*50, "\n"*5) - so_items = [] for item in self.items: so_items.append(item.name) - print("SO Items: ", so_items) - linked_po = frappe.get_all( 'Purchase Order Item', filters = { @@ -838,8 +834,6 @@ class AccountsController(TransactionBase): pluck='parent' ) - print("Before unlinking: ", linked_po) - if linked_po: frappe.db.set_value( 'Purchase Order Item', { @@ -854,19 +848,6 @@ class AccountsController(TransactionBase): frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po))) - linked_po = frappe.get_all( - 'Purchase Order Item', - filters = { - 'sales_order': self.name, - 'sales_order_item': ['in', so_items], - 'docstatus': ['<', 2] - }, - pluck='parent' - ) - print("After unlinking: ", linked_po) - - print("\n"*5, "*"*50, "\n"*5) - def get_tax_map(self): tax_map = {} for tax in self.get('taxes'): From 1a9d2684e2c8e9f89ea82dbade5cbe9b72f9e364 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 28 Oct 2021 19:27:45 +0530 Subject: [PATCH 382/416] fix: Remove debugger --- erpnext/selling/doctype/sales_order/sales_order.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c9490fcae4..dcf478bda6 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -207,12 +207,6 @@ class SalesOrder(SellingController): from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count update_coupon_code_count(self.coupon_code,'cancelled') - def cancel(self): - import pdb - - pdb.set_trace() - super(SalesOrder, self).cancel() - def update_project(self): if frappe.db.get_single_value('Selling Settings', 'sales_update_frequency') != "Each Transaction": return From d24cfff3886b8502c8f98cb4421c5e37a46ec3f2 Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Thu, 28 Oct 2021 20:06:48 +0530 Subject: [PATCH 383/416] fix: Remove unused imports --- erpnext/controllers/accounts_controller.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 9267eb5b8e..14ad4228c4 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -19,8 +19,7 @@ from frappe.utils import ( get_link_to_form, getdate, nowdate, - now, - today + today, ) from six import text_type From 051aaa708d7ab8a0019e19c9c8b8371afa03abc6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 11:35:34 +0530 Subject: [PATCH 384/416] fix: Ignore PO on So cancel --- erpnext/controllers/accounts_controller.py | 4 ++-- erpnext/selling/doctype/sales_order/sales_order.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 14ad4228c4..37b8f9d6d3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -823,7 +823,7 @@ class AccountsController(TransactionBase): for item in self.items: so_items.append(item.name) - linked_po = frappe.get_all( + linked_po = list(set(frappe.get_all( 'Purchase Order Item', filters = { 'sales_order': self.name, @@ -831,7 +831,7 @@ class AccountsController(TransactionBase): 'docstatus': ['<', 2] }, pluck='parent' - ) + ))) if linked_po: frappe.db.set_value( diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index f6926906b8..d46c46f90e 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -78,6 +78,8 @@ frappe.ui.form.on("Sales Order", { }); erpnext.queries.setup_warehouse_query(frm); + + frm.ignore_doctypes_on_cancel_all = ['Purchase Order']; }, delivery_date: function(frm) { From aa9e78bed19b72584a5248afd83ca82011727e13 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 12:45:19 +0530 Subject: [PATCH 385/416] fix: Accounting Dimension filters not honouring user permissions --- erpnext/controllers/queries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index eeb659dee5..05ece4defe 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -566,7 +566,7 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters) query_filters.append(['name', query_selector, dimensions]) - output = frappe.get_all(doctype, filters=query_filters) + output = frappe.get_list(doctype, filters=query_filters) result = [d.name for d in output] return [(d,) for d in set(result)] From afe09d4e80c3000ce866f0b43ecced878f715273 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 13:45:07 +0530 Subject: [PATCH 386/416] test: remove unnecessary creation of new company (#28137) --- erpnext/tests/test_woocommerce.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py index 881f286bae..3ce68d89bc 100644 --- a/erpnext/tests/test_woocommerce.py +++ b/erpnext/tests/test_woocommerce.py @@ -12,12 +12,6 @@ from erpnext.erpnext_integrations.connectors.woocommerce_connection import order class TestWoocommerce(unittest.TestCase): def setUp(self): - if not frappe.db.exists('Company', 'Woocommerce'): - company = frappe.new_doc("Company") - company.company_name = "Woocommerce" - company.abbr = "W" - company.default_currency = "INR" - company.save() woo_settings = frappe.get_doc("Woocommerce Settings") if not woo_settings.secret: @@ -26,14 +20,14 @@ class TestWoocommerce(unittest.TestCase): woo_settings.api_consumer_key = "ck_fd43ff5756a6abafd95fadb6677100ce95a758a1" woo_settings.api_consumer_secret = "cs_94360a1ad7bef7fa420a40cf284f7b3e0788454e" woo_settings.enable_sync = 1 - woo_settings.company = "Woocommerce" - woo_settings.tax_account = "Sales Expenses - W" - woo_settings.f_n_f_account = "Expenses - W" + woo_settings.company = "_Test Company" + woo_settings.tax_account = "Sales Expenses - _TC" + woo_settings.f_n_f_account = "Expenses - _TC" woo_settings.creation_user = "Administrator" woo_settings.save(ignore_permissions=True) def test_sales_order_for_woocommerce(self): - frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} + frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"_Test Company","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]} order() self.assertTrue(frappe.get_value("Customer",{"woocommerce_email":"tony@gmail.com"})) From 292419bc9ebc405ef8a4861125070abb0d321db1 Mon Sep 17 00:00:00 2001 From: Marica Date: Fri, 29 Oct 2021 13:49:27 +0530 Subject: [PATCH 387/416] fix: Skip empty rows while updating unsaved BOM cost (#28136) - Dont try to get valuation rate if row has no item code - Dont try to add exploded items if row has no item code --- erpnext/manufacturing/doctype/bom/bom.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 232e3a0b0f..2cd8f8c15a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -307,6 +307,9 @@ class BOM(WebsiteGenerator): existing_bom_cost = self.total_cost for d in self.get("items"): + if not d.item_code: + continue + rate = self.get_rm_rate({ "company": self.company, "item_code": d.item_code, @@ -599,7 +602,7 @@ class BOM(WebsiteGenerator): for d in self.get('items'): if d.bom_no: self.get_child_exploded_items(d.bom_no, d.stock_qty) - else: + elif d.item_code: self.add_to_cur_exploded_items(frappe._dict({ 'item_code' : d.item_code, 'item_name' : d.item_name, From 8ccd3fee9e7092d6bb7d81e8ef6d59e25c13a29d Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Fri, 29 Oct 2021 16:08:52 +0530 Subject: [PATCH 388/416] fix: COA importer importing all accounts as group --- .../doctype/account/chart_of_accounts/chart_of_accounts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py index 05caafe1c4..3596c34017 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py @@ -81,7 +81,7 @@ def add_suffix_if_duplicate(account_name, account_number, accounts): def identify_is_group(child): if child.get("is_group"): is_group = child.get("is_group") - elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])): + elif len(set(child.keys()) - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])): is_group = 1 else: is_group = 0 From 75a76e634d17507a568ff007d20226ffa8f7dff3 Mon Sep 17 00:00:00 2001 From: Subin Tom <36098155+nemesis189@users.noreply.github.com> Date: Fri, 29 Oct 2021 16:45:04 +0530 Subject: [PATCH 389/416] fix: POS consolidated invoice rounded total issue (#28006) --- .../pos_invoice_merge_log.py | 11 +++++ erpnext/controllers/taxes_and_totals.py | 40 ++++++++++++------- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 4f26ed43db..28bd10283e 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -114,6 +114,8 @@ class POSInvoiceMergeLog(Document): def merge_pos_invoice_into(self, invoice, data): items, payments, taxes = [], [], [] loyalty_amount_sum, loyalty_points_sum = 0, 0 + rounding_adjustment, base_rounding_adjustment = 0, 0 + rounded_total, base_rounded_total = 0, 0 for doc in data: map_doc(doc, invoice, table_map={ "doctype": invoice.doctype }) @@ -162,6 +164,11 @@ class POSInvoiceMergeLog(Document): found = True if not found: payments.append(payment) + rounding_adjustment += doc.rounding_adjustment + rounded_total += doc.rounded_total + base_rounding_adjustment += doc.rounding_adjustment + base_rounded_total += doc.rounded_total + if loyalty_points_sum: invoice.redeem_loyalty_points = 1 @@ -171,6 +178,10 @@ class POSInvoiceMergeLog(Document): invoice.set('items', items) invoice.set('payments', payments) invoice.set('taxes', taxes) + invoice.set('rounding_adjustment',rounding_adjustment) + invoice.set('rounding_adjustment',base_rounding_adjustment) + invoice.set('base_rounded_total',base_rounded_total) + invoice.set('rounded_total',rounded_total) invoice.additional_discount_percentage = 0 invoice.discount_amount = 0.0 invoice.taxes_and_charges = None diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 70cc8a58bf..7e7f598bc4 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -260,7 +260,9 @@ class calculate_taxes_and_totals(object): self.doc.round_floats_in(self.doc, ["total", "base_total", "net_total", "base_net_total"]) def calculate_taxes(self): - self.doc.rounding_adjustment = 0 + if not self.doc.get('is_consolidated'): + self.doc.rounding_adjustment = 0 + # maintain actual tax rate based on idx actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))] for tax in self.doc.get("taxes") if tax.charge_type == "Actual"]) @@ -312,7 +314,9 @@ class calculate_taxes_and_totals(object): # adjust Discount Amount loss in last tax iteration if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ - and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total": + and self.doc.discount_amount \ + and self.doc.apply_discount_on == "Grand Total" \ + and not self.doc.get('is_consolidated'): self.doc.rounding_adjustment = flt(self.doc.grand_total - flt(self.doc.discount_amount) - tax.total, self.doc.precision("rounding_adjustment")) @@ -405,11 +409,16 @@ class calculate_taxes_and_totals(object): self.doc.rounding_adjustment = diff def calculate_totals(self): - self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \ - if self.doc.get("taxes") else flt(self.doc.net_total) + if self.doc.get("taxes"): + self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) + else: + self.doc.grand_total = flt(self.doc.net_total) - self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total + if self.doc.get("taxes"): + self.doc.total_taxes_and_charges = flt(self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment), self.doc.precision("total_taxes_and_charges")) + else: + self.doc.total_taxes_and_charges = 0.0 self._set_in_company_currency(self.doc, ["total_taxes_and_charges", "rounding_adjustment"]) @@ -446,19 +455,20 @@ class calculate_taxes_and_totals(object): self.doc.total_net_weight += d.total_weight def set_rounded_total(self): - if self.doc.meta.get_field("rounded_total"): - if self.doc.is_rounded_total_disabled(): - self.doc.rounded_total = self.doc.base_rounded_total = 0 - return + if not self.doc.get('is_consolidated'): + if self.doc.meta.get_field("rounded_total"): + if self.doc.is_rounded_total_disabled(): + self.doc.rounded_total = self.doc.base_rounded_total = 0 + return - self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, - self.doc.currency, self.doc.precision("rounded_total")) + self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, + self.doc.currency, self.doc.precision("rounded_total")) - #if print_in_rate is set, we would have already calculated rounding adjustment - self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, - self.doc.precision("rounding_adjustment")) + #if print_in_rate is set, we would have already calculated rounding adjustment + self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, + self.doc.precision("rounding_adjustment")) - self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) + self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) def _cleanup(self): if not self.doc.get('is_consolidated'): From b44945380dd0af192b12cdf96df7ac23b77a79c3 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 20 Oct 2021 17:37:52 +0530 Subject: [PATCH 390/416] fix: incorrect amount of serial_nos fetched --- erpnext/stock/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index c4a0497b74..b131125c67 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -104,7 +104,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None serial_nos = last_entry.get("serial_no") if (serial_nos and - len(get_serial_nos_data(serial_nos)) < last_entry.qty_after_transaction): + len(get_serial_nos_data(serial_nos)) <= last_entry.qty_after_transaction): serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) @@ -121,6 +121,7 @@ def get_serial_nos_data_after_transactions(args): WHERE item_code = %(item_code)s and warehouse = %(warehouse)s and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) + and is_cancelled = 0 order by posting_date, posting_time asc """, args, as_dict=1) for d in data: From deb6b38fab47339098c903b253a5e478a2a63b65 Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Thu, 21 Oct 2021 17:17:11 +0530 Subject: [PATCH 391/416] refactor: replaced db.sql with qb --- erpnext/stock/utils.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index b131125c67..518bdf11e3 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -115,14 +115,25 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None return last_entry.qty_after_transaction if last_entry else 0.0 def get_serial_nos_data_after_transactions(args): + from pypika import CustomFunction + serial_nos = [] - data = frappe.db.sql(""" SELECT serial_no, actual_qty - FROM `tabStock Ledger Entry` - WHERE - item_code = %(item_code)s and warehouse = %(warehouse)s - and timestamp(posting_date, posting_time) < timestamp(%(posting_date)s, %(posting_time)s) - and is_cancelled = 0 - order by posting_date, posting_time asc """, args, as_dict=1) + args = frappe._dict(args) + sle = frappe.qb.DocType('Stock Ledger Entry') + Timestamp = CustomFunction('timestamp', ['date', 'time']) + + data = frappe.qb.from_( + sle + ).select( + 'serial_no','actual_qty' + ).where( + (sle.item_code == args.item_code) + & (sle.warehouse == args.warehouse) + & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) + & (sle.is_cancelled == 0) + ).orderby( + sle.posting_date, sle.posting_time + ).run(as_dict=1) for d in data: if d.actual_qty > 0: From 2aa019ae4c148a01ed57b8cffb461cb90748791a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 14:32:13 +0530 Subject: [PATCH 392/416] fix: fetch serial nos from ledger unconditionally --- erpnext/stock/utils.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 518bdf11e3..463b314291 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -101,11 +101,7 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None if with_valuation_rate: if with_serial_no: - serial_nos = last_entry.get("serial_no") - - if (serial_nos and - len(get_serial_nos_data(serial_nos)) <= last_entry.qty_after_transaction): - serial_nos = get_serial_nos_data_after_transactions(args) + serial_nos = get_serial_nos_data_after_transactions(args) return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos) if last_entry else (0.0, 0.0, 0.0)) From f4b60a48f5bb8b8b9e8446aa0ab441f84b321e55 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 14:56:54 +0530 Subject: [PATCH 393/416] refactor: simplify sr no fetching --- erpnext/stock/utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 463b314291..38ca25ee54 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -113,12 +113,12 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None def get_serial_nos_data_after_transactions(args): from pypika import CustomFunction - serial_nos = [] + serial_nos = set() args = frappe._dict(args) sle = frappe.qb.DocType('Stock Ledger Entry') Timestamp = CustomFunction('timestamp', ['date', 'time']) - data = frappe.qb.from_( + stock_ledger_entries = frappe.qb.from_( sle ).select( 'serial_no','actual_qty' @@ -131,11 +131,12 @@ def get_serial_nos_data_after_transactions(args): sle.posting_date, sle.posting_time ).run(as_dict=1) - for d in data: - if d.actual_qty > 0: - serial_nos.extend(get_serial_nos_data(d.serial_no)) + for stock_ledger_entry in stock_ledger_entries: + changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no) + if stock_ledger_entry.actual_qty > 0: + serial_nos.update(changed_serial_no) else: - serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no))) + serial_nos.difference_update(changed_serial_no) return '\n'.join(serial_nos) From ff9cfe0d14849d2103700ce84244c25b22075581 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 29 Oct 2021 16:30:12 +0530 Subject: [PATCH 394/416] fix: sort by creation to break tie --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 38ca25ee54..e1d5a89082 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -128,7 +128,7 @@ def get_serial_nos_data_after_transactions(args): & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)) & (sle.is_cancelled == 0) ).orderby( - sle.posting_date, sle.posting_time + sle.posting_date, sle.posting_time, sle.creation ).run(as_dict=1) for stock_ledger_entry in stock_ledger_entries: From 15e9b5170d9c17e25d96ba142ca3be3bb7f68156 Mon Sep 17 00:00:00 2001 From: Ganga Manoj Date: Fri, 29 Oct 2021 18:07:11 +0530 Subject: [PATCH 395/416] fix: Make status filter optional (#28126) --- .../fixed_asset_register/fixed_asset_register.js | 5 ++--- .../fixed_asset_register/fixed_asset_register.py | 11 ++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 75f42a9f78..06989a95da 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -16,9 +16,8 @@ frappe.query_reports["Fixed Asset Register"] = { fieldname:"status", label: __("Status"), fieldtype: "Select", - options: "In Location\nDisposed", - default: 'In Location', - reqd: 1 + options: "\nIn Location\nDisposed", + default: 'In Location' }, { "fieldname":"filter_based_on", diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index e370b9d0cb..63685fef46 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -45,12 +45,13 @@ def get_conditions(filters): if filters.get('cost_center'): conditions["cost_center"] = filters.get('cost_center') - # In Store assets are those that are not sold or scrapped - operand = 'not in' - if status not in 'In Location': - operand = 'in' + if status: + # In Store assets are those that are not sold or scrapped + operand = 'not in' + if status not in 'In Location': + operand = 'in' - conditions['status'] = (operand, ['Sold', 'Scrapped']) + conditions['status'] = (operand, ['Sold', 'Scrapped']) return conditions From 1a6e98ed488e5ed2e55f14b9f1d5166abfecb0f4 Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Fri, 29 Oct 2021 20:52:47 +0530 Subject: [PATCH 396/416] fix(Payment Reconciliation): clear child tables on company/party change (#28008) --- .../payment_reconciliation.js | 132 +++++++++--------- erpnext/accounts/utils.py | 3 +- 2 files changed, 70 insertions(+), 65 deletions(-) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js index 412833bd19..ad5a84094e 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js @@ -4,9 +4,14 @@ frappe.provide("erpnext.accounts"); erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationController extends frappe.ui.form.Controller { onload() { - var me = this; + const default_company = frappe.defaults.get_default('company'); + this.frm.set_value('company', default_company); - this.frm.set_query("party_type", function() { + this.frm.set_value('party_type', ''); + this.frm.set_value('party', ''); + this.frm.set_value('receivable_payable_account', ''); + + this.frm.set_query("party_type", () => { return { "filters": { "name": ["in", Object.keys(frappe.boot.party_account_types)], @@ -14,44 +19,30 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } }); - this.frm.set_query('receivable_payable_account', function() { - check_mandatory(me.frm); + this.frm.set_query('receivable_payable_account', () => { return { filters: { - "company": me.frm.doc.company, + "company": this.frm.doc.company, "is_group": 0, - "account_type": frappe.boot.party_account_types[me.frm.doc.party_type] + "account_type": frappe.boot.party_account_types[this.frm.doc.party_type] } }; }); - this.frm.set_query('bank_cash_account', function() { - check_mandatory(me.frm, true); + this.frm.set_query('bank_cash_account', () => { return { filters:[ - ['Account', 'company', '=', me.frm.doc.company], + ['Account', 'company', '=', this.frm.doc.company], ['Account', 'is_group', '=', 0], ['Account', 'account_type', 'in', ['Bank', 'Cash']] ] }; }); - - this.frm.set_value('party_type', ''); - this.frm.set_value('party', ''); - this.frm.set_value('receivable_payable_account', ''); - - var check_mandatory = (frm, only_company=false) => { - var title = __("Mandatory"); - if (only_company && !frm.doc.company) { - frappe.throw({message: __("Please Select a Company First"), title: title}); - } else if (!frm.doc.company || !frm.doc.party_type) { - frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title}); - } - }; } refresh() { this.frm.disable_save(); + this.frm.set_df_property('invoices', 'cannot_delete_rows', true); this.frm.set_df_property('payments', 'cannot_delete_rows', true); this.frm.set_df_property('allocation', 'cannot_delete_rows', true); @@ -85,76 +76,92 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } company() { - var me = this; + this.frm.set_value('party', ''); this.frm.set_value('receivable_payable_account', ''); - me.frm.clear_table("allocation"); - me.frm.clear_table("invoices"); - me.frm.clear_table("payments"); - me.frm.refresh_fields(); - me.frm.trigger('party'); + } + + party_type() { + this.frm.set_value('party', ''); } party() { - var me = this; - if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) { + this.frm.set_value('receivable_payable_account', ''); + this.frm.trigger("clear_child_tables"); + + if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) { return frappe.call({ method: "erpnext.accounts.party.get_party_account", args: { - company: me.frm.doc.company, - party_type: me.frm.doc.party_type, - party: me.frm.doc.party + company: this.frm.doc.company, + party_type: this.frm.doc.party_type, + party: this.frm.doc.party }, - callback: function(r) { + callback: (r) => { if (!r.exc && r.message) { - me.frm.set_value("receivable_payable_account", r.message); + this.frm.set_value("receivable_payable_account", r.message); } - me.frm.refresh(); + this.frm.refresh(); + } }); } } + receivable_payable_account() { + this.frm.trigger("clear_child_tables"); + this.frm.refresh(); + } + + clear_child_tables() { + this.frm.clear_table("invoices"); + this.frm.clear_table("payments"); + this.frm.clear_table("allocation"); + this.frm.refresh_fields(); + } + get_unreconciled_entries() { - var me = this; + this.frm.clear_table("allocation"); return this.frm.call({ - doc: me.frm.doc, + doc: this.frm.doc, method: 'get_unreconciled_entries', - callback: function(r, rt) { - if (!(me.frm.doc.payments.length || me.frm.doc.invoices.length)) { - frappe.throw({message: __("No invoice and payment records found for this party")}); + callback: () => { + if (!(this.frm.doc.payments.length || this.frm.doc.invoices.length)) { + frappe.throw({message: __("No Unreconciled Invoices and Payments found for this party and account")}); + } else if (!(this.frm.doc.invoices.length)) { + frappe.throw({message: __("No Outstanding Invoices found for this party")}); + } else if (!(this.frm.doc.payments.length)) { + frappe.throw({message: __("No Unreconciled Payments found for this party")}); } - me.frm.refresh(); + this.frm.refresh(); } }); } allocate() { - var me = this; - let payments = me.frm.fields_dict.payments.grid.get_selected_children(); + let payments = this.frm.fields_dict.payments.grid.get_selected_children(); if (!(payments.length)) { - payments = me.frm.doc.payments; + payments = this.frm.doc.payments; } - let invoices = me.frm.fields_dict.invoices.grid.get_selected_children(); + let invoices = this.frm.fields_dict.invoices.grid.get_selected_children(); if (!(invoices.length)) { - invoices = me.frm.doc.invoices; + invoices = this.frm.doc.invoices; } - return me.frm.call({ - doc: me.frm.doc, + return this.frm.call({ + doc: this.frm.doc, method: 'allocate_entries', args: { payments: payments, invoices: invoices }, - callback: function() { - me.frm.refresh(); + callback: () => { + this.frm.refresh(); } }); } reconcile() { - var me = this; - var show_dialog = me.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); + var show_dialog = this.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account); if (show_dialog && show_dialog.length) { @@ -186,10 +193,10 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo label: __("Difference Account"), fieldname: 'difference_account', reqd: 1, - get_query: function() { + get_query: () => { return { filters: { - company: me.frm.doc.company, + company: this.frm.doc.company, is_group: 0 } } @@ -203,7 +210,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo }] }, ], - primary_action: function() { + primary_action: () => { const args = dialog.get_values()["allocation"]; args.forEach(d => { @@ -211,7 +218,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo "difference_account", d.difference_account); }); - me.reconcile_payment_entries(); + this.reconcile_payment_entries(); dialog.hide(); }, primary_action_label: __('Reconcile Entries') @@ -237,15 +244,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo } reconcile_payment_entries() { - var me = this; - return this.frm.call({ - doc: me.frm.doc, + doc: this.frm.doc, method: 'reconcile', - callback: function(r, rt) { - me.frm.clear_table("allocation"); - me.frm.refresh_fields(); - me.frm.refresh(); + callback: () => { + this.frm.clear_table("allocation"); + this.frm.refresh(); } }); } diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index fdd8d092eb..fb23d6fc49 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -450,7 +450,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False): # new row with references new_row = journal_entry.append("accounts") - new_row.update(jv_detail.as_dict().copy()) + + new_row.update((frappe.copy_doc(jv_detail)).as_dict()) new_row.set(d["dr_or_cr"], d["allocated_amount"]) new_row.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit', From ad5cf467c03b695fe3f12a98ab05367189db969e Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Sat, 30 Oct 2021 13:07:36 +0530 Subject: [PATCH 397/416] fix: update tax template name for 18% GST --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index b7e895db36..8a1338583b 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -1195,7 +1195,7 @@ "*": { "item_tax_templates": [ { - "title": "GST 9%", + "title": "GST 18%", "taxes": [ { "tax_type": { From 541c892f9757f01a4ce7b72e5bc537260e65a3f8 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 30 Oct 2021 18:22:46 +0530 Subject: [PATCH 398/416] fix: Error for missing PAN no field --- .../tax_withholding_category.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index c36f3cb201..6ab0f9618a 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -58,15 +58,24 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): pan_no = '' parties = [] party_type, party = get_party_details(inv) + has_pan_field = frappe.get_meta(party_type).has_field("pan") if not tax_withholding_category: - tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan']) + if has_pan_field: + fields = ['tax_withholding_category', 'pan'] + else: + fields = ['tax_withholding_category'] + + tax_withholding_details = frappe.db.get_value(party_type, party, fields, as_dict=1) + + tax_withholding_category = tax_withholding_details.get('tax_withholding_category') + pan_no = tax_withholding_details.get('pan') if not tax_withholding_category: return # if tax_withholding_category passed as an argument but not pan_no - if not pan_no: + if not pan_no and has_pan_field: pan_no = frappe.db.get_value(party_type, party, 'pan') # Get others suppliers with the same PAN No From cae29b71d863daf102fd002b7f32592817633026 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 31 Oct 2021 09:24:07 +0530 Subject: [PATCH 399/416] fix: patch update_category_in_ltds_certificate --- erpnext/patches/v13_0/update_category_in_ltds_certificate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py index 4d4645269c..a5f5a23449 100644 --- a/erpnext/patches/v13_0/update_category_in_ltds_certificate.py +++ b/erpnext/patches/v13_0/update_category_in_ltds_certificate.py @@ -6,6 +6,8 @@ def execute(): if not company: return + frappe.reload_doc('regional', 'doctype', 'lower_deduction_certificate') + ldc = frappe.qb.DocType("Lower Deduction Certificate").as_("ldc") supplier = frappe.qb.DocType("Supplier") From 623776dd48325399441175ed04c2fd2a1d3f34c1 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Mon, 1 Nov 2021 13:37:31 +0530 Subject: [PATCH 400/416] feat: added company field in prospect (#28139) * feat: added company field in prospect * fix: review changes --- erpnext/crm/doctype/prospect/prospect.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json index 3d6fba5123..0e872ace04 100644 --- a/erpnext/crm/doctype/prospect/prospect.json +++ b/erpnext/crm/doctype/prospect/prospect.json @@ -20,6 +20,7 @@ "website", "column_break_13", "prospect_owner", + "company", "leads_section", "prospect_lead", "address_and_contact_section", @@ -153,14 +154,23 @@ "fieldname": "address_and_contact_section", "fieldtype": "Section Break", "label": "Address and Contact" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2021-08-27 16:24:42.961967", + "migration_hash": "f39fb8f4e18a0e7fd391f0b4b52d8375", + "modified": "2021-11-01 13:10:36.759249", "modified_by": "Administrator", "module": "CRM", "name": "Prospect", + "naming_rule": "By fieldname", "owner": "Administrator", "permissions": [ { From a0727b2e824e5299a893197624872122e3d6eb74 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 13:17:40 +0530 Subject: [PATCH 401/416] fix: pass company while fetching valuation rate If company is not supplied and valuation rate is 0, then default company is used for checking if perpetual inventory is enabled or not. This makes little sense as different companies can have different setting for perpetual inventory. --- erpnext/stock/stock_ledger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index bdbec52f7e..ce82f089d2 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -600,7 +600,7 @@ class update_entries_after(object): if not allow_zero_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_incoming_value_for_serial_nos(self, sle, serial_nos): # get rate from serial nos within same company @@ -667,7 +667,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) def get_fifo_values(self, sle): incoming_rate = flt(sle.incoming_rate) @@ -700,7 +700,7 @@ class update_entries_after(object): if not allow_zero_valuation_rate: _rate = get_valuation_rate(sle.item_code, sle.warehouse, sle.voucher_type, sle.voucher_no, self.allow_zero_rate, - currency=erpnext.get_company_currency(sle.company)) + currency=erpnext.get_company_currency(sle.company), company=sle.company) else: _rate = 0 From f7ffe04a4b0a65b02f20f6ca180a86c1d5a3874f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 13:21:14 +0530 Subject: [PATCH 402/416] fix: use warehouse to find company --- erpnext/stock/stock_ledger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ce82f089d2..70f4bcaef7 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -911,10 +911,11 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None): def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True): - # Get valuation rate from last sle for the same item and warehouse - if not company: - company = erpnext.get_default_company() + if not company: + company = frappe.get_cached_value("Warehouse", warehouse, "company") + + # Get valuation rate from last sle for the same item and warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` force index (item_warehouse) where From 1eab3a44f6721dbbc0d8213147a57c0da68acf47 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 1 Nov 2021 14:16:57 +0530 Subject: [PATCH 403/416] fix(ux): stock levels take time after item merge Item merge creates a repost and depending on number of entries it can take from 1 to n hours for it to finish. (depending upon queued up reposts) Added message so users don't feel confused till this operation is finished. --- erpnext/stock/doctype/item/item.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 04e4653d93..fa42c7d934 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -676,6 +676,8 @@ class Item(WebsiteGenerator): def after_rename(self, old_name, new_name, merge): if merge: self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name) + frappe.msgprint(_("It can take upto few hours for accurate stock values to be visible after merging items."), + indicator="orange", title="Note") if self.route: invalidate_cache_for_item(self) From 27cbeb920e90f976145e8ca5c696dbd49bd7ec0d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 26 Sep 2021 14:10:52 +0530 Subject: [PATCH 404/416] test(patch): run patch tests for major releases Run patch tests one at a time v10 db -> v12-> v13 -> .... -> frappe:corresponding base branch and PR. --- .github/workflows/patch.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml index 92a19621d1..f8abb6c774 100644 --- a/.github/workflows/patch.yml +++ b/.github/workflows/patch.yml @@ -86,4 +86,27 @@ jobs: cd ~/frappe-bench/ wget https://erpnext.com/files/v10-erpnext.sql.gz bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz + + git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git + git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git + + for version in $(seq 12 13) + do + echo "Updating to v$version" + branch_name="version-$version" + + git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name + git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name + + git -C "apps/frappe" checkout -q -f $branch_name + git -C "apps/erpnext" checkout -q -f $branch_name + + bench setup requirements --python + bench --site test_site migrate + done + + + echo "Updating to latest version" + git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" + git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA" bench --site test_site migrate From c587f18735336e93edee1e4e1cd85413a74cb82a Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Tue, 2 Nov 2021 12:09:41 +0530 Subject: [PATCH 405/416] chore: migrate docker to github actions --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 4b1147e79f..21ba981df4 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -11,4 +11,4 @@ jobs: - name: curl run: | apk add curl bash - curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests + curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.GITHUB_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}' From 048210a8f67fe0544216361d7342619581318570 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 15:49:58 +0530 Subject: [PATCH 406/416] fix: Remove warehouse filter on Batch field for Material Receipt --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index ac8303eda3..80203a9ef4 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,7 +88,9 @@ frappe.ui.form.on('Stock Entry', { } } - filters["warehouse"] = item.s_warehouse || item.t_warehouse; + if (frm.doc.purpose != "Material Receipt") { + filters["warehouse"] = item.s_warehouse || item.t_warehouse; + } return { query : "erpnext.controllers.queries.get_batch_no", From 48886ee70524132d1f4cf6751b56ccbc96d765a5 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 15:57:41 +0530 Subject: [PATCH 407/416] chore: Add comment above fix for future reference --- erpnext/stock/doctype/stock_entry/stock_entry.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 80203a9ef4..c4b8131305 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -88,6 +88,8 @@ frappe.ui.form.on('Stock Entry', { } } + // User could want to select a manually created empty batch (no warehouse) + // or a pre-existing batch if (frm.doc.purpose != "Material Receipt") { filters["warehouse"] = item.s_warehouse || item.t_warehouse; } From d72709dd817b879fa3bac2387689e0a9d4728e8f Mon Sep 17 00:00:00 2001 From: Anuja Pawar <60467153+Anuja-pawar@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:28:46 +0530 Subject: [PATCH 408/416] fix(Payment Entry): splitting outstanding rows as per payment terms (#27946) --- .../doctype/payment_entry/payment_entry.py | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 9b4a91d4e9..8bbe3db914 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -389,7 +389,7 @@ class PaymentEntry(AccountsController): invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100) - for key, allocated_amount in iteritems(invoice_payment_amount_map): + for idx, (key, allocated_amount) in enumerate(iteritems(invoice_payment_amount_map), 1): if not invoice_paid_amount_map.get(key): frappe.throw(_('Payment term {0} not used in {1}').format(key[0], key[1])) @@ -407,7 +407,7 @@ class PaymentEntry(AccountsController): (allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0])) else: if allocated_amount > outstanding: - frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0])) + frappe.throw(_('Row #{0}: Cannot allocate more than {1} against payment term {2}').format(idx, outstanding, key[0])) if allocated_amount and outstanding: frappe.db.sql(""" @@ -1053,12 +1053,6 @@ def get_outstanding_reference_documents(args): party_account_currency = get_account_currency(args.get("party_account")) company_currency = frappe.get_cached_value('Company', args.get("company"), "default_currency") - # Get negative outstanding sales /purchase invoices - negative_outstanding_invoices = [] - if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), - args.get("party_account"), args.get("company"), party_account_currency, company_currency) - # Get positive outstanding sales /purchase invoices/ Fees condition = "" if args.get("voucher_type") and args.get("voucher_no"): @@ -1105,6 +1099,12 @@ def get_outstanding_reference_documents(args): orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), args.get("party"), args.get("company"), party_account_currency, company_currency, filters=args) + # Get negative outstanding sales /purchase invoices + negative_outstanding_invoices = [] + if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"): + negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"), args.get("party"), + args.get("party_account"), party_account_currency, company_currency, condition=condition) + data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: @@ -1137,22 +1137,26 @@ def split_invoices_based_on_payment_terms(outstanding_invoices): 'invoice_amount': flt(d.invoice_amount), 'outstanding_amount': flt(d.outstanding_amount), 'payment_amount': payment_term.payment_amount, - 'payment_term': payment_term.payment_term, - 'allocated_amount': payment_term.outstanding + 'payment_term': payment_term.payment_term })) + outstanding_invoices_after_split = [] if invoice_ref_based_on_payment_terms: for idx, ref in invoice_ref_based_on_payment_terms.items(): - voucher_no = outstanding_invoices[idx]['voucher_no'] - voucher_type = outstanding_invoices[idx]['voucher_type'] + voucher_no = ref[0]['voucher_no'] + voucher_type = ref[0]['voucher_type'] - frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format( + frappe.msgprint(_("Spliting {} {} into {} row(s) as per Payment Terms").format( voucher_type, voucher_no, len(ref)), alert=True) - outstanding_invoices.pop(idx - 1) - outstanding_invoices += invoice_ref_based_on_payment_terms[idx] + outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx] - return outstanding_invoices + existing_row = list(filter(lambda x: x.get('voucher_no') == voucher_no, outstanding_invoices)) + index = outstanding_invoices.index(existing_row[0]) + outstanding_invoices.pop(index) + + outstanding_invoices_after_split += outstanding_invoices + return outstanding_invoices_after_split def get_orders_to_be_billed(posting_date, party_type, party, company, party_account_currency, company_currency, cost_center=None, filters=None): @@ -1219,7 +1223,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, return order_list def get_negative_outstanding_invoices(party_type, party, party_account, - company, party_account_currency, company_currency, cost_center=None): + party_account_currency, company_currency, cost_center=None, condition=None): voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" supplier_condition = "" if voucher_type == "Purchase Invoice": @@ -1241,19 +1245,21 @@ def get_negative_outstanding_invoices(party_type, party, party_account, `tab{voucher_type}` where {party_type} = %s and {party_account} = %s and docstatus = 1 and - company = %s and outstanding_amount < 0 + outstanding_amount < 0 {supplier_condition} + {condition} order by posting_date, name """.format(**{ "supplier_condition": supplier_condition, + "condition": condition, "rounded_total_field": rounded_total_field, "grand_total_field": grand_total_field, "voucher_type": voucher_type, "party_type": scrub(party_type), "party_account": "debit_to" if party_type == "Customer" else "credit_to", "cost_center": cost_center - }), (party, party_account, company), as_dict=True) + }), (party, party_account), as_dict=True) @frappe.whitelist() From f047c6ffc80775789be50707d9a1c0320c87a4fe Mon Sep 17 00:00:00 2001 From: GangaManoj Date: Tue, 2 Nov 2021 16:43:31 +0530 Subject: [PATCH 409/416] fix: Test for WDV --- erpnext/assets/doctype/asset/test_asset.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 81c679f851..170122527a 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -563,10 +563,16 @@ class TestDepreciationMethods(AssetSetup): company_flag = frappe.flags.company frappe.flags.company = "_Test Company" + finance_book = frappe.new_doc("Finance Book") + finance_book.finance_book_name = "Income Tax" + finance_book.for_income_tax = 1 + finance_book.insert(ignore_if_duplicate = True) + asset = create_asset( calculate_depreciation = 1, available_for_use_date = "2030-07-12", purchase_date = "2030-01-01", + finance_book = finance_book.name, depreciation_method = "Written Down Value", expected_value_after_useful_life = 12500, depreciation_start_date = "2030-12-31", From 890a7868584db345028420c4148e41dc0f33cbe3 Mon Sep 17 00:00:00 2001 From: hrwx Date: Mon, 1 Nov 2021 14:16:51 +0000 Subject: [PATCH 410/416] fix: do not generate multiple invoices --- .../doctype/subscription/subscription.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index de9550233f..b75530c4b0 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -502,9 +502,11 @@ class Subscription(Document): # Check invoice dates and make sure it doesn't have outstanding invoices return getdate() >= getdate(self.current_invoice_start) - def is_current_invoice_generated(self): + def is_current_invoice_generated(self, _current_start_date=None, _current_end_date=None): invoice = self.get_current_invoice() - _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) + + if not (_current_start_date and _current_end_date): + _current_start_date, _current_end_date = self.update_subscription_period(date=add_days(self.current_invoice_end, 1), return_date=True) if invoice and getdate(_current_start_date) <= getdate(invoice.posting_date) <= getdate(_current_end_date): return True @@ -523,7 +525,9 @@ class Subscription(Document): if getdate() > getdate(self.current_invoice_end) and self.is_prepaid_to_invoice(): self.update_subscription_period(add_days(self.current_invoice_end, 1)) - if not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + if not self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ + and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) @@ -560,8 +564,10 @@ class Subscription(Document): self.set_status_grace_period() # Generate invoices periodically even if current invoice are unpaid - if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice() - or self.is_prepaid_to_invoice()): + if self.generate_new_invoices_past_due_date and not \ + self.is_current_invoice_generated(self.current_invoice_start, self.current_invoice_end) \ + and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): + prorate = frappe.db.get_single_value('Subscription Settings', 'prorate') self.generate_invoice(prorate) From 7681600b5e6e2421dfdfee39661aaf87d8327e3b Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 2 Nov 2021 17:47:44 +0530 Subject: [PATCH 411/416] fix: test wdv method for indian region --- erpnext/assets/doctype/asset/test_asset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 170122527a..f162d9f39d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -1030,6 +1030,7 @@ def create_asset(**args): if asset.calculate_depreciation: asset.append("finance_books", { + "finance_book": args.finance_book, "depreciation_method": args.depreciation_method or "Straight Line", "frequency_of_depreciation": args.frequency_of_depreciation or 12, "total_number_of_depreciations": args.total_number_of_depreciations or 5, From 857d87da97d230367e1e5ac1e3fe8baa13b8130a Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Tue, 2 Nov 2021 17:52:45 +0530 Subject: [PATCH 412/416] fix: replaced "=" with "in" for multiple statuses in query #28193 fix: replaced "=" with "in" for multiple statuses in query --- .../purchase_order_analysis/purchase_order_analysis.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py index 1b25dd45d2..a566d56811 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py @@ -41,10 +41,13 @@ def get_conditions(filters): if filters.get("from_date") and filters.get("to_date"): conditions += " and po.transaction_date between %(from_date)s and %(to_date)s" - for field in ['company', 'name', 'status']: + for field in ['company', 'name']: if filters.get(field): conditions += f" and po.{field} = %({field})s" + if filters.get('status'): + conditions += " and po.status in %(status)s" + if filters.get('project'): conditions += " and poi.project = %(project)s" From 734b57deec069fc3f573604c60a3157da81eff45 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Nov 2021 18:34:55 +0530 Subject: [PATCH 413/416] fix: Serial Nos not set in the row after scanning in popup - Avoid whitspaces while calculating length of serial nos --- erpnext/selling/sales_common.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index ddd4c4e6a5..a86e60494e 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -206,8 +206,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran var me = this; var item = frappe.get_doc(cdt, cdn); - if (item.serial_no && item.qty === item.serial_no.split(`\n`).length) { - return; + // check if serial nos entered are as much as qty in row + if (item.serial_no) { + let serial_nos = item.serial_no.split(`\n`).filter(sn => sn.trim()); // filter out whitespaces + if (item.qty === serial_nos.length) return; } if (item.serial_no && !item.batch_no) { From 66348e1a035d98e91cb7228e9bd2b7286c5cc274 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 2 Nov 2021 20:26:40 +0530 Subject: [PATCH 414/416] fix: Error on LDC creation --- .../lower_deduction_certificate/lower_deduction_certificate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py index 7afbc00980..821e0171bb 100644 --- a/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py +++ b/erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py @@ -31,7 +31,7 @@ class LowerDeductionCertificate(Document): <= fiscal_year.year_end_date): frappe.throw(_("Valid Upto date not in Fiscal Year {0}").format(frappe.bold(self.fiscal_year))) - def tax_withholding_category(self): + def validate_supplier_against_tax_category(self): duplicate_certificate = frappe.db.get_value('Lower Deduction Certificate', {'supplier': self.supplier, 'tax_withholding_category': self.tax_withholding_category, 'name': ("!=", self.name)}, ['name', 'valid_from', 'valid_upto'], as_dict=True) From c2697bca76318ff4a5839df2dcb4743af2ef196c Mon Sep 17 00:00:00 2001 From: Revant Nandgaonkar Date: Wed, 3 Nov 2021 13:52:37 +0530 Subject: [PATCH 415/416] ci: change GITHUB_PAT to CI_PAT --- .github/workflows/docker-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 21ba981df4..5b607a9940 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -11,4 +11,4 @@ jobs: - name: curl run: | apk add curl bash - curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.GITHUB_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}' + curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}' From cb064b06adb5c82ec021ad2e005102f50da60fef Mon Sep 17 00:00:00 2001 From: Noah Jacob Date: Wed, 3 Nov 2021 17:23:57 +0530 Subject: [PATCH 416/416] fix: added job_card_item link in material request (#28222) * fix: added job_card_item links in material request * fix: add no copy to row references --- erpnext/manufacturing/doctype/job_card/job_card.py | 3 ++- .../doctype/material_request/material_request.py | 3 ++- .../material_request_item/material_request_item.json | 12 +++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index e1d79be81c..b3b94071f0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -605,7 +605,8 @@ def make_material_request(source_name, target_doc=None): "doctype": "Material Request Item", "field_map": { "required_qty": "qty", - "uom": "stock_uom" + "uom": "stock_uom", + "name": "job_card_item" }, "postprocess": update_item, } diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 17df9777b1..ec01cae256 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -502,7 +502,8 @@ def make_stock_entry(source_name, target_doc=None): "field_map": { "name": "material_request_item", "parent": "material_request", - "uom": "stock_uom" + "uom": "stock_uom", + "job_card_item": "job_card_item" }, "postprocess": update_item, "condition": lambda doc: doc.ordered_qty < doc.stock_qty diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 25bbbbd4b3..2bad42a0eb 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -52,6 +52,7 @@ "sales_order_item", "production_plan", "material_request_plan_item", + "job_card_item", "col_break4", "expense_account", "section_break_46", @@ -444,16 +445,25 @@ { "fieldname": "qty_info_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "job_card_item", + "fieldtype": "Data", + "hidden": 1, + "no_copy": 1, + "print_hide": 1, + "label": "Job Card Item" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-02 11:44:36.553064", + "modified": "2021-11-03 14:40:24.409826", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified",