From 1f20f6348d93b12f2400beef7acbab40f4e1f2a5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:13:29 +0530 Subject: [PATCH 01/69] feat: added membership settings doctype --- .../doctype/membership_settings/__init__.py | 0 .../membership_settings.js | 12 +++ .../membership_settings.json | 89 +++++++++++++++++++ .../membership_settings.py | 17 ++++ .../test_membership_settings.py | 10 +++ 5 files changed, 128 insertions(+) create mode 100644 erpnext/non_profit/doctype/membership_settings/__init__.py create mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.js create mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.json create mode 100644 erpnext/non_profit/doctype/membership_settings/membership_settings.py create mode 100644 erpnext/non_profit/doctype/membership_settings/test_membership_settings.py diff --git a/erpnext/non_profit/doctype/membership_settings/__init__.py b/erpnext/non_profit/doctype/membership_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js new file mode 100644 index 0000000000..092c854b43 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js @@ -0,0 +1,12 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Membership Settings', { + refresh: function(frm) { + // if (frm.doc.enable_razorpay) { + // frm.add_custom_button(__("Fetch Plans from RazorPay"), () => { + // frm.trigger("fetch_razorpay_plans") + // }); + // } + } +}); diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json new file mode 100644 index 0000000000..d64e392287 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json @@ -0,0 +1,89 @@ +{ + "actions": [], + "creation": "2020-03-29 12:57:03.005120", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable_razorpay", + "razorpay_settings_section", + "billing_cycle", + "billing_frequency", + "column_break_2", + "auto_capture_payment", + "create_subscription", + "auto_create_invoice" + ], + "fields": [ + { + "fieldname": "billing_cycle", + "fieldtype": "Select", + "label": "Billing Cycle", + "options": "Monthly\nYearly" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "enable_razorpay", + "fieldtype": "Check", + "label": "Enable RazorPay For Memberships" + }, + { + "default": "0", + "fieldname": "create_subscription", + "fieldtype": "Check", + "label": "Create Subscription" + }, + { + "default": "0", + "depends_on": "eval:doc.enable_razorpay", + "fieldname": "auto_capture_payment", + "fieldtype": "Check", + "label": "Auto Capture Payment" + }, + { + "default": "0", + "fieldname": "auto_create_invoice", + "fieldtype": "Check", + "label": "Auto Create Invoice" + }, + { + "depends_on": "eval:doc.enable_razorpay", + "fieldname": "razorpay_settings_section", + "fieldtype": "Section Break", + "label": "RazorPay Settings" + }, + { + "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", + "fieldname": "billing_frequency", + "fieldtype": "Data", + "label": "Billing Frequency" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-03-30 16:02:00.060583", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Membership 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/non_profit/doctype/membership_settings/membership_settings.py b/erpnext/non_profit/doctype/membership_settings/membership_settings.py new file mode 100644 index 0000000000..2b8e37f2a6 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.py @@ -0,0 +1,17 @@ +# -*- 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.integrations.utils import get_payment_gateway_controller +from frappe.model.document import Document + +class MembershipSettings(Document): + pass + +@frappe.whitelist() +def get_plans_for_membership(*args, **kwargs): + controller = get_payment_gateway_controller("Razorpay") + plans = controller.get_plans() + return [plan.get("item") for plan in plans.get("items")] \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py b/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py new file mode 100644 index 0000000000..2ad7984583 --- /dev/null +++ b/erpnext/non_profit/doctype/membership_settings/test_membership_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestMembershipSettings(unittest.TestCase): + pass From e836cdec135ac393e89ab28288695f1547a33d41 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:14:10 +0530 Subject: [PATCH 02/69] feat: update member and membership doctype --- erpnext/non_profit/doctype/member/member.json | 723 ++++-------------- .../membership_type/membership_type.json | 167 ++-- 2 files changed, 198 insertions(+), 692 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index 941497494c..c6c6dcd366 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -1,604 +1,171 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 1, - "autoname": "naming_series:", - "beta": 0, - "creation": "2017-09-11 09:24:52.898356", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2017-09-11 09:24:52.898356", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "member_name", + "membership_expiry_date", + "column_break_5", + "membership_type", + "email", + "email_id", + "image", + "customer_section", + "customer", + "customer_name", + "supplier_section", + "supplier", + "address_contacts", + "address_html", + "column_break_9", + "contact_html" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "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": "Series", - "length": 0, - "no_copy": 0, - "options": "NPO-MEM-.YYYY.-", - "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": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "NPO-MEM-.YYYY.-", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "member_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": "Member 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": 0 - }, + "fieldname": "member_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Member Name", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_expiry_date", - "fieldtype": "Date", - "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": "Membership Expiry Date", - "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 - }, + "fieldname": "membership_expiry_date", + "fieldtype": "Date", + "label": "Membership Expiry Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "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 - }, + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_type", - "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": "Membership Type", - "length": 0, - "no_copy": 0, - "options": "Membership Type", - "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": "membership_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Membership Type", + "options": "Membership Type", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "email", - "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": "Email", - "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": 0 - }, + "fieldname": "email", + "fieldtype": "Link", + "in_list_view": 1, + "label": "User", + "options": "User" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "image", - "fieldtype": "Attach Image", - "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": "Image", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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 - }, + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "customer_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": "Customer", - "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 - }, + "collapsible": 1, + "fieldname": "customer_section", + "fieldtype": "Section Break", + "label": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "fieldtype": "Link", - "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": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "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 - }, + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "customer.customer_name", - "fieldname": "customer_name", - "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": "Customer Name", - "length": 0, - "no_copy": 0, - "options": "", - "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 - }, + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "supplier_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": "Supplier", - "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 - }, + "collapsible": 1, + "fieldname": "supplier_section", + "fieldtype": "Section Break", + "label": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier", - "fieldtype": "Link", - "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": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "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 - }, + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_contacts", - "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": "Address and Contact", - "length": 0, - "no_copy": 0, - "options": "fa fa-map-marker", - "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 - }, + "fieldname": "address_contacts", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-map-marker" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_html", - "fieldtype": "HTML", - "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": "Address HTML", - "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 - }, + "fieldname": "address_html", + "fieldtype": "HTML", + "label": "Address HTML" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "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 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_html", - "fieldtype": "HTML", - "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": "Contact HTML", - "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 + "fieldname": "contact_html", + "fieldtype": "HTML", + "label": "Contact HTML" + }, + { + "fieldname": "email_id", + "fieldtype": "Data", + "label": "Email Address" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 14:44:23.218109", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Member", - "name_case": "", - "owner": "Administrator", + ], + "image_field": "image", + "links": [], + "modified": "2020-03-30 13:42:06.488851", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Member", + "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": "Non Profit Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "share": 1, "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": "Non Profit Member", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Member", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Non Profit", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "member_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "restrict_to_domain": "Non Profit", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "member_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.json b/erpnext/non_profit/doctype/membership_type/membership_type.json index 35a7902c9f..319078fd6c 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.json +++ b/erpnext/non_profit/doctype/membership_type/membership_type.json @@ -1,124 +1,63 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:membership_type", - "beta": 0, - "creation": "2017-09-18 12:56:56.343999", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "field:membership_type", + "creation": "2017-09-18 12:56:56.343999", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "membership_type", + "amount", + "razorpay_plan_id" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_type", - "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": 1, - "label": "Membership Type", - "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 - }, + "fieldname": "membership_type", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Membership Type", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "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": "Amount", - "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 + "fieldname": "amount", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Amount", + "reqd": 1 + }, + { + "fieldname": "razorpay_plan_id", + "fieldtype": "Data", + "hidden": 1, + "label": "Razorpay Plan ID", + "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": "2017-12-05 07:03:45.860757", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Membership Type", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-03-30 12:54:07.850857", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Membership Type", + "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": "Non Profit Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Non Profit", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "restrict_to_domain": "Non Profit", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 1d6395c5cf57db4cb9989c24c3d4bd9d9d7db64c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:14:44 +0530 Subject: [PATCH 03/69] feat: add PAN details field to member for India --- erpnext/regional/india/setup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 970a831e0e..adc0b2cb4d 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -507,6 +507,14 @@ def make_custom_fields(update=True): 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } + ], + "Member": [ + { + 'fieldname': 'pan_number', + 'label': 'PAN Details', + 'fieldtype': 'Data', + 'insert_after': 'email' + } ] } create_custom_fields(custom_fields, update=update) @@ -728,4 +736,4 @@ def get_tds_details(accounts, fiscal_year): doctype="Tax Withholding Category", accounts=accounts, rates=[{"fiscal_year": fiscal_year, "tax_withholding_rate": 20, "single_threshold": 2500, "cumulative_threshold": 0}]) - ] + ] \ No newline at end of file From 12816343065f20eacb4be1ad148b66b242fbd6b5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:15:22 +0530 Subject: [PATCH 04/69] feat: client code to handle razorpay fields --- .../non_profit/doctype/membership_type/membership_type.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js index 3ef39aee7d..ce8225e384 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.js +++ b/erpnext/non_profit/doctype/membership_type/membership_type.js @@ -2,7 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Membership Type', { - refresh: function() { - + refresh: function(frm) { + frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + if (val) frm.set_df_property('razorpay_plan_id', 'hidden', 'false'); + }) } }); From 28fafd0ddafd4a43fb8da4e74f772180bf315a67 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:15:37 +0530 Subject: [PATCH 05/69] feat: setup subscription API --- erpnext/non_profit/doctype/member/member.py | 5 +- .../doctype/membership/membership.py | 103 +++++++++++++++++- 2 files changed, 105 insertions(+), 3 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 9afaf90e7a..cec1ea0112 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -14,7 +14,10 @@ class Member(Document): def validate(self): - self.validate_email_type(self.email) + if self.email: + self.validate_email_type(self.email) + if self.email_id: + self.validate_email_type(self.email_id) def validate_email_type(self, email): from frappe.utils import validate_email_address diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 98bee56979..409d6f3af4 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -5,7 +5,8 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.utils import add_days, add_years, nowdate, getdate +from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint +from frappe.integrations.utils import get_payment_gateway_controller from frappe import _ import erpnext @@ -43,11 +44,109 @@ class Membership(Document): else: self.from_date = nowdate() - self.to_date = add_years(self.from_date, 1) + if frappe.db.get_single_value("Membership Settings", "billing_cycle") == "Yearly": + self.to_date = add_years(self.from_date, 1) + else: + self.to_date = add_months(self.from_date, 1) def on_payment_authorized(self, status_changed_to=None): if status_changed_to in ("Completed", "Authorized"): self.load_from_db() self.db_set('paid', 1) + def setup_subscription(self): + membership_settings = frappe.get_doc("Membership Settings") + if not membership_settings.enable_razorpay: + frappe.throw("Please enable Razorpay to setup subscription") + controller = get_payment_gateway_controller("Razorpay") + settings = controller.get_settings({}) + + plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") + + if not plan_id: + frappe.throw(_("Please setup Razorpay Plan ID")) + + subscription_details = { + "plan_id": plan_id, + "billing_frequency": cint(membership_settings.billing_frequency), + "customer_notify": 1 + } + + args = { + 'subscription_details': subscription_details + } + + subscription = controller.setup_subscription(settings, **args) + + return subscription + + +def get_member_if_exists(email, plan): + member_list = frappe.get_all("Member", filters={'email': email, 'membership_type': plan}) + if member_list and member_list[0]: + return member_list[0]['name'] + else: + return None + +def create_member(user_details): + member = frappe.new_doc("Member") + member.update({ + "member_name": user_details.fullname, + "email_id": user_details.email, + "pan_number": user_details.pan, + "membership_type": user_details.plan_id, + "customer": create_customer(user_details) + }) + + member.insert(ignore_permissions=True) + return member + +def create_customer(user_details): + customer = frappe.new_doc("Customer") + customer.customer_name = user_details.fullname + customer.customer_type = "Individual" + customer.insert(ignore_permissions=True) + + try: + contact = frappe.new_doc("Contact") + contact.first_name = user_details.fullname + contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) + contact.add_email(user_details.email, is_primary=1) + contact.insert(ignore_permissions=True) + + contact.append("links", { + "link_doctype": "Customer", + "link_name": customer.name + }) + + contact.insert() + except Exception: + error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + + return customer.name + +def create_membership(member): + membership = frappe.new_doc("Membership") + membership.update({ + "member": member.name, + "membership_status": "New", + "membership_type": member.membership_type, + "currency": "INR", + "amount": plan.amount + }) + + membership.insert(ignore_permissions=True) + +@frappe.whitelist(allow_guest=True) +def create_membership_subscription(user_details): + # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} + user_details = frappe._dict(user_details) + member = get_member_if_exists(user_details.email, user_details.plan_id) + plan = frappe.get_doc("Membership Type", user_details.plan_id) + if not member: + member = create_member(user_details) + + membership = create_membership(member, plan) + + return membership.setup_subscription() From 3a2c8560baa3fcfb48d902f25173c458f8316d97 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 30 Mar 2020 18:15:51 +0530 Subject: [PATCH 06/69] feat: update desk page for Non Profits --- .../non_profit/desk_page/non_profit/non_profit.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/non_profit/desk_page/non_profit/non_profit.json b/erpnext/non_profit/desk_page/non_profit/non_profit.json index 5d759ba113..dcbd9c37a6 100644 --- a/erpnext/non_profit/desk_page/non_profit/non_profit.json +++ b/erpnext/non_profit/desk_page/non_profit/non_profit.json @@ -1,28 +1,34 @@ { "cards": [ { + "hidden": 0, "icon": "icon-list", "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]", "title": "Loan Management" }, { + "hidden": 0, "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]", "title": "Grant Application" }, { - "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Memebership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Memebership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n }\n]", + "hidden": 0, + "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]", "title": "Membership" }, { + "hidden": 0, "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]", "title": "Volunteer" }, { + "hidden": 0, "icon": "fa fa-star", "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]", "title": "Chapter" }, { + "hidden": 0, "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]", "title": "Donor" } @@ -38,7 +44,7 @@ "idx": 0, "is_standard": 1, "label": "Non Profit", - "modified": "2020-03-12 16:30:36.683012", + "modified": "2020-03-29 13:06:18.070131", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", From 0c8a0c9c3af14708dd27fb534351b8a875319435 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 28 Mar 2020 18:44:53 +0530 Subject: [PATCH 07/69] fix: no server side validations for accounts in asset category --- .../doctype/asset_category/asset_category.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index fc08841be9..e9ef0c7d9e 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -11,12 +11,29 @@ from frappe.model.document import Document class AssetCategory(Document): def validate(self): self.validate_finance_books() + self.validate_accounts() def validate_finance_books(self): for d in self.finance_books: for field in ("Total Number of Depreciations", "Frequency of Depreciation"): if cint(d.get(frappe.scrub(field)))<1: frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) + + def validate_accounts(self): + account_type_map = { + 'fixed_asset_account': { 'account_type': 'Fixed Asset' }, + 'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' }, + 'depreciation_expense_account': { 'account_type': 'Expense' }, + 'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' } + } + for d in self.accounts: + for account in account_type_map.keys(): + if d.get(account): + account_type = frappe.db.get_value('Account', d.get(account), 'account_type') + if account_type != account_type_map[account]['account_type']: + frappe.throw(_("Row {}: Account Type of {} should be {} account".format(d.idx, frappe.bold(frappe.unscrub(account)), + frappe.bold(account_type_map[account]['account_type']))), title=_("Invalid Account")) + @frappe.whitelist() def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None): From 474222074b5fe3240ca4a2f3f69a4015be2b7a30 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 2 Apr 2020 22:17:41 +0530 Subject: [PATCH 08/69] fix: tests --- .../accounts/doctype/account/test_account.py | 5 +++-- .../doctype/asset_category/asset_category.py | 19 ++++++++++++------- .../purchase_receipt/test_purchase_receipt.py | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index dc23b2b2d0..9894b9309a 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -69,6 +69,7 @@ class TestAccount(unittest.TestCase): acc.account_name = "Accumulated Depreciation" acc.parent_account = "Fixed Assets - _TC" acc.company = "_Test Company" + acc.account_type = "Accumulated Depreciation" acc.insert() doc = frappe.get_doc("Account", "Securities and Deposits - _TC") @@ -149,8 +150,8 @@ def _make_test_records(verbose): # fixed asset depreciation ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], - ["_Test Accumulated Depreciations", "Current Assets", 0, None, None], - ["_Test Depreciations", "Expenses", 0, None, None], + ["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None], + ["_Test Depreciations", "Expenses", 0, "Expense", None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], # Receivable / Payable Account diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index e9ef0c7d9e..770b1ee9a4 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -23,16 +23,21 @@ class AssetCategory(Document): account_type_map = { 'fixed_asset_account': { 'account_type': 'Fixed Asset' }, 'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' }, - 'depreciation_expense_account': { 'account_type': 'Expense' }, + 'depreciation_expense_account': { 'root_type': 'Expense' }, 'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' } } for d in self.accounts: - for account in account_type_map.keys(): - if d.get(account): - account_type = frappe.db.get_value('Account', d.get(account), 'account_type') - if account_type != account_type_map[account]['account_type']: - frappe.throw(_("Row {}: Account Type of {} should be {} account".format(d.idx, frappe.bold(frappe.unscrub(account)), - frappe.bold(account_type_map[account]['account_type']))), title=_("Invalid Account")) + for fieldname in account_type_map.keys(): + if d.get(fieldname): + selected_account = d.get(fieldname) + key_to_match = account_type_map[fieldname].keys()[0] + selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match) + expected_key_type = account_type_map[fieldname][key_to_match] + + if selected_key_type != expected_key_type: + frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account." + .format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type))), + title=_("Invalid Account")) @frappe.whitelist() diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index cba7f20153..cd190da689 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -356,8 +356,8 @@ class TestPurchaseReceipt(unittest.TestCase): 'accounts': [{ 'company_name': '_Test Company', 'fixed_asset_account': '_Test Fixed Asset - _TC', - 'accumulated_depreciation_account': 'Depreciation - _TC', - 'depreciation_expense_account': 'Depreciation - _TC' + 'accumulated_depreciation_account': '_Test Accumulated Depreciations - _TC', + 'depreciation_expense_account': '_Test Depreciation - _TC' }] }).insert() From 4b667160a0988dfecf70fcb919e98347bc43c519 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:44:17 +0530 Subject: [PATCH 09/69] feat: added razorpay details to membership --- .../doctype/membership/membership.js | 8 +- .../doctype/membership/membership.json | 619 ++++-------------- .../doctype/membership/membership.py | 59 +- 3 files changed, 205 insertions(+), 481 deletions(-) diff --git a/erpnext/non_profit/doctype/membership/membership.js b/erpnext/non_profit/doctype/membership/membership.js index 421087995a..554549a0bd 100644 --- a/erpnext/non_profit/doctype/membership/membership.js +++ b/erpnext/non_profit/doctype/membership/membership.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('Membership', { - onload:function(frm) { + setup: function(frm) { + frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + if (val) frm.set_df_property('razorpay_details_section', 'hidden', false); + }) + }, + + onload: function(frm) { frm.add_fetch('membership_type', 'amount', 'amount'); } }); diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json index 9a204b19bf..9f10d0cfc7 100644 --- a/erpnext/non_profit/doctype/membership/membership.json +++ b/erpnext/non_profit/doctype/membership/membership.json @@ -1,501 +1,164 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "NPO-MSH-.YYYY.-.#####", - "beta": 0, - "creation": "2017-09-11 11:39:18.492184", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "autoname": "NPO-MSH-.YYYY.-.#####", + "creation": "2017-09-11 11:39:18.492184", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "member", + "membership_type", + "column_break_3", + "membership_status", + "membership_validity_section", + "from_date", + "to_date", + "column_break_8", + "member_since_date", + "payment_details", + "paid", + "currency", + "amount", + "razorpay_details_section", + "subscription_id", + "payment_id", + "webhook_payload" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "member", - "fieldtype": "Link", - "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": "Member", - "length": 0, - "no_copy": 0, - "options": "Member", - "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 - }, + "fieldname": "member", + "fieldtype": "Link", + "label": "Member", + "options": "Member" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_type", - "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": "Membership Type", - "length": 0, - "no_copy": 0, - "options": "Membership Type", - "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": "membership_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Membership Type", + "options": "Membership Type", + "reqd": 1 + }, { - "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 - }, + "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": "membership_status", - "fieldtype": "Select", - "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": "Membership Status", - "length": 0, - "no_copy": 0, - "options": "New\nCurrent\nExpired\nPending\nCancelled", - "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 - }, + "fieldname": "membership_status", + "fieldtype": "Select", + "label": "Membership Status", + "options": "New\nCurrent\nExpired\nPending\nCancelled" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "membership_validity_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": "Validity", - "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 - }, + "fieldname": "membership_validity_section", + "fieldtype": "Section Break", + "label": "Validity" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "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", - "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": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "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", - "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": "to_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "To", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_8", - "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 - }, + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "member_since_date", - "fieldtype": "Date", - "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": "Member Since", - "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 - }, + "fieldname": "member_since_date", + "fieldtype": "Date", + "label": "Member Since" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payment_details", - "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": "Payment Details", - "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 - }, + "fieldname": "payment_details", + "fieldtype": "Section Break", + "label": "Payment Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "paid", - "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": "Paid", - "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 - }, + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "label": "Paid" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "currency", - "fieldtype": "Select", - "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": "Currency", - "length": 0, - "no_copy": 0, - "options": "USD\nINR", - "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 - }, + "fieldname": "currency", + "fieldtype": "Select", + "label": "Currency", + "options": "USD\nINR" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Float", - "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": "Amount", - "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 + "fieldname": "amount", + "fieldtype": "Float", + "label": "Amount" + }, + { + "fieldname": "razorpay_details_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Razorpay Details" + }, + { + "fieldname": "subscription_id", + "fieldtype": "Data", + "label": "Subscription ID", + "read_only": 1 + }, + { + "fieldname": "payment_id", + "fieldtype": "Data", + "label": "Payment ID", + "read_only": 1 + }, + { + "fieldname": "webhook_payload", + "fieldtype": "Code", + "label": "Webhook Payload", + "options": "JSON", + "read_only": 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": "2018-08-21 16:15:42.323446", - "modified_by": "Administrator", - "module": "Non Profit", - "name": "Membership", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-04-06 14:29:33.856060", + "modified_by": "Administrator", + "module": "Non Profit", + "name": "Membership", + "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": "Non Profit Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Manager", + "share": 1, "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": "Non Profit Member", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Non Profit Member", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "restrict_to_domain": "Non Profit", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "restrict_to_domain": "Non Profit", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 409d6f3af4..360c59f4ec 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -126,20 +126,39 @@ def create_customer(user_details): return customer.name -def create_membership(member): +def create_membership(member, plan): membership = frappe.new_doc("Membership") membership.update({ "member": member.name, "membership_status": "New", "membership_type": member.membership_type, "currency": "INR", - "amount": plan.amount + "amount": plan.amount, + "from_date": getdate() }) membership.insert(ignore_permissions=True) + return membership + @frappe.whitelist(allow_guest=True) def create_membership_subscription(user_details): + """Summary + + Args: + user_details (TYPE): Description + + Returns: + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, + 'subscription_id': 'sub_EZycCvXFvqnC6p' + } + """ # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} user_details = frappe._dict(user_details) member = get_member_if_exists(user_details.email, user_details.plan_id) @@ -150,3 +169,39 @@ def create_membership_subscription(user_details): membership = create_membership(member, plan) return membership.setup_subscription() + +@frappe.whitelist(allow_guest=True) +def razorpay_subscription_started(data={}): + data = { + "entity": "event", + "event": "subscription.activated", + "payload": { + "subscription": { + "entity": { + "id": "sub_EZZsbKwt5xRYH6", + "entity": "subscription", + "plan_id": "plan_EXwyxDYDCj3X4v", + "customer_id": "cust_EZZw80dUQgID8C", + "current_start": 1585822073, + "current_end": 1588357800, + "start_at": 1585822073, + "end_at": 1646159400, + "auth_attempts": 0, + "total_count": 24, + "paid_count": 1, + } + }, + "payment": { + "entity": { + "id": "pay_EZZw7v9NEUFCVJ", + "amount": 10000, + "currency": "INR", + "method": "card", + } + } + }, + "created_at": 1585822079 + } + data = frappe._dict(data) + + pass \ No newline at end of file From d92b7ab2580b3d9062976a9950970defa6d0d964 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:44:33 +0530 Subject: [PATCH 10/69] fix: use false explicitly --- erpnext/non_profit/doctype/membership_type/membership_type.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js index ce8225e384..226981dc78 100644 --- a/erpnext/non_profit/doctype/membership_type/membership_type.js +++ b/erpnext/non_profit/doctype/membership_type/membership_type.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Membership Type', { refresh: function(frm) { frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { - if (val) frm.set_df_property('razorpay_plan_id', 'hidden', 'false'); + if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false); }) } }); From 8d6cded07f480eada7a6542a2e6b5af45104e64a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:44:42 +0530 Subject: [PATCH 11/69] feat: added subscription web hook --- .../doctype/membership/membership.py | 119 +++++++++++++----- 1 file changed, 86 insertions(+), 33 deletions(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 360c59f4ec..574ae7fb7b 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -3,6 +3,8 @@ # For license information, please see license.txt from __future__ import unicode_literals +import json +from datetime import datetime import frappe from frappe.model.document import Document from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint @@ -168,40 +170,91 @@ def create_membership_subscription(user_details): membership = create_membership(member, plan) - return membership.setup_subscription() + subscription = membership.setup_subscription() + + membership.subscription_id = subscription.get('subscription_id') + membership.save(ignore_permissions=True) + + return subscription + +def get_membership_based_on_subscription(subscription_id, custom_filters={}): + filters = {'subscription_id': subscription.id} + filters.update(custom_filters) + + memberships = frappe.get_all("Membership", filters=filters, order_by="creation") + if not memberships: + return None + + return frappe.get_doc("Membership", memberships[0]['name']) + @frappe.whitelist(allow_guest=True) -def razorpay_subscription_started(data={}): - data = { - "entity": "event", - "event": "subscription.activated", - "payload": { - "subscription": { - "entity": { - "id": "sub_EZZsbKwt5xRYH6", - "entity": "subscription", - "plan_id": "plan_EXwyxDYDCj3X4v", - "customer_id": "cust_EZZw80dUQgID8C", - "current_start": 1585822073, - "current_end": 1588357800, - "start_at": 1585822073, - "end_at": 1646159400, - "auth_attempts": 0, - "total_count": 24, - "paid_count": 1, - } - }, - "payment": { - "entity": { - "id": "pay_EZZw7v9NEUFCVJ", - "amount": 10000, - "currency": "INR", - "method": "card", - } - } - }, - "created_at": 1585822079 - } +def trigger_razorpay_subscription(data): + if isinstance(data, six.string_types): + data = json.loads(data) data = frappe._dict(data) - pass \ No newline at end of file + subscription = data.payload.get("subscription", {}).get('entity', {}) + subscription = frappe._dict(subscription) + + payment = data.payload.get("payment", {}).get('entity', {}) + payment = frappe._dict(payment) + + if data.event == "subscription.activated": + membership = get_membership_based_on_subscription(subscription.id, {"membership_status": "New"}) + else + prev_membership = get_membership_based_on_subscription(subscription.id, {"payment_id": payment.id, "paid": 1}) + if prev_membership: + print("payment already done") + return + prev_membership = get_membership_based_on_subscription(subscription.id) + membership = frappe.new_doc("Membership") + membership.update({ + "member": prev_membership.member, + "membership_status": "Current", + "membership_type": prev_membership.membership_type, + "currency": "INR", + }) + + subscription_charged(subscription, payment, membership) + +def subscription_charged(subscription, payment, membership=None): + data = { + "subscription": subscription, + "payment": payment, + } + membership.paid = 1 + membership.payment_id = payment.id + membership.webhook_payload = json.dumps(data, indent=4, sort_keys=True) + membership.from_date = datetime.fromtimestamp(subscription.current_start) + membership.to_date = datetime.fromtimestamp(subscription.current_end) + membership.amount = payment.amount / 100 # Convert to rupees from paise + + if membership.is_new(): + membership.insert() + else: + membership.save() + + return True + + + + + + + + + + + + + + + + + + + + + + From 65e11a9640089b93c200bc5fa13c3a93007afeb2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 14:49:28 +0530 Subject: [PATCH 12/69] refactor: razorpay subscription for memberships --- erpnext/non_profit/doctype/member/member.js | 8 + erpnext/non_profit/doctype/member/member.json | 49 +++- erpnext/non_profit/doctype/member/member.py | 110 ++++++++- .../doctype/membership/membership.py | 209 ++++-------------- 4 files changed, 208 insertions(+), 168 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js index eb74dc1507..3e9d0baba5 100644 --- a/erpnext/non_profit/doctype/member/member.js +++ b/erpnext/non_profit/doctype/member/member.js @@ -2,6 +2,14 @@ // For license information, please see license.txt frappe.ui.form.on('Member', { + setup: function(frm) { + frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => { + if (val && (frm.doc.subscription_id || frm.doc.customer_id)) { + frm.set_df_property('razorpay_details_section', 'hidden', false); + } + }) + }, + refresh: function(frm) { frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'}; diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json index c6c6dcd366..bb73a843ee 100644 --- a/erpnext/non_profit/doctype/member/member.json +++ b/erpnext/non_profit/doctype/member/member.json @@ -23,7 +23,14 @@ "address_contacts", "address_html", "column_break_9", - "contact_html" + "contact_html", + "razorpay_details_section", + "subscription_id", + "customer_id", + "subscription_activated", + "column_break_21", + "subscription_start", + "subscription_end" ], "fields": [ { @@ -127,11 +134,49 @@ "fieldname": "email_id", "fieldtype": "Data", "label": "Email Address" + }, + { + "fieldname": "subscription_id", + "fieldtype": "Data", + "label": "Subscription ID", + "read_only": 1 + }, + { + "fieldname": "customer_id", + "fieldtype": "Data", + "label": "Customer ID", + "read_only": 1 + }, + { + "fieldname": "razorpay_details_section", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Razorpay Details" + }, + { + "fieldname": "column_break_21", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "subscription_activated", + "fieldtype": "Check", + "label": "Subscription Activated" + }, + { + "fieldname": "subscription_start", + "fieldtype": "Date", + "label": "Subscription Start " + }, + { + "fieldname": "subscription_end", + "fieldtype": "Date", + "label": "Subscription End" } ], "image_field": "image", "links": [], - "modified": "2020-03-30 13:42:06.488851", + "modified": "2020-04-07 14:20:33.215700", "modified_by": "Administrator", "module": "Non Profit", "name": "Member", diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index cec1ea0112..1ae8699db1 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -3,9 +3,12 @@ # 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.contacts.address_and_contact import load_address_and_contact - +from frappe.utils import cint +from frappe.integrations.utils import get_payment_gateway_controller class Member(Document): def onload(self): @@ -21,4 +24,107 @@ class Member(Document): def validate_email_type(self, email): from frappe.utils import validate_email_address - validate_email_address(email.strip(), True) \ No newline at end of file + validate_email_address(email.strip(), True) + + def setup_subscription(self): + membership_settings = frappe.get_doc("Membership Settings") + if not membership_settings.enable_razorpay: + frappe.throw("Please enable Razorpay to setup subscription") + + controller = get_payment_gateway_controller("Razorpay") + settings = controller.get_settings({}) + + plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") + + if not plan_id: + frappe.throw(_("Please setup Razorpay Plan ID")) + + subscription_details = { + "plan_id": plan_id, + "billing_frequency": cint(membership_settings.billing_frequency), + "customer_notify": 1 + } + + args = { + 'subscription_details': subscription_details + } + + subscription = controller.setup_subscription(settings, **args) + + return subscription + +def get_or_create_member(user_details): + member_list = frappe.get_all("Member", filters={'email': user_details.email, 'membership_type': user_details.plan_id}) + if member_list and member_list[0]: + return member_list[0]['name'] + else: + return create_member(user_details) + +def create_member(user_details): + member = frappe.new_doc("Member") + member.update({ + "member_name": user_details.fullname, + "email_id": user_details.email, + "pan_number": user_details.pan, + "membership_type": user_details.plan_id, + "customer": create_customer(user_details) + }) + + member.insert(ignore_permissions=True) + return member + +def create_customer(user_details): + customer = frappe.new_doc("Customer") + customer.customer_name = user_details.fullname + customer.customer_type = "Individual" + customer.insert(ignore_permissions=True) + + try: + contact = frappe.new_doc("Contact") + contact.first_name = user_details.fullname + contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) + contact.add_email(user_details.email, is_primary=1) + contact.insert(ignore_permissions=True) + + contact.append("links", { + "link_doctype": "Customer", + "link_name": customer.name + }) + + contact.insert() + except Exception: + error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + + return customer.name + +@frappe.whitelist(allow_guest=True) +def create_member_subscription_order(user_details): + """Summary + + Args: + user_details (TYPE): Description + + Returns: + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, + 'subscription_id': 'sub_EZycCvXFvqnC6p' + } + """ + # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} + user_details = frappe._dict(user_details) + plan = frappe.get_doc("Membership Type", user_details.plan_id) + member = get_or_create_member(user_details) + if not member: + member = create_member(user_details) + + subscription = member.setup_subscription() + + member.subscription_id = subscription.get('subscription_id') + member.save(ignore_permissions=True) + + return subscription \ No newline at end of file diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 574ae7fb7b..00ecd8fbdf 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -4,11 +4,12 @@ from __future__ import unicode_literals import json -from datetime import datetime import frappe +import six +from datetime import datetime from frappe.model.document import Document +from frappe.email import sendmail_to_system_managers from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint -from frappe.integrations.utils import get_payment_gateway_controller from frappe import _ import erpnext @@ -56,139 +57,16 @@ class Membership(Document): self.load_from_db() self.db_set('paid', 1) - def setup_subscription(self): - membership_settings = frappe.get_doc("Membership Settings") - if not membership_settings.enable_razorpay: - frappe.throw("Please enable Razorpay to setup subscription") +def get_member_based_on_subscription(subscription_id, email): + members = frappe.get_all("Member", filters={ + 'subscription_id': subscription_id, + 'email_id': email + }, order_by="creation desc") - controller = get_payment_gateway_controller("Razorpay") - settings = controller.get_settings({}) - - plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id") - - if not plan_id: - frappe.throw(_("Please setup Razorpay Plan ID")) - - subscription_details = { - "plan_id": plan_id, - "billing_frequency": cint(membership_settings.billing_frequency), - "customer_notify": 1 - } - - args = { - 'subscription_details': subscription_details - } - - subscription = controller.setup_subscription(settings, **args) - - return subscription + return frappe.get_doc("Member", members[0]['name']) -def get_member_if_exists(email, plan): - member_list = frappe.get_all("Member", filters={'email': email, 'membership_type': plan}) - if member_list and member_list[0]: - return member_list[0]['name'] - else: - return None - -def create_member(user_details): - member = frappe.new_doc("Member") - member.update({ - "member_name": user_details.fullname, - "email_id": user_details.email, - "pan_number": user_details.pan, - "membership_type": user_details.plan_id, - "customer": create_customer(user_details) - }) - - member.insert(ignore_permissions=True) - return member - -def create_customer(user_details): - customer = frappe.new_doc("Customer") - customer.customer_name = user_details.fullname - customer.customer_type = "Individual" - customer.insert(ignore_permissions=True) - - try: - contact = frappe.new_doc("Contact") - contact.first_name = user_details.fullname - contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1) - contact.add_email(user_details.email, is_primary=1) - contact.insert(ignore_permissions=True) - - contact.append("links", { - "link_doctype": "Customer", - "link_name": customer.name - }) - - contact.insert() - except Exception: - error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) - - return customer.name - -def create_membership(member, plan): - membership = frappe.new_doc("Membership") - membership.update({ - "member": member.name, - "membership_status": "New", - "membership_type": member.membership_type, - "currency": "INR", - "amount": plan.amount, - "from_date": getdate() - }) - - membership.insert(ignore_permissions=True) - - return membership - -@frappe.whitelist(allow_guest=True) -def create_membership_subscription(user_details): - """Summary - - Args: - user_details (TYPE): Description - - Returns: - Dictionary: Dictionary with subscription details - { - 'subscription_details': { - 'plan_id': 'plan_EXwyxDYDCj3X4v', - 'billing_frequency': 24, - 'customer_notify': 1 - }, - 'subscription_id': 'sub_EZycCvXFvqnC6p' - } - """ - # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} - user_details = frappe._dict(user_details) - member = get_member_if_exists(user_details.email, user_details.plan_id) - plan = frappe.get_doc("Membership Type", user_details.plan_id) - if not member: - member = create_member(user_details) - - membership = create_membership(member, plan) - - subscription = membership.setup_subscription() - - membership.subscription_id = subscription.get('subscription_id') - membership.save(ignore_permissions=True) - - return subscription - -def get_membership_based_on_subscription(subscription_id, custom_filters={}): - filters = {'subscription_id': subscription.id} - filters.update(custom_filters) - - memberships = frappe.get_all("Membership", filters=filters, order_by="creation") - if not memberships: - return None - - return frappe.get_doc("Membership", memberships[0]['name']) - - -@frappe.whitelist(allow_guest=True) +@frappe.whitelist() def trigger_razorpay_subscription(data): if isinstance(data, six.string_types): data = json.loads(data) @@ -200,49 +78,52 @@ def trigger_razorpay_subscription(data): payment = data.payload.get("payment", {}).get('entity', {}) payment = frappe._dict(payment) + try: + data_json = json.dumps(data, indent=4, sort_keys=True) + member = get_member_based_on_subscription(subscription.id, payment.email) + except Exception as e: + error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed")) + notify_failure(log) + raise e + if data.event == "subscription.activated": - membership = get_membership_based_on_subscription(subscription.id, {"membership_status": "New"}) - else - prev_membership = get_membership_based_on_subscription(subscription.id, {"payment_id": payment.id, "paid": 1}) - if prev_membership: - print("payment already done") - return - prev_membership = get_membership_based_on_subscription(subscription.id) + member.customer_id = payment.customer_id + member.subscription_start = datetime.fromtimestamp(subscription.start_at) + member.subscription_end = datetime.fromtimestamp(subscription.end_at) + member.subscription_activated = 1 + member.save(ignore_permissions=True) + elif data.event == "subscription.charged": membership = frappe.new_doc("Membership") membership.update({ - "member": prev_membership.member, + "member": member.name, "membership_status": "Current", - "membership_type": prev_membership.membership_type, + "membership_type": member.membership_type, "currency": "INR", + "paid": 1, + "payment_id": payment.id, + "webhook_payload": data_json, + "from_date": datetime.fromtimestamp(subscription.current_start), + "to_date": datetime.fromtimestamp(subscription.current_end), + "amount": payment.amount / 100 # Convert to rupees from paise }) - - subscription_charged(subscription, payment, membership) - -def subscription_charged(subscription, payment, membership=None): - data = { - "subscription": subscription, - "payment": payment, - } - membership.paid = 1 - membership.payment_id = payment.id - membership.webhook_payload = json.dumps(data, indent=4, sort_keys=True) - membership.from_date = datetime.fromtimestamp(subscription.current_start) - membership.to_date = datetime.fromtimestamp(subscription.current_end) - membership.amount = payment.amount / 100 # Convert to rupees from paise - - if membership.is_new(): - membership.insert() - else: - membership.save() + membership.insert(ignore_permissions=True) return True - - - - +def notify_failure(log): + try: + content = """Dear System Manager, +Razorpay webhook for creating renewing membership subscription failed due to some reason. Please check the following error log linked below + +Error Log: {0} + +Regards, +Administrator""".format(get_link_to_form("Error Log", log.name)) + sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content) + except: + pass From 31051679c823389e061b79f9b1ca0dba49bf6f51 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 18:43:11 +0530 Subject: [PATCH 13/69] feat: remove unused fields --- .../membership_settings.json | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json index d64e392287..ce843b3e8a 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json @@ -8,11 +8,7 @@ "enable_razorpay", "razorpay_settings_section", "billing_cycle", - "billing_frequency", - "column_break_2", - "auto_capture_payment", - "create_subscription", - "auto_create_invoice" + "billing_frequency" ], "fields": [ { @@ -21,35 +17,12 @@ "label": "Billing Cycle", "options": "Monthly\nYearly" }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, { "default": "0", "fieldname": "enable_razorpay", "fieldtype": "Check", "label": "Enable RazorPay For Memberships" }, - { - "default": "0", - "fieldname": "create_subscription", - "fieldtype": "Check", - "label": "Create Subscription" - }, - { - "default": "0", - "depends_on": "eval:doc.enable_razorpay", - "fieldname": "auto_capture_payment", - "fieldtype": "Check", - "label": "Auto Capture Payment" - }, - { - "default": "0", - "fieldname": "auto_create_invoice", - "fieldtype": "Check", - "label": "Auto Create Invoice" - }, { "depends_on": "eval:doc.enable_razorpay", "fieldname": "razorpay_settings_section", @@ -65,7 +38,7 @@ ], "issingle": 1, "links": [], - "modified": "2020-03-30 16:02:00.060583", + "modified": "2020-04-07 18:42:51.496807", "modified_by": "Administrator", "module": "Non Profit", "name": "Membership Settings", From 3e7a8abf296897eda59cd4500847564311c5bbbc Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 7 Apr 2020 21:00:57 +0530 Subject: [PATCH 14/69] feat: Provision to set Default Item Manufacturer - Is Default checkbox added in Item Manufacturer - Default Item Manufacturer and Part No fields added to Item Master - Manufacturer Part No field editable in all child tables with validation - Manufacturer and Part No auto fetched via get_item_details in child table --- .../purchase_invoice_item.json | 5 +-- .../purchase_order_item.json | 5 +-- .../supplier_quotation_item.json | 7 ++-- erpnext/public/js/controllers/buying.js | 26 ++++++++++++- erpnext/stock/doctype/item/item.json | 16 +++++++- .../item_manufacturer/item_manufacturer.json | 14 ++++++- .../item_manufacturer/item_manufacturer.py | 38 +++++++++++++++++++ .../material_request_item.json | 5 +-- .../purchase_receipt_item.json | 5 +-- erpnext/stock/get_item_details.py | 3 ++ 10 files changed, 105 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index 9c974260a2..db3f72ada0 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -754,8 +754,7 @@ { "fieldname": "manufacturer_part_no", "fieldtype": "Data", - "label": "Manufacturer Part Number", - "read_only": 1 + "label": "Manufacturer Part Number" }, { "depends_on": "is_fixed_asset", @@ -777,7 +776,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-01 14:20:17.297284", + "modified": "2020-04-07 18:34:35.104178", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 6768dfabfe..e37e1dd99d 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -702,8 +702,7 @@ { "fieldname": "manufacturer_part_no", "fieldtype": "Data", - "label": "Manufacturer Part Number", - "read_only": 1 + "label": "Manufacturer Part Number" }, { "default": "0", @@ -723,7 +722,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2019-12-06 13:17:12.142799", + "modified": "2020-04-07 18:35:17.558928", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index 7d7d6f4d3d..b50e834ec7 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "hash", "creation": "2013-05-22 12:43:10", "doctype": "DocType", @@ -522,8 +523,7 @@ { "fieldname": "manufacturer_part_no", "fieldtype": "Data", - "label": "Manufacturer Part Number", - "read_only": 1 + "label": "Manufacturer Part Number" }, { "fieldname": "column_break_15", @@ -532,7 +532,8 @@ ], "idx": 1, "istable": 1, - "modified": "2019-06-02 05:32:46.019237", + "links": [], + "modified": "2020-04-07 18:35:51.175947", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation Item", diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 27a9de95e0..afbdbc661d 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -379,7 +379,31 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ } }); } - } + }, + + manufacturer_part_no: function(doc, cdt, cdn) { + const row = locals[cdt][cdn]; + + if (row.manufacturer_part_no) { + frappe.model.get_value('Item Manufacturer', + { + 'item_code': row.item_code, + 'manufacturer': row.manufacturer, + 'manufacturer_part_no': row.manufacturer_part_no + }, + 'name', + function(data) { + if (!data) { + let msg = { + message: __("Manufacturer Part Number {0} is invalid", [row.manufacturer_part_no]), + title: __("Invalid Part Number") + } + frappe.throw(msg); + } + }); + + } + } }); cur_frm.add_fetch('project', 'cost_center', 'cost_center'); diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index aa6b2fedd7..7d2e3112fb 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -114,6 +114,8 @@ "is_sub_contracted_item", "column_break_74", "customer_code", + "default_item_manufacturer", + "default_manufacturer_part_no", "website_section", "show_in_website", "show_variant_in_website", @@ -1038,6 +1040,18 @@ "fieldname": "auto_create_assets", "fieldtype": "Check", "label": "Auto Create Assets on Purchase" + }, + { + "fieldname": "default_item_manufacturer", + "fieldtype": "Data", + "label": "Default Item Manufacturer", + "read_only": 1 + }, + { + "fieldname": "default_manufacturer_part_no", + "fieldtype": "Data", + "label": "Default Manufacturer Part No", + "read_only": 1 } ], "has_web_view": 1, @@ -1046,7 +1060,7 @@ "image_field": "image", "links": [], "max_attachments": 1, - "modified": "2020-03-24 16:14:36.950677", + "modified": "2020-04-07 15:56:06.195722", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json index 956c92e673..0cef6eafae 100644 --- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json +++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "creation": "2019-06-02 04:41:37.332911", "doctype": "DocType", @@ -10,7 +11,8 @@ "manufacturer_part_no", "column_break_3", "item_name", - "description" + "description", + "is_default" ], "fields": [ { @@ -52,9 +54,17 @@ "fieldtype": "Small Text", "label": "Description", "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Default" } ], - "modified": "2019-06-06 19:07:31.175919", + "links": [], + "modified": "2020-04-07 20:25:55.507905", "modified_by": "Administrator", "module": "Stock", "name": "Item Manufacturer", diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py index 67eab82d97..c27d1be789 100644 --- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py +++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py @@ -11,6 +11,10 @@ from frappe.model.document import Document class ItemManufacturer(Document): def validate(self): self.validate_duplicate_entry() + self.manage_default_item_manufacturer() + + def on_trash(self): + self.manage_default_item_manufacturer(delete=True) def validate_duplicate_entry(self): if self.is_new(): @@ -24,6 +28,40 @@ class ItemManufacturer(Document): frappe.throw(_("Duplicate entry against the item code {0} and manufacturer {1}") .format(self.item_code, self.manufacturer)) + def manage_default_item_manufacturer(self, delete=False): + from frappe.model.utils import set_default + + item = frappe.get_doc("Item", self.item_code) + default_manufacturer = item.default_item_manufacturer + default_part_no = item.default_manufacturer_part_no + + if not self.is_default: + # if unchecked and default in Item master, clear it. + if default_manufacturer == self.manufacturer and default_part_no == self.manufacturer_part_no: + frappe.db.set_value("Item", item.name, + { + "default_item_manufacturer": None, + "default_manufacturer_part_no": None + }) + + elif self.is_default: + set_default(self, "item_code") + manufacturer, manufacturer_part_no = default_manufacturer, default_part_no + + if delete: + manufacturer, manufacturer_part_no = None, None + + elif (default_manufacturer != self.manufacturer) or \ + (default_manufacturer == self.manufacturer and default_part_no != self.manufacturer_part_no): + manufacturer = self.manufacturer + manufacturer_part_no = self.manufacturer_part_no + + frappe.db.set_value("Item", item.name, + { + "default_item_manufacturer": manufacturer, + "default_manufacturer_part_no": manufacturer_part_no + }) + @frappe.whitelist() def get_item_manufacturer_part_no(item_code, manufacturer): return frappe.db.get_value("Item Manufacturer", 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 9d1dafb136..30206b62d0 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -404,14 +404,13 @@ { "fieldname": "manufacturer_part_no", "fieldtype": "Data", - "label": "Manufacturer Part Number", - "read_only": 1 + "label": "Manufacturer Part Number" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-02-25 03:09:10.698967", + "modified": "2020-04-07 18:37:54.495112", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index a8b9c81ff0..b15f23c303 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -801,8 +801,7 @@ { "fieldname": "manufacturer_part_no", "fieldtype": "Data", - "label": "Manufacturer Part Number", - "read_only": 1 + "label": "Manufacturer Part Number" }, { "depends_on": "is_fixed_asset", @@ -832,7 +831,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-11 14:19:48.799370", + "modified": "2020-04-07 18:38:21.141558", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index b1bfc90588..61429cc1c1 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -341,6 +341,9 @@ def get_basic_details(args, item, overwrite_warehouse=True): else: out["manufacturer_part_no"] = None out["manufacturer"] = None + else: + out["manufacturer"], out["manufacturer_part_no"] = frappe.get_value("Item", item.name, + ["default_item_manufacturer", "default_manufacturer_part_no"] ) child_doctype = args.doctype + ' Item' meta = frappe.get_meta(child_doctype) From debee1c9770ec5c1efe67faf8f9f6d820970fc6b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 9 Apr 2020 12:41:24 +0530 Subject: [PATCH 15/69] fix: travis --- erpnext/accounts/doctype/account/test_account.py | 2 +- erpnext/assets/doctype/asset_category/asset_category.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index 9894b9309a..89bb0184af 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -151,7 +151,7 @@ def _make_test_records(verbose): # fixed asset depreciation ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None], - ["_Test Depreciations", "Expenses", 0, "Expense", None], + ["_Test Depreciations", "Expenses", 0, None, None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], # Receivable / Payable Account diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 770b1ee9a4..d4ea250494 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -30,7 +30,7 @@ class AssetCategory(Document): for fieldname in account_type_map.keys(): if d.get(fieldname): selected_account = d.get(fieldname) - key_to_match = account_type_map[fieldname].keys()[0] + key_to_match = account_type_map.get(fieldname).keys()[0] # acount_type or root_type selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match) expected_key_type = account_type_map[fieldname][key_to_match] From 397f894a7c39048ba2eaf0b3093d39e1dc0e09a4 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 11 Apr 2020 18:10:59 +0530 Subject: [PATCH 16/69] fix: cannot iterate over dict_keys --- erpnext/assets/doctype/asset_category/asset_category.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index d4ea250494..2178b7d50f 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -30,7 +30,7 @@ class AssetCategory(Document): for fieldname in account_type_map.keys(): if d.get(fieldname): selected_account = d.get(fieldname) - key_to_match = account_type_map.get(fieldname).keys()[0] # acount_type or root_type + key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match) expected_key_type = account_type_map[fieldname][key_to_match] From affa2fba6bb5e61e416cc161a32f0bb2334e9ecc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 13 Apr 2020 13:39:36 +0530 Subject: [PATCH 17/69] feat: updated non-profit.json --- .../desk_page/non_profit/non_profit.json | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/erpnext/non_profit/desk_page/non_profit/non_profit.json b/erpnext/non_profit/desk_page/non_profit/non_profit.json index dcbd9c37a6..ebe6194893 100644 --- a/erpnext/non_profit/desk_page/non_profit/non_profit.json +++ b/erpnext/non_profit/desk_page/non_profit/non_profit.json @@ -2,35 +2,33 @@ "cards": [ { "hidden": 0, - "icon": "icon-list", - "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]", - "title": "Loan Management" + "label": "Loan Management", + "links": "[\n {\n \"description\": \"Define various loan types\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Application\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]", - "title": "Grant Application" + "label": "Grant Application", + "links": "[\n {\n \"description\": \"Grant information.\",\n \"label\": \"Grant Application\",\n \"name\": \"Grant Application\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Membership" + "label": "Membership", + "links": "[\n {\n \"description\": \"Member information.\",\n \"label\": \"Member\",\n \"name\": \"Member\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Details\",\n \"label\": \"Membership\",\n \"name\": \"Membership\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Membership Type Details\",\n \"label\": \"Membership Type\",\n \"name\": \"Membership Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Billing and Gateway Settings\",\n \"label\": \"Membership Settings\",\n \"name\": \"Membership Settings\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]", - "title": "Volunteer" + "label": "Volunteer", + "links": "[\n {\n \"description\": \"Volunteer information.\",\n \"label\": \"Volunteer\",\n \"name\": \"Volunteer\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Volunteer Type information.\",\n \"label\": \"Volunteer Type\",\n \"name\": \"Volunteer Type\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-star", - "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]", - "title": "Chapter" + "label": "Chapter", + "links": "[\n {\n \"description\": \"Chapter information.\",\n \"label\": \"Chapter\",\n \"name\": \"Chapter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]", - "title": "Donor" + "label": "Donor", + "links": "[\n {\n \"description\": \"Donor information.\",\n \"label\": \"Donor\",\n \"name\": \"Donor\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Donor Type information.\",\n \"label\": \"Donor Type\",\n \"name\": \"Donor Type\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Domains", @@ -44,7 +42,7 @@ "idx": 0, "is_standard": 1, "label": "Non Profit", - "modified": "2020-03-29 13:06:18.070131", + "modified": "2020-04-13 13:41:52.373705", "modified_by": "Administrator", "module": "Non Profit", "name": "Non Profit", @@ -52,5 +50,31 @@ "pin_to_bottom": 0, "pin_to_top": 0, "restrict_to_domain": "Non Profit", - "shortcuts": [] + "shortcuts": [ + { + "label": "Member", + "link_to": "Member", + "type": "DocType" + }, + { + "label": "Membership Settings", + "link_to": "Membership Settings", + "type": "DocType" + }, + { + "label": "Membership", + "link_to": "Membership", + "type": "DocType" + }, + { + "label": "Chapter", + "link_to": "Chapter", + "type": "DocType" + }, + { + "label": "Chapter Member", + "link_to": "Chapter Member", + "type": "DocType" + } + ] } \ No newline at end of file From cb354685d5c8007cf7d483dc59fe58631d9b2a95 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 14 Apr 2020 12:00:02 +0530 Subject: [PATCH 18/69] fix: linting issues --- erpnext/non_profit/doctype/member/member.py | 16 ++++++++-------- .../non_profit/doctype/membership/membership.py | 15 --------------- .../membership_settings/membership_settings.js | 6 +----- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 1ae8699db1..2f84cf5538 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -102,16 +102,16 @@ def create_member_subscription_order(user_details): """Summary Args: - user_details (TYPE): Description + user_details (TYPE): Description Returns: - Dictionary: Dictionary with subscription details - { - 'subscription_details': { - 'plan_id': 'plan_EXwyxDYDCj3X4v', - 'billing_frequency': 24, - 'customer_notify': 1 - }, + Dictionary: Dictionary with subscription details + { + 'subscription_details': { + 'plan_id': 'plan_EXwyxDYDCj3X4v', + 'billing_frequency': 24, + 'customer_notify': 1 + }, 'subscription_id': 'sub_EZycCvXFvqnC6p' } """ diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 00ecd8fbdf..d11f6992f9 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -124,18 +124,3 @@ Administrator""".format(get_link_to_form("Error Log", log.name)) sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content) except: pass - - - - - - - - - - - - - - - diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.js b/erpnext/non_profit/doctype/membership_settings/membership_settings.js index 092c854b43..c01a0b23d5 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.js +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.js @@ -3,10 +3,6 @@ frappe.ui.form.on('Membership Settings', { refresh: function(frm) { - // if (frm.doc.enable_razorpay) { - // frm.add_custom_button(__("Fetch Plans from RazorPay"), () => { - // frm.trigger("fetch_razorpay_plans") - // }); - // } + } }); From 4fdb4aa1d00f42e7c7b26e6eb6bdb73a0837f5ef Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 14 Apr 2020 12:00:15 +0530 Subject: [PATCH 19/69] feat: use int for billing frequency --- .../doctype/membership_settings/membership_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json index ce843b3e8a..56b8eac4b1 100644 --- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json +++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json @@ -32,7 +32,7 @@ { "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", "fieldname": "billing_frequency", - "fieldtype": "Data", + "fieldtype": "Int", "label": "Billing Frequency" } ], From 10a6f4bd058f7944ce7897c7288c0b23b3098249 Mon Sep 17 00:00:00 2001 From: vishdha Date: Mon, 13 Apr 2020 08:22:34 +0530 Subject: [PATCH 20/69] fix: Chart of account importer UX improved --- erpnext/accounts/doctype/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 0a72d4fa4e..bd013dc60e 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -89,7 +89,7 @@ class Account(NestedSet): throw(_("Root cannot be edited."), RootNotEditable) if not self.parent_account and not self.is_group: - frappe.throw(_("Root Account must be a group")) + frappe.throw(_("Root Account must be a group for {0}".format(self.name))) def validate_root_company_and_sync_account_to_children(self): # ignore validation while creating new compnay or while syncing to child companies From 5008be1db58e5c40a8a91ff172e1d57ce63fcf38 Mon Sep 17 00:00:00 2001 From: vishdha Date: Mon, 13 Apr 2020 18:43:14 +0530 Subject: [PATCH 21/69] fix: message bold --- erpnext/accounts/doctype/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index bd013dc60e..9e29ce6748 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -89,7 +89,7 @@ class Account(NestedSet): throw(_("Root cannot be edited."), RootNotEditable) if not self.parent_account and not self.is_group: - frappe.throw(_("Root Account must be a group for {0}".format(self.name))) + frappe.throw(_("Root Account must be a group for {0}".format(frappe.bold(self.name)))) def validate_root_company_and_sync_account_to_children(self): # ignore validation while creating new compnay or while syncing to child companies From d0f6f1808beca8926a07d96968d390eba95cdeb7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Apr 2020 17:03:49 +0530 Subject: [PATCH 22/69] fix: minor issues --- .../chart_of_accounts_importer.py | 3 +-- erpnext/stock/doctype/pick_list/test_pick_list.py | 1 + .../doctype/stock_reconciliation/stock_reconciliation.json | 7 ++++--- .../stock_reconciliation/test_stock_reconciliation.py | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) 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 b6f5396ccb..99221c1100 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 @@ -152,10 +152,9 @@ def build_forest(data): return [parent_account] elif account_name == child: parent_account_list = return_parent(data, parent_account) - if not parent_account_list: + if not parent_account_list and parent_account: frappe.throw(_("The parent account {0} does not exists in the uploaded template").format( frappe.bold(parent_account))) - return [child] + parent_account_list charts_map, paths = {}, [] diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 6b4f73b140..1b9ff41cc3 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -111,6 +111,7 @@ class TestPickList(unittest.TestCase): stock_reconciliation = frappe.get_doc({ 'doctype': 'Stock Reconciliation', + 'purpose': 'Stock Reconciliation', 'company': '_Test Company', 'items': [{ 'item_code': '_Test Serialized Item', diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index 7f4efba33f..b7d1497319 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -4,6 +4,7 @@ "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", "doctype": "DocType", "document_type": "Document", + "engine": "InnoDB", "field_order": [ "naming_series", "company", @@ -44,11 +45,11 @@ "reqd": 1 }, { - "default": "Stock Reconciliation", "fieldname": "purpose", "fieldtype": "Select", "label": "Purpose", - "options": "Opening Stock\nStock Reconciliation" + "options": "\nOpening Stock\nStock Reconciliation", + "reqd": 1 }, { "fieldname": "col1", @@ -153,7 +154,7 @@ "idx": 1, "is_submittable": 1, "max_attachments": 1, - "modified": "2019-05-26 09:03:09.542141", + "modified": "2020-04-08 17:02:47.196206", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e6d7e3fea7..51d027f22e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -240,6 +240,7 @@ def create_batch_or_serial_no_items(): def create_stock_reconciliation(**args): args = frappe._dict(args) sr = frappe.new_doc("Stock Reconciliation") + sr.purpose = args.purpose or "Stock Reconciliation" sr.posting_date = args.posting_date or nowdate() sr.posting_time = args.posting_time or nowtime() sr.set_posting_time = 1 From c4c44a9a2bb71b2e91b3ee56417328d0d27f79e0 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 15 Apr 2020 12:58:24 +0530 Subject: [PATCH 23/69] Initial JS setup --- .../crm/doctype/opportunity/opportunity_calendar.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 erpnext/crm/doctype/opportunity/opportunity_calendar.js diff --git a/erpnext/crm/doctype/opportunity/opportunity_calendar.js b/erpnext/crm/doctype/opportunity/opportunity_calendar.js new file mode 100644 index 0000000000..983be9bf2d --- /dev/null +++ b/erpnext/crm/doctype/opportunity/opportunity_calendar.js @@ -0,0 +1,12 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt +frappe.views.calendar["Opportunity"] = { + field_map: { + "start": "contact_date", + "end": "contact_date", + "id": "name", + "title": "customer_name", + "allDay": "allDay" + }, + get_events_method: 'frappe.desk.doctype.event.event.get_events' +} From 9ee81104110b735df1e592c7fd001464f06e9e9d Mon Sep 17 00:00:00 2001 From: Anupam K Date: Wed, 15 Apr 2020 18:37:35 +0530 Subject: [PATCH 24/69] Adding Calendar view for Opportunity --- .../crm/doctype/opportunity/opportunity.py | 24 +++++++++++++++++++ .../opportunity/opportunity_calendar.js | 11 +++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index 5e640e78c8..1b071ea1b7 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -336,3 +336,27 @@ def make_opportunity_from_communication(communication, ignore_communication_link link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links) return opportunity.name +@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("Opportunity", filters) + + data = frappe.db.sql(""" + select + distinct `tabOpportunity`.name, `tabOpportunity`.customer_name, `tabOpportunity`.opportunity_amount, + `tabOpportunity`.title, `tabOpportunity`.contact_date + from + `tabOpportunity` + where + (`tabOpportunity`.contact_date between %(start)s and %(end)s) + {conditions} + """.format(conditions=conditions), { + "start": start, + "end": end + }, as_dict=True, update={"allDay": 0}) + return data \ No newline at end of file diff --git a/erpnext/crm/doctype/opportunity/opportunity_calendar.js b/erpnext/crm/doctype/opportunity/opportunity_calendar.js index 983be9bf2d..58fa2b8cd8 100644 --- a/erpnext/crm/doctype/opportunity/opportunity_calendar.js +++ b/erpnext/crm/doctype/opportunity/opportunity_calendar.js @@ -7,6 +7,13 @@ frappe.views.calendar["Opportunity"] = { "id": "name", "title": "customer_name", "allDay": "allDay" - }, - get_events_method: 'frappe.desk.doctype.event.event.get_events' + }, + options: { + header: { + left: 'prev,next today', + center: 'title', + right: 'month' + } + }, + get_events_method: 'erpnext.crm.doctype.opportunity.opportunity.get_events' } From 0b98a6296b69c6e548e0b9a76b322684720ea88b Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 15 Apr 2020 21:19:04 +0530 Subject: [PATCH 25/69] fix: Fetch Material Requests with type Issue as well in Stock Entry via Get Items from --- erpnext/stock/doctype/stock_entry/stock_entry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 3bb941573b..d1048fc195 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -220,8 +220,8 @@ frappe.ui.form.on('Stock Entry', { }, get_query_filters: { docstatus: 1, - material_request_type: "Material Transfer", - status: ['!=', 'Transferred'] + material_request_type: ["in", ["Material Transfer", "Material Issue"]], + status: ["not in", ["Transferred", "Issued"]] } }) }, __("Get items from")); From 34640777cd3a734cdccd792bfd71d54376a8da2b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 15 Apr 2020 22:08:04 +0530 Subject: [PATCH 26/69] fix: pos not accessible without default customer --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 82aedb6247..3c40112ae6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -440,11 +440,12 @@ class SalesInvoice(SellingController): if pos.get("company_address"): self.company_address = pos.get("company_address") - customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) - - customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list') - - selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list') + if self.customer: + customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) + customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list') + selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list') + else: + selling_price_list = pos.get('selling_price_list') if selling_price_list: self.set('selling_price_list', selling_price_list) From f53299e98a3bb82c85e6b5c3be1b49bd0d5ca408 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 15 Apr 2020 22:08:12 +0530 Subject: [PATCH 27/69] fix: warehouse unset when cannot find item warehouse * cannot set delivery date when all items gets deleted and new are added --- erpnext/controllers/accounts_controller.py | 24 ++++---- erpnext/public/js/utils.js | 69 +++++++++++++--------- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d95753df90..4045250c33 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1123,36 +1123,39 @@ def get_supplier_block_status(party_name): } return info -def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_code): +def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): """ Returns a Sales Order Item child item containing the default values """ p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) child_item = frappe.new_doc('Sales Order Item', p_doc, child_docname) - item = frappe.get_doc("Item", item_code) + item = frappe.get_doc("Item", trans_item.get('item_code')) child_item.item_code = item.item_code child_item.item_name = item.item_name child_item.description = item.description - child_item.reqd_by_date = p_doc.delivery_date + child_item.delivery_date = trans_item.get('delivery_date') or p_doc.delivery_date child_item.uom = item.stock_uom - child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0 + child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True) + if not child_item.warehouse: + frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.") + .format(frappe.bold("default warehouse"), frappe.bold(item.item_code))) return child_item -def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, item_code): +def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, trans_item): """ Returns a Purchase Order Item child item containing the default values """ p_doc = frappe.get_doc(parent_doctype, parent_doctype_name) child_item = frappe.new_doc('Purchase Order Item', p_doc, child_docname) - item = frappe.get_doc("Item", item_code) + item = frappe.get_doc("Item", trans_item.get('item_code')) child_item.item_code = item.item_code child_item.item_name = item.item_name child_item.description = item.description - child_item.schedule_date = p_doc.schedule_date + child_item.schedule_date = trans_item.get('schedule_date') or p_doc.schedule_date child_item.uom = item.stock_uom - child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0 + child_item.conversion_factor = get_conversion_factor(item.item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.base_rate = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation return child_item @@ -1196,9 +1199,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil if not d.get("docname"): new_child_flag = True if parent_doctype == "Sales Order": - child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) + child_item = set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) if parent_doctype == "Purchase Order": - child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) + child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d) else: child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")): @@ -1243,6 +1246,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil child_item.flags.ignore_validate_update_after_submit = True if new_child_flag: + parent.load_from_db() child_item.idx = len(parent.items) + 1 child_item.insert() else: diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 4d44eae086..58969f2a9f 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -436,6 +436,44 @@ erpnext.utils.update_child_items = function(opts) { const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row; const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname; this.data = []; + const fields = [{ + fieldtype:'Data', + fieldname:"docname", + read_only: 1, + hidden: 1, + }, { + fieldtype:'Link', + fieldname:"item_code", + options: 'Item', + in_list_view: 1, + read_only: 0, + disabled: 0, + label: __('Item Code') + }, { + fieldtype:'Float', + fieldname:"qty", + default: 0, + read_only: 0, + in_list_view: 1, + label: __('Qty') + }, { + fieldtype:'Currency', + fieldname:"rate", + default: 0, + read_only: 0, + in_list_view: 1, + label: __('Rate') + }]; + + if (frm.doc.doctype == 'Sales Order' || frm.doc.doctype == 'Purchase Order' ) { + fields.splice(2, 0, { + fieldtype: 'Date', + fieldname: frm.doc.doctype == 'Sales Order' ? "delivery_date" : "schedule_date", + in_list_view: 1, + label: frm.doc.doctype == 'Sales Order' ? __("Delivery Date") : __("Reqd by date") + }) + } + const dialog = new frappe.ui.Dialog({ title: __("Update Items"), fields: [ @@ -450,34 +488,7 @@ erpnext.utils.update_child_items = function(opts) { get_data: () => { return this.data; }, - fields: [{ - fieldtype:'Data', - fieldname:"docname", - read_only: 1, - hidden: 1, - }, { - fieldtype:'Link', - fieldname:"item_code", - options: 'Item', - in_list_view: 1, - read_only: 0, - disabled: 0, - label: __('Item Code') - }, { - fieldtype:'Float', - fieldname:"qty", - default: 0, - read_only: 0, - in_list_view: 1, - label: __('Qty') - }, { - fieldtype:'Currency', - fieldname:"rate", - default: 0, - read_only: 0, - in_list_view: 1, - label: __('Rate') - }] + fields: fields }, ], primary_action: function() { @@ -506,6 +517,8 @@ erpnext.utils.update_child_items = function(opts) { "docname": d.name, "name": d.name, "item_code": d.item_code, + "delivery_date": d.delivery_date, + "schedule_date": d.schedule_date, "qty": d.qty, "rate": d.rate, }); From 5b36d15927b56ec9aa01bbd0eb45695124f95aa3 Mon Sep 17 00:00:00 2001 From: vishdha Date: Thu, 16 Apr 2020 11:09:54 +0530 Subject: [PATCH 28/69] fix: fix Transalation --- erpnext/accounts/doctype/account/account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 9e29ce6748..c6de6410eb 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -89,7 +89,7 @@ class Account(NestedSet): throw(_("Root cannot be edited."), RootNotEditable) if not self.parent_account and not self.is_group: - frappe.throw(_("Root Account must be a group for {0}".format(frappe.bold(self.name)))) + frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name))) def validate_root_company_and_sync_account_to_children(self): # ignore validation while creating new compnay or while syncing to child companies From 2ad7e81560f92cbe542c8635a27926c50f17ac13 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 14 Apr 2020 04:07:14 +0530 Subject: [PATCH 29/69] fix: handle errors in enqueued methods and update status --- .../tally_migration/tally_migration.js | 51 +++-- .../tally_migration/tally_migration.py | 198 +++++++++++------- 2 files changed, 159 insertions(+), 90 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 104ac570c6..682f78efb7 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -2,15 +2,40 @@ // For license information, please see license.txt frappe.ui.form.on('Tally Migration', { - onload: function(frm) { + onload: function (frm) { + let reload_status = true; frappe.realtime.on("tally_migration_progress_update", function (data) { + if (reload_status) { + frappe.model.with_doc(frm.doc.doctype, frm.doc.name, () => { + frm.refresh_header(); + }); + reload_status = false; + } frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); - if (data.count == data.total) { - window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); + let error_occurred = data.count === -1; + if (data.count == data.total || error_occurred) { + window.setTimeout((title) => { + frm.dashboard.hide_progress(title) + frm.reload_doc(); + if (error_occurred) { + frappe.msgprint({ + message: __("An error has occurred during {0}. Check {1} for more details", + [ + repl("%(tally_document)s", { + tally_document: frm.docname + }), + "Error Log" + ] + ), + title: __("Tally Migration Error"), + indicator: "red" + }); + } + }, 2000, data.title); } }); }, - refresh: function(frm) { + refresh: function (frm) { if (frm.doc.master_data && !frm.doc.is_master_data_imported) { if (frm.doc.is_master_data_processed) { if (frm.doc.status != "Importing Master Data") { @@ -34,17 +59,17 @@ frappe.ui.form.on('Tally Migration', { } } }, - add_button: function(frm, label, method) { + add_button: function (frm, label, method) { frm.add_custom_button( label, - () => frm.call({ - doc: frm.doc, - method: method, - freeze: true, - callback: () => { - frm.remove_custom_button(label); - } - }) + () => { + frm.call({ + doc: frm.doc, + method: method, + freeze: true + }); + frm.reload_doc(); + } ); } }); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 01eee5b61f..ac42d38516 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -4,20 +4,25 @@ from __future__ import unicode_literals -from decimal import Decimal import json +import sys import re import traceback import zipfile +from decimal import Decimal + +from bs4 import BeautifulSoup as bs + import frappe +from erpnext import encode_company_abbr +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification from frappe.model.document import Document from frappe.model.naming import getseries, revert_series_if_last from frappe.utils.data import format_datetime -from bs4 import BeautifulSoup as bs -from erpnext import encode_company_abbr -from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts + PRIMARY_ACCOUNT = "Primary" VOUCHER_CHUNK_SIZE = 500 @@ -39,13 +44,15 @@ class TallyMigration(Document): return string master_file = frappe.get_doc("File", {"file_url": data_file}) + master_file_path = master_file.get_full_path() - with zipfile.ZipFile(master_file.get_full_path()) as zf: - encoded_content = zf.read(zf.namelist()[0]) - try: - content = encoded_content.decode("utf-8-sig") - except UnicodeDecodeError: - content = encoded_content.decode("utf-16") + if zipfile.is_zipfile(master_file_path): + with zipfile.ZipFile(master_file_path) as zf: + encoded_content = zf.read(zf.namelist()[0]) + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + content = encoded_content.decode("utf-16") master = bs(sanitize(emptify(content)), "xml") collection = master.BODY.IMPORTDATA.REQUESTDATA @@ -58,7 +65,8 @@ class TallyMigration(Document): "file_name": key + ".json", "attached_to_doctype": self.doctype, "attached_to_name": self.name, - "content": json.dumps(value) + "content": json.dumps(value), + "is_private": True }).insert() setattr(self, key, f.file_url) @@ -168,8 +176,8 @@ class TallyMigration(Document): address = "\n".join([a.string for a in account.find_all("ADDRESS")]) addresses.append({ "doctype": "Address", - "address_line1": address[:140].strip(), - "address_line2": address[140:].strip(), + "address_line1": address[:140], + "address_line2": address[140:], "country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, "state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, "gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, @@ -188,37 +196,46 @@ class TallyMigration(Document): items = [] for item in collection.find_all("STOCKITEM"): + stock_uom = item.BASEUNITS.string if item.BASEUNITS else "Unit" #self.default_uom items.append({ "doctype": "Item", "item_code" : item.NAME.string, - "stock_uom": item.BASEUNITS.string, + "stock_uom": stock_uom, "is_stock_item": 0, "item_group": "All Item Groups", "item_defaults": [{"company": self.erpnext_company}] }) + return items, uoms + try: + self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5) + collection = self.get_collection(self.master_data) + company = get_company_name(collection) + self.tally_company = company + self.erpnext_company = company - self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5) - collection = self.get_collection(self.master_data) + self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5) + chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection) - company = get_company_name(collection) - self.tally_company = company - self.erpnext_company = company + self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5) + parties, addresses = get_parties_addresses(collection, customers, suppliers) - self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5) - chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection) - self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5) - parties, addresses = get_parties_addresses(collection, customers, suppliers) - self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5) - items, uoms = get_stock_items_uoms(collection) - data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms} - self.publish("Process Master Data", _("Done"), 5, 5) + self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5) + items, uoms = get_stock_items_uoms(collection) + data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms} - self.dump_processed_data(data) - self.is_master_data_processed = 1 - self.status = "" - self.save() + self.publish("Process Master Data", _("Done"), 5, 5) + self.dump_processed_data(data) + + self.is_master_data_processed = 1 + + except: + self.publish("Process Master Data", _("Process Failed"), -1, 5) + self.log() + + finally: + self.set_status() def publish(self, title, message, count, total): frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total}) @@ -256,7 +273,6 @@ class TallyMigration(Document): except: self.log(address) - def create_items_uoms(items_file_url, uoms_file_url): uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url}) for uom in json.loads(uoms_file.get_content()): @@ -273,16 +289,26 @@ class TallyMigration(Document): except: self.log(item) - self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4) - create_company_and_coa(self.chart_of_accounts) - self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4) - create_parties_and_addresses(self.parties, self.addresses) - self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4) - create_items_uoms(self.items, self.uoms) - self.publish("Import Master Data", _("Done"), 4, 4) - self.status = "" - self.is_master_data_imported = 1 - self.save() + try: + self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4) + create_company_and_coa(self.chart_of_accounts) + + self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4) + create_parties_and_addresses(self.parties, self.addresses) + + self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4) + create_items_uoms(self.items, self.uoms) + + self.publish("Import Master Data", _("Done"), 4, 4) + + self.is_master_data_imported = 1 + + except: + self.publish("Import Master Data", _("Process Failed"), -1, 5) + self.log() + + finally: + self.set_status() def _process_day_book_data(self): def get_vouchers(collection): @@ -373,12 +399,12 @@ class TallyMigration(Document): account_field = "expense_account" items = [] for entry in inventory_entries: - qty, uom = entry.ACTUALQTY.string.strip().split() + qty, uom = entry.ACTUALQTY.string.split() items.append({ "item_code": entry.STOCKITEMNAME.string, "description": entry.STOCKITEMNAME.string, - "qty": qty.strip(), - "uom": uom.strip(), + "qty": qty, + "uom": uom, "conversion_factor": 1, "price_list_rate": entry.RATE.string.split("/")[0], "cost_center": self.default_cost_center, @@ -408,15 +434,24 @@ class TallyMigration(Document): elif frappe.db.exists({"doctype": "Customer", "customer_name": party}): return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company) - self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3) - collection = self.get_collection(self.day_book_data) - self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3) - vouchers = get_vouchers(collection) - self.publish("Process Day Book Data", _("Done"), 3, 3) - self.dump_processed_data({"vouchers": vouchers}) - self.status = "" - self.is_day_book_data_processed = 1 - self.save() + try: + self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3) + collection = self.get_collection(self.day_book_data) + + self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3) + vouchers = get_vouchers(collection) + + self.publish("Process Day Book Data", _("Done"), 3, 3) + self.dump_processed_data({"vouchers": vouchers}) + + self.is_day_book_data_processed = 1 + + except: + self.publish("Process Day Book Data", _("Process Failed"), -1, 5) + self.log() + + finally: + self.set_status() def _import_day_book_data(self): def create_fiscal_years(vouchers): @@ -454,23 +489,31 @@ class TallyMigration(Document): "currency": "INR" }).insert() - frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable") - frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable") - frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account) + try: + frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable") + frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable") + frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account) - vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers}) - vouchers = json.loads(vouchers_file.get_content()) + vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers}) + vouchers = json.loads(vouchers_file.get_content()) - create_fiscal_years(vouchers) - create_price_list() - create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) + create_fiscal_years(vouchers) + create_price_list() + create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) - total = len(vouchers) - is_last = False - for index in range(0, total, VOUCHER_CHUNK_SIZE): - if index + VOUCHER_CHUNK_SIZE >= total: - is_last = True - frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last) + total = len(vouchers) + is_last = False + + for index in range(0, total, VOUCHER_CHUNK_SIZE): + if index + VOUCHER_CHUNK_SIZE >= total: + is_last = True + frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last) + + except: + self.log() + + finally: + self.set_status() def _import_vouchers(self, start, total, is_last=False): frappe.flags.in_migrate = True @@ -494,25 +537,26 @@ class TallyMigration(Document): frappe.flags.in_migrate = False def process_master_data(self): - self.status = "Processing Master Data" - self.save() + self.set_status("Processing Master Data") frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600) def import_master_data(self): - self.status = "Importing Master Data" - self.save() + self.set_status("Importing Master Data") frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600) def process_day_book_data(self): - self.status = "Processing Day Book Data" - self.save() + self.set_status("Processing Day Book Data") frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600) def import_day_book_data(self): - self.status = "Importing Day Book Data" - self.save() + self.set_status("Importing Day Book Data") frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600) def log(self, data=None): - message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()]) + data = data or self.status + message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]) return frappe.log_error(title="Tally Migration Error", message=message) + + def set_status(self, status=""): + self.status = status + self.save() From 72a94941bf5ae1413efa0e91c983cb74fb1a1737 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Apr 2020 16:12:40 +0530 Subject: [PATCH 30/69] fix(tally-migration): DocType improvement --- .../tally_migration/tally_migration.json | 26 +++++++++++++++++-- .../tally_migration/tally_migration.py | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index 26415caf8d..dc6f093ac9 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -1,4 +1,5 @@ { + "actions": [], "beta": 1, "creation": "2019-02-01 14:27:09.485238", "doctype": "DocType", @@ -14,6 +15,7 @@ "tally_debtors_account", "company_section", "tally_company", + "default_uom", "column_break_8", "erpnext_company", "processed_files_section", @@ -43,6 +45,7 @@ "label": "Status" }, { + "description": "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs", "fieldname": "master_data", "fieldtype": "Attach", "in_list_view": 1, @@ -50,6 +53,7 @@ }, { "default": "Sundry Creditors", + "description": "Creditors Account set in Tally", "fieldname": "tally_creditors_account", "fieldtype": "Data", "label": "Tally Creditors Account", @@ -61,6 +65,7 @@ }, { "default": "Sundry Debtors", + "description": "Debtors Account set in Tally", "fieldname": "tally_debtors_account", "fieldtype": "Data", "label": "Tally Debtors Account", @@ -72,6 +77,7 @@ "fieldtype": "Section Break" }, { + "description": "Company Name as per Imported Tally Data", "fieldname": "tally_company", "fieldtype": "Data", "label": "Tally Company", @@ -82,9 +88,11 @@ "fieldtype": "Column Break" }, { + "description": "Your Company set in ERPNext", "fieldname": "erpnext_company", "fieldtype": "Data", - "label": "ERPNext Company" + "label": "ERPNext Company", + "read_only_depends_on": "eval:doc.is_master_data_processed == 1" }, { "fieldname": "processed_files_section", @@ -155,24 +163,28 @@ "options": "Cost Center" }, { + "default": "0", "fieldname": "is_master_data_processed", "fieldtype": "Check", "label": "Is Master Data Processed", "read_only": 1 }, { + "default": "0", "fieldname": "is_day_book_data_processed", "fieldtype": "Check", "label": "Is Day Book Data Processed", "read_only": 1 }, { + "default": "0", "fieldname": "is_day_book_data_imported", "fieldtype": "Check", "label": "Is Day Book Data Imported", "read_only": 1 }, { + "default": "0", "fieldname": "is_master_data_imported", "fieldtype": "Check", "label": "Is Master Data Imported", @@ -188,13 +200,23 @@ "fieldtype": "Column Break" }, { + "description": "Day Book Data exported from Tally that consists of all historic transactions", "fieldname": "day_book_data", "fieldtype": "Attach", "in_list_view": 1, "label": "Day Book Data" + }, + { + "default": "Unit", + "description": "UOM in case unspecified in imported data", + "fieldname": "default_uom", + "fieldtype": "Link", + "label": "Default UOM", + "options": "UOM" } ], - "modified": "2019-04-29 05:46:54.394967", + "links": [], + "modified": "2020-04-16 13:03:28.894919", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index ac42d38516..9c8cb7ace7 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -196,7 +196,7 @@ class TallyMigration(Document): items = [] for item in collection.find_all("STOCKITEM"): - stock_uom = item.BASEUNITS.string if item.BASEUNITS else "Unit" #self.default_uom + stock_uom = item.BASEUNITS.string if item.BASEUNITS else self.default_uom items.append({ "doctype": "Item", "item_code" : item.NAME.string, From 6abdfc586b3049e67c8291326a8c30b59c1cd976 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Apr 2020 15:51:12 +0530 Subject: [PATCH 31/69] fix: strip data fields of whitespaces --- .../tally_migration/tally_migration.py | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 9c8cb7ace7..98a30273e6 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -72,7 +72,7 @@ class TallyMigration(Document): def _process_master_data(self): def get_company_name(collection): - return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string + return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip() def get_coa_customers_suppliers(collection): root_type_map = { @@ -105,17 +105,17 @@ class TallyMigration(Document): # If Ledger doesn't have PARENT field then don't create Account # For example "Profit & Loss A/c" if account.PARENT: - yield account.PARENT.string, account["NAME"], 0 + yield account.PARENT.string.strip(), account["NAME"], 0 def get_parent(account): if account.PARENT: - return account.PARENT.string + return account.PARENT.string.strip() return { ("Yes", "No"): "Application of Funds (Assets)", ("Yes", "Yes"): "Expenses", ("No", "Yes"): "Income", ("No", "No"): "Source of Funds (Liabilities)", - }[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)] + }[(account.ISDEEMEDPOSITIVE.string.strip(), account.ISREVENUE.string.strip())] def get_children_and_parent_dict(accounts): children, parents = {}, {} @@ -153,38 +153,38 @@ class TallyMigration(Document): parties, addresses = [], [] for account in collection.find_all("LEDGER"): party_type = None - if account.NAME.string in customers: + if account.NAME.string.strip() in customers: party_type = "Customer" parties.append({ "doctype": party_type, - "customer_name": account.NAME.string, - "tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, + "customer_name": account.NAME.string.strip(), + "tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None, "customer_group": "All Customer Groups", "territory": "All Territories", "customer_type": "Individual", }) - elif account.NAME.string in suppliers: + elif account.NAME.string.strip() in suppliers: party_type = "Supplier" parties.append({ "doctype": party_type, - "supplier_name": account.NAME.string, - "pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, + "supplier_name": account.NAME.string.strip(), + "pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None, "supplier_group": "All Supplier Groups", "supplier_type": "Individual", }) if party_type: - address = "\n".join([a.string for a in account.find_all("ADDRESS")]) + address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")]) addresses.append({ "doctype": "Address", - "address_line1": address[:140], - "address_line2": address[140:], - "country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, - "state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, - "gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, - "pin_code": account.PINCODE.string if account.PINCODE else None, - "mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None, - "phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None, - "gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None, + "address_line1": address[:140].strip(), + "address_line2": address[140:].strip(), + "country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None, + "state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None, + "gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None, + "pin_code": account.PINCODE.string.strip() if account.PINCODE else None, + "mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None, + "phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None, + "gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None, "links": [{"link_doctype": party_type, "link_name": account["NAME"]}], }) return parties, addresses @@ -192,15 +192,15 @@ class TallyMigration(Document): def get_stock_items_uoms(collection): uoms = [] for uom in collection.find_all("UNIT"): - uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string}) + uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string.strip()}) items = [] for item in collection.find_all("STOCKITEM"): - stock_uom = item.BASEUNITS.string if item.BASEUNITS else self.default_uom + stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom items.append({ "doctype": "Item", - "item_code" : item.NAME.string, - "stock_uom": stock_uom, + "item_code" : item.NAME.string.strip(), + "stock_uom": stock_uom.strip(), "is_stock_item": 0, "item_group": "All Item Groups", "item_defaults": [{"company": self.erpnext_company}] @@ -314,10 +314,10 @@ class TallyMigration(Document): def get_vouchers(collection): vouchers = [] for voucher in collection.find_all("VOUCHER"): - if voucher.ISCANCELLED.string == "Yes": + if voucher.ISCANCELLED.string.strip() == "Yes": continue inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST") - if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries: + if voucher.VOUCHERTYPENAME.string.strip() not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries: function = voucher_to_invoice else: function = voucher_to_journal_entry @@ -333,15 +333,15 @@ class TallyMigration(Document): accounts = [] ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") for entry in ledger_entries: - account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center} - if entry.ISPARTYLEDGER.string == "Yes": - party_details = get_party(entry.LEDGERNAME.string) + account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center} + if entry.ISPARTYLEDGER.string.strip() == "Yes": + party_details = get_party(entry.LEDGERNAME.string.strip()) if party_details: party_type, party_account = party_details account["party_type"] = party_type account["account"] = party_account - account["party"] = entry.LEDGERNAME.string - amount = Decimal(entry.AMOUNT.string) + account["party"] = entry.LEDGERNAME.string.strip() + amount = Decimal(entry.AMOUNT.string.strip()) if amount > 0: account["credit_in_account_currency"] = str(abs(amount)) else: @@ -350,21 +350,21 @@ class TallyMigration(Document): journal_entry = { "doctype": "Journal Entry", - "tally_guid": voucher.GUID.string, - "posting_date": voucher.DATE.string, + "tally_guid": voucher.GUID.string.strip(), + "posting_date": voucher.DATE.string.strip(), "company": self.erpnext_company, "accounts": accounts, } return journal_entry def voucher_to_invoice(voucher): - if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]: + if voucher.VOUCHERTYPENAME.string.strip() in ["Sales", "Credit Note"]: doctype = "Sales Invoice" party_field = "customer" account_field = "debit_to" account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company) price_list_field = "selling_price_list" - elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]: + elif voucher.VOUCHERTYPENAME.string.strip() in ["Purchase", "Debit Note"]: doctype = "Purchase Invoice" party_field = "supplier" account_field = "credit_to" @@ -377,10 +377,10 @@ class TallyMigration(Document): invoice = { "doctype": doctype, - party_field: voucher.PARTYNAME.string, - "tally_guid": voucher.GUID.string, - "posting_date": voucher.DATE.string, - "due_date": voucher.DATE.string, + party_field: voucher.PARTYNAME.string.strip(), + "tally_guid": voucher.GUID.string.strip(), + "posting_date": voucher.DATE.string.strip(), + "due_date": voucher.DATE.string.strip(), "items": get_voucher_items(voucher, doctype), "taxes": get_voucher_taxes(voucher), account_field: account_name, @@ -399,17 +399,17 @@ class TallyMigration(Document): account_field = "expense_account" items = [] for entry in inventory_entries: - qty, uom = entry.ACTUALQTY.string.split() + qty, uom = entry.ACTUALQTY.string.strip().split() items.append({ - "item_code": entry.STOCKITEMNAME.string, - "description": entry.STOCKITEMNAME.string, - "qty": qty, - "uom": uom, + "item_code": entry.STOCKITEMNAME.string.strip(), + "description": entry.STOCKITEMNAME.string.strip(), + "qty": qty.strip(), + "uom": uom.strip(), "conversion_factor": 1, - "price_list_rate": entry.RATE.string.split("/")[0], + "price_list_rate": entry.RATE.string.strip().split("/")[0], "cost_center": self.default_cost_center, "warehouse": self.default_warehouse, - account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company), + account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string.strip(), self.erpnext_company), }) return items @@ -417,13 +417,13 @@ class TallyMigration(Document): ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") taxes = [] for entry in ledger_entries: - if entry.ISPARTYLEDGER.string == "No": - tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company) + if entry.ISPARTYLEDGER.string.strip() == "No": + tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company) taxes.append({ "charge_type": "Actual", "account_head": tax_account, "description": tax_account, - "tax_amount": entry.AMOUNT.string, + "tax_amount": entry.AMOUNT.string.strip(), "cost_center": self.default_cost_center, }) return taxes From 33eb35d8b1caf031315e81b59f7605c8aff346e1 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Thu, 16 Apr 2020 16:16:09 +0530 Subject: [PATCH 32/69] fix: incorrect transalation --- erpnext/assets/doctype/asset_category/asset_category.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 2178b7d50f..9bf4df423c 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -35,8 +35,8 @@ class AssetCategory(Document): expected_key_type = account_type_map[fieldname][key_to_match] if selected_key_type != expected_key_type: - frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account." - .format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type))), + frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.") + .format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)), title=_("Invalid Account")) From 14cdb24290a602663a4c296db66dcd200bb32b8d Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 16 Apr 2020 16:21:35 +0530 Subject: [PATCH 33/69] fix: serial and batch selection from delivery note bug fix (#21291) --- erpnext/public/js/controllers/transaction.js | 4 ++++ erpnext/selling/sales_common.js | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8e8c48feb0..f126f6a62d 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -551,6 +551,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ if(!d[k]) d[k] = v; }); + if (d.has_batch_no && d.has_serial_no) { + d.batch_no = undefined; + } + erpnext.show_serial_batch_selector(me.frm, d, (item) => { me.frm.script_manager.trigger('qty', item.doctype, item.name); if (!me.frm.doc.set_warehouse) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index af100692c6..095b7c3dff 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -228,9 +228,15 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ warehouse: function(doc, cdt, cdn) { var me = this; var item = frappe.get_doc(cdt, cdn); + + if (item.serial_no && item.qty === item.serial_no.split(`\n`).length) { + return; + } + if (item.serial_no && !item.batch_no) { item.serial_no = null; } + var has_batch_no; frappe.db.get_value('Item', {'item_code': item.item_code}, 'has_batch_no', (r) => { has_batch_no = r && r.has_batch_no; From 496c6a3baa38567dfe6f2d9230c11e9236555afd Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Apr 2020 16:51:59 +0530 Subject: [PATCH 34/69] style: removed unused imports and updated formatting --- .../doctype/tally_migration/tally_migration.js | 2 +- .../doctype/tally_migration/tally_migration.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 682f78efb7..d84c8234ef 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Tally Migration', { let error_occurred = data.count === -1; if (data.count == data.total || error_occurred) { window.setTimeout((title) => { - frm.dashboard.hide_progress(title) + frm.dashboard.hide_progress(title); frm.reload_doc(); if (error_occurred) { frappe.msgprint({ diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 98a30273e6..13474e19ee 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import json -import sys import re import traceback import zipfile @@ -18,7 +17,6 @@ from erpnext import encode_company_abbr from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_field -from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification from frappe.model.document import Document from frappe.model.naming import getseries, revert_series_if_last from frappe.utils.data import format_datetime From 03af0299a65639593fa32f5a52fe48b6954d55a5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 16 Apr 2020 20:11:32 +0530 Subject: [PATCH 35/69] fix: requested qty calculation and some other small fixes (#21296) * fix: Made received qty readonly and no-copy * fix: Made release date mandatory * fix: requested qty calculation fix for UOM --- .../purchase_invoice/purchase_invoice.js | 3 +- .../material_request/material_request.py | 7 ++-- .../material_request/test_material_request.py | 32 +++++++++++++++++ .../material_request_item.json | 8 +++-- erpnext/stock/stock_balance.py | 36 +++++++++++-------- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c5c54837a7..9292b633fc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -174,7 +174,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ read_only: 0, fieldtype:'Date', label: __('Release Date'), - default: me.frm.doc.release_date + default: me.frm.doc.release_date, + reqd: 1 }, { fieldname: 'hold_comment', diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 5b242a51bc..2d9855713c 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -175,12 +175,11 @@ class MaterialRequest(BuyingController): frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty) - target_ref_field = 'qty' if self.material_request_type == "Manufacture" else 'stock_qty' self._update_percent_field({ "target_dt": "Material Request Item", "target_parent_dt": self.doctype, "target_parent_field": "per_ordered", - "target_ref_field": target_ref_field, + "target_ref_field": "stock_qty", "target_field": "ordered_qty", "name": self.name, }, update_modified) @@ -499,7 +498,7 @@ def raise_work_orders(material_request): default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") for d in mr.items: - if (d.qty - d.ordered_qty) >0: + if (d.stock_qty - d.ordered_qty) > 0: if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}): wo_order = frappe.new_doc("Work Order") wo_order.update({ @@ -531,7 +530,7 @@ def raise_work_orders(material_request): msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message)) if errors: - frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors)) + frappe.throw(_("Work Order cannot be created for following reason:") + '\n' + new_line_sep(errors)) return work_orders diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index b925aedd1a..30c47c3671 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -563,6 +563,36 @@ class TestMaterialRequest(unittest.TestCase): item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0] self.assertEqual(requested_qty, new_requested_qty) + def test_requested_qty_multi_uom(self): + existing_requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + + mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture', + uom="_Test UOM 1", conversion_factor=12) + + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + + self.assertEqual(requested_qty, existing_requested_qty + 120) + + work_order = raise_work_orders(mr.name) + wo = frappe.get_doc("Work Order", work_order[0]) + wo.qty = 50 + wo.wip_warehouse = "_Test Warehouse 1 - _TC" + wo.submit() + + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + self.assertEqual(requested_qty, existing_requested_qty + 70) + + wo.cancel() + + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + self.assertEqual(requested_qty, existing_requested_qty + 120) + + mr.reload() + mr.cancel() + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + self.assertEqual(requested_qty, existing_requested_qty) + + def test_multi_uom_for_purchase(self): from erpnext.stock.doctype.material_request.material_request import make_purchase_order @@ -633,6 +663,8 @@ def make_material_request(**args): mr.append("items", { "item_code": args.item_code or "_Test Item", "qty": args.qty or 10, + "uom": args.uom or "_Test UOM", + "conversion_factor": args.conversion_factor or 1, "schedule_date": args.schedule_date or today(), "warehouse": args.warehouse or "_Test Warehouse - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC" 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 30206b62d0..56049131bb 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-02-22 01:28:02", "doctype": "DocType", @@ -374,7 +373,10 @@ { "fieldname": "received_qty", "fieldtype": "Float", - "label": "Received Quantity" + "label": "Received Quantity", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 }, { "collapsible": 1, @@ -410,7 +412,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-07 18:37:54.495112", + "modified": "2020-04-16 09:00:00.992835", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 2bdb04ed2c..d9434e3fe7 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -113,24 +113,32 @@ def get_reserved_qty(item_code, warehouse): return flt(reserved_qty[0][0]) if reserved_qty else 0 def get_indented_qty(item_code, warehouse): - inward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor) - from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr - where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Purchase', 'Manufacture') - and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name - and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) - - outward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor) + # Ordered Qty is maintained in purchase UOM + requested_qty_for_purchase_and_manufacture = frappe.db.sql(""" + select sum(mr_item.stock_qty - mr_item.ordered_qty) from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Material Issue', 'Material Transfer') - and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name - and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) + and mr.material_request_type in ('Purchase', 'Manufacture') + and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1 + """, (item_code, warehouse)) + requested_qty_for_purchase_and_manufacture = flt(requested_qty_for_purchase_and_manufacture[0][0]) \ + if requested_qty_for_purchase_and_manufacture else 0 - inward_qty, outward_qty = flt(inward_qty[0][0]) if inward_qty else 0, flt(outward_qty[0][0]) if outward_qty else 0 - indented_qty = inward_qty - outward_qty + requested_qty_for_issue_and_transfer = frappe.db.sql(""" + select sum(mr_item.stock_qty - mr_item.ordered_qty) + from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr + where mr_item.item_code=%s and mr_item.warehouse=%s + and mr.material_request_type in ('Material Issue', 'Material Transfer') + and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1 + """, (item_code, warehouse)) + requested_qty_for_issue_and_transfer = flt(requested_qty_for_issue_and_transfer[0][0]) \ + if requested_qty_for_issue_and_transfer else 0 - return indented_qty + requested_qty = requested_qty_for_purchase_and_manufacture - requested_qty_for_issue_and_transfer + + return requested_qty def get_ordered_qty(item_code, warehouse): ordered_qty = frappe.db.sql(""" From bf78ed6947ca32346384527d19a5fa6913b05cb4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Apr 2020 22:53:47 +0530 Subject: [PATCH 36/69] fix: Loan Repayment code clean up and other fixes --- erpnext/hr/doctype/salary_slip/salary_slip.py | 18 +-- .../loan_management/doctype/loan/test_loan.py | 80 +++------- .../loan_interest_accrual.json | 24 +-- .../loan_repayment/loan_repayment.json | 27 ++-- .../doctype/loan_repayment/loan_repayment.py | 147 +++++++++--------- .../doctype/loan_repayment_detail/__init__.py | 0 .../loan_repayment_detail.json | 43 +++++ .../loan_repayment_detail.py | 10 ++ .../salary_slip_loan/salary_slip_loan.json | 13 +- .../page/point_of_sale/point_of_sale.js | 2 +- 10 files changed, 191 insertions(+), 173 deletions(-) create mode 100644 erpnext/loan_management/doctype/loan_repayment_detail/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json create mode 100644 erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index b3c803b564..223c4e3e3b 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -776,22 +776,16 @@ class SalarySlip(TransactionBase): for payment in self.get('loans'): amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment") + total_amount = amounts['interest_amount'] + amounts['payable_principal_amount'] + if payment.total_payment > total_amount: + frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2} + against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment), + frappe.bold(total_amount), frappe.bold(payment.loan))) - if payment.interest_amount > amounts['interest_amount']: - frappe.throw(_("""Row {0}: Paid Interest amount {1} is greater than pending interest amount {2} - against loan {3}""").format(payment.idx, frappe.bold(payment.interest_amount), - frappe.bold(amounts['interest_amount']), frappe.bold(payment.loan))) - - if payment.principal_amount > amounts['payable_principal_amount']: - frappe.throw(_("""Row {0}: Paid Principal amount {1} is greater than pending principal amount {2} - against loan {3}""").format(payment.idx, frappe.bold(payment.principal_amount), - frappe.bold(amounts['payable_principal_amount']), frappe.bold(payment.loan))) - - payment.total_payment = payment.interest_amount + payment.principal_amount self.total_interest_amount += payment.interest_amount self.total_principal_amount += payment.principal_amount - self.total_loan_repayment = self.total_interest_amount + self.total_principal_amount + self.total_loan_repayment += payment.total_payment def get_loan_details(self): diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 108672b25a..2d1ad33ed0 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -149,13 +149,19 @@ class TestLoan(unittest.TestCase): repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68) repayment_entry.save() + repayment_entry.submit() penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year)) - - self.assertEquals(flt(repayment_entry.interest_payable, 2), flt(accrued_interest_amount, 2)) self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2)) - repayment_entry.submit() + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) + + loan.load_from_db() + + self.assertEquals(amounts[0], repayment_entry.interest_payable) + self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid - + penalty_amount - amounts[0], 2)) def test_loan_closure_repayment(self): pledges = [] @@ -189,15 +195,19 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), - "Loan Closure", 13315.0681) - repayment_entry.save() + "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry.submit() - repayment_entry.amount_paid = repayment_entry.payable_amount + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) - self.assertEquals(flt(repayment_entry.interest_payable, 3), flt(accrued_interest_amount, 3)) + unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3), + flt(accrued_interest_amount, 3)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) - repayment_entry.submit() loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -227,57 +237,15 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5), - "Regular Payment", 89768.7534247) + "Regular Payment", 89768.75) - repayment_entry.save() repayment_entry.submit() - repayment_entry.load_from_db() + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) - self.assertEquals(repayment_entry.interest_payable, 11250.00) - self.assertEquals(repayment_entry.payable_principal_amount, 78303.00) - - def test_partial_loan_repayment(self): - pledges = [] - pledges.append({ - "loan_security": "Test Security 1", - "qty": 4000.00, - "haircut": 50 - }) - - loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) - - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name, - posting_date=get_first_day(nowdate())) - - loan.submit() - - self.assertEquals(loan.loan_amount, 1000000) - - first_date = '2019-10-01' - last_date = '2019-10-30' - - no_of_days = date_diff(last_date, first_date) + 1 - - accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ - / (days_in_year(get_datetime().year) * 100) - - make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) - - process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 15)) - process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 30)) - - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 1), "Regular Payment", 6500) - repayment_entry.save() - repayment_entry.submit() - - penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) - - lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name') - lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name') - - self.assertTrue(lia1) - self.assertTrue(lia2) + self.assertEquals(amounts[0], 11250.00) + self.assertEquals(amounts[1], 78303.00) def test_security_shortfall(self): pledges = [] @@ -294,7 +262,7 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount) - frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 100 + frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100 where loan_security='Test Security 2'""") create_process_loan_security_shortfall() diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index a26112011c..5fc3e8f4b6 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -15,12 +15,13 @@ "company", "posting_date", "is_term_loan", - "is_paid", "section_break_7", "pending_principal_amount", "payable_principal_amount", + "paid_principal_amount", "column_break_14", "interest_amount", + "paid_interest_amount", "section_break_15", "process_loan_interest_accrual", "repayment_schedule_name", @@ -101,13 +102,6 @@ "label": "Company", "options": "Company" }, - { - "default": "0", - "fieldname": "is_paid", - "fieldtype": "Check", - "label": "Is Paid", - "read_only": 1 - }, { "default": "0", "fetch_from": "loan.is_term_loan", @@ -143,12 +137,24 @@ "hidden": 1, "label": "Repayment Schedule Name", "read_only": 1 + }, + { + "fieldname": "paid_principal_amount", + "fieldtype": "Currency", + "label": "Paid Principal Amount", + "options": "Company:company:default_currency" + }, + { + "fieldname": "paid_interest_amount", + "fieldtype": "Currency", + "label": "Paid Interest Amount", + "options": "Company:company:default_currency" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2020-04-10 18:31:02.369857", + "modified": "2020-04-16 11:24:23.258404", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 4b930c50ae..789c129946 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -30,9 +30,8 @@ "reference_number", "column_break_21", "reference_date", - "paid_accrual_entries", - "partial_paid_entry", "principal_amount_paid", + "repayment_details", "amended_from" ], "fields": [ @@ -155,13 +154,6 @@ "options": "Company:company:default_currency", "read_only": 1 }, - { - "fieldname": "paid_accrual_entries", - "fieldtype": "Text", - "hidden": 1, - "label": "Paid Accrual Entries", - "read_only": 1 - }, { "default": "0", "fetch_from": "against_loan.is_term_loan", @@ -197,13 +189,6 @@ "fieldname": "column_break_21", "fieldtype": "Column Break" }, - { - "fieldname": "partial_paid_entry", - "fieldtype": "Text", - "hidden": 1, - "label": "Partial Paid Entry", - "read_only": 1 - }, { "default": "0.0", "fieldname": "principal_amount_paid", @@ -225,11 +210,18 @@ "fieldtype": "Date", "label": "Due Date", "read_only": 1 + }, + { + "fieldname": "repayment_details", + "fieldtype": "Table", + "hidden": 1, + "label": "Repayment Details", + "options": "Loan Repayment Detail" } ], "is_submittable": 1, "links": [], - "modified": "2020-02-26 06:18:54.934538", + "modified": "2020-04-16 18:14:45.166754", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", @@ -264,7 +256,6 @@ "write": 1 } ], - "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 2d2ca4c2f4..87e8a15ab4 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -19,11 +19,11 @@ class LoanRepayment(AccountsController): def validate(self): amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type) self.set_missing_values(amounts) - - def before_submit(self): - self.mark_as_paid() + self.validate_amount() + self.allocate_amounts(amounts['pending_accrual_entries']) def on_submit(self): + self.update_paid_amount() self.make_gl_entries() def on_cancel(self): @@ -38,32 +38,25 @@ class LoanRepayment(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) if not self.interest_payable: - self.interest_payable = amounts['interest_amount'] + self.interest_payable = flt(amounts['interest_amount'], 2) if not self.penalty_amount: - self.penalty_amount = amounts['penalty_amount'] + self.penalty_amount = flt(amounts['penalty_amount'], 2) if not self.pending_principal_amount: - self.pending_principal_amount = amounts['pending_principal_amount'] + self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2) if not self.payable_principal_amount and self.is_term_loan: - self.payable_principal_amount = amounts['payable_principal_amount'] + self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2) if not self.payable_amount: - self.payable_amount = amounts['payable_amount'] - - if amounts.get('paid_accrual_entries'): - self.paid_accrual_entries = frappe.as_json(amounts.get('paid_accrual_entries')) + self.payable_amount = flt(amounts['payable_amount'], 2) if amounts.get('due_date'): self.due_date = amounts.get('due_date') - def mark_as_paid(self): - paid_entries = [] - paid_amount = self.amount_paid - interest_paid = paid_amount - - if not paid_amount: + def validate_amount(self): + if not self.amount_paid: frappe.throw(_("Amount paid cannot be zero")) if self.amount_paid < self.penalty_amount: @@ -74,37 +67,15 @@ class LoanRepayment(AccountsController): msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount) frappe.throw(msg) + def update_paid_amount(self): loan = frappe.get_doc("Loan", self.against_loan) - if self.paid_accrual_entries: - paid_accrual_entries = json.loads(self.paid_accrual_entries) - - if paid_amount - self.penalty_amount > 0 and self.paid_accrual_entries: - - interest_paid = paid_amount - self.penalty_amount - - for lia, interest_amount in iteritems(paid_accrual_entries): - if interest_amount <= interest_paid: - paid_entries.append(lia) - interest_paid -= interest_amount - elif interest_paid: - self.partial_paid_entry = frappe.as_json({"name": lia, "interest_amount": interest_amount}) - frappe.db.set_value("Loan Interest Accrual", lia, "interest_amount", - interest_amount - interest_paid) - interest_paid = 0 - - if paid_entries: - self.paid_accrual_entries = frappe.as_json(paid_entries) - else: - self.paid_accrual_entries = "" - - if interest_paid: - self.principal_amount_paid = interest_paid - - if paid_entries: - frappe.db.sql("""UPDATE `tabLoan Interest Accrual` - SET is_paid = 1 where name in (%s)""" #nosec - % ", ".join(['%s']*len(paid_entries)), tuple(paid_entries)) + for payment in self.repayment_details: + frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` + SET paid_principal_amount = `paid_principal_amount` + %s, + paid_interest_amount = `paid_interest_amount` + %s + WHERE name = %s""", + (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual)) if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2): frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") @@ -116,21 +87,14 @@ class LoanRepayment(AccountsController): update_shortfall_status(self.against_loan, self.principal_amount_paid) def mark_as_unpaid(self): - loan = frappe.get_doc("Loan", self.against_loan) - if self.paid_accrual_entries: - paid_accrual_entries = json.loads(self.paid_accrual_entries) - - if self.paid_accrual_entries: - frappe.db.sql("""UPDATE `tabLoan Interest Accrual` - SET is_paid = 0 where name in (%s)""" #nosec - % ", ".join(['%s']*len(paid_accrual_entries)), tuple(paid_accrual_entries)) - - if self.partial_paid_entry: - partial_paid_entry = json.loads(self.partial_paid_entry) - frappe.db.set_value("Loan Interest Accrual", partial_paid_entry["name"], "interest_amount", - partial_paid_entry["interest_amount"]) + for payment in self.repayment_details: + frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` + SET paid_principal_amount = `paid_principal_amount` - %s, + paid_interest_amount = `paid_interest_amount` - %s + WHERE name = %s""", + (payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual)) frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid - self.amount_paid, @@ -139,6 +103,38 @@ class LoanRepayment(AccountsController): if loan.status == "Loan Closure Requested": frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed") + def allocate_amounts(self, paid_entries): + self.set('repayment_details', []) + self.principal_amount_paid = 0 + + if self.amount_paid - self.penalty_amount > 0 and paid_entries: + interest_paid = self.amount_paid - self.penalty_amount + for lia, amounts in iteritems(paid_entries): + if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid: + interest_amount = amounts['interest_amount'] + paid_principal = amounts['payable_principal_amount'] + self.principal_amount_paid += paid_principal + interest_paid -= (interest_amount + paid_principal) + elif interest_paid: + if interest_paid >= amounts['interest_amount']: + interest_amount = amounts['interest_amount'] + paid_principal = interest_paid - interest_amount + self.principal_amount_paid += paid_principal + interest_paid = 0 + else: + interest_amount = interest_paid + interest_paid = 0 + paid_principal=0 + + self.append('repayment_details', { + 'loan_interest_accrual': lia, + 'paid_interest_amount': interest_amount, + 'paid_principal_amount': paid_principal + }) + + if interest_paid: + self.principal_amount_paid += interest_paid + def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] loan_details = frappe.get_doc("Loan", self.against_loan) @@ -223,7 +219,7 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, "posting_date": posting_date, "applicant": applicant, "penalty_amount": penalty_amount, - "interst_payable": interest_payable, + "interest_payable": interest_payable, "payable_principal_amount": payable_principal_amount, "amount_paid": amount_paid, "loan_type": loan_type @@ -232,15 +228,22 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, return lr def get_accrued_interest_entries(against_loan): - accrued_interest_entries = frappe.get_all("Loan Interest Accrual", - fields=["name", "interest_amount", "posting_date", "payable_principal_amount"], - filters = { - "loan": against_loan, - "is_paid": 0, - "docstatus": 1 - }, order_by="posting_date") - return accrued_interest_entries + unpaid_accrued_entries = frappe.db.sql( + """ + SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount, + payable_principal_amount - paid_principal_amount as payable_principal_amount + FROM + `tabLoan Interest Accrual` + WHERE + loan = %s + AND (interest_amount - paid_interest_amount > 0 OR + payable_principal_amount - paid_principal_amount > 0) + AND + docstatus = 1 + """, (against_loan), as_dict=1) + + return unpaid_accrued_entries # This function returns the amounts that are payable at the time of loan repayment based on posting date # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable @@ -273,8 +276,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): total_pending_interest += entry.interest_amount payable_principal_amount += entry.payable_principal_amount - pending_accrual_entries.setdefault(entry.name, - flt(entry.interest_amount) + flt(entry.payable_principal_amount)) + pending_accrual_entries.setdefault(entry.name, { + 'interest_amount': flt(entry.interest_amount), + 'payable_principal_amount': flt(entry.payable_principal_amount) + }) final_due_date = due_date @@ -291,7 +296,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): amounts["interest_amount"] = total_pending_interest amounts["penalty_amount"] = penalty_amount amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount - amounts["paid_accrual_entries"] = pending_accrual_entries + amounts["pending_accrual_entries"] = pending_accrual_entries if final_due_date: amounts["due_date"] = final_due_date diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py b/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json new file mode 100644 index 0000000000..cff1dbb1d2 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json @@ -0,0 +1,43 @@ +{ + "actions": [], + "creation": "2020-04-15 18:31:54.026923", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan_interest_accrual", + "paid_principal_amount", + "paid_interest_amount" + ], + "fields": [ + { + "fieldname": "loan_interest_accrual", + "fieldtype": "Link", + "label": "Loan Interest Accrual", + "options": "Loan Interest Accrual" + }, + { + "fieldname": "paid_principal_amount", + "fieldtype": "Currency", + "label": "Paid Principal Amount", + "options": "Company:company:default_currency" + }, + { + "fieldname": "paid_interest_amount", + "fieldtype": "Currency", + "label": "Paid Interest Amount", + "options": "Company:company:default_currency" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-15 21:50:03.837019", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Repayment Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py new file mode 100644 index 0000000000..a83b9b5941 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class LoanRepaymentDetail(Document): + pass diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json index f7e211656e..2f4fe24945 100644 --- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json +++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json @@ -28,7 +28,6 @@ { "fieldname": "loan_account", "fieldtype": "Link", - "in_list_view": 1, "label": "Loan Account", "options": "Account", "read_only": 1 @@ -50,21 +49,23 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Principal Amount", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 }, { "fieldname": "interest_amount", "fieldtype": "Currency", "in_list_view": 1, "label": "Interest Amount", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 }, { "fieldname": "total_payment", "fieldtype": "Currency", + "in_list_view": 1, "label": "Total Payment", - "options": "Company:company:default_currency", - "read_only": 1 + "options": "Company:company:default_currency" }, { "fieldname": "loan_repayment_entry", @@ -84,7 +85,7 @@ ], "istable": 1, "links": [], - "modified": "2020-04-09 20:01:53.546364", + "modified": "2020-04-16 13:17:04.798335", "modified_by": "Administrator", "module": "Loan Management", "name": "Salary Slip Loan", diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index f175687f26..7011cf9804 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -287,7 +287,7 @@ erpnext.pos.PointOfSale = class PointOfSale { if (in_list(['serial_no', 'batch_no'], field)) { args[field] = value; } - + // add to cur_frm const item = this.frm.add_child('items', args); frappe.flags.hide_serial_batch_dialog = true; From 250daa3f9444f1548ac30345bd95ecfdc96c5c84 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 16 Apr 2020 23:02:17 +0530 Subject: [PATCH 37/69] fix: job card time issue --- .../doctype/job_card/job_card.js | 31 +++++++++++++++++-- .../doctype/job_card/job_card.py | 12 ++++--- .../doctype/work_order/work_order.js | 2 ++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 8c7876d48d..bab0dfb6b4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -82,7 +82,9 @@ frappe.ui.form.on('Job Card', { frm.set_value('current_time' , 0); } - frm.save(); + frm.save("Save", () => {}, "", () => { + frm.doc.time_logs.pop(-1); + }); }, complete_job: function(frm, completed_time, completed_qty) { @@ -105,6 +107,24 @@ frappe.ui.form.on('Job Card', { }); }, + validate: function(frm) { + if ((!frm.doc.time_logs || !frm.doc.time_logs.length) && frm.doc.started_time) { + frm.trigger("reset_timer"); + } + }, + + employee: function(frm) { + if (frm.doc.job_started && !frm.doc.current_time) { + frm.trigger("reset_timer"); + } + }, + + reset_timer: function(frm) { + frm.set_value('started_time' , ''); + frm.set_value('job_started', 0); + frm.set_value('current_time' , 0); + }, + make_dashboard: function(frm) { if(frm.doc.__islocal) return; @@ -137,12 +157,12 @@ frappe.ui.form.on('Job Card', { updateStopwatch(current); }, 1000); } - + function updateStopwatch(increment) { var hours = Math.floor(increment / 3600); var minutes = Math.floor((increment - (hours * 3600)) / 60); var seconds = increment - (hours * 3600) - (minutes * 60); - + $(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString()); $(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString()); $(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString()); @@ -205,5 +225,10 @@ frappe.ui.form.on('Job Card', { frappe.ui.form.on('Job Card Time Log', { completed_qty: function(frm) { frm.events.set_total_completed_qty(frm); + }, + + to_time: function(frm) { + frm.set_value('job_started', 0); + frm.set_value('started_time', ''); } }) \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index f8c60f2a11..e9627a5514 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -9,7 +9,7 @@ from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate, - get_time, add_to_date, time_diff, add_days, get_datetime_str) + get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations @@ -189,11 +189,15 @@ class JobCard(Document): def validate_job_card(self): if not self.time_logs: - frappe.throw(_("Time logs are required for job card {0}").format(self.name)) + frappe.throw(_("Time logs are required for {0} {1}") + .format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name))) if self.for_quantity and self.total_completed_qty != self.for_quantity: - frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})" - .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))) + total_completed_qty = frappe.bold(_("Total Completed Qty")) + qty_to_manufacture = frappe.bold(_("Qty to Manufacture")) + + frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})" + .format(total_completed_qty, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity)))) def update_work_order(self): if not self.work_order: diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 9c8aa453a6..d541866f8b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -240,6 +240,8 @@ frappe.ui.form.on("Work Order", { }); }, __("Job Card"), __("Create")); + dialog.fields_dict["operations"].grid.wrapper.find('.grid-add-row').hide(); + var pending_qty = 0; frm.doc.operations.forEach(data => { if(data.completed_qty != frm.doc.qty) { From 7eaa7f2e41693724e431e81fc4e11a3814109434 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 17 Apr 2020 10:52:28 +0530 Subject: [PATCH 38/69] fix: requested qty for customer provided item and rate for sales (#21299) * fix: requested qty for customer provided item and rate for sales * fix: requested qty for material transfer * fix: customer provided item can be sales item * fix: requested qty test cases --- .../sales_invoice/test_sales_invoice.py | 10 -- erpnext/controllers/stock_controller.py | 3 - .../delivery_note/test_delivery_note.py | 9 -- .../material_request/test_material_request.py | 124 +++++++----------- erpnext/stock/stock_balance.py | 18 ++- 5 files changed, 53 insertions(+), 111 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0e54b62caa..a2819af508 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1926,16 +1926,6 @@ class TestSalesInvoice(unittest.TestCase): item.taxes = [] item.save() - def test_customer_provided_parts_si(self): - create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) - si = create_sales_invoice(item_code='CUST-0987', rate=0) - self.assertEqual(si.get("items")[0].allow_zero_valuation_rate, 1) - self.assertEqual(si.get("items")[0].amount, 0) - - # test if Sales Invoice with rate is allowed - si2 = create_sales_invoice(item_code='CUST-0987', do_not_save=True) - self.assertRaises(frappe.ValidationError, si2.save) - def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") args = frappe._dict(args) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4037f2f60f..55a2c435a1 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -383,9 +383,6 @@ class StockController(AccountsController): # Customer Provided parts will have zero valuation rate if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 - if d.parenttype in ["Delivery Note", "Sales Invoice"] and d.rate: - frappe.throw(_("Row #{0}: {1} cannot have {2} as it is a Customer Provided Item") - .format(d.idx, frappe.bold(d.item_code), frappe.bold("Rate"))) def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None, company=None): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 47a72b21a6..d7a93fb691 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -434,15 +434,6 @@ class TestDeliveryNote(unittest.TestCase): update_delivery_note_status(dn.name, "Closed") self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed") - def test_customer_provided_parts_dn(self): - create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) - dn = create_delivery_note(item_code='CUST-0987', rate=0) - self.assertEqual(dn.get("items")[0].allow_zero_valuation_rate, 1) - - # test if Delivery Note with rate is allowed against Customer Provided Item - dn2 = create_delivery_note(item_code='CUST-0987', do_not_save=True) - self.assertRaises(frappe.ValidationError, dn2.save) - def test_dn_billing_status_case1(self): # SO -> DN -> SI so = make_sales_order() diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 30c47c3671..19924b1636 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -7,7 +7,8 @@ from __future__ import unicode_literals import frappe, unittest, erpnext from frappe.utils import flt, today -from erpnext.stock.doctype.material_request.material_request import raise_work_orders +from erpnext.stock.doctype.material_request.material_request \ + import raise_work_orders, make_stock_entry, make_purchase_order, make_supplier_quotation from erpnext.stock.doctype.item.test_item import create_item class TestMaterialRequest(unittest.TestCase): @@ -15,8 +16,6 @@ class TestMaterialRequest(unittest.TestCase): erpnext.set_perpetual_inventory(0) def test_make_purchase_order(self): - from erpnext.stock.doctype.material_request.material_request import make_purchase_order - mr = frappe.copy_doc(test_records[0]).insert() self.assertRaises(frappe.ValidationError, make_purchase_order, @@ -30,8 +29,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEqual(len(po.get("items")), len(mr.get("items"))) def test_make_supplier_quotation(self): - from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation - mr = frappe.copy_doc(test_records[0]).insert() self.assertRaises(frappe.ValidationError, make_supplier_quotation, mr.name) @@ -45,12 +42,9 @@ class TestMaterialRequest(unittest.TestCase): def test_make_stock_entry(self): - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - mr = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_stock_entry, - mr.name) + self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name) mr = frappe.get_doc("Material Request", mr.name) mr.material_request_type = "Material Transfer" @@ -62,40 +56,40 @@ class TestMaterialRequest(unittest.TestCase): def _insert_stock_entry(self, qty1, qty2, warehouse = None ): se = frappe.get_doc({ - "company": "_Test Company", - "doctype": "Stock Entry", - "posting_date": "2013-03-01", - "posting_time": "00:00:00", - "purpose": "Material Receipt", - "items": [ - { - "conversion_factor": 1.0, - "doctype": "Stock Entry Detail", - "item_code": "_Test Item Home Desktop 100", - "parentfield": "items", - "basic_rate": 100, - "qty": qty1, - "stock_uom": "_Test UOM 1", - "transfer_qty": qty1, - "uom": "_Test UOM 1", - "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", - "cost_center": "_Test Cost Center - _TC" - }, - { - "conversion_factor": 1.0, - "doctype": "Stock Entry Detail", - "item_code": "_Test Item Home Desktop 200", - "parentfield": "items", - "basic_rate": 100, - "qty": qty2, - "stock_uom": "_Test UOM 1", - "transfer_qty": qty2, - "uom": "_Test UOM 1", - "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", - "cost_center": "_Test Cost Center - _TC" - } - ] - }) + "company": "_Test Company", + "doctype": "Stock Entry", + "posting_date": "2013-03-01", + "posting_time": "00:00:00", + "purpose": "Material Receipt", + "items": [ + { + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item Home Desktop 100", + "parentfield": "items", + "basic_rate": 100, + "qty": qty1, + "stock_uom": "_Test UOM 1", + "transfer_qty": qty1, + "uom": "_Test UOM 1", + "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", + "cost_center": "_Test Cost Center - _TC" + }, + { + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item Home Desktop 200", + "parentfield": "items", + "basic_rate": 100, + "qty": qty2, + "stock_uom": "_Test UOM 1", + "transfer_qty": qty2, + "uom": "_Test UOM 1", + "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", + "cost_center": "_Test Cost Center - _TC" + } + ] + }) se.set_stock_entry_type() se.insert() @@ -198,14 +192,7 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # check if per complete is None - mr.load_from_db() - self.assertEqual(mr.per_ordered, 0) - self.assertEqual(mr.get("items")[0].ordered_qty, 0) - self.assertEqual(mr.get("items")[1].ordered_qty, 0) - # map a purchase order - from erpnext.stock.doctype.material_request.material_request import make_purchase_order po_doc = make_purchase_order(mr.name) po_doc.supplier = "_Test Supplier" po_doc.transaction_date = "2013-07-07" @@ -276,10 +263,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0) - - from erpnext.stock.doctype.material_request.material_request import make_stock_entry + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) # map a stock entry se_doc = make_stock_entry(mr.name) @@ -331,8 +316,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 27.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 1.5) + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 27.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 1.5) # check if per complete is as expected for Stock Entry cancelled se.cancel() @@ -344,8 +329,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0) + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_completed_qty_for_over_transfer(self): existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") @@ -357,14 +342,7 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # check if per complete is None - mr.load_from_db() - self.assertEqual(mr.per_ordered, 0) - self.assertEqual(mr.get("items")[0].ordered_qty, 0) - self.assertEqual(mr.get("items")[1].ordered_qty, 0) - # map a stock entry - from erpnext.stock.doctype.material_request.material_request import make_stock_entry se_doc = make_stock_entry(mr.name) se_doc.update({ @@ -425,8 +403,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0) + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_incorrect_mapping_of_stock_entry(self): # submit material request of type Transfer @@ -435,9 +413,6 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # map a stock entry - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - se_doc = make_stock_entry(mr.name) se_doc.update({ "posting_date": "2013-03-01", @@ -468,8 +443,6 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # map a stock entry - from erpnext.stock.doctype.material_request.material_request import make_stock_entry se_doc = make_stock_entry(mr.name) self.assertEqual(se_doc.get("items")[0].s_warehouse, "_Test Warehouse - _TC") @@ -483,8 +456,6 @@ class TestMaterialRequest(unittest.TestCase): return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")) def test_make_stock_entry_for_material_issue(self): - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - mr = frappe.copy_doc(test_records[0]).insert() self.assertRaises(frappe.ValidationError, make_stock_entry, @@ -503,8 +474,6 @@ class TestMaterialRequest(unittest.TestCase): return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100", "warehouse": "_Test Warehouse - _TC"}, "indented_qty")) - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - existing_requested_qty = _get_requested_qty() mr = frappe.copy_doc(test_records[0]) @@ -594,8 +563,6 @@ class TestMaterialRequest(unittest.TestCase): def test_multi_uom_for_purchase(self): - from erpnext.stock.doctype.material_request.material_request import make_purchase_order - mr = frappe.copy_doc(test_records[0]) mr.material_request_type = 'Purchase' item = mr.items[0] @@ -637,7 +604,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEqual(mr.per_ordered, 100) def test_customer_provided_parts_mr(self): - from erpnext.stock.doctype.material_request.material_request import make_stock_entry create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC") diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index d9434e3fe7..5697315360 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -113,30 +113,28 @@ def get_reserved_qty(item_code, warehouse): return flt(reserved_qty[0][0]) if reserved_qty else 0 def get_indented_qty(item_code, warehouse): - # Ordered Qty is maintained in purchase UOM - requested_qty_for_purchase_and_manufacture = frappe.db.sql(""" + # Ordered Qty is always maintained in stock UOM + inward_qty = frappe.db.sql(""" select sum(mr_item.stock_qty - mr_item.ordered_qty) from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Purchase', 'Manufacture') + and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer') and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name and mr.status!='Stopped' and mr.docstatus=1 """, (item_code, warehouse)) - requested_qty_for_purchase_and_manufacture = flt(requested_qty_for_purchase_and_manufacture[0][0]) \ - if requested_qty_for_purchase_and_manufacture else 0 + inward_qty = flt(inward_qty[0][0]) if inward_qty else 0 - requested_qty_for_issue_and_transfer = frappe.db.sql(""" + outward_qty = frappe.db.sql(""" select sum(mr_item.stock_qty - mr_item.ordered_qty) from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Material Issue', 'Material Transfer') + and mr.material_request_type = 'Material Issue' and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name and mr.status!='Stopped' and mr.docstatus=1 """, (item_code, warehouse)) - requested_qty_for_issue_and_transfer = flt(requested_qty_for_issue_and_transfer[0][0]) \ - if requested_qty_for_issue_and_transfer else 0 + outward_qty = flt(outward_qty[0][0]) if outward_qty else 0 - requested_qty = requested_qty_for_purchase_and_manufacture - requested_qty_for_issue_and_transfer + requested_qty = inward_qty - outward_qty return requested_qty From bd14a64bfb6ba95b82860adb5c0b381c70b7363f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 19:52:47 +0530 Subject: [PATCH 39/69] fix: add label to gl entry --- erpnext/accounts/report/general_ledger/general_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 649b363c32..f776d93301 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -365,6 +365,7 @@ def get_columns(filters): columns = [ { + "label": _("GL Entry"), "fieldname": "gl_entry", "fieldtype": "Link", "options": "GL Entry", From 9793500154bb8b4152ac025b26a503b065286eed Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 18 Apr 2020 19:02:07 +0530 Subject: [PATCH 40/69] fix: account name not showing in the gl print --- erpnext/accounts/report/general_ledger/general_ledger.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 9a2205a579..378fa3791c 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -2,7 +2,7 @@

{% if (filters.party_name) { %} {%= filters.party_name %} - {% } else if (filters.party) { %} + {% } else if (filters.party && filters.party.length) { %} {%= filters.party %} {% } else if (filters.account) { %} {%= filters.account %} From 3cec5cbb0f4be9950e4561bc11d59c822d8d82cc Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 18 Apr 2020 22:13:48 +0530 Subject: [PATCH 41/69] fix: Total amount field ordering in transactions (#21313) --- .../doctype/purchase_invoice/purchase_invoice.json | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 6 ++++-- erpnext/buying/doctype/purchase_order/purchase_order.json | 8 +++++--- erpnext/selling/doctype/sales_order/sales_order.json | 4 ++-- erpnext/stock/doctype/delivery_note/delivery_note.json | 4 ++-- .../stock/doctype/purchase_receipt/purchase_receipt.json | 4 ++-- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3cd988ccd2..3af236cbf9 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -73,9 +73,9 @@ "base_total", "base_net_total", "column_break_28", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_49", @@ -1298,7 +1298,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:13:49.610538", + "modified": "2020-04-17 13:05:25.199832", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e239f9143d..918fa140b2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:05", @@ -74,9 +75,9 @@ "base_total", "base_net_total", "column_break_32", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "taxes_and_charges", "column_break_38", @@ -1577,7 +1578,8 @@ "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, - "modified": "2020-02-10 04:57:11.221180", + "links": [], + "modified": "2020-04-17 12:38:41.435728", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 4d83690391..578858ca52 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -63,9 +64,9 @@ "base_total", "base_net_total", "column_break_26", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_50", @@ -170,8 +171,8 @@ "search_index": 1 }, { - "description": "Fetch items based on Default Supplier.", "depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))", + "description": "Fetch items based on Default Supplier.", "fieldname": "get_items_from_open_material_requests", "fieldtype": "Button", "label": "Get Items from Open Material Requests" @@ -1054,7 +1055,8 @@ "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, - "modified": "2020-01-14 18:54:39.694448", + "links": [], + "modified": "2020-04-17 13:04:28.185197", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 54e87f7a3b..6462d3bc8c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -60,9 +60,9 @@ "base_total", "base_net_total", "column_break_33", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_38", @@ -1196,7 +1196,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:15:28.605085", + "modified": "2020-04-17 12:50:39.640534", "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 6f9d83d674..9f5dee901c 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -66,9 +66,9 @@ "base_total", "base_net_total", "column_break_33", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_39", @@ -1256,7 +1256,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2019-12-31 19:17:13.122644", + "modified": "2020-04-17 12:51:41.288600", "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 e38bb38b19..eed5749b06 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -59,9 +59,9 @@ "base_total", "base_net_total", "column_break_27", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_charges_section", "tax_category", "shipping_col", @@ -1082,7 +1082,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-04-06 16:31:37.444891", + "modified": "2020-04-17 13:06:26.970288", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From bd53f3b08021cf435f1d6d3d195dfeb22d94f2e7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sun, 19 Apr 2020 12:47:48 +0530 Subject: [PATCH 42/69] fix: filters as dictionary --- erpnext/support/doctype/issue/issue.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index fd72807418..117267f1a4 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -335,8 +335,13 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord ignore_permissions = False if is_website_user(): - if not filters: filters = [] - filters.append(("Issue", "customer", "=", customer)) if customer else filters.append(("Issue", "raised_by", "=", user)) + if not filters: filters = {} + + if customer: + filters["customer"] = customer + else: + filters["raised_by"] = user + ignore_permissions = True return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) From 6d492381593f1ed42ce3f7b6cd028d10a5b60e3e Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Sun, 19 Apr 2020 16:49:10 +0200 Subject: [PATCH 43/69] Update project.json (#21274) unnecessary requirement for project creation as this is a field of the Type select which will be pre-filled. When creating a project of list view it will also bring up this field as it has "reqd": 1. A new projekt will have the status set to open in most cases. Co-authored-by: Nabin Hait Co-authored-by: Himanshu --- erpnext/projects/doctype/project/project.json | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 49abab1c13..f3cecd9059 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -83,7 +83,6 @@ "oldfieldname": "status", "oldfieldtype": "Select", "options": "Open\nCompleted\nCancelled", - "reqd": 1, "search_index": 1 }, { From f03efdc6b1ae2ff5bce8e47144ec14283230e084 Mon Sep 17 00:00:00 2001 From: Sahil Khan Date: Fri, 17 Apr 2020 13:15:15 +0530 Subject: [PATCH 44/69] fix(patch): reload 'Import Supplier Invoice' doc --- erpnext/patches/v12_0/set_permission_einvoicing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v12_0/set_permission_einvoicing.py b/erpnext/patches/v12_0/set_permission_einvoicing.py index 1095c8c43e..e2235105f9 100644 --- a/erpnext/patches/v12_0/set_permission_einvoicing.py +++ b/erpnext/patches/v12_0/set_permission_einvoicing.py @@ -10,6 +10,8 @@ def execute(): make_custom_fields() + frappe.reload_doc("regional", "doctype", "import_supplier_invoice") + add_permission('Import Supplier Invoice', 'Accounts Manager', 0) update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'write', 1) - update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'create', 1) \ No newline at end of file + update_permission_property('Import Supplier Invoice', 'Accounts Manager', 0, 'create', 1) From 745b632ea167e9f98cfadd36a62dd26c3e8b719a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 20 Apr 2020 18:25:51 +0530 Subject: [PATCH 45/69] fix: linting fixes --- erpnext/non_profit/doctype/member/member.py | 8 ++++---- erpnext/non_profit/doctype/membership/membership.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index 2f84cf5538..571f87af87 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -92,14 +92,15 @@ def create_customer(user_details): }) contact.insert() - except Exception: - error_log = frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + except Exception as e: + frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) + pass return customer.name @frappe.whitelist(allow_guest=True) def create_member_subscription_order(user_details): - """Summary + """Create Member subscription and order for payment Args: user_details (TYPE): Description @@ -117,7 +118,6 @@ def create_member_subscription_order(user_details): """ # {"plan_id":"IFF Starter","fullname":"Shivam Mishra","mobile":"7506056962","email":"shivam@shivam.dev","pan":"Testing123"} user_details = frappe._dict(user_details) - plan = frappe.get_doc("Membership Type", user_details.plan_id) member = get_or_create_member(user_details) if not member: member = create_member(user_details) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index d11f6992f9..a523a238e4 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -9,7 +9,7 @@ import six from datetime import datetime from frappe.model.document import Document from frappe.email import sendmail_to_system_managers -from frappe.utils import add_days, add_years, nowdate, getdate, add_months, cint +from frappe.utils import add_days, add_years, nowdate, getdate, add_months, get_link_to_form from frappe import _ import erpnext @@ -83,7 +83,7 @@ def trigger_razorpay_subscription(data): member = get_member_based_on_subscription(subscription.id, payment.email) except Exception as e: error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed")) - notify_failure(log) + notify_failure(error_log) raise e if data.event == "subscription.activated": From d093a377ef6c3bae54b785f7f2846d0f09bfc673 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 21 Apr 2020 00:19:19 +0530 Subject: [PATCH 46/69] fix: free item quantity issue --- erpnext/public/js/controllers/transaction.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 8e8c48feb0..3fc26cf5e7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1430,6 +1430,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for (let key in free_item_data) { row_to_modify[key] = free_item_data[key]; } + } if (items && items.length && free_item_data) { + items[0].qty = free_item_data.qty } }, From a92eaff83b870ea8dcfb2a4832cf1ac6c1fd1338 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 21 Apr 2020 11:27:10 +0530 Subject: [PATCH 47/69] fix: scrap items order not showing correctly in stock entry (#21346) --- .../stock/doctype/stock_entry/stock_entry.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 7cf822bf49..95f9d4633b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -865,14 +865,6 @@ class StockEntry(StockController): self.add_to_stock_entry_detail(item_dict) - if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]: - scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty) - for item in itervalues(scrap_item_dict): - if self.pro_doc and self.pro_doc.scrap_warehouse: - item["to_warehouse"] = self.pro_doc.scrap_warehouse - - self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no) - # fetch the serial_no of the first stock entry for the second stock entry if self.work_order and self.purpose == "Manufacture": self.set_serial_nos(self.work_order) @@ -883,9 +875,20 @@ class StockEntry(StockController): if self.purpose in ("Manufacture", "Repack"): self.load_items_from_bom() + self.set_scrap_items() self.set_actual_qty() self.calculate_rate_and_amount(raise_error_if_no_rate=False) + def set_scrap_items(self): + if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]: + scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty) + for item in itervalues(scrap_item_dict): + item.idx = '' + if self.pro_doc and self.pro_doc.scrap_warehouse: + item["to_warehouse"] = self.pro_doc.scrap_warehouse + + self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no) + def set_work_order_details(self): if not getattr(self, "pro_doc", None): self.pro_doc = frappe._dict() From 9ea1ad4051bf1b9ae6a0a9405b5a0813cbdfc1ce Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 21 Apr 2020 12:52:29 +0530 Subject: [PATCH 48/69] fix: Re-order Item Error Email format (#21342) * fix: Re-order Item Error Email format * fix: Translated strings --- erpnext/stock/reorder_item.py | 20 +++++++++----------- erpnext/utilities/transaction_base.py | 6 ++++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py index 97776739a8..4c721acdc1 100644 --- a/erpnext/stock/reorder_item.py +++ b/erpnext/stock/reorder_item.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import erpnext +import json from frappe.utils import flt, nowdate, add_days, cint from frappe import _ @@ -198,19 +199,16 @@ def send_email_notification(mr_list): subject=_('Auto Material Requests Generated'), message = msg) def notify_errors(exceptions_list): - subject = "[Important] [ERPNext] Auto Reorder Errors" - content = """Dear System Manager, + subject = _("[Important] [ERPNext] Auto Reorder Errors") + content = _("Dear System Manager,") + "
" + _("An error occured for certain Items while creating Material Requests based on Re-order level. \ + Please rectify these issues :") + "
" -An error occured for certain Items while creating Material Requests based on Re-order level. + for exception in exceptions_list: + exception = json.loads(exception) + error_message = """
{0}

""".format(_(exception.get("message"))) + content += error_message -Please rectify these issues: ---- -
-%s
-
---- -Regards, -Administrator""" % ("\n\n".join(exceptions_list),) + content += _("Regards,") + "
" + _("Administrator") from frappe.email import sendmail_to_system_managers sendmail_to_system_managers(subject, content) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index f88ffd44e3..14674c067c 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -126,7 +126,7 @@ class TransactionBase(StatusUpdater): frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ") .format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate)) frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.") - .format(frappe.bold("Maintain Same Rate Throughout Sales Cycle"), + .format(frappe.bold(_("Maintain Same Rate Throughout Sales Cycle")), get_link_to_form("Selling Settings", "Selling Settings", frappe.bold("Selling Settings")))) def get_link_filters(self, for_doctype): @@ -179,4 +179,6 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None): qty = d.get(f) if qty: if abs(cint(qty) - flt(qty)) > 0.0000001: - frappe.throw(_("Quantity ({0}) cannot be a fraction in row {1}").format(qty, d.idx), UOMMustBeIntegerError) + frappe.throw(_("Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}.") \ + .format(qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field))), + UOMMustBeIntegerError) From ca1c37c56d2700254992e7f9da78be0a3e9fabde Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 21 Apr 2020 12:54:07 +0530 Subject: [PATCH 49/69] fix(Healthcare): remove hardcoded UOM during Item creation for Templates (#21350) --- .../clinical_procedure_template.py | 3 +- .../lab_test_template/lab_test_template.py | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py index 3f295afc3e..f32b7cf9d8 100644 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py +++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py @@ -79,6 +79,7 @@ def create_item_from_template(doc): if doc.is_billable and not doc.disabled: disabled = 0 + uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom') item = frappe.get_doc({ 'doctype': 'Item', 'item_code': doc.template, @@ -92,7 +93,7 @@ def create_item_from_template(doc): 'show_in_website': 0, 'is_pro_applicable': 0, 'disabled': disabled, - 'stock_uom': 'Unit' + 'stock_uom': uom }).insert(ignore_permissions=True, ignore_mandatory=True) make_item_price(item.name, doc.rate) diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py index 6bbb4f1c3a..e2b47b4559 100644 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py +++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py @@ -74,26 +74,27 @@ class LabTestTemplate(Document): def create_item_from_template(doc): - if doc.is_billable: + disabled = doc.disabled + if doc.is_billable and not doc.disabled: disabled = 0 - else: - disabled = 1 + + uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom') # insert item item = frappe.get_doc({ - "doctype": "Item", - "item_code": doc.lab_test_code, - "item_name":doc.lab_test_name, - "item_group": doc.lab_test_group, - "description":doc.lab_test_description, - "is_sales_item": 1, - "is_service_item": 1, - "is_purchase_item": 0, - "is_stock_item": 0, - "show_in_website": 0, - "is_pro_applicable": 0, - "disabled": disabled, - "stock_uom": "Unit" - }).insert(ignore_permissions=True) + "doctype": "Item", + "item_code": doc.lab_test_code, + "item_name":doc.lab_test_name, + "item_group": doc.lab_test_group, + "description":doc.lab_test_description, + "is_sales_item": 1, + "is_service_item": 1, + "is_purchase_item": 0, + "is_stock_item": 0, + "show_in_website": 0, + "is_pro_applicable": 0, + "disabled": disabled, + "stock_uom": uom + }).insert(ignore_permissions=True, ignore_mandatory=True) # insert item price # get item price list to insert item price From a9d59fad9475a99b039436226296bb180f0f10d2 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 21 Apr 2020 12:57:44 +0530 Subject: [PATCH 50/69] fix: on item change UOM not updated (#21242) * fix: on item change UOM not updated * removing uom from js --- erpnext/public/js/controllers/transaction.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 55c2c32659..3443abc734 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -499,7 +499,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ conversion_factor: item.conversion_factor, weight_per_unit: item.weight_per_unit, weight_uom: item.weight_uom, - uom : item.uom, manufacturer: item.manufacturer, stock_uom: item.stock_uom, pos_profile: me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '', From 34bbd5b612664b570a8bfae141709c121a91aed9 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 21 Apr 2020 13:00:14 +0530 Subject: [PATCH 51/69] fix: Purchase Invoice Form Cleanup (#21230) * fix: Purchase Invoice Form Cleanup * fix: Made Supplier field mandatory Co-authored-by: Nabin Hait --- .../purchase_invoice/purchase_invoice.json | 22 ++++----- .../purchase_invoice_item.json | 45 ++++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3af236cbf9..0e0945454c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -13,23 +13,18 @@ "supplier_name", "tax_id", "due_date", - "is_paid", - "is_return", - "apply_tds", "column_break1", "company", "posting_date", "posting_time", "set_posting_time", + "is_paid", + "is_return", + "apply_tds", "amended_from", "accounting_dimensions_section", "cost_center", "dimension_col_break", - "sb_14", - "on_hold", - "release_date", - "cb_17", - "hold_comment", "supplier_invoice_details", "bill_no", "column_break_15", @@ -137,10 +132,15 @@ "terms", "printing_settings", "letter_head", - "group_same_items", - "column_break_112", "select_print_heading", + "column_break_112", + "group_same_items", "language", + "sb_14", + "on_hold", + "release_date", + "cb_17", + "hold_comment", "more_info", "credit_to", "party_account_currency", @@ -190,6 +190,7 @@ "oldfieldtype": "Link", "options": "Supplier", "print_hide": 1, + "reqd": 1, "search_index": 1 }, { @@ -1232,6 +1233,7 @@ "print_hide": 1 }, { + "collapsible": 1, "fieldname": "subscription_section", "fieldtype": "Section Break", "label": "Subscription Section", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index db3f72ada0..c0a47d52c5 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -12,15 +12,11 @@ "item_name", "description_section", "description", - "item_group", "brand", - "image_section", + "col_break7", + "item_group", "image", "image_view", - "manufacture_details", - "manufacturer", - "column_break_13", - "manufacturer_part_no", "quantity_and_rate", "received_qty", "qty", @@ -55,20 +51,19 @@ "item_tax_amount", "landed_cost_voucher_amount", "rm_supp_cost", - "item_weight_details", - "weight_per_unit", - "total_weight", - "column_break_38", - "weight_uom", "warehouse_section", "warehouse", - "rejected_warehouse", "from_warehouse", "quality_inspection", - "batch_no", - "col_br_wh", "serial_no", + "col_br_wh", + "rejected_warehouse", + "batch_no", "rejected_serial_no", + "manufacture_details", + "manufacturer", + "column_break_13", + "manufacturer_part_no", "accounting", "expense_account", "col_break5", @@ -92,6 +87,11 @@ "po_detail", "purchase_receipt", "pr_detail", + "item_weight_details", + "weight_per_unit", + "total_weight", + "column_break_38", + "weight_uom", "accounting_dimensions_section", "project", "dimension_col_break", @@ -558,9 +558,10 @@ "print_hide": 1 }, { + "fetch_from": "item_code.item_group", + "fetch_if_empty": 1, "fieldname": "item_group", - "fieldtype": "Link", - "hidden": 1, + "fieldtype": "Data", "label": "Item Group", "oldfieldname": "item_group", "oldfieldtype": "Link", @@ -720,12 +721,6 @@ "fieldname": "section_break_82", "fieldtype": "Section Break" }, - { - "collapsible": 1, - "fieldname": "image_section", - "fieldtype": "Section Break", - "label": "Image" - }, { "collapsible": 1, "fieldname": "accounting_dimensions_section", @@ -737,6 +732,7 @@ "fieldtype": "Column Break" }, { + "collapsible": 1, "fieldname": "manufacture_details", "fieldtype": "Section Break", "label": "Manufacture" @@ -771,6 +767,11 @@ "ignore_user_permissions": 1, "label": "Supplier Warehouse", "options": "Warehouse" + }, + { + "collapsible": 1, + "fieldname": "col_break7", + "fieldtype": "Column Break" } ], "idx": 1, From 27c6f694359bf211bd2e457af6b2ef59d48ff5a9 Mon Sep 17 00:00:00 2001 From: Dharmraj-48 <60173537+Dharmraj-48@users.noreply.github.com> Date: Tue, 21 Apr 2020 13:10:09 +0530 Subject: [PATCH 52/69] fix : Create Pick List Fos Sales Order List (#20915) Co-authored-by: Dharmraj Co-authored-by: Nabin Hait --- .../doctype/sales_order/sales_order_list.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js index 26d96d59f2..288d8847ce 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_list.js +++ b/erpnext/selling/doctype/sales_order/sales_order_list.js @@ -47,6 +47,41 @@ frappe.listview_settings['Sales Order'] = { listview.page.add_menu_item(__("Re-open"), function() { listview.call_for_selected_items(method, {"status": "Submitted"}); }); + }, + onload: function(doclist) { + const action = () => { + const selected_docs = doclist.get_checked_items(); + const docnames = doclist.get_checked_items(true); + + if (selected_docs.length > 0) { + for (let doc of selected_docs) { + if (!doc.docstatus) { + frappe.throw(__("Cannot create a Pick List from Draft documents.")); + } + }; + + frappe.new_doc("Pick List") + .then(() => { + frappe.call({ + type: "POST", + method: "frappe.model.mapper.map_docs", + args: { + "method": "erpnext.selling.doctype.sales_order.sales_order.create_pick_list", + "source_names": docnames, + "target_doc": cur_frm.doc + }, + callback: function (r) { + if (!r.exc) { + frappe.model.sync(r.message); + cur_frm.dirty(); + cur_frm.refresh(); + } + } + }); + }) + }; + }; + doclist.page.add_actions_menu_item(__('Create Pick List'), action, false); } }; From 9942acd9d4cb983f57c738cad85c334aa0801216 Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 21 Apr 2020 13:13:39 +0530 Subject: [PATCH 53/69] fix [ux]: Material Request Form Cleanup and UX (#20810) * fix (ux): Material Request Form Cleanup * fix: Get Items from popup UX Co-authored-by: Nabin Hait --- erpnext/public/js/controllers/buying.js | 114 +++++++++--------- erpnext/regional/india/setup.py | 4 +- .../material_request/material_request.js | 92 ++++++++------ .../material_request/material_request.json | 24 +++- .../material_request_dashboard.py | 7 +- .../material_request_item.json | 40 +++--- 6 files changed, 161 insertions(+), 120 deletions(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index afbdbc661d..d5dc412e7d 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -444,75 +444,69 @@ erpnext.buying.get_items_from_product_bundle = function(frm) { "fieldname": "quantity", "reqd": 1, "default": 1 - }, - { - "fieldtype": "Button", - "label": __("Get Items"), - "fieldname": "get_items", - "cssClass": "btn-primary" } - ] - }); - - dialog.fields_dict.get_items.$input.click(function() { - var args = dialog.get_values(); - if(!args) return; - dialog.hide(); - return frappe.call({ - type: "GET", - method: "erpnext.stock.doctype.packed_item.packed_item.get_items_from_product_bundle", - args: { + ], + primary_action_label: 'Get Items', + primary_action(args){ + if(!args) return; + dialog.hide(); + return frappe.call({ + type: "GET", + method: "erpnext.stock.doctype.packed_item.packed_item.get_items_from_product_bundle", args: { - item_code: args.product_bundle, - quantity: args.quantity, - parenttype: frm.doc.doctype, - parent: frm.doc.name, - supplier: frm.doc.supplier, - currency: frm.doc.currency, - conversion_rate: frm.doc.conversion_rate, - price_list: frm.doc.buying_price_list, - price_list_currency: frm.doc.price_list_currency, - plc_conversion_rate: frm.doc.plc_conversion_rate, - company: frm.doc.company, - is_subcontracted: frm.doc.is_subcontracted, - transaction_date: frm.doc.transaction_date || frm.doc.posting_date, - ignore_pricing_rule: frm.doc.ignore_pricing_rule, - doctype: frm.doc.doctype - } - }, - freeze: true, - callback: function(r) { - const first_row_is_empty = function(child_table){ - if($.isArray(child_table) && child_table.length > 0) { - return !child_table[0].item_code; + args: { + item_code: args.product_bundle, + quantity: args.quantity, + parenttype: frm.doc.doctype, + parent: frm.doc.name, + supplier: frm.doc.supplier, + currency: frm.doc.currency, + conversion_rate: frm.doc.conversion_rate, + price_list: frm.doc.buying_price_list, + price_list_currency: frm.doc.price_list_currency, + plc_conversion_rate: frm.doc.plc_conversion_rate, + company: frm.doc.company, + is_subcontracted: frm.doc.is_subcontracted, + transaction_date: frm.doc.transaction_date || frm.doc.posting_date, + ignore_pricing_rule: frm.doc.ignore_pricing_rule, + doctype: frm.doc.doctype } - return false; - }; + }, + freeze: true, + callback: function(r) { + const first_row_is_empty = function(child_table){ + if($.isArray(child_table) && child_table.length > 0) { + return !child_table[0].item_code; + } + return false; + }; - const remove_empty_first_row = function(frm){ - if (first_row_is_empty(frm.doc.items)){ - frm.doc.items = frm.doc.items.splice(1); - } - }; + const remove_empty_first_row = function(frm){ + if (first_row_is_empty(frm.doc.items)){ + frm.doc.items = frm.doc.items.splice(1); + } + }; - if(!r.exc && r.message) { - remove_empty_first_row(frm); - for ( var i=0; i< r.message.length; i++ ) { - var d = frm.add_child("items"); - var item = r.message[i]; - for ( var key in item) { - if ( !is_null(item[key]) ) { - d[key] = item[key]; + if(!r.exc && r.message) { + remove_empty_first_row(frm); + for ( var i=0; i< r.message.length; i++ ) { + var d = frm.add_child("items"); + var item = r.message[i]; + for ( var key in item) { + if ( !is_null(item[key]) ) { + d[key] = item[key]; + } + } + if(frappe.meta.get_docfield(d.doctype, "price_list_rate", d.name)) { + frm.script_manager.trigger("price_list_rate", d.doctype, d.name); } } - if(frappe.meta.get_docfield(d.doctype, "price_list_rate", d.name)) { - frm.script_manager.trigger("price_list_rate", d.doctype, d.name); - } + frm.refresh_field("items"); } - frm.refresh_field("items"); } - } - }) + }) + } }); + dialog.show(); } diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index ae6f4ddf82..4be6804db5 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -85,7 +85,7 @@ def make_custom_fields(update=True): 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', + 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', @@ -388,7 +388,7 @@ def make_custom_fields(update=True): 'Item': [ dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Link', options='GST HSN Code', insert_after='item_group'), - dict(fieldname='is_nil_exempt', label='Is nil rated or exempted', + dict(fieldname='is_nil_exempt', label='Is Nil Rated or Exempted', fieldtype='Check', insert_after='gst_hsn_code'), dict(fieldname='is_non_gst', label='Is Non GST ', fieldtype='Check', insert_after='is_nil_exempt') diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 6110ea822c..b97da693b4 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -12,7 +12,8 @@ frappe.ui.form.on('Material Request', { 'Purchase Order': 'Purchase Order', 'Request for Quotation': 'Request for Quotation', 'Supplier Quotation': 'Supplier Quotation', - 'Work Order': 'Work Order' + 'Work Order': 'Work Order', + 'Purchase Receipt': 'Purchase Receipt' }; // formatter for material request item @@ -27,11 +28,20 @@ frappe.ui.form.on('Material Request', { // set schedule_date set_schedule_date(frm); - frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc) { + + let filters = {'company': frm.doc.company} + + frm.set_query("warehouse", "items", function() { return { - filters: {'company': doc.company} + filters: filters }; - }; + }); + + frm.set_query("set_warehouse", function(){ + return { + filters: filters + }; + }); }, onload_post_render: function(frm) { @@ -129,12 +139,13 @@ frappe.ui.form.on('Material Request', { source_doctype: "Sales Order", target: frm, setters: { - company: frm.doc.company + customer: frm.doc.customer || undefined }, get_query_filters: { docstatus: 1, status: ["not in", ["Closed", "On Hold"]], per_delivered: ["<", 99.99], + company: frm.doc.company } }); }, @@ -182,46 +193,46 @@ frappe.ui.form.on('Material Request', { options:"BOM", reqd: 1, get_query: function() { return {filters: { docstatus:1 }}; }}, - {"fieldname":"warehouse", "fieldtype":"Link", "label":__("Warehouse"), + {"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"), options:"Warehouse", reqd: 1}, {"fieldname":"qty", "fieldtype":"Float", "label":__("Quantity"), reqd: 1, "default": 1}, {"fieldname":"fetch_exploded", "fieldtype":"Check", - "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, - {fieldname:"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} - ] - }); - d.get_input("fetch").on("click", function() { - var values = d.get_values(); - if(!values) return; - values["company"] = frm.doc.company; - if(!frm.doc.company) frappe.throw(__("Company field is required")); - frappe.call({ - method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", - args: values, - callback: function(r) { - if (!r.message) { - frappe.throw(__("BOM does not contain any stock item")); - } else { - erpnext.utils.remove_empty_first_row(frm, "items"); - $.each(r.message, function(i, item) { - var d = frappe.model.add_child(cur_frm.doc, "Material Request Item", "items"); - d.item_code = item.item_code; - d.item_name = item.item_name; - d.description = item.description; - d.warehouse = values.warehouse; - d.uom = item.stock_uom; - d.stock_uom = item.stock_uom; - d.conversion_factor = 1; - d.qty = item.qty; - d.project = item.project; - }); + "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1} + ], + primary_action_label: 'Get Items', + primary_action(values) { + if(!values) return; + values["company"] = frm.doc.company; + if(!frm.doc.company) frappe.throw(__("Company field is required")); + frappe.call({ + method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", + args: values, + callback: function(r) { + if (!r.message) { + frappe.throw(__("BOM does not contain any stock item")); + } else { + erpnext.utils.remove_empty_first_row(frm, "items"); + $.each(r.message, function(i, item) { + var d = frappe.model.add_child(cur_frm.doc, "Material Request Item", "items"); + d.item_code = item.item_code; + d.item_name = item.item_name; + d.description = item.description; + d.warehouse = values.warehouse; + d.uom = item.stock_uom; + d.stock_uom = item.stock_uom; + d.conversion_factor = 1; + d.qty = item.qty; + d.project = item.project; + }); + } + d.hide(); + refresh_field("items"); } - d.hide(); - refresh_field("items"); - } - }); + }); + } }); + d.show(); }, @@ -248,7 +259,8 @@ frappe.ui.form.on('Material Request', { run_link_triggers: true }); }, - __('Enter Supplier') + __('Enter Supplier'), + __('Create') ) }, diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index d0025d12ab..536f5fa0ac 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -1,9 +1,11 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-03-07 14:48:38", "doctype": "DocType", "document_type": "Document", + "engine": "InnoDB", "field_order": [ "type_section", "naming_series", @@ -14,6 +16,8 @@ "schedule_date", "company", "amended_from", + "warehouse_section", + "set_warehouse", "items_section", "scan_barcode", "items", @@ -66,7 +70,7 @@ "fieldtype": "Select", "in_list_view": 1, "in_standard_filter": 1, - "label": "Type", + "label": "Purpose", "options": "Purchase\nMaterial Transfer\nMaterial Issue\nManufacture\nCustomer Provided", "reqd": 1 }, @@ -85,7 +89,7 @@ "allow_on_submit": 1, "fieldname": "schedule_date", "fieldtype": "Date", - "label": "Required Date" + "label": "Required By" }, { "fieldname": "company", @@ -190,6 +194,7 @@ "width": "100px" }, { + "depends_on": "eval:doc.docstatus==1", "fieldname": "per_ordered", "fieldtype": "Percent", "label": "% Ordered", @@ -200,6 +205,7 @@ "read_only": 1 }, { + "depends_on": "eval:doc.docstatus==1", "fieldname": "per_received", "fieldtype": "Percent", "label": "% Received", @@ -270,12 +276,24 @@ "options": "Job Card", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "warehouse_section", + "fieldtype": "Section Break" + }, + { + "description": "Sets 'For Warehouse' in each row of the Items table.", + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Set Warehouse", + "options": "Warehouse" } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, - "modified": "2019-04-29 11:45:07.570292", + "links": [], + "modified": "2020-03-02 20:21:09.990867", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py index cbd64784c6..0e4fb7a6dd 100644 --- a/erpnext/stock/doctype/material_request/material_request_dashboard.py +++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py @@ -8,7 +8,12 @@ def get_data(): 'transactions': [ { 'label': _('Related'), - 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', 'Stock Entry', 'Pick List'] + 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order'] + }, + { + 'label': _('Stock'), + 'items': ['Stock Entry', 'Purchase Receipt', 'Pick List'] + }, { 'label': _('Manufacturing'), 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 56049131bb..2bdc268c78 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -7,36 +7,38 @@ "engine": "InnoDB", "field_order": [ "item_code", - "col_break1", "item_name", + "col_break1", + "schedule_date", "section_break_4", "description", "item_group", "brand", "image_section", "image", - "manufacture_details", - "manufacturer", "column_break_12", "manufacturer_part_no", "quantity_and_warehouse", "qty", - "uom", - "conversion_factor", "stock_uom", "warehouse", "col_break2", - "schedule_date", - "rate", - "amount", + "uom", + "conversion_factor", "stock_qty", + "rate_and_amount_section_break", + "rate", + "col_break3", + "amount", + "manufacture_details", + "manufacturer", "more_info", "lead_time_date", "sales_order", "sales_order_item", "production_plan", "material_request_plan_item", - "col_break3", + "col_break4", "min_order_qty", "projected_qty", "actual_qty", @@ -175,7 +177,7 @@ "fieldname": "schedule_date", "fieldtype": "Date", "in_list_view": 1, - "label": "Required Date", + "label": "Required By", "oldfieldname": "schedule_date", "oldfieldtype": "Date", "print_width": "100px", @@ -185,14 +187,12 @@ { "fieldname": "rate", "fieldtype": "Currency", - "label": "Rate", - "no_copy": 1 + "label": "Rate" }, { "fieldname": "amount", "fieldtype": "Currency", "label": "Amount", - "no_copy": 1, "read_only": 1 }, { @@ -204,6 +204,7 @@ "read_only": 1 }, { + "collapsible": 1, "fieldname": "more_info", "fieldtype": "Section Break", "label": "More Information" @@ -331,6 +332,7 @@ "read_only": 1 }, { + "collapsible": 1, "fieldname": "accounting_details", "fieldtype": "Section Break", "label": "Accounting Details" @@ -389,6 +391,7 @@ "fieldtype": "Column Break" }, { + "collapsible": 1, "fieldname": "manufacture_details", "fieldtype": "Section Break", "label": "Manufacture" @@ -406,7 +409,16 @@ { "fieldname": "manufacturer_part_no", "fieldtype": "Data", - "label": "Manufacturer Part Number" + "label": "Manufacturer Part Number", + "read_only": 1 + }, + { + "fieldname": "rate_and_amount_section_break", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break4", + "fieldtype": "Column Break" } ], "idx": 1, From bda0ddf1cfe47a71794a1e13ec93c1875b8f125b Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 21 Apr 2020 13:15:05 +0530 Subject: [PATCH 54/69] fix: Purchase Receipt Form Cleanup (#21232) --- .../purchase_receipt_item_supplied.json | 52 ++++++++++++---- .../purchase_receipt/purchase_receipt.json | 36 ++++++----- .../purchase_receipt_item.json | 60 +++++++++---------- 3 files changed, 91 insertions(+), 57 deletions(-) diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json index 6f2fbe5c37..ea5863020a 100644 --- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json +++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json @@ -1,24 +1,31 @@ { + "actions": [], "creation": "2013-02-22 01:27:42", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "main_item_code", - "rm_item_code", "description", - "batch_no", - "serial_no", + "bom_detail_no", "col_break1", - "required_qty", - "consumed_qty", + "rm_item_code", "stock_uom", - "rate", - "amount", "conversion_factor", - "current_stock", "reference_name", - "bom_detail_no" + "secbreak_1", + "rate", + "col_break2", + "amount", + "secbreak_2", + "required_qty", + "col_break3", + "consumed_qty", + "current_stock", + "secbreak_3", + "batch_no", + "col_break4", + "serial_no" ], "fields": [ { @@ -152,11 +159,36 @@ "oldfieldname": "bom_detail_no", "oldfieldtype": "Data", "read_only": 1 + }, + { + "fieldname": "secbreak_1", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break2", + "fieldtype": "Column Break" + }, + { + "fieldname": "secbreak_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break3", + "fieldtype": "Column Break" + }, + { + "fieldname": "secbreak_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break4", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, - "modified": "2019-11-21 16:25:29.909112", + "links": [], + "modified": "2020-04-10 18:09:33.997618", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Receipt Item Supplied", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index eed5749b06..cbc6cc32b3 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -16,10 +16,10 @@ "supplier_name", "supplier_delivery_note", "column_break1", + "company", "posting_date", "posting_time", "set_posting_time", - "company", "is_return", "return_against", "section_addresses", @@ -49,11 +49,6 @@ "items_section", "scan_barcode", "items", - "pricing_rule_details", - "pricing_rules", - "get_current_stock", - "raw_material_details", - "supplied_items", "section_break0", "total_qty", "base_total", @@ -62,6 +57,12 @@ "total_net_weight", "total", "net_total", + "total_net_weight", + "pricing_rule_details", + "pricing_rules", + "raw_material_details", + "get_current_stock", + "supplied_items", "taxes_charges_section", "tax_category", "shipping_col", @@ -113,13 +114,13 @@ "auto_repeat", "printing_settings", "letter_head", - "select_print_heading", "language", - "group_same_items", - "column_break_97", - "other_details", "instructions", + "column_break_97", + "select_print_heading", + "other_details", "remarks", + "group_same_items", "transporter_info", "transporter_name", "column_break5", @@ -404,6 +405,7 @@ "fieldtype": "Section Break" }, { + "description": "Sets 'Accepted Warehouse' in each row of the items table.", "fieldname": "set_warehouse", "fieldtype": "Link", "label": "Accepted Warehouse", @@ -411,7 +413,7 @@ "print_hide": 1 }, { - "description": "Warehouse where you are maintaining stock of rejected items", + "description": "Sets 'Rejected Warehouse' in each row of the items table.", "fieldname": "rejected_warehouse", "fieldtype": "Link", "label": "Rejected Warehouse", @@ -429,7 +431,7 @@ "default": "No", "fieldname": "is_subcontracted", "fieldtype": "Select", - "label": "Raw Materials Supplied", + "label": "Raw Materials Consumed", "oldfieldname": "is_subcontracted", "oldfieldtype": "Select", "options": "No\nYes", @@ -465,6 +467,7 @@ "reqd": 1 }, { + "collapsible": 1, "fieldname": "pricing_rule_details", "fieldtype": "Section Break", "label": "Pricing Rules" @@ -477,9 +480,10 @@ "read_only": 1 }, { + "depends_on": "supplied_items", "fieldname": "get_current_stock", "fieldtype": "Button", - "label": "Get current stock", + "label": "Get Current Stock", "oldfieldtype": "Button", "options": "get_current_stock", "print_hide": 1 @@ -489,7 +493,7 @@ "collapsible_depends_on": "supplied_items", "fieldname": "raw_material_details", "fieldtype": "Section Break", - "label": "Raw Materials Supplied", + "label": "Raw Materials Consumed", "oldfieldtype": "Section Break", "options": "fa fa-table", "print_hide": 1, @@ -498,7 +502,7 @@ { "fieldname": "supplied_items", "fieldtype": "Table", - "label": "Supplied Items", + "label": "Consumed Items", "no_copy": 1, "oldfieldname": "pr_raw_material_details", "oldfieldtype": "Table", @@ -1082,7 +1086,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-04-17 13:06:26.970288", + "modified": "2020-04-18 18:02:18.020763", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index b15f23c303..bc6bce95d6 100644 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -15,15 +15,11 @@ "item_name", "section_break_4", "description", - "item_group", "brand", - "image_section", + "image_column", + "item_group", "image", "image_view", - "manufacture_details", - "manufacturer", - "column_break_16", - "manufacturer_part_no", "received_and_accepted", "received_qty", "qty", @@ -32,6 +28,7 @@ "uom", "stock_uom", "conversion_factor", + "stock_qty", "retain_sample", "sample_quantity", "rate_and_amount", @@ -60,11 +57,6 @@ "rm_supp_cost", "landed_cost_voucher_amount", "billed_amt", - "item_weight_details", - "weight_per_unit", - "total_weight", - "column_break_41", - "weight_uom", "warehouse_and_reference", "warehouse", "rejected_warehouse", @@ -77,20 +69,27 @@ "asset_category", "schedule_date", "quality_inspection", - "stock_qty", "purchase_order_item", "material_request_item", "section_break_45", "allow_zero_valuation_rate", "bom", - "col_break5", "serial_no", + "col_break5", + "include_exploded_items", "batch_no", - "column_break_48", "rejected_serial_no", "expense_account", - "include_exploded_items", "item_tax_rate", + "item_weight_details", + "weight_per_unit", + "total_weight", + "column_break_41", + "weight_uom", + "manufacture_details", + "manufacturer", + "column_break_16", + "manufacturer_part_no", "accounting_dimensions_section", "project", "dimension_col_break", @@ -526,7 +525,7 @@ { "fieldname": "stock_qty", "fieldtype": "Float", - "label": "Qty as per Stock UOM", + "label": "Accepted Qty as per Stock UOM", "oldfieldname": "stock_qty", "oldfieldtype": "Currency", "print_hide": 1, @@ -553,17 +552,13 @@ "fieldname": "batch_no", "fieldtype": "Link", "in_list_view": 1, - "label": "Batch No!", + "label": "Batch No", "no_copy": 1, "oldfieldname": "batch_no", "oldfieldtype": "Link", "options": "Batch", "print_hide": 1 }, - { - "fieldname": "column_break_48", - "fieldtype": "Column Break" - }, { "depends_on": "eval:!doc.is_fixed_asset", "fieldname": "rejected_serial_no", @@ -658,6 +653,7 @@ "read_only": 1 }, { + "fetch_from": "item_code.brand", "fieldname": "brand", "fieldtype": "Link", "hidden": 1, @@ -669,9 +665,9 @@ "read_only": 1 }, { + "fetch_from": "item_code.item_group", "fieldname": "item_group", "fieldtype": "Link", - "hidden": 1, "label": "Item Group", "oldfieldname": "item_group", "oldfieldtype": "Link", @@ -748,22 +744,19 @@ "fieldname": "section_break_80", "fieldtype": "Section Break" }, - { - "collapsible": 1, - "fieldname": "image_section", - "fieldtype": "Section Break", - "label": "Image" - }, { "fieldname": "material_request", "fieldtype": "Link", "label": "Material Request", - "options": "Material Request" + "options": "Material Request", + "read_only": 1 }, { "fieldname": "material_request_item", "fieldtype": "Data", - "label": "Material Request Item" + "hidden": 1, + "label": "Material Request Item", + "read_only": 1 }, { "fieldname": "expense_account", @@ -826,12 +819,17 @@ "ignore_user_permissions": 1, "label": "Supplier Warehouse", "options": "Warehouse" + }, + { + "collapsible": 1, + "fieldname": "image_column", + "fieldtype": "Column Break" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-07 18:38:21.141558", + "modified": "2020-04-10 19:01:21.154963", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", From a529a5078fafd00642b713b8ab57438bfbd1a97c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 21 Apr 2020 13:50:26 +0530 Subject: [PATCH 55/69] Update purchase_receipt.json --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index cbc6cc32b3..467a206d18 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -57,7 +57,6 @@ "total_net_weight", "total", "net_total", - "total_net_weight", "pricing_rule_details", "pricing_rules", "raw_material_details", @@ -1153,4 +1152,4 @@ "timeline_field": "supplier", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} From 5f7785444877ae26884115ce97cea0cad2273fb2 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 21 Apr 2020 14:28:08 +0530 Subject: [PATCH 56/69] Revert "fix : Create Pick List Fos Sales Order List (#20915)" (#21352) This reverts commit 27c6f694359bf211bd2e457af6b2ef59d48ff5a9. --- .../doctype/sales_order/sales_order_list.js | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js index 288d8847ce..26d96d59f2 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_list.js +++ b/erpnext/selling/doctype/sales_order/sales_order_list.js @@ -47,41 +47,6 @@ frappe.listview_settings['Sales Order'] = { listview.page.add_menu_item(__("Re-open"), function() { listview.call_for_selected_items(method, {"status": "Submitted"}); }); - }, - onload: function(doclist) { - const action = () => { - const selected_docs = doclist.get_checked_items(); - const docnames = doclist.get_checked_items(true); - - if (selected_docs.length > 0) { - for (let doc of selected_docs) { - if (!doc.docstatus) { - frappe.throw(__("Cannot create a Pick List from Draft documents.")); - } - }; - - frappe.new_doc("Pick List") - .then(() => { - frappe.call({ - type: "POST", - method: "frappe.model.mapper.map_docs", - args: { - "method": "erpnext.selling.doctype.sales_order.sales_order.create_pick_list", - "source_names": docnames, - "target_doc": cur_frm.doc - }, - callback: function (r) { - if (!r.exc) { - frappe.model.sync(r.message); - cur_frm.dirty(); - cur_frm.refresh(); - } - } - }); - }) - }; - }; - doclist.page.add_actions_menu_item(__('Create Pick List'), action, false); } }; From 690855d3a19cc4cb045e778a1fc9d195ad626bc1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 22 Apr 2020 02:45:42 +0530 Subject: [PATCH 57/69] fix: unsupported operand type issue in pricing rule --- erpnext/accounts/doctype/pricing_rule/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 100bb1d3e3..b358f56671 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -330,9 +330,9 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args): if pr_doc.mixed_conditions: amt = args.get('qty') * args.get("price_list_rate") if args.get("item_code") != row.get("item_code"): - amt = row.get('qty') * (row.get("price_list_rate") or args.get("rate")) + amt = flt(row.get('qty')) * flt(row.get("price_list_rate") or args.get("rate")) - sum_qty += row.get("stock_qty") or args.get("stock_qty") or args.get("qty") + sum_qty += flt(row.get("stock_qty")) or flt(args.get("stock_qty")) or flt(args.get("qty")) sum_amt += amt if pr_doc.is_cumulative: From 8fb863da07af21711d21dcc2d726895762b9a38d Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Wed, 22 Apr 2020 11:23:14 +0530 Subject: [PATCH 58/69] chore: remove oldfieldtype from purchase invoice item (#21365) fixes issue where framework expects data field type to be validated with options either set to "Email" or "Phone". removing oldfieldtype works for now, should be fixed inside framework Signed-off-by: Chinmay D. Pai --- .../purchase_invoice_item/purchase_invoice_item.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index c0a47d52c5..b4d1a73f97 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -563,9 +563,6 @@ "fieldname": "item_group", "fieldtype": "Data", "label": "Item Group", - "oldfieldname": "item_group", - "oldfieldtype": "Link", - "options": "Item Group", "print_hide": 1, "read_only": 1 }, @@ -777,7 +774,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-07 18:34:35.104178", + "modified": "2020-04-22 10:37:35.103176", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -785,4 +782,4 @@ "permissions": [], "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} From 471cf95300093c36f41563ebaac1c10131ca6528 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 22 Apr 2020 11:46:31 +0530 Subject: [PATCH 59/69] Update purchase_invoice_item.json --- .../purchase_invoice_item/purchase_invoice_item.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index b4d1a73f97..52a5be0984 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -550,21 +550,21 @@ }, { "fieldname": "brand", - "fieldtype": "Data", + "fieldtype": "Link", "hidden": 1, "label": "Brand", - "oldfieldname": "brand", - "oldfieldtype": "Data", - "print_hide": 1 + "print_hide": 1, + "options": "Brand" }, { "fetch_from": "item_code.item_group", "fetch_if_empty": 1, "fieldname": "item_group", - "fieldtype": "Data", + "fieldtype": "Link", "label": "Item Group", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "options": "Item Group" }, { "description": "Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges", From 9c494d3e72d95c6b95f379a0d00545a225f35c33 Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 22 Apr 2020 11:48:19 +0530 Subject: [PATCH 60/69] fix: asset maintenance fixes (#21277) * fix: asset maintenance fixes * fix: tests --- .../asset_maintenance/asset_maintenance.js | 39 ------------------- .../asset_maintenance/asset_maintenance.py | 7 ++-- .../test_asset_maintenance.py | 6 ++- 3 files changed, 7 insertions(+), 45 deletions(-) diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js index 3c135d466c..001fc26ffe 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js @@ -24,26 +24,6 @@ frappe.ui.form.on('Asset Maintenance', { return indicator; } ); - - frm.set_query('select_serial_no', function(doc){ - return { - asset: frm.doc.asset_name - } - }) - }, - - select_serial_no: (frm) => { - let serial_nos = frm.doc.serial_no || frm.doc.select_serial_no; - if (serial_nos) { - serial_nos = serial_nos.split('\n'); - serial_nos.push(frm.doc.select_serial_no); - - const unique_sn = serial_nos.filter(function(elem, index, self) { - return index === self.indexOf(elem); - }); - - frm.set_value("serial_no", unique_sn.join('\n')); - } }, refresh: (frm) => { @@ -93,25 +73,6 @@ frappe.ui.form.on('Asset Maintenance Task', { }, end_date: (frm, cdt, cdn) => { get_next_due_date(frm, cdt, cdn); - }, - assign_to: (frm, cdt, cdn) => { - var d = locals[cdt][cdn]; - if (frm.doc.__islocal) { - frappe.model.set_value(cdt, cdn, "assign_to", ""); - frappe.model.set_value(cdt, cdn, "assign_to_name", ""); - frappe.throw(__("Please save before assigning task.")); - } - if (d.assign_to) { - return frappe.call({ - method: 'erpnext.assets.doctype.asset_maintenance.asset_maintenance.assign_tasks', - args: { - asset_maintenance_name: frm.doc.name, - assign_to_member: d.assign_to, - maintenance_task: d.maintenance_task, - next_due_date: d.next_due_date - } - }); - } } }); diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index ecb55dde9a..3f046113a0 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -16,12 +16,11 @@ class AssetMaintenance(Document): throw(_("Start date should be less than end date for task {0}").format(task.maintenance_task)) if getdate(task.next_due_date) < getdate(nowdate()): task.maintenance_status = "Overdue" + if not task.assign_to and self.docstatus == 0: + throw(_("Row #{}: Please asign task to a member.").format(task.idx)) def on_update(self): for task in self.get('asset_maintenance_tasks'): - if not task.assign_to: - task.db_set("assign_to", self.maintenance_manager) - task.db_set("assign_to_name", self.maintenance_manager_name) assign_tasks(self.name, task.assign_to, task.maintenance_task, task.next_due_date) self.sync_maintenance_tasks() @@ -108,7 +107,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task): @frappe.whitelist() def get_team_members(doctype, txt, searchfield, start, page_len, filters): - return frappe.db.get_values('Maintenance Team Member', {'parent':filters.get("maintenance_team")}) + return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") }) @frappe.whitelist() def get_maintenance_log(asset_name): diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py index 6c2fd67a9a..392fbdd2af 100644 --- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py @@ -125,13 +125,15 @@ def get_maintenance_tasks(): "start_date": nowdate(), "periodicity": "Monthly", "maintenance_type": "Preventive Maintenance", - "maintenance_status": "Planned" + "maintenance_status": "Planned", + "assign_to": "marcus@abc.com" }, {"maintenance_task": "Check Gears", "start_date": nowdate(), "periodicity": "Yearly", "maintenance_type": "Calibration", - "maintenance_status": "Planned" + "maintenance_status": "Planned", + "assign_to": "thalia@abc.com" } ] From 55baccd63aeefafc54120e7bcbfe01730b36176f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 22 Apr 2020 11:50:06 +0530 Subject: [PATCH 61/69] fix: specify column width --- .../customer_acquisition_and_loyalty.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index 28dd056407..aa57665a81 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -53,10 +53,11 @@ def execute(filters=None): new[1], repeat[1], new[1] + repeat[1]]) return [ - _("Year"), _("Month"), - _("New Customers") + ":Int", - _("Repeat Customers") + ":Int", - _("Total") + ":Int", + _("Year") + "::100", + _("Month") + "::100", + _("New Customers") + ":Int:100", + _("Repeat Customers") + ":Int:100", + _("Total") + ":Int:100", _("New Customer Revenue") + ":Currency:150", _("Repeat Customer Revenue") + ":Currency:150", _("Total Revenue") + ":Currency:150" From eaa956b994bff442d48469ec99459ad43aa5b7a1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 22 Apr 2020 13:07:12 +0530 Subject: [PATCH 62/69] feat(Healthcare): Rehabilitation Module (#21216) * feat: added rehab sub-module doctypes * feat: rehab module * feat: prescribe procedures in Patient Encounter * feat: create Therapy Plan on Encounter submission * feat: manage item for Therapy Type * feat: book appointments, get prescribed therapies for Therapy Sessions * feat: invoice Therapy Sessions * feat: plan completion progress bar and exercise countsindicators * feat: Motor Assessment Scale * feat: add editable card view for exercise steps * fix: add rehab in healthcare desk page * fix: card deletion not working when child table is hidden * feat: automatically fetch therapies according to Body Part * fix: added tests for Therapy Type and Plan * fix: add exercises according to body parts in Therapy Type * fix: label for Exercise Instructions * fix: exercise cards css * feat: add dashboard for Therapy Plan * feat: Patient Assessment Template and Patient Assessment * feat: add title fields in Therapy Plan and Session * fix: remove Motor Assessment Scale * fix: fetch assessment description * feat: create Patient Assessment from Therapy Session * fix: anti pattern code * fix: update desk page * fix: exercise card rendering * fix(test): filter out disabled Items in test_mapper * fix: get stock uom from Stock Settings for Therapy Type Item creation * fix: multiline SQL query * fix: permissions for DocTypes Co-authored-by: Nabin Hait --- erpnext/config/healthcare.py | 36 +++ erpnext/controllers/tests/test_mapper.py | 2 +- .../desk_page/healthcare/healthcare.json | 67 +++--- .../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 | 10 + .../doctype/body_part/test_body_part.py | 10 + .../doctype/body_part_link/__init__.py | 0 .../body_part_link/body_part_link.json | 32 +++ .../doctype/body_part_link/body_part_link.py | 10 + .../healthcare/doctype/exercise/__init__.py | 0 .../healthcare/doctype/exercise/exercise.json | 61 +++++ .../healthcare/doctype/exercise/exercise.py | 10 + .../exercise_difficulty_level/__init__.py | 0 .../exercise_difficulty_level.js | 8 + .../exercise_difficulty_level.json | 45 ++++ .../exercise_difficulty_level.py | 10 + .../test_exercise_difficulty_level.py | 10 + .../doctype/exercise_type/__init__.py | 0 .../doctype/exercise_type/exercise_type.js | 180 +++++++++++++++ .../doctype/exercise_type/exercise_type.json | 144 ++++++++++++ .../doctype/exercise_type/exercise_type.py | 15 ++ .../exercise_type/test_exercise_type.py | 10 + .../doctype/exercise_type_step/__init__.py | 0 .../exercise_type_step.json | 44 ++++ .../exercise_type_step/exercise_type_step.py | 10 + .../patient_appointment.js | 81 +++++++ .../patient_appointment.json | 27 ++- .../patient_appointment.py | 35 ++- .../doctype/patient_assessment/__init__.py | 0 .../patient_assessment/patient_assessment.js | 86 +++++++ .../patient_assessment.json | 172 ++++++++++++++ .../patient_assessment/patient_assessment.py | 36 +++ .../test_patient_assessment.py | 10 + .../patient_assessment_detail/__init__.py | 0 .../patient_assessment_detail.json | 32 +++ .../patient_assessment_detail.py | 10 + .../patient_assessment_parameter/__init__.py | 0 .../patient_assessment_parameter.js | 8 + .../patient_assessment_parameter.json | 45 ++++ .../patient_assessment_parameter.py | 10 + .../test_patient_assessment_parameter.py | 10 + .../patient_assessment_sheet/__init__.py | 0 .../patient_assessment_sheet.json | 57 +++++ .../patient_assessment_sheet.py | 10 + .../patient_assessment_template/__init__.py | 0 .../patient_assessment_template.js | 8 + .../patient_assessment_template.json | 109 +++++++++ .../patient_assessment_template.py | 10 + .../test_patient_assessment_template.py | 10 + .../patient_encounter/patient_encounter.js | 4 + .../patient_encounter/patient_encounter.json | 29 ++- .../patient_encounter/patient_encounter.py | 19 ++ .../doctype/therapy_plan/__init__.py | 0 .../doctype/therapy_plan/test_therapy_plan.py | 57 +++++ .../doctype/therapy_plan/therapy_plan.js | 90 ++++++++ .../doctype/therapy_plan/therapy_plan.json | 151 ++++++++++++ .../doctype/therapy_plan/therapy_plan.py | 42 ++++ .../therapy_plan/therapy_plan_dashboard.py | 13 ++ .../doctype/therapy_plan/therapy_plan_list.js | 11 + .../doctype/therapy_plan_detail/__init__.py | 0 .../therapy_plan_detail.json | 48 ++++ .../therapy_plan_detail.py | 10 + .../doctype/therapy_session/__init__.py | 0 .../therapy_session/test_therapy_session.py | 10 + .../therapy_session/therapy_session.js | 60 +++++ .../therapy_session/therapy_session.json | 218 ++++++++++++++++++ .../therapy_session/therapy_session.py | 55 +++++ .../therapy_session_dashboard.py | 13 ++ .../doctype/therapy_type/__init__.py | 0 .../doctype/therapy_type/test_therapy_type.py | 50 ++++ .../doctype/therapy_type/therapy_type.js | 93 ++++++++ .../doctype/therapy_type/therapy_type.json | 211 +++++++++++++++++ .../doctype/therapy_type/therapy_type.py | 122 ++++++++++ erpnext/healthcare/utils.py | 22 +- .../production_plan/production_plan.js | 2 +- erpnext/public/build.json | 48 ++-- erpnext/public/css/erpnext.css | 36 +++ erpnext/public/less/erpnext.less | 46 ++++ 80 files changed, 2928 insertions(+), 65 deletions(-) create mode 100644 erpnext/healthcare/doctype/body_part/__init__.py create mode 100644 erpnext/healthcare/doctype/body_part/body_part.js create mode 100644 erpnext/healthcare/doctype/body_part/body_part.json create mode 100644 erpnext/healthcare/doctype/body_part/body_part.py create mode 100644 erpnext/healthcare/doctype/body_part/test_body_part.py create mode 100644 erpnext/healthcare/doctype/body_part_link/__init__.py create mode 100644 erpnext/healthcare/doctype/body_part_link/body_part_link.json create mode 100644 erpnext/healthcare/doctype/body_part_link/body_part_link.py create mode 100644 erpnext/healthcare/doctype/exercise/__init__.py create mode 100644 erpnext/healthcare/doctype/exercise/exercise.json create mode 100644 erpnext/healthcare/doctype/exercise/exercise.py create mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py create mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js create mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json create mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py create mode 100644 erpnext/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py create mode 100644 erpnext/healthcare/doctype/exercise_type/__init__.py create mode 100644 erpnext/healthcare/doctype/exercise_type/exercise_type.js create mode 100644 erpnext/healthcare/doctype/exercise_type/exercise_type.json create mode 100644 erpnext/healthcare/doctype/exercise_type/exercise_type.py create mode 100644 erpnext/healthcare/doctype/exercise_type/test_exercise_type.py create mode 100644 erpnext/healthcare/doctype/exercise_type_step/__init__.py create mode 100644 erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json create mode 100644 erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.py create mode 100644 erpnext/healthcare/doctype/patient_assessment/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_assessment/patient_assessment.js create mode 100644 erpnext/healthcare/doctype/patient_assessment/patient_assessment.json create mode 100644 erpnext/healthcare/doctype/patient_assessment/patient_assessment.py create mode 100644 erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_detail/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json create mode 100644 erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js create mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json create mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_sheet/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json create mode 100644 erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_template/__init__.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js create mode 100644 erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json create mode 100644 erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.py create mode 100644 erpnext/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py create mode 100644 erpnext/healthcare/doctype/therapy_plan/__init__.py create mode 100644 erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py create mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan.js create mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan.json create mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan.py create mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py create mode 100644 erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js create mode 100644 erpnext/healthcare/doctype/therapy_plan_detail/__init__.py create mode 100644 erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json create mode 100644 erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py create mode 100644 erpnext/healthcare/doctype/therapy_session/__init__.py create mode 100644 erpnext/healthcare/doctype/therapy_session/test_therapy_session.py create mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session.js create mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session.json create mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session.py create mode 100644 erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py create mode 100644 erpnext/healthcare/doctype/therapy_type/__init__.py create mode 100644 erpnext/healthcare/doctype/therapy_type/test_therapy_type.py create mode 100644 erpnext/healthcare/doctype/therapy_type/therapy_type.js create mode 100644 erpnext/healthcare/doctype/therapy_type/therapy_type.json create mode 100644 erpnext/healthcare/doctype/therapy_type/therapy_type.py diff --git a/erpnext/config/healthcare.py b/erpnext/config/healthcare.py index 2b461273ad..da24d11538 100644 --- a/erpnext/config/healthcare.py +++ b/erpnext/config/healthcare.py @@ -214,5 +214,41 @@ def get_data(): "label": _("Lab Test Report") } ] + }, + { + "label": _("Rehabilitation"), + "icon": "icon-cog", + "items": [ + { + "type": "doctype", + "name": "Exercise Type", + "label": _("Exercise Type") + }, + { + "type": "doctype", + "name": "Exercise Difficulty Level", + "label": _("Exercise Difficulty Level") + }, + { + "type": "doctype", + "name": "Therapy Type", + "label": _("Therapy Type") + }, + { + "type": "doctype", + "name": "Therapy Plan", + "label": _("Therapy Plan") + }, + { + "type": "doctype", + "name": "Therapy Session", + "label": _("Therapy Session") + }, + { + "type": "doctype", + "name": "Motor Assessment Scale", + "label": _("Motor Assessment Scale") + } + ] } ] diff --git a/erpnext/controllers/tests/test_mapper.py b/erpnext/controllers/tests/test_mapper.py index d02308d8f2..8839e002a4 100644 --- a/erpnext/controllers/tests/test_mapper.py +++ b/erpnext/controllers/tests/test_mapper.py @@ -13,7 +13,7 @@ class TestMapper(unittest.TestCase): '''Test mapping of multiple source docs on a single target doc''' make_test_records("Item") - items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0}) + items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0, 'disabled': 0}) customers = frappe.get_all("Customer") if items and customers: # Make source docs (quotations) and a target doc (sales order) diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json index 54798ba08f..24c6d6fc37 100644 --- a/erpnext/healthcare/desk_page/healthcare/healthcare.json +++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json @@ -1,48 +1,53 @@ { "cards": [ { - "icon": "", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]", - "title": "Masters" + "hidden": 0, + "label": "Masters", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient\",\n\t\t\"label\": \"Patient\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Practitioner\",\n\t\t\"label\":\"Healthcare Practitioner\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Practitioner Schedule\",\n\t\t\"label\": \"Practitioner Schedule\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Department\",\n\t\t\"label\": \"Medical Department\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit Type\",\n\t\t\"label\": \"Healthcare Service Unit Type\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Service Unit\",\n\t\t\"label\": \"Healthcare Service Unit\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code Standard\",\n\t\t\"label\": \"Medical Code Standard\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Medical Code\",\n\t\t\"label\": \"Medical Code\"\n\t}\n]" }, { - "icon": "", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]", - "title": "Consultation Setup" + "hidden": 0, + "label": "Consultation Setup", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Appointment Type\",\n\t\t\"label\": \"Appointment Type\"\n },\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure Template\",\n\t\t\"label\": \"Clinical Procedure Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Dosage\",\n\t\t\"label\": \"Prescription Dosage\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Prescription Duration\",\n\t\t\"label\": \"Prescription Duration\"\n\t},\n\t{\n\t \"type\": \"doctype\",\n\t\t\"name\": \"Antibiotic\",\n\t\t\"label\": \"Antibiotic\"\n\t}\n]" }, { - "icon": "icon-star", - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]", - "title": "Consultation" + "hidden": 0, + "label": "Consultation", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Appointment\",\n\t\t\"label\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Clinical Procedure\",\n\t\t\"label\": \"Clinical Procedure\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Encounter\",\n\t\t\"label\": \"Patient Encounter\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Vital Signs\",\n\t\t\"label\": \"Vital Signs\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Complaint\",\n\t\t\"label\": \"Complaint\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Diagnosis\",\n\t\t\"label\": \"Diagnosis\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Fee Validity\",\n\t\t\"label\": \"Fee Validity\"\n\t}\n]" }, { - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]", - "title": "Settings" + "hidden": 0, + "label": "Settings", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Healthcare Settings\",\n\t\t\"label\": \"Healthcare Settings\",\n\t\t\"onboard\": 1\n\t}\n]" }, { - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]", - "title": "Laboratory Setup" + "hidden": 0, + "label": "Laboratory Setup", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Template\",\n\t\t\"label\": \"Lab Test Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test Sample\",\n\t\t\"label\": \"Lab Test Sample\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test UOM\",\n\t\t\"label\": \"Lab Test UOM\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sensitivity\",\n\t\t\"label\": \"Sensitivity\"\n\t}\n]" }, { - "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]", - "title": "Laboratory" + "hidden": 0, + "label": "Laboratory", + "links": "[\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Lab Test\",\n\t\t\"label\": \"Lab Test\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Sample Collection\",\n\t\t\"label\": \"Sample Collection\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Dosage Form\",\n\t\t\"label\": \"Dosage Form\"\n\t}\n]" }, { - "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]", - "title": "Records and History" + "hidden": 0, + "label": "Rehabilitation and Physiotherapy", + "links": "[\n {\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Exercise Type\",\n\t\t\"label\": \"Exercise Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Type\",\n\t\t\"label\": \"Therapy Type\",\n\t\t\"onboard\": 1\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Plan\",\n\t\t\"label\": \"Therapy Plan\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Therapy Session\",\n\t\t\"label\": \"Therapy Session\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment Template\",\n\t\t\"label\": \"Patient Assessment Template\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Assessment\",\n\t\t\"label\": \"Patient Assessment\"\n\t}\n]" }, { - "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]", - "title": "Reports" + "hidden": 0, + "label": "Records and History", + "links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]" + }, + { + "hidden": 0, + "label": "Reports", + "links": "[\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Patient Appointment Analytics\",\n\t\t\"doctype\": \"Patient Appointment\"\n\t},\n\t{\n\t\t\"type\": \"report\",\n\t\t\"is_query_report\": true,\n\t\t\"name\": \"Lab Test Report\",\n\t\t\"doctype\": \"Lab Test\",\n\t\t\"label\": \"Lab Test Report\"\n\t}\n]" } ], "category": "Domains", - "charts": [ - { - "chart_name": "Patient Appointments", - "label": "Patient Appointments" - } - ], + "charts": [], "charts_label": "", "creation": "2020-03-02 17:23:17.919682", "developer_mode_only": 0, @@ -53,7 +58,7 @@ "idx": 0, "is_standard": 1, "label": "Healthcare", - "modified": "2020-03-26 16:10:44.629795", + "modified": "2020-04-20 11:42:43.889576", "modified_by": "Administrator", "module": "Healthcare", "name": "Healthcare", @@ -64,32 +69,32 @@ "shortcuts": [ { "format": "{} Open", - "is_query_report": 0, + "label": "Patient Appointment", "link_to": "Patient Appointment", "stats_filter": "{\n \"status\": \"Open\"\n}", "type": "DocType" }, { "format": "{} Active", - "is_query_report": 0, + "label": "Patient", "link_to": "Patient", "stats_filter": "{\n \"status\": \"Active\"\n}", "type": "DocType" }, { "format": "{} Vacant", - "is_query_report": 0, + "label": "Healthcare Service Unit", "link_to": "Healthcare Service Unit", "stats_filter": "{\n \"occupancy_status\": \"Vacant\",\n \"is_group\": 0\n}", "type": "DocType" }, { - "is_query_report": 0, + "label": "Healthcare Practitioner", "link_to": "Healthcare Practitioner", "type": "DocType" }, { - "is_query_report": 0, + "label": "Patient History", "link_to": "patient_history", "type": "Page" } diff --git a/erpnext/healthcare/doctype/body_part/__init__.py b/erpnext/healthcare/doctype/body_part/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/body_part/body_part.js b/erpnext/healthcare/doctype/body_part/body_part.js new file mode 100644 index 0000000000..d2f9d09937 --- /dev/null +++ b/erpnext/healthcare/doctype/body_part/body_part.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Body Part', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/healthcare/doctype/body_part/body_part.json b/erpnext/healthcare/doctype/body_part/body_part.json new file mode 100644 index 0000000000..6e3d1d4ce3 --- /dev/null +++ b/erpnext/healthcare/doctype/body_part/body_part.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "autoname": "field:body_part", + "creation": "2020-04-10 12:21:55.036402", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "body_part" + ], + "fields": [ + { + "fieldname": "body_part", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Body Part", + "reqd": 1, + "unique": 1 + } + ], + "links": [], + "modified": "2020-04-10 12:26:44.087985", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Body Part", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..300493a52b --- /dev/null +++ b/erpnext/healthcare/doctype/body_part/body_part.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class BodyPart(Document): + pass diff --git a/erpnext/healthcare/doctype/body_part/test_body_part.py b/erpnext/healthcare/doctype/body_part/test_body_part.py new file mode 100644 index 0000000000..cb3a61150e --- /dev/null +++ b/erpnext/healthcare/doctype/body_part/test_body_part.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestBodyPart(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/body_part_link/__init__.py b/erpnext/healthcare/doctype/body_part_link/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/body_part_link/body_part_link.json b/erpnext/healthcare/doctype/body_part_link/body_part_link.json new file mode 100644 index 0000000000..400b7c6fe8 --- /dev/null +++ b/erpnext/healthcare/doctype/body_part_link/body_part_link.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-04-10 12:23:15.259816", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "body_part" + ], + "fields": [ + { + "fieldname": "body_part", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Body Part", + "options": "Body Part", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-10 12:25:23.101749", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Body Part Link", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..0371529769 --- /dev/null +++ b/erpnext/healthcare/doctype/body_part_link/body_part_link.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class BodyPartLink(Document): + pass diff --git a/erpnext/healthcare/doctype/exercise/__init__.py b/erpnext/healthcare/doctype/exercise/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/exercise/exercise.json b/erpnext/healthcare/doctype/exercise/exercise.json new file mode 100644 index 0000000000..2486a5d53a --- /dev/null +++ b/erpnext/healthcare/doctype/exercise/exercise.json @@ -0,0 +1,61 @@ +{ + "actions": [], + "creation": "2020-03-11 09:25:00.968572", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "exercise_type", + "difficulty_level", + "counts_target", + "counts_completed", + "assistance_level" + ], + "fields": [ + { + "fieldname": "exercise_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Exercise Type", + "options": "Exercise Type", + "reqd": 1 + }, + { + "fetch_from": "exercise_type.difficulty_level", + "fieldname": "difficulty_level", + "fieldtype": "Link", + "label": "Difficulty Level", + "options": "Exercise Difficulty Level" + }, + { + "fieldname": "counts_target", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Counts Target" + }, + { + "depends_on": "eval:doc.parenttype==\"Therapy\";", + "fieldname": "counts_completed", + "fieldtype": "Int", + "label": "Counts Completed" + }, + { + "fieldname": "assistance_level", + "fieldtype": "Select", + "label": "Assistance Level", + "options": "\nPassive\nActive Assist\nActive" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-10 13:41:06.662351", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/healthcare/doctype/exercise/exercise.py b/erpnext/healthcare/doctype/exercise/exercise.py new file mode 100644 index 0000000000..efd89997fe --- /dev/null +++ b/erpnext/healthcare/doctype/exercise/exercise.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class Exercise(Document): + pass diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py b/erpnext/healthcare/doctype/exercise_difficulty_level/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js new file mode 100644 index 0000000000..ff51c34f3f --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Exercise Difficulty Level', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json new file mode 100644 index 0000000000..a6aed75e7a --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "autoname": "field:difficulty_level", + "creation": "2020-03-29 21:12:55.835941", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "difficulty_level" + ], + "fields": [ + { + "fieldname": "difficulty_level", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Difficulty Level", + "reqd": 1, + "unique": 1 + } + ], + "links": [], + "modified": "2020-03-31 23:14:33.554066", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise Difficulty Level", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..17e97b8960 --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_difficulty_level/exercise_difficulty_level.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ExerciseDifficultyLevel(Document): + pass 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 new file mode 100644 index 0000000000..80ef3a7de8 --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_difficulty_level/test_exercise_difficulty_level.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestExerciseDifficultyLevel(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/exercise_type/__init__.py b/erpnext/healthcare/doctype/exercise_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.js b/erpnext/healthcare/doctype/exercise_type/exercise_type.js new file mode 100644 index 0000000000..f450c9bccb --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.js @@ -0,0 +1,180 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Exercise Type', { + refresh: function(frm) { + let wrapper = frm.fields_dict.steps_html.wrapper; + + frm.ExerciseEditor = new erpnext.ExerciseEditor(frm, wrapper); + } +}); + +erpnext.ExerciseEditor = Class.extend({ + init: function(frm, wrapper) { + this.wrapper = wrapper; + this.frm = frm; + this.make(frm, wrapper); + }, + + make: function(frm, wrapper) { + $(this.wrapper).empty(); + + this.exercise_toolbar = $('

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

').appendTo(this.wrapper); + + let me = this; + + this.exercise_toolbar.find(".btn-add") + .html(__('Add')) + .on("click", function() { + me.show_add_card_dialog(frm); + }); + + if (frm.doc.steps_table.length > 0) { + this.make_cards(frm); + this.make_buttons(frm); + } + }, + + make_cards: function(frm) { + var me = this; + $(me.exercise_cards).empty(); + this.row = $('
').appendTo(me.exercise_cards); + + $.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: function(frm) { + let me = this; + $('.btn-edit').on('click', function() { + let id = $(this).attr('data-id'); + me.show_edit_card_dialog(frm, id); + }); + + $('.btn-del').on('click', function() { + let id = $(this).attr('data-id'); + $('#card-'+id).addClass("zoomOutDelete"); + + setTimeout(() => { + // not using grid_rows[id].remove because + // grid_rows is not defined when the table is hidden + frm.doc.steps_table.pop(id); + frm.refresh_field('steps_table'); + $('#col-'+id).remove(); + }, 300); + }); + }, + + show_add_card_dialog: function(frm) { + let me = this; + let d = new frappe.ui.Dialog({ + title: __('Add Exercise Step'), + fields: [ + { + "label": "Title", + "fieldname": "title", + "fieldtype": "Data", + "reqd": 1 + }, + { + "label": "Attach Image", + "fieldname": "image", + "fieldtype": "Attach Image" + }, + { + "label": "Step Description", + "fieldname": "step_description", + "fieldtype": "Long Text" + } + ], + primary_action: function() { + let data = d.get_values(); + let i = frm.doc.steps_table.length; + $(repl(` +
+
+
+ ... +

%(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: function(frm, id) { + let new_dialog = new frappe.ui.Dialog({ + title: __("Edit Exercise Step"), + fields: [ + { + "label": "Title", + "fieldname": "title", + "fieldtype": "Data", + "reqd": 1 + }, + { + "label": "Attach Image", + "fieldname": "image", + "fieldtype": "Attach Image" + }, + { + "label": "Step Description", + "fieldname": "step_description", + "fieldtype": "Long Text" + } + ], + primary_action: () => { + let data = new_dialog.get_values(); + $('#card-'+id).find('.card-title').html(data.title); + $('#card-'+id).find('img').attr('src', data.image); + $('#card-'+id).find('.card-text').html(data.step_description); + + frm.doc.steps_table[id].title = data.title; + frm.doc.steps_table[id].image = data.image; + frm.doc.steps_table[id].description = data.step_description; + refresh_field('steps_table'); + new_dialog.hide(); + }, + primary_action_label: __("Save"), + }); + + new_dialog.set_values({ + title: frm.doc.steps_table[id].title, + image: frm.doc.steps_table[id].image, + step_description: frm.doc.steps_table[id].description + }); + new_dialog.show(); + } +}); diff --git a/erpnext/healthcare/doctype/exercise_type/exercise_type.json b/erpnext/healthcare/doctype/exercise_type/exercise_type.json new file mode 100644 index 0000000000..0db9c6e796 --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.json @@ -0,0 +1,144 @@ +{ + "actions": [], + "creation": "2020-03-29 21:37:03.366344", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "exercise_name", + "body_parts", + "column_break_3", + "difficulty_level", + "section_break_5", + "description", + "section_break_7", + "exercise_steps", + "column_break_9", + "exercise_video", + "section_break_11", + "steps_html", + "section_break_13", + "steps_table" + ], + "fields": [ + { + "fieldname": "exercise_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Exercise Name", + "reqd": 1 + }, + { + "fieldname": "difficulty_level", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Difficulty Level", + "options": "Exercise Difficulty Level" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "description", + "fieldtype": "Long Text", + "label": "Description" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "exercise_steps", + "fieldtype": "Attach", + "label": "Exercise Instructions" + }, + { + "fieldname": "exercise_video", + "fieldtype": "Link", + "label": "Exercise Video", + "options": "Video" + }, + { + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, + { + "fieldname": "steps_html", + "fieldtype": "HTML", + "label": "Steps" + }, + { + "fieldname": "steps_table", + "fieldtype": "Table", + "hidden": 1, + "label": "Steps Table", + "options": "Exercise Type Step" + }, + { + "fieldname": "section_break_11", + "fieldtype": "Section Break", + "label": "Exercise Steps" + }, + { + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, + { + "fieldname": "body_parts", + "fieldtype": "Table MultiSelect", + "label": "Body Parts", + "options": "Body Part Link" + } + ], + "links": [], + "modified": "2020-04-21 13:05:36.555060", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..fb635c8578 --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_type/exercise_type.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ExerciseType(Document): + def autoname(self): + if self.difficulty_level: + self.name = ' - '.join(filter(None, [self.exercise_name, self.difficulty_level])) + else: + self.name = self.exercise_name + diff --git a/erpnext/healthcare/doctype/exercise_type/test_exercise_type.py b/erpnext/healthcare/doctype/exercise_type/test_exercise_type.py new file mode 100644 index 0000000000..bf217e893a --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_type/test_exercise_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestExerciseType(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/exercise_type_step/__init__.py b/erpnext/healthcare/doctype/exercise_type_step/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json b/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json new file mode 100644 index 0000000000..b37ff007cb --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.json @@ -0,0 +1,44 @@ +{ + "actions": [], + "creation": "2020-03-31 23:01:18.761967", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "title", + "image", + "description" + ], + "fields": [ + { + "fieldname": "title", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Title", + "reqd": 1 + }, + { + "fieldname": "image", + "fieldtype": "Attach Image", + "label": "Image" + }, + { + "fieldname": "description", + "fieldtype": "Long Text", + "in_list_view": 1, + "label": "Description" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-02 20:39:34.258512", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Exercise Type Step", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..13d7e5732f --- /dev/null +++ b/erpnext/healthcare/doctype/exercise_type_step/exercise_type_step.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ExerciseTypeStep(Document): + pass diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js index efa6b249b3..fa589347ff 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js @@ -102,6 +102,13 @@ frappe.ui.form.on('Patient Appointment', { frm: frm, }); }, __('Create')); + } else if (frm.doc.therapy_type) { + frm.add_custom_button(__('Therapy Session'),function(){ + frappe.model.open_mapped_doc({ + method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session', + frm: frm, + }) + }, 'Create'); } else { frm.add_custom_button(__('Patient Encounter'), function() { frappe.model.open_mapped_doc({ @@ -123,6 +130,16 @@ frappe.ui.form.on('Patient Appointment', { } }, + therapy_type: function(frm) { + if (frm.doc.therapy_type) { + frappe.db.get_value('Therapy Type', frm.doc.therapy_type, 'default_duration', (r) => { + if (r.default_duration) { + frm.set_value('duration', r.default_duration) + } + }); + } + }, + get_procedure_from_encounter: function(frm) { get_prescribed_procedure(frm); }, @@ -148,6 +165,26 @@ frappe.ui.form.on('Patient Appointment', { } } }); + }, + + get_prescribed_therapies: function(frm) { + if (frm.doc.patient) { + frappe.call({ + method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies", + args: {patient: frm.doc.patient}, + callback: function(r) { + if (r.message) { + show_therapy_types(frm, r.message); + } else { + frappe.msgprint({ + title: __('Not Therapies Prescribed'), + message: __('There are no Therapies prescribed for Patient {0}', [frm.doc.patient.bold()]), + indicator: 'blue' + }); + } + } + }); + } } }); @@ -393,6 +430,50 @@ let show_procedure_templates = function(frm, result){ d.show(); }; +let show_therapy_types = function(frm, result) { + var d = new frappe.ui.Dialog({ + title: __('Prescribed Therapies'), + fields: [ + { + fieldtype: 'HTML', fieldname: 'therapy_type' + } + ] + }); + var html_field = d.fields_dict.therapy_type.$wrapper; + $.each(result, function(x, y){ + var row = $(repl('
\ +
%(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')); diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json index 7f9a671d47..57e6c479d1 100644 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json @@ -20,6 +20,9 @@ "procedure_template", "get_procedure_from_encounter", "procedure_prescription", + "therapy_type", + "get_prescribed_therapies", + "therapy_plan", "service_unit", "section_break_12", "practitioner", @@ -269,6 +272,28 @@ "print_hide": 1, "report_hide": 1 }, + { + "depends_on": "eval:doc.patient;", + "fieldname": "therapy_type", + "fieldtype": "Link", + "label": "Therapy", + "options": "Therapy Type", + "set_only_once": 1 + }, + { + "depends_on": "eval:doc.patient && doc.__islocal;", + "fieldname": "get_prescribed_therapies", + "fieldtype": "Button", + "label": "Get Prescribed Therapies" + }, + { + "depends_on": "eval: doc.patient && doc.therapy_type", + "fieldname": "therapy_plan", + "fieldtype": "Link", + "label": "Therapy Plan", + "mandatory_depends_on": "eval: doc.patient && doc.therapy_type", + "options": "Therapy Plan" + }, { "fieldname": "ref_sales_invoice", "fieldtype": "Link", @@ -285,7 +310,7 @@ } ], "links": [], - "modified": "2020-03-27 11:27:33.773195", + "modified": "2020-03-31 16:16:32.116865", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Appointment", diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py index a2d9d0240f..512d44ec37 100755 --- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py +++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py @@ -412,11 +412,36 @@ def get_events(start, end, filters=None): @frappe.whitelist() def get_procedure_prescribed(patient): - return frappe.db.sql("""select pp.name, pp.procedure, pp.parent, ct.practitioner, - ct.encounter_date, pp.practitioner, pp.date, pp.department - from `tabPatient Encounter` ct, `tabProcedure Prescription` pp - where ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0 - order by ct.creation desc""", {'patient': patient}) + return frappe.db.sql( + """ + SELECT + pp.name, pp.procedure, pp.parent, ct.practitioner, + ct.encounter_date, pp.practitioner, pp.date, pp.department + FROM + `tabPatient Encounter` ct, `tabProcedure Prescription` pp + WHERE + ct.patient=%(patient)s and pp.parent=ct.name and pp.appointment_booked=0 + ORDER BY + ct.creation desc + """, {'patient': patient} + ) + + +@frappe.whitelist() +def get_prescribed_therapies(patient): + return frappe.db.sql( + """ + SELECT + t.therapy_type, t.name, t.parent, e.practitioner, + e.encounter_date, e.therapy_plan, e.visit_department + FROM + `tabPatient Encounter` e, `tabTherapy Plan Detail` t + WHERE + e.patient=%(patient)s and t.parent=e.name + ORDER BY + e.creation desc + """, {'patient': patient} + ) def update_appointment_status(): diff --git a/erpnext/healthcare/doctype/patient_assessment/__init__.py b/erpnext/healthcare/doctype/patient_assessment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js new file mode 100644 index 0000000000..c7074e88d5 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js @@ -0,0 +1,86 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient Assessment', { + refresh: function(frm) { + if (frm.doc.assessment_template) { + frm.trigger('set_score_range'); + } + + if (!frm.doc.__islocal) { + frm.trigger('show_patient_progress'); + } + }, + + assessment_template: function(frm) { + if (frm.doc.assessment_template) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Patient Assessment Template', + name: frm.doc.assessment_template + }, + callback: function(data) { + frm.doc.assessment_sheet = []; + $.each(data.message.parameters, function(_i, e) { + let entry = frm.add_child('assessment_sheet'); + entry.parameter = e.assessment_parameter; + }); + + frm.set_value('scale_min', data.message.scale_min); + frm.set_value('scale_max', data.message.scale_max); + frm.set_value('assessment_description', data.message.assessment_description); + frm.set_value('total_score', data.message.scale_max * data.message.parameters.length); + frm.trigger('set_score_range'); + refresh_field('assessment_sheet'); + } + }); + } + }, + + set_score_range: function(frm) { + let options = []; + for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) { + options.push(i); + } + frappe.meta.get_docfield('Patient Assessment Sheet', 'score', frm.doc.name).options = [''].concat(options); + }, + + calculate_total_score: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + let total_score = 0; + $.each(frm.doc.assessment_sheet || [], function(_i, item) { + if (item.score) { + total_score += parseInt(item.score); + } + }); + + frm.set_value('total_score_obtained', total_score); + }, + + show_patient_progress: function(frm) { + let bars = []; + let message = ''; + let added_min = false; + + let title = __('{0} out of {1}', [frm.doc.total_score_obtained, frm.doc.total_score]); + + bars.push({ + 'title': title, + 'width': (frm.doc.total_score_obtained / frm.doc.total_score * 100) + '%', + 'progress_class': 'progress-bar-success' + }); + if (bars[0].width == '0%') { + bars[0].width = '0.5%'; + added_min = 0.5; + } + message = title; + frm.dashboard.add_progress(__('Status'), bars, message); + }, +}); + +frappe.ui.form.on('Patient Assessment Sheet', { + score: function(frm, cdt, cdn) { + frm.events.calculate_total_score(frm, cdt, cdn); + } +}); \ No newline at end of file diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json new file mode 100644 index 0000000000..3952a8153f --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json @@ -0,0 +1,172 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-04-19 22:45:12.356209", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "therapy_session", + "patient", + "assessment_template", + "column_break_4", + "healthcare_practitioner", + "assessment_datetime", + "assessment_description", + "section_break_7", + "assessment_sheet", + "section_break_9", + "total_score_obtained", + "column_break_11", + "total_score", + "scale_min", + "scale_max", + "amended_from" + ], + "fields": [ + { + "fetch_from": "therapy_session.patient", + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "assessment_template", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Template", + "options": "Patient Assessment Template", + "reqd": 1 + }, + { + "fieldname": "therapy_session", + "fieldtype": "Link", + "label": "Therapy Session", + "options": "Therapy Session" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fetch_from": "therapy_session.practitioner", + "fieldname": "healthcare_practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "assessment_datetime", + "fieldtype": "Datetime", + "label": "Assessment Datetime" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "assessment_sheet", + "fieldtype": "Table", + "label": "Assessment Sheet", + "options": "Patient Assessment Sheet" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_score", + "fieldtype": "Int", + "label": "Total Score", + "read_only": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_score_obtained", + "fieldtype": "Int", + "label": "Total Score Obtained", + "read_only": 1 + }, + { + "fieldname": "scale_min", + "fieldtype": "Int", + "hidden": 1, + "label": "Scale Min", + "read_only": 1 + }, + { + "fieldname": "scale_max", + "fieldtype": "Int", + "hidden": 1, + "label": "Scale Max", + "read_only": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-PA-.YYYY.-" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Patient Assessment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "assessment_description", + "fieldtype": "Small Text", + "label": "Assessment Description" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-04-21 13:23:09.815007", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment", + "owner": "Administrator", + "permissions": [ + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..3033a3e6ac --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc + +class PatientAssessment(Document): + def validate(self): + self.set_total_score() + + def set_total_score(self): + total_score = 0 + for entry in self.assessment_sheet: + total_score += int(entry.score) + self.total_score_obtained = total_score + +@frappe.whitelist() +def create_patient_assessment(source_name, target_doc=None): + doc = get_mapped_doc('Therapy Session', source_name, { + 'Therapy Session': { + 'doctype': 'Patient Assessment', + 'field_map': [ + ['therapy_session', 'name'], + ['patient', 'patient'], + ['practitioner', 'practitioner'] + ] + } + }, target_doc) + + return doc + + + diff --git a/erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py b/erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py new file mode 100644 index 0000000000..3fda8550f6 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment/test_patient_assessment.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPatientAssessment(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/patient_assessment_detail/__init__.py b/erpnext/healthcare/doctype/patient_assessment_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json b/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json new file mode 100644 index 0000000000..179f441044 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-04-19 19:33:00.115395", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_parameter" + ], + "fields": [ + { + "fieldname": "assessment_parameter", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Assessment Parameter", + "options": "Patient Assessment Parameter", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-19 19:33:00.115395", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..0519599ac0 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_detail/patient_assessment_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientAssessmentDetail(Document): + pass diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/__init__.py b/erpnext/healthcare/doctype/patient_assessment_parameter/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js new file mode 100644 index 0000000000..2c5d270d57 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient Assessment Parameter', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json new file mode 100644 index 0000000000..098bdefea7 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "autoname": "field:assessment_parameter", + "creation": "2020-04-15 14:34:46.551042", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_parameter" + ], + "fields": [ + { + "fieldname": "assessment_parameter", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Assessment Parameter", + "reqd": 1, + "unique": 1 + } + ], + "links": [], + "modified": "2020-04-20 09:22:19.135196", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Parameter", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..b8e0074717 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_parameter/patient_assessment_parameter.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientAssessmentParameter(Document): + pass 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 new file mode 100644 index 0000000000..e722f9905e --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_parameter/test_patient_assessment_parameter.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPatientAssessmentParameter(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/patient_assessment_sheet/__init__.py b/erpnext/healthcare/doctype/patient_assessment_sheet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json b/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json new file mode 100644 index 0000000000..64e4aef7cf --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2020-04-19 23:07:02.220244", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parameter", + "score", + "time", + "column_break_4", + "comments" + ], + "fields": [ + { + "fieldname": "parameter", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parameter", + "options": "Patient Assessment Parameter", + "reqd": 1 + }, + { + "fieldname": "score", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Score", + "reqd": 1 + }, + { + "fieldname": "time", + "fieldtype": "Time", + "label": "Time" + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "comments", + "fieldtype": "Small Text", + "label": "Comments" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-20 09:56:28.746619", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Sheet", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..40da763013 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_sheet/patient_assessment_sheet.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientAssessmentSheet(Document): + pass diff --git a/erpnext/healthcare/doctype/patient_assessment_template/__init__.py b/erpnext/healthcare/doctype/patient_assessment_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js new file mode 100644 index 0000000000..40419362a4 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Patient Assessment Template', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json new file mode 100644 index 0000000000..de006b1805 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.json @@ -0,0 +1,109 @@ +{ + "actions": [], + "autoname": "field:assessment_name", + "creation": "2020-04-19 19:33:13.204707", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "assessment_name", + "section_break_2", + "parameters", + "assessment_scale_details_section", + "scale_min", + "scale_max", + "column_break_8", + "assessment_description" + ], + "fields": [ + { + "fieldname": "parameters", + "fieldtype": "Table", + "label": "Parameters", + "options": "Patient Assessment Detail" + }, + { + "fieldname": "assessment_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Assessment Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break", + "label": "Assessment Parameters" + }, + { + "fieldname": "assessment_scale_details_section", + "fieldtype": "Section Break", + "label": "Assessment Scale" + }, + { + "fieldname": "scale_min", + "fieldtype": "Int", + "label": "Scale Minimum" + }, + { + "fieldname": "scale_max", + "fieldtype": "Int", + "label": "Scale Maximum" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "assessment_description", + "fieldtype": "Small Text", + "label": "Assessment Description" + } + ], + "links": [], + "modified": "2020-04-21 13:14:19.075167", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Patient Assessment Template", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..083cab5d01 --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_template/patient_assessment_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PatientAssessmentTemplate(Document): + pass 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 new file mode 100644 index 0000000000..86dbd5438c --- /dev/null +++ b/erpnext/healthcare/doctype/patient_assessment_template/test_patient_assessment_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPatientAssessmentTemplate(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js index 83c5d2be9c..78e789d359 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js @@ -3,6 +3,10 @@ frappe.ui.form.on('Patient Encounter', { setup: function(frm) { + frm.get_field('therapies').grid.editable_fields = [ + {fieldname: 'therapy_type', columns: 8}, + {fieldname: 'no_of_sessions', columns: 2} + ]; frm.get_field('drug_prescription').grid.editable_fields = [ {fieldname: 'drug_code', columns: 2}, {fieldname: 'drug_name', columns: 2}, diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json index d00e7bc7dd..5f11039e19 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json @@ -42,6 +42,10 @@ "lab_test_prescription", "sb_procedures", "procedure_prescription", + "rehabilitation_section", + "therapy_plan", + "therapies", + "section_break_33", "encounter_comment", "amended_from" ], @@ -256,6 +260,29 @@ "print_hide": 1, "read_only": 1 }, + { + "fieldname": "rehabilitation_section", + "fieldtype": "Section Break", + "label": "Rehabilitation" + }, + { + "fieldname": "therapies", + "fieldtype": "Table", + "label": "Therapies", + "options": "Therapy Plan Detail" + }, + { + "fieldname": "section_break_33", + "fieldtype": "Section Break" + }, + { + "fieldname": "therapy_plan", + "fieldtype": "Link", + "hidden": 1, + "label": "Therapy Plan", + "options": "Therapy Plan", + "read_only": 1 + }, { "fieldname": "appointment_type", "fieldtype": "Link", @@ -291,7 +318,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-02-27 12:42:21.751964", + "modified": "2020-04-14 16:18:08.180457", "modified_by": "Administrator", "module": "Healthcare", "name": "Patient Encounter", diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py index ade4748ece..767643bc73 100644 --- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py +++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe import _ from frappe.model.document import Document from frappe.utils import cstr from frappe import _ @@ -22,6 +23,24 @@ class PatientEncounter(Document): frappe.db.set_value('Patient Appointment', self.appointment, 'status', 'Open') delete_medical_record(self) + def on_submit(self): + create_therapy_plan(self) + +def create_therapy_plan(encounter): + if len(encounter.therapies): + doc = frappe.new_doc('Therapy Plan') + doc.patient = encounter.patient + doc.start_date = encounter.encounter_date + for entry in encounter.therapies: + doc.append('therapy_plan_details', { + 'therapy_type': entry.therapy_type, + 'no_of_sessions': entry.no_of_sessions + }) + doc.save(ignore_permissions=True) + if doc.get('name'): + encounter.db_set('therapy_plan', doc.name) + frappe.msgprint(_('Therapy Plan {0} created successfully.').format(frappe.bold(doc.name)), alert=True) + def insert_encounter_to_medical_record(doc): subject = set_subject_field(doc) medical_record = frappe.new_doc('Patient Medical Record') diff --git a/erpnext/healthcare/doctype/therapy_plan/__init__.py b/erpnext/healthcare/doctype/therapy_plan/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py new file mode 100644 index 0000000000..526bb95b70 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from frappe.utils import getdate +from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type +from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session +from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient + +class TestTherapyPlan(unittest.TestCase): + def test_creation_on_encounter_submission(self): + patient, medical_department, practitioner = create_healthcare_docs() + encounter = create_encounter(patient, medical_department, practitioner) + self.assertTrue(frappe.db.exists('Therapy Plan', encounter.therapy_plan)) + + def test_status(self): + plan = create_therapy_plan() + self.assertEquals(plan.status, 'Not Started') + + session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab') + frappe.get_doc(session).submit() + self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'In Progress') + + session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab') + frappe.get_doc(session).submit() + self.assertEquals(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed') + + +def create_therapy_plan(): + patient = create_patient() + therapy_type = create_therapy_type() + plan = frappe.new_doc('Therapy Plan') + plan.patient = patient + plan.start_date = getdate() + plan.append('therapy_plan_details', { + 'therapy_type': therapy_type.name, + 'no_of_sessions': 2 + }) + plan.save() + return plan + +def create_encounter(patient, medical_department, practitioner): + encounter = frappe.new_doc('Patient Encounter') + encounter.patient = patient + encounter.practitioner = practitioner + encounter.medical_department = medical_department + therapy_type = create_therapy_type() + encounter.append('therapies', { + 'therapy_type': therapy_type.name, + 'no_of_sessions': 2 + }) + encounter.save() + encounter.submit() + return encounter diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js new file mode 100644 index 0000000000..dea0cfeb84 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js @@ -0,0 +1,90 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Plan', { + setup: function(frm) { + frm.get_field('therapy_plan_details').grid.editable_fields = [ + {fieldname: 'therapy_type', columns: 6}, + {fieldname: 'no_of_sessions', columns: 2}, + {fieldname: 'sessions_completed', columns: 2} + ]; + }, + + refresh: function(frm) { + if (!frm.doc.__islocal) { + frm.trigger('show_progress_for_therapies'); + } + + if (!frm.doc.__islocal && frm.doc.status != 'Completed') { + let therapy_types = (frm.doc.therapy_plan_details || []).map(function(d){ return d.therapy_type }); + const fields = [{ + fieldtype: 'Link', + label: __('Therapy Type'), + fieldname: 'therapy_type', + options: 'Therapy Type', + reqd: 1, + get_query: function() { + return { + filters: { 'therapy_type': ['in', therapy_types]} + } + } + }]; + + frm.add_custom_button(__('Therapy Session'), function() { + frappe.prompt(fields, data => { + frappe.call({ + method: 'erpnext.healthcare.doctype.therapy_plan.therapy_plan.make_therapy_session', + args: { + therapy_plan: frm.doc.name, + patient: frm.doc.patient, + therapy_type: data.therapy_type + }, + freeze: true, + callback: function(r) { + if (r.message) { + frappe.model.sync(r.message); + frappe.set_route('Form', r.message.doctype, r.message.name); + } + } + }); + }, __('Select Therapy Type'), __('Create')); + }, __('Create')); + } + }, + + show_progress_for_therapies: function(frm) { + let bars = []; + let message = ''; + let added_min = false; + + // completed sessions + let title = __('{0} sessions completed', [frm.doc.total_sessions_completed]); + if (frm.doc.total_sessions_completed === 1) { + title = __('{0} session completed', [frm.doc.total_sessions_completed]); + } + title += __(' out of {0}', [frm.doc.total_sessions]); + + bars.push({ + 'title': title, + 'width': (frm.doc.total_sessions_completed / frm.doc.total_sessions * 100) + '%', + 'progress_class': 'progress-bar-success' + }); + if (bars[0].width == '0%') { + bars[0].width = '0.5%'; + added_min = 0.5; + } + message = title; + frm.dashboard.add_progress(__('Status'), bars, message); + }, +}); + +frappe.ui.form.on('Therapy Plan Detail', { + no_of_sessions: function(frm) { + let total = 0; + $.each(frm.doc.therapy_plan_details, function(_i, e) { + total += e.no_of_sessions; + }); + frm.set_value('total_sessions', total); + refresh_field('total_sessions'); + } +}); \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json new file mode 100644 index 0000000000..ca78b6618e --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.json @@ -0,0 +1,151 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-03-29 20:56:49.758602", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "patient", + "patient_name", + "column_break_4", + "status", + "start_date", + "section_break_3", + "therapy_plan_details", + "title", + "section_break_9", + "total_sessions", + "column_break_11", + "total_sessions_completed" + ], + "fields": [ + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Start Date", + "reqd": 1 + }, + { + "fieldname": "section_break_3", + "fieldtype": "Section Break" + }, + { + "fieldname": "therapy_plan_details", + "fieldtype": "Table", + "label": "Therapy Plan Details", + "options": "Therapy Plan Detail", + "reqd": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "HLC-THP-.YYYY.-" + }, + { + "fetch_from": "patient.patient_name", + "fieldname": "patient_name", + "fieldtype": "Data", + "label": "Patient Name", + "read_only": 1 + }, + { + "default": "{patient_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, + { + "fieldname": "total_sessions", + "fieldtype": "Int", + "label": "Total Sessions", + "read_only": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_sessions_completed", + "fieldtype": "Int", + "label": "Total Sessions Completed", + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Not Started\nIn Progress\nCompleted\nCancelled", + "read_only": 1 + } + ], + "links": [], + "modified": "2020-04-21 13:13:43.956014", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Plan", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "search_fields": "patient", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..201264f829 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class TherapyPlan(Document): + def validate(self): + self.set_totals() + self.set_status() + + def set_status(self): + if not self.total_sessions_completed: + self.status = 'Not Started' + else: + if self.total_sessions_completed < self.total_sessions: + self.status = 'In Progress' + elif self.total_sessions_completed == self.total_sessions: + self.status = 'Completed' + + def set_totals(self): + total_sessions = sum([int(d.no_of_sessions) for d in self.get('therapy_plan_details')]) + total_sessions_completed = sum([int(d.sessions_completed) for d in self.get('therapy_plan_details')]) + self.db_set('total_sessions', total_sessions) + self.db_set('total_sessions_completed', total_sessions_completed) + + +@frappe.whitelist() +def make_therapy_session(therapy_plan, patient, therapy_type): + therapy_type = frappe.get_doc('Therapy Type', therapy_type) + + therapy_session = frappe.new_doc('Therapy Session') + therapy_session.therapy_plan = therapy_plan + therapy_session.patient = patient + therapy_session.therapy_type = therapy_type.name + therapy_session.duration = therapy_type.default_duration + therapy_session.rate = therapy_type.rate + therapy_session.exercises = therapy_type.exercises + + return therapy_session.as_dict() \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py new file mode 100644 index 0000000000..df647829db --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'therapy_plan', + 'transactions': [ + { + 'label': _('Therapy Sessions'), + 'items': ['Therapy Session'] + } + ] + } diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js new file mode 100644 index 0000000000..63967aff33 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan_list.js @@ -0,0 +1,11 @@ +frappe.listview_settings['Therapy Plan'] = { + get_indicator: function(doc) { + var colors = { + 'Completed': 'green', + 'In Progress': 'orange', + 'Not Started': 'red', + 'Cancelled': 'grey' + }; + return [__(doc.status), colors[doc.status], 'status,=,' + doc.status]; + } +}; diff --git a/erpnext/healthcare/doctype/therapy_plan_detail/__init__.py b/erpnext/healthcare/doctype/therapy_plan_detail/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json new file mode 100644 index 0000000000..9eb20e2ef3 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.json @@ -0,0 +1,48 @@ +{ + "actions": [], + "creation": "2020-03-29 20:52:57.068731", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "therapy_type", + "no_of_sessions", + "sessions_completed" + ], + "fields": [ + { + "fieldname": "therapy_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Therapy Type", + "options": "Therapy Type", + "reqd": 1 + }, + { + "fieldname": "no_of_sessions", + "fieldtype": "Int", + "in_list_view": 1, + "label": "No of Sessions" + }, + { + "default": "0", + "depends_on": "eval:doc.parenttype=='Therapy Plan';", + "fieldname": "sessions_completed", + "fieldtype": "Int", + "label": "Sessions Completed", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-30 22:02:01.740109", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Plan Detail", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..44211f32e3 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_plan_detail/therapy_plan_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class TherapyPlanDetail(Document): + pass diff --git a/erpnext/healthcare/doctype/therapy_session/__init__.py b/erpnext/healthcare/doctype/therapy_session/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/therapy_session/test_therapy_session.py b/erpnext/healthcare/doctype/therapy_session/test_therapy_session.py new file mode 100644 index 0000000000..75bb8df196 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_session/test_therapy_session.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestTherapySession(unittest.TestCase): + pass diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.js b/erpnext/healthcare/doctype/therapy_session/therapy_session.js new file mode 100644 index 0000000000..bb675752bb --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.js @@ -0,0 +1,60 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Session', { + setup: function(frm) { + frm.get_field('exercises').grid.editable_fields = [ + {fieldname: 'exercise_type', columns: 7}, + {fieldname: 'counts_target', columns: 1}, + {fieldname: 'counts_completed', columns: 1}, + {fieldname: 'assistance_level', columns: 1} + ]; + }, + + refresh: function(frm) { + if (!frm.doc.__islocal) { + let target = 0; + let completed = 0; + $.each(frm.doc.exercises, function(_i, e) { + target += e.counts_target; + completed += e.counts_completed; + }); + frm.dashboard.add_indicator(__('Counts Targetted: {0}', [target]), 'blue'); + frm.dashboard.add_indicator(__('Counts Completed: {0}', [completed]), (completed < target) ? 'orange' : 'green'); + } + + if (frm.doc.docstatus === 1) { + frm.add_custom_button(__('Patient Assessment'),function() { + frappe.model.open_mapped_doc({ + method: 'erpnext.healthcare.doctype.patient_assessment.patient_assessment.create_patient_assessment', + frm: frm, + }) + }, 'Create'); + } + }, + + therapy_type: function(frm) { + if (frm.doc.therapy_type) { + frappe.call({ + 'method': 'frappe.client.get', + args: { + doctype: 'Therapy Type', + name: frm.doc.therapy_type + }, + callback: function(data) { + frm.set_value('duration', data.message.default_duration); + frm.set_value('rate', data.message.rate); + frm.doc.exercises = []; + $.each(data.message.exercises, function(_i, e) { + let exercise = frm.add_child('exercises'); + exercise.exercise_type = e.exercise_type; + exercise.difficulty_level = e.difficulty_level; + exercise.counts_target = e.counts_target; + exercise.assistance_level = e.assistance_level; + }); + refresh_field('exercises'); + } + }); + } + } +}); \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json new file mode 100644 index 0000000000..5ff719672f --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json @@ -0,0 +1,218 @@ +{ + "actions": [], + "autoname": "naming_series:", + "creation": "2020-03-11 08:57:40.669857", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "appointment", + "patient", + "patient_age", + "gender", + "column_break_5", + "therapy_plan", + "therapy_type", + "practitioner", + "department", + "details_section", + "duration", + "rate", + "location", + "company", + "column_break_12", + "service_unit", + "start_date", + "start_time", + "invoiced", + "exercises_section", + "exercises", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HLC-THP-.YYYY.-" + }, + { + "fieldname": "appointment", + "fieldtype": "Link", + "label": "Appointment", + "options": "Patient Appointment" + }, + { + "fieldname": "patient", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Patient", + "options": "Patient", + "reqd": 1 + }, + { + "fetch_from": "patient.sex", + "fieldname": "gender", + "fieldtype": "Link", + "label": "Gender", + "options": "Gender", + "read_only": 1 + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "practitioner", + "fieldtype": "Link", + "label": "Healthcare Practitioner", + "options": "Healthcare Practitioner" + }, + { + "fieldname": "department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department" + }, + { + "fieldname": "details_section", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "fetch_from": "therapy_template.default_duration", + "fieldname": "duration", + "fieldtype": "Int", + "label": "Duration" + }, + { + "fieldname": "location", + "fieldtype": "Select", + "label": "Location", + "options": "\nCenter\nHome\nTele" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fetch_from": "therapy_template.rate", + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate" + }, + { + "fieldname": "exercises_section", + "fieldtype": "Section Break", + "label": "Exercises" + }, + { + "fieldname": "exercises", + "fieldtype": "Table", + "label": "Exercises", + "options": "Exercise" + }, + { + "depends_on": "eval: doc.therapy_plan", + "fieldname": "therapy_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Therapy Type", + "options": "Therapy Type", + "reqd": 1 + }, + { + "fieldname": "therapy_plan", + "fieldtype": "Link", + "label": "Therapy Plan", + "options": "Therapy Plan", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Therapy Session", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit" + }, + { + "fieldname": "start_date", + "fieldtype": "Date", + "label": "Start Date" + }, + { + "fieldname": "start_time", + "fieldtype": "Time", + "label": "Start Time" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "default": "0", + "fieldname": "invoiced", + "fieldtype": "Check", + "label": "Invoiced", + "read_only": 1 + }, + { + "fieldname": "patient_age", + "fieldtype": "Data", + "label": "Patient Age", + "read_only": 1 + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-04-21 13:16:46.378798", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Session", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 1, + "search_fields": "patient,appointment,therapy_plan,therapy_type", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "patient", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..45d2ee60e6 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc + +class TherapySession(Document): + def on_submit(self): + self.update_sessions_count_in_therapy_plan() + + def on_cancel(self): + self.update_sessions_count_in_therapy_plan(on_cancel=True) + + def update_sessions_count_in_therapy_plan(self, on_cancel=False): + therapy_plan = frappe.get_doc('Therapy Plan', self.therapy_plan) + for entry in therapy_plan.therapy_plan_details: + if entry.therapy_type == self.therapy_type: + if on_cancel: + entry.sessions_completed -= 1 + else: + entry.sessions_completed += 1 + therapy_plan.save() + + +@frappe.whitelist() +def create_therapy_session(source_name, target_doc=None): + def set_missing_values(source, target): + therapy_type = frappe.get_doc('Therapy Type', source.therapy_type) + target.exercises = therapy_type.exercises + + doc = get_mapped_doc('Patient Appointment', source_name, { + 'Patient Appointment': { + 'doctype': 'Therapy Session', + 'field_map': [ + ['appointment', 'name'], + ['patient', 'patient'], + ['patient_age', 'patient_age'], + ['gender', 'patient_sex'], + ['therapy_type', 'therapy_type'], + ['therapy_plan', 'therapy_plan'], + ['practitioner', 'practitioner'], + ['department', 'department'], + ['start_date', 'appointment_date'], + ['start_time', 'appointment_time'], + ['service_unit', 'service_unit'], + ['company', 'company'], + ['invoiced', 'invoiced'] + ] + } + }, target_doc, set_missing_values) + + return doc \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py b/erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py new file mode 100644 index 0000000000..9de7e29323 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session_dashboard.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'therapy_session', + 'transactions': [ + { + 'label': _('Assessments'), + 'items': ['Patient Assessment'] + } + ] + } diff --git a/erpnext/healthcare/doctype/therapy_type/__init__.py b/erpnext/healthcare/doctype/therapy_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py new file mode 100644 index 0000000000..03a1be8a4e --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestTherapyType(unittest.TestCase): + def test_therapy_type_item(self): + therapy_type = create_therapy_type() + self.assertTrue(frappe.db.exists('Item', therapy_type.item)) + + therapy_type.disabled = 1 + therapy_type.save() + self.assertEquals(frappe.db.get_value('Item', therapy_type.item, 'disabled'), 1) + +def create_therapy_type(): + exercise = create_exercise_type() + therapy_type = frappe.db.exists('Therapy Type', 'Basic Rehab') + if not therapy_type: + therapy_type = frappe.new_doc('Therapy Type') + therapy_type.therapy_type = 'Basic Rehab' + therapy_type.default_duration = 30 + therapy_type.is_billable = 1 + therapy_type.rate = 5000 + therapy_type.item_code = 'Basic Rehab' + therapy_type.item_name = 'Basic Rehab' + therapy_type.item_group = 'Services' + therapy_type.append('exercises', { + 'exercise_type': exercise.name, + 'counts_target': 10, + 'assistance_level': 'Passive' + }) + therapy_type.save() + else: + therapy_type = frappe.get_doc('Therapy Type', 'Basic Rehab') + return therapy_type + +def create_exercise_type(): + exercise_type = frappe.db.exists('Exercise Type', 'Sit to Stand') + if not exercise_type: + exercise_type = frappe.new_doc('Exercise Type') + exercise_type.exercise_name = 'Sit to Stand' + exercise_type.append('steps_table', { + 'title': 'Step 1', + 'description': 'Squat and Rise' + }) + exercise_type.save() + return exercise_type \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.js b/erpnext/healthcare/doctype/therapy_type/therapy_type.js new file mode 100644 index 0000000000..7a61b0def0 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.js @@ -0,0 +1,93 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Therapy Type', { + setup: function(frm) { + frm.get_field('exercises').grid.editable_fields = [ + {fieldname: 'exercise_type', columns: 7}, + {fieldname: 'difficulty_level', columns: 1}, + {fieldname: 'counts_target', columns: 1}, + {fieldname: 'assistance_level', columns: 1} + ]; + }, + + refresh: function(frm) { + if (!frm.doc.__islocal) { + cur_frm.add_custom_button(__('Change Item Code'), function() { + change_template_code(frm.doc); + }); + } + }, + + therapy_type: function(frm) { + if (!frm.doc.item_code) + frm.set_value('item_code', frm.doc.therapy_type); + if (!frm.doc.description) + frm.set_value('description', frm.doc.therapy_type); + mark_change_in_item(frm); + }, + + rate: function(frm) { + mark_change_in_item(frm); + }, + + is_billable: function (frm) { + mark_change_in_item(frm); + }, + + item_group: function(frm) { + mark_change_in_item(frm); + }, + + description: function(frm) { + mark_change_in_item(frm); + }, + + medical_department: function(frm) { + mark_change_in_item(frm); + } +}); + +let mark_change_in_item = function(frm) { + if (!frm.doc.__islocal) { + frm.doc.change_in_item = 1; + } +}; + +let change_template_code = function(doc) { + let d = new frappe.ui.Dialog({ + title:__('Change Item Code'), + fields:[ + { + 'fieldtype': 'Data', + 'label': 'Item Code', + 'fieldname': 'item_code', + reqd: 1 + } + ], + primary_action: function() { + let values = d.get_values(); + + if (values) { + frappe.call({ + 'method': 'erpnext.healthcare.doctype.therapy_type.therapy_type.change_item_code_from_therapy', + 'args': {item_code: values.item_code, doc: doc}, + callback: function () { + cur_frm.reload_doc(); + frappe.show_alert({ + message: 'Item Code renamed successfully', + indicator: 'green' + }); + } + }); + } + d.hide(); + }, + primary_action_label: __('Change Item Code') + }); + d.show(); + + d.set_values({ + 'item_code': doc.item_code + }); +}; diff --git a/erpnext/healthcare/doctype/therapy_type/therapy_type.json b/erpnext/healthcare/doctype/therapy_type/therapy_type.json new file mode 100644 index 0000000000..0b3c3caeaa --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.json @@ -0,0 +1,211 @@ +{ + "actions": [], + "autoname": "field:therapy_type", + "creation": "2020-03-29 20:48:31.715063", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "disabled", + "section_break_2", + "therapy_type", + "default_duration", + "medical_department", + "column_break_3", + "is_billable", + "rate", + "healthcare_service_unit", + "item_details_section", + "item", + "item_code", + "item_name", + "item_group", + "column_break_12", + "description", + "section_break_18", + "therapy_for", + "add_exercises", + "section_break_6", + "exercises", + "change_in_item" + ], + "fields": [ + { + "fieldname": "therapy_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Therapy Type", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_billable", + "fieldtype": "Check", + "label": "Is Billable" + }, + { + "depends_on": "eval:doc.is_billable;", + "fieldname": "rate", + "fieldtype": "Currency", + "label": "Rate", + "mandatory_depends_on": "eval:doc.is_billable;" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Exercises" + }, + { + "fieldname": "exercises", + "fieldtype": "Table", + "label": "Exercises", + "options": "Exercise" + }, + { + "fieldname": "default_duration", + "fieldtype": "Int", + "label": "Default Duration (In Minutes)" + }, + { + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, + { + "fieldname": "item_details_section", + "fieldtype": "Section Break", + "label": "Item Details" + }, + { + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item", + "read_only": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Data", + "label": "Item Code", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group", + "reqd": 1 + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "reqd": 1 + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "medical_department", + "fieldtype": "Link", + "label": "Medical Department", + "options": "Medical Department" + }, + { + "default": "0", + "fieldname": "change_in_item", + "fieldtype": "Check", + "hidden": 1, + "label": "Change In Item", + "print_hide": 1, + "read_only": 1, + "report_hide": 1 + }, + { + "fieldname": "therapy_for", + "fieldtype": "Table MultiSelect", + "label": "Therapy For", + "options": "Body Part Link" + }, + { + "fieldname": "healthcare_service_unit", + "fieldtype": "Link", + "label": "Healthcare Service Unit", + "options": "Healthcare Service Unit" + }, + { + "depends_on": "eval: doc.therapy_for", + "fieldname": "add_exercises", + "fieldtype": "Button", + "label": "Add Exercises", + "options": "add_exercises" + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break" + } + ], + "links": [], + "modified": "2020-04-21 13:09:04.006289", + "modified_by": "Administrator", + "module": "Healthcare", + "name": "Therapy Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Healthcare Administrator", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Physician", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ 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 new file mode 100644 index 0000000000..ea3d84e7c5 --- /dev/null +++ b/erpnext/healthcare/doctype/therapy_type/therapy_type.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from frappe import _ +from frappe.utils import cint +from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc + +class TherapyType(Document): + def validate(self): + self.enable_disable_item() + + def after_insert(self): + create_item_from_therapy(self) + + def on_update(self): + if self.change_in_item: + self.update_item_and_item_price() + + def enable_disable_item(self): + if self.is_billable: + if self.disabled: + frappe.db.set_value('Item', self.item, 'disabled', 1) + else: + frappe.db.set_value('Item', self.item, 'disabled', 0) + + def update_item_and_item_price(self): + if self.is_billable and self.item: + item_doc = frappe.get_doc('Item', {'item_code': self.item}) + item_doc.item_name = self.item_name + item_doc.item_group = self.item_group + item_doc.description = self.description + item_doc.disabled = 0 + item_doc.ignore_mandatory = True + item_doc.save(ignore_permissions=True) + + if self.rate: + item_price = frappe.get_doc('Item Price', {'item_code': self.item}) + item_price.item_name = self.item_name + item_price.price_list_name = self.rate + item_price.ignore_mandatory = True + item_price.save() + + elif not self.is_billable and self.item: + frappe.db.set_value('Item', self.item, 'disabled', 1) + + self.db_set('change_in_item', 0) + + def add_exercises(self): + exercises = self.get_exercises_for_body_parts() + last_idx = max([cint(d.idx) for d in self.get('exercises')] or [0,]) + for i, d in enumerate(exercises): + ch = self.append('exercises', {}) + ch.exercise_type = d.parent + ch.idx = last_idx + i + 1 + + def get_exercises_for_body_parts(self): + body_parts = [entry.body_part for entry in self.therapy_for] + + exercises = frappe.db.sql( + """ + SELECT DISTINCT + b.parent, e.name, e.difficulty_level + FROM + `tabExercise Type` e, `tabBody Part Link` b + WHERE + b.body_part IN %(body_parts)s AND b.parent=e.name + """, {'body_parts': body_parts}, as_dict=1) + + return exercises + + +def create_item_from_therapy(doc): + disabled = doc.disabled + if doc.is_billable and not doc.disabled: + disabled = 0 + + uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom') + + item = frappe.get_doc({ + 'doctype': 'Item', + 'item_code': doc.item_code, + 'item_name': doc.item_name, + 'item_group': doc.item_group, + 'description': doc.description, + 'is_sales_item': 1, + 'is_service_item': 1, + 'is_purchase_item': 0, + 'is_stock_item': 0, + 'show_in_website': 0, + 'is_pro_applicable': 0, + 'disabled': disabled, + 'stock_uom': uom + }).insert(ignore_permissions=True, ignore_mandatory=True) + + make_item_price(item.name, doc.rate) + doc.db_set('item', item.name) + + +def make_item_price(item, item_price): + price_list_name = frappe.db.get_value('Price List', {'selling': 1}) + frappe.get_doc({ + 'doctype': 'Item Price', + 'price_list': price_list_name, + 'item_code': item, + 'price_list_rate': item_price + }).insert(ignore_permissions=True, ignore_mandatory=True) + +@frappe.whitelist() +def change_item_code_from_therapy(item_code, doc): + doc = frappe._dict(json.loads(doc)) + + if frappe.db.exists('Item', {'item_code': item_code}): + frappe.throw(_('Item with Item Code {0} already exists').format(item_code)) + else: + rename_doc('Item', doc.item, item_code, ignore_permissions=True) + frappe.db.set_value('Therapy Type', doc.name, 'item_code', item_code) + return diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py index 246242ad84..9a32c737cf 100644 --- a/erpnext/healthcare/utils.py +++ b/erpnext/healthcare/utils.py @@ -30,8 +30,9 @@ def get_healthcare_services_to_invoice(patient): lab_tests = get_lab_tests_to_invoice(patient) clinical_procedures = get_clinical_procedures_to_invoice(patient) inpatient_services = get_inpatient_services_to_invoice(patient) + therapy_sessions = get_therapy_sessions_to_invoice(patient) - items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services + items_to_invoice += encounters + lab_tests + clinical_procedures + inpatient_services + therapy_sessions return items_to_invoice def validate_customer_created(patient): @@ -243,6 +244,25 @@ def get_inpatient_services_to_invoice(patient): return services_to_invoice +def get_therapy_sessions_to_invoice(patient): + therapy_sessions_to_invoice = [] + therapy_sessions = frappe.get_list( + 'Therapy Session', + fields='*', + filters={'patient': patient.name, 'invoiced': False} + ) + for therapy in therapy_sessions: + if not therapy.appointment: + if therapy.therapy_type and frappe.db.get_value('Therapy Type', therapy.therapy_type, 'is_billable'): + therapy_sessions_to_invoice.append({ + 'reference_type': 'Therapy Session', + 'reference_name': therapy.name, + 'service': frappe.db.get_value('Therapy Type', therapy.therapy_type, 'item') + }) + + return therapy_sessions_to_invoice + + def get_service_item_and_practitioner_charge(doc): is_inpatient = doc.inpatient_record if is_inpatient: diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 96e5cd57c3..b49b0ba0f7 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -188,7 +188,7 @@ frappe.ui.form.on('Production Plan', { }, get_items_for_mr: function(frm) { - const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', + const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type']; frappe.call({ method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests", diff --git a/erpnext/public/build.json b/erpnext/public/build.json index 1490374d2d..e94d1ffe5c 100644 --- a/erpnext/public/build.json +++ b/erpnext/public/build.json @@ -1,18 +1,18 @@ { - "css/erpnext.css": [ - "public/less/erpnext.less", - "public/less/hub.less", - "public/less/call_popup.less" - ], - "css/marketplace.css": [ - "public/less/hub.less" - ], - "js/erpnext-web.min.js": [ - "public/js/website_utils.js", - "public/js/shopping_cart.js" - ], + "css/erpnext.css": [ + "public/less/erpnext.less", + "public/less/hub.less", + "public/less/call_popup.less" + ], + "css/marketplace.css": [ + "public/less/hub.less" + ], + "js/erpnext-web.min.js": [ + "public/js/website_utils.js", + "public/js/shopping_cart.js" + ], "css/erpnext-web.css": [ - "public/scss/website.scss" + "public/scss/website.scss" ], "js/marketplace.min.js": [ "public/js/hub/marketplace.js" @@ -47,15 +47,15 @@ "public/js/templates/item_quick_entry.html", "public/js/utils/item_quick_entry.js", "public/js/utils/customer_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" - ], - "js/item-dashboard.min.js": [ - "stock/dashboard/item_dashboard.html", - "stock/dashboard/item_dashboard_list.html", - "stock/dashboard/item_dashboard.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" + ], + "js/item-dashboard.min.js": [ + "stock/dashboard/item_dashboard.html", + "stock/dashboard/item_dashboard_list.html", + "stock/dashboard/item_dashboard.js" + ] } diff --git a/erpnext/public/css/erpnext.css b/erpnext/public/css/erpnext.css index c55e422151..6e4efcb668 100644 --- a/erpnext/public/css/erpnext.css +++ b/erpnext/public/css/erpnext.css @@ -370,3 +370,39 @@ body[data-route="pos"] .collapse-btn { .leaderboard .list-item_content { padding-right: 45px; } +.exercise-card { + box-shadow: 0 1px 3px rgba(0,0,0,0.30); + border-radius: 2px; + padding: 6px 6px 6px 8px; + margin-top: 10px; + height: 100% !important; +} +.exercise-card .card-img-top { + width: 100%; + height: 15vw; + object-fit: cover; +} +.exercise-card .btn-edit { + position: absolute; + bottom: 10px; + left: 20px; +} +.exercise-card .btn-del { + position: absolute; + bottom: 10px; + left: 50px; +} +.exercise-card .card-body { + margin-bottom: 10px; +} +.exercise-card .card-footer { + padding: 10px; +} +.exercise-row { + height: 100% !important; + display: flex; + flex-wrap: wrap; +} +.exercise-col { + padding: 10px; +} diff --git a/erpnext/public/less/erpnext.less b/erpnext/public/less/erpnext.less index abe48685f0..8685837d33 100644 --- a/erpnext/public/less/erpnext.less +++ b/erpnext/public/less/erpnext.less @@ -458,4 +458,50 @@ body[data-route="pos"] { .list-item_content { padding-right: 45px; } +} + +// Healthcare + +.exercise-card { + box-shadow: 0 1px 3px rgba(0,0,0,0.30); + border-radius: 2px; + padding: 6px 6px 6px 8px; + margin-top: 10px; + height: 100% !important; + + .card-img-top { + width: 100%; + height: 15vw; + object-fit: cover; + } + + .btn-edit { + position: absolute; + bottom: 10px; + left: 20px; + } + + .btn-del { + position: absolute; + bottom: 10px; + left: 50px; + } + + .card-body { + margin-bottom: 10px; + } + + .card-footer { + padding: 10px; + } +} + +.exercise-row { + height: 100% !important; + display: flex; + flex-wrap: wrap; +} + +.exercise-col { + padding: 10px; } \ No newline at end of file From 28deddeb6f1eb5faca18821bb94daee243082d36 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 23 Apr 2020 09:44:58 +0530 Subject: [PATCH 63/69] fix: incorrect out value in stock balance due to precision issue (#21379) --- erpnext/stock/report/stock_balance/stock_balance.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index ff03381389..ab87ee114d 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -170,6 +170,8 @@ def get_item_warehouse_map(filters, sle): from_date = getdate(filters.get("from_date")) to_date = getdate(filters.get("to_date")) + float_precision = cint(frappe.db.get_default("float_precision")) or 3 + for d in sle: key = (d.company, d.item_code, d.warehouse) if key not in iwb_map: @@ -184,7 +186,7 @@ def get_item_warehouse_map(filters, sle): qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)] if d.voucher_type == "Stock Reconciliation": - qty_diff = flt(d.qty_after_transaction) - qty_dict.bal_qty + qty_diff = flt(d.qty_after_transaction) - flt(qty_dict.bal_qty) else: qty_diff = flt(d.actual_qty) @@ -195,7 +197,7 @@ def get_item_warehouse_map(filters, sle): qty_dict.opening_val += value_diff elif d.posting_date >= from_date and d.posting_date <= to_date: - if qty_diff > 0: + if flt(qty_diff, float_precision) >= 0: qty_dict.in_qty += qty_diff qty_dict.in_val += value_diff else: @@ -206,16 +208,15 @@ def get_item_warehouse_map(filters, sle): qty_dict.bal_qty += qty_diff qty_dict.bal_val += value_diff - iwb_map = filter_items_with_no_transactions(iwb_map) + iwb_map = filter_items_with_no_transactions(iwb_map, float_precision) return iwb_map -def filter_items_with_no_transactions(iwb_map): +def filter_items_with_no_transactions(iwb_map, float_precision): for (company, item, warehouse) in sorted(iwb_map): qty_dict = iwb_map[(company, item, warehouse)] no_transactions = True - float_precision = cint(frappe.db.get_default("float_precision")) or 3 for key, val in iteritems(qty_dict): val = flt(val, float_precision) qty_dict[key] = val From e8a651bc00ef72a1d81c8e5c6dccf44570b54211 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 23 Apr 2020 09:45:09 +0530 Subject: [PATCH 64/69] fix: BOM stock report (#21380) --- .../report/bom_stock_report/bom_stock_report.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 65f4d08459..75ebcbc971 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -18,10 +18,10 @@ def get_columns(): """return columns""" columns = [ _("Item") + ":Link/Item:150", - _("Description") + "::500", - _("Qty per BOM Line") + ":Float:100", - _("Required Qty") + ":Float:100", - _("In Stock Qty") + ":Float:100", + _("Description") + "::300", + _("BOM Qty") + ":Float:160", + _("Required Qty") + ":Float:120", + _("In Stock Qty") + ":Float:120", _("Enough Parts to Build") + ":Float:200", ] @@ -59,13 +59,14 @@ def get_bom_stock(filters): bom_item.item_code, bom_item.description , bom_item.{qty_field}, - bom_item.{qty_field} * {qty_to_produce}, + bom_item.{qty_field} * {qty_to_produce} / bom.quantity, sum(ledger.actual_qty) as actual_qty, - sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce}))) + sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity))) FROM - {table} AS bom_item + `tabBOM` AS bom INNER JOIN {table} AS bom_item + ON bom.name = bom_item.parent LEFT JOIN `tabBin` AS ledger - ON bom_item.item_code = ledger.item_code + ON bom_item.item_code = ledger.item_code {conditions} WHERE bom_item.parent = '{bom}' and bom_item.parenttype='BOM' From 6fa6caf46cd983294ccb065f30a15fbb10949cd7 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 23 Apr 2020 09:46:29 +0530 Subject: [PATCH 65/69] fix: patch and validation message to fix target warehouse issue (#21371) --- erpnext/controllers/selling_controller.py | 9 +++ erpnext/patches.txt | 1 + ...ock_ledger_entries_for_target_warehouse.py | 71 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 2b21ee8aa4..90ba8b3644 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -46,6 +46,7 @@ class SellingController(StockController): set_default_income_account_for_item(self) self.set_customer_address() self.validate_for_duplicate_items() + self.validate_target_warehouse() def set_missing_values(self, for_validate=False): @@ -403,6 +404,14 @@ class SellingController(StockController): else: chk_dupl_itm.append(f) + def validate_target_warehouse(self): + items = self.get("items") + (self.get("packed_items") or []) + + for d in items: + if d.get("target_warehouse") and d.get("warehouse") == d.get("target_warehouse"): + warehouse = frappe.bold(d.get("target_warehouse")) + frappe.throw(_("Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same") + .format(d.idx, warehouse, warehouse)) def validate_items(self): # validate items to see if they have is_sales_item enabled diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9ef0b8d510..8478c1020d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -667,3 +667,4 @@ 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 +erpnext.patches.v12_0.repost_stock_ledger_entries_for_target_warehouse diff --git a/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py new file mode 100644 index 0000000000..13e935b2d3 --- /dev/null +++ b/erpnext/patches/v12_0/repost_stock_ledger_entries_for_target_warehouse.py @@ -0,0 +1,71 @@ +# Copyright (c) 2020, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + warehouse_perm = frappe.get_all("User Permission", + fields=["count(*) as p_count", "is_default", "user"], filters={"allow": "Warehouse"}, group_by="user") + + if not warehouse_perm: + return + + execute_patch = False + for perm_data in warehouse_perm: + if perm_data.p_count == 1 or (perm_data.p_count > 1 and frappe.get_all("User Permission", + filters = {"user": perm_data.user, "allow": "warehouse", "is_default": 1}, limit=1)): + execute_patch = True + break + + if not execute_patch: return + + for doctype in ["Sales Invoice", "Delivery Note"]: + if not frappe.get_meta(doctype + ' Item').get_field("target_warehouse").hidden: continue + + cond = "" + if doctype == "Sales Invoice": + cond = " AND parent_doc.update_stock = 1" + + data = frappe.db.sql(""" SELECT parent_doc.name as name, child_doc.name as child_name + FROM + `tab{doctype}` parent_doc, `tab{doctype} Item` child_doc + WHERE + parent_doc.name = child_doc.parent AND parent_doc.docstatus < 2 + AND child_doc.target_warehouse is not null AND child_doc.target_warehouse != '' + AND child_doc.creation > '2020-04-16' {cond} + """.format(doctype=doctype, cond=cond), as_dict=1) + + if data: + names = [d.child_name for d in data] + frappe.db.sql(""" UPDATE `tab{0} Item` set target_warehouse = null + WHERE name in ({1}) """.format(doctype, ','.join(["%s"] * len(names) )), tuple(names)) + + frappe.db.sql(""" UPDATE `tabPacked Item` set target_warehouse = null + WHERE parenttype = '{0}' and parent_detail_docname in ({1}) + """.format(doctype, ','.join(["%s"] * len(names) )), tuple(names)) + + parent_names = list(set([d.name for d in data])) + + for d in parent_names: + doc = frappe.get_doc(doctype, d) + if doc.docstatus != 1: continue + + doc.docstatus = 2 + doc.update_stock_ledger() + doc.make_gl_entries_on_cancel(repost_future_gle=False) + + # update stock & gl entries for submit state of PR + doc.docstatus = 1 + doc.update_stock_ledger() + doc.make_gl_entries() + + if frappe.get_meta('Sales Order Item').get_field("target_warehouse").hidden: + frappe.db.sql(""" UPDATE `tabSales Order Item` set target_warehouse = null + WHERE creation > '2020-04-16' and docstatus < 2 """) + + frappe.db.sql(""" UPDATE `tabPacked Item` set target_warehouse = null + WHERE creation > '2020-04-16' and docstatus < 2 and parenttype = 'Sales Order' """) + + + From 168babfebc2c1e7023747bf9f2a5d4f6edef7782 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 23 Apr 2020 09:48:50 +0530 Subject: [PATCH 66/69] fix: bom update cost issue (#21372) * fix: bom update cost is not working * added test case for bom cost --- erpnext/manufacturing/doctype/bom/bom.py | 3 +- .../bom_update_tool/bom_update_tool.py | 7 ++-- .../bom_update_tool/test_bom_update_tool.py | 32 ++++++++++++++++++- .../production_plan/test_production_plan.py | 6 ++-- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 6ccd12aed3..a83d193b6a 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -246,12 +246,13 @@ class BOM(WebsiteGenerator): if rate: d.rate = rate d.amount = flt(d.rate) * flt(d.qty) + d.db_update() if self.docstatus == 1: self.flags.ignore_validate_update_after_submit = True self.calculate_cost() if save: - self.save() + self.db_update() self.update_exploded_items() # update parent BOMs diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 2758a42371..e6c10ad12b 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -82,7 +82,7 @@ def enqueue_replace_bom(args): @frappe.whitelist() def enqueue_update_cost(): - frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost") + frappe.enqueue("erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_cost", timeout=40000) frappe.msgprint(_("Queued for updating latest price in all Bill of Materials. It may take a few minutes.")) def update_latest_price_in_all_boms(): @@ -98,6 +98,9 @@ def replace_bom(args): doc.replace_bom() def update_cost(): + frappe.db.auto_commit_on_many_writes = 1 bom_list = get_boms_in_bottom_up_order() for bom in bom_list: - frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) \ No newline at end of file + frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True) + + frappe.db.auto_commit_on_many_writes = 0 \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py index 154addf14e..ac9a409bcb 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py @@ -5,6 +5,9 @@ from __future__ import unicode_literals import unittest import frappe +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom +from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost test_records = frappe.get_test_records('BOM') @@ -27,4 +30,31 @@ class TestBOMUpdateTool(unittest.TestCase): # reverse, as it affects other testcases update_tool.current_bom = bom_doc.name update_tool.new_bom = current_bom - update_tool.replace_bom() \ No newline at end of file + update_tool.replace_bom() + + def test_bom_cost(self): + for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]: + item_doc = create_item(item, valuation_rate=100) + if item_doc.valuation_rate != 100.00: + frappe.db.set_value("Item", item_doc.name, "valuation_rate", 100) + + bom_no = frappe.db.get_value('BOM', {'item': 'BOM Cost Test Item 1'}, "name") + if not bom_no: + doc = make_bom(item = 'BOM Cost Test Item 1', + raw_materials =['BOM Cost Test Item 2', 'BOM Cost Test Item 3'], currency="INR") + else: + doc = frappe.get_doc("BOM", bom_no) + + self.assertEquals(doc.total_cost, 200) + + frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 200) + update_cost() + + doc.load_from_db() + self.assertEquals(doc.total_cost, 300) + + frappe.db.set_value("Item", "BOM Cost Test Item 2", "valuation_rate", 100) + update_cost() + + doc.load_from_db() + self.assertEquals(doc.total_cost, 200) diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index f70c9cc43f..26f580db33 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -192,9 +192,10 @@ def make_bom(**args): args = frappe._dict(args) bom = frappe.get_doc({ - 'doctype': "BOM", + 'doctype': 'BOM', 'is_default': 1, 'item': args.item, + 'currency': args.currency or 'USD', 'quantity': args.quantity or 1, 'company': args.company or '_Test Company' }) @@ -211,4 +212,5 @@ def make_bom(**args): }) bom.insert(ignore_permissions=True) - bom.submit() \ No newline at end of file + bom.submit() + return bom \ No newline at end of file From fa8396feb5114f9fd6b0739cc1052eaf908eb1ec Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 23 Apr 2020 10:34:57 +0530 Subject: [PATCH 67/69] fix: Budget against accounting dimensions (#21268) * fix: Budget warning against custom accounting dimension * fix: Codacy Co-authored-by: Nabin Hait --- erpnext/accounts/doctype/budget/budget.py | 66 +++++++++++-------- .../accounts/doctype/budget/test_budget.py | 37 ++++++----- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 084514cbfa..d93b6ffbaf 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -9,6 +9,7 @@ from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money, nowd from frappe.model.naming import make_autoname from erpnext.accounts.utils import get_fiscal_year from frappe.model.document import Document +from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions class BudgetError(frappe.ValidationError): pass class DuplicateBudgetError(frappe.ValidationError): pass @@ -98,30 +99,32 @@ def validate_expense_against_budget(args): if not (args.get('account') and args.get('cost_center')) and args.item_code: args.cost_center, args.account = get_item_details(args) - if not (args.cost_center or args.project) and not args.account: + if not args.account: return - for budget_against in ['project', 'cost_center']: + for budget_against in ['project', 'cost_center'] + get_accounting_dimensions(): if (args.get(budget_against) and args.account and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})): - if args.project and budget_against == 'project': - condition = "and b.project=%s" % frappe.db.escape(args.project) - args.budget_against_field = "Project" + doctype = frappe.unscrub(budget_against) - elif args.cost_center and budget_against == 'cost_center': - cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"]) - condition = """and exists(select name from `tabCost Center` - where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt) - args.budget_against_field = "Cost Center" + if frappe.get_cached_value('DocType', doctype, 'is_tree'): + lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"]) + condition = """and exists(select name from `tab%s` + where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec + args.is_tree = True + else: + condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against))) + args.is_tree = False - args.budget_against = args.get(budget_against) + args.budget_against_field = budget_against + args.budget_against_doctype = doctype budget_records = frappe.db.sql(""" select b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution, ifnull(b.applicable_on_material_request, 0) as for_material_request, - ifnull(applicable_on_purchase_order,0) as for_purchase_order, + ifnull(applicable_on_purchase_order, 0) as for_purchase_order, ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses, b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded, b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr, @@ -132,9 +135,7 @@ def validate_expense_against_budget(args): b.name=ba.parent and b.fiscal_year=%s and ba.account=%s and b.docstatus=1 {condition} - """.format(condition=condition, - budget_against_field=frappe.scrub(args.get("budget_against_field"))), - (args.fiscal_year, args.account), as_dict=True) + """.format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec if budget_records: validate_budget_records(args, budget_records) @@ -230,10 +231,10 @@ def get_ordered_amount(args, budget): def get_other_condition(args, budget, for_doc): condition = "expense_account = '%s'" % (args.expense_account) - budget_against_field = frappe.scrub(args.get("budget_against_field")) + budget_against_field = args.get("budget_against_field") if budget_against_field and args.get(budget_against_field): - condition += " and child.%s = '%s'" %(budget_against_field, args.get(budget_against_field)) + condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field)) if args.get('fiscal_year'): date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date' @@ -246,19 +247,30 @@ def get_other_condition(args, budget, for_doc): return condition def get_actual_expense(args): + if not args.budget_against_doctype: + args.budget_against_doctype = frappe.unscrub(args.budget_against_field) + + budget_against_field = args.get('budget_against_field') condition1 = " and gle.posting_date <= %(month_end_date)s" \ if args.get("month_end_date") else "" - if args.budget_against_field == "Cost Center": - lft_rgt = frappe.db.get_value(args.budget_against_field, - args.budget_against, ["lft", "rgt"], as_dict=1) + + if args.is_tree: + lft_rgt = frappe.db.get_value(args.budget_against_doctype, + args.get(budget_against_field), ["lft", "rgt"], as_dict=1) + args.update(lft_rgt) - condition2 = """and exists(select name from `tabCost Center` - where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)""" - elif args.budget_against_field == "Project": - condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)" + condition2 = """and exists(select name from `tab{doctype}` + where lft>=%(lft)s and rgt<=%(rgt)s + and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec + budget_against_field=budget_against_field) + else: + condition2 = """and exists(select name from `tab{doctype}` + where name=gle.{budget_against} and + gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype, + budget_against = budget_against_field) - return flt(frappe.db.sql(""" + amount = flt(frappe.db.sql(""" select sum(gle.debit) - sum(gle.credit) from `tabGL Entry` gle where gle.account=%(account)s @@ -267,7 +279,9 @@ def get_actual_expense(args): and gle.company=%(company)s and gle.docstatus=1 {condition2} - """.format(condition1=condition1, condition2=condition2), (args))[0][0]) + """.format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec + + return amount def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget): distribution = {} diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 33aefd67d1..9c19791d29 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -13,7 +13,7 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ class TestBudget(unittest.TestCase): def test_monthly_budget_crossed_ignore(self): - set_total_expense_zero("2013-02-28", "Cost Center") + set_total_expense_zero("2013-02-28", "cost_center") budget = make_budget(budget_against="Cost Center") @@ -26,7 +26,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_monthly_budget_crossed_stop1(self): - set_total_expense_zero("2013-02-28", "Cost Center") + set_total_expense_zero("2013-02-28", "cost_center") budget = make_budget(budget_against="Cost Center") @@ -41,7 +41,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_exception_approver_role(self): - set_total_expense_zero("2013-02-28", "Cost Center") + set_total_expense_zero("2013-02-28", "cost_center") budget = make_budget(budget_against="Cost Center") @@ -114,7 +114,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_monthly_budget_crossed_stop2(self): - set_total_expense_zero("2013-02-28", "Project") + set_total_expense_zero("2013-02-28", "project") budget = make_budget(budget_against="Project") @@ -129,7 +129,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_yearly_budget_crossed_stop1(self): - set_total_expense_zero("2013-02-28", "Cost Center") + set_total_expense_zero("2013-02-28", "cost_center") budget = make_budget(budget_against="Cost Center") @@ -141,7 +141,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_yearly_budget_crossed_stop2(self): - set_total_expense_zero("2013-02-28", "Project") + set_total_expense_zero("2013-02-28", "project") budget = make_budget(budget_against="Project") @@ -153,7 +153,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_monthly_budget_on_cancellation1(self): - set_total_expense_zero("2013-02-28", "Cost Center") + set_total_expense_zero("2013-02-28", "cost_center") budget = make_budget(budget_against="Cost Center") @@ -177,7 +177,7 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_monthly_budget_on_cancellation2(self): - set_total_expense_zero("2013-02-28", "Project") + set_total_expense_zero("2013-02-28", "project") budget = make_budget(budget_against="Project") @@ -201,8 +201,8 @@ class TestBudget(unittest.TestCase): budget.cancel() def test_monthly_budget_against_group_cost_center(self): - set_total_expense_zero("2013-02-28", "Cost Center") - set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC") + set_total_expense_zero("2013-02-28", "cost_center") + set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC") budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") @@ -241,25 +241,30 @@ class TestBudget(unittest.TestCase): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): - if budget_against_field == "Project": + if budget_against_field == "project": budget_against = "_Test Project" else: budget_against = budget_against_CC or "_Test Cost Center - _TC" - existing_expense = get_actual_expense(frappe._dict({ + + args = frappe._dict({ "account": "_Test Account Cost for Goods Sold - _TC", "cost_center": "_Test Cost Center - _TC", "monthly_end_date": posting_date, "company": "_Test Company", "fiscal_year": "_Test Fiscal Year 2013", "budget_against_field": budget_against_field, - "budget_against": budget_against - })) + }) + + if not args.get(budget_against_field): + args[budget_against_field] = budget_against + + existing_expense = get_actual_expense(args) if existing_expense: - if budget_against_field == "Cost Center": + if budget_against_field == "cost_center": make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) - elif budget_against_field == "Project": + elif budget_against_field == "project": make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28") From b1e68bd022f4ccd4860220a1a1fcb6fee90f1016 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:19:30 +0530 Subject: [PATCH 68/69] fix: Rename bank account type doctypes (#21179) * fix: Rename bank account type doctypes Co-authored-by: Saqib --- .../account_subtype/account_subtype.json | 134 ------------------ .../account_subtype/test_account_subtype.js | 23 --- .../doctype/account_type/account_type.js | 8 -- .../doctype/account_type/account_type.json | 134 ------------------ .../doctype/account_type/test_account_type.py | 9 -- .../doctype/bank_account/bank_account.json | 6 +- .../__init__.py | 0 .../bank_account_subtype.js} | 2 +- .../bank_account_subtype.json | 134 ++++++++++++++++++ .../bank_account_subtype.py} | 2 +- .../test_bank_account_subtype.js} | 6 +- .../test_bank_account_subtype.py} | 2 +- .../__init__.py | 0 .../bank_account_type/bank_account_type.js | 8 ++ .../bank_account_type/bank_account_type.json | 68 +++++++++ .../bank_account_type.py} | 5 +- .../test_bank_account_type.py | 10 ++ .../doctype/plaid_settings/plaid_settings.py | 8 +- .../plaid_settings/test_plaid_settings.py | 12 +- erpnext/patches.txt | 1 + .../v12_0/rename_account_type_doctype.py | 7 + 21 files changed, 250 insertions(+), 329 deletions(-) delete mode 100644 erpnext/accounts/doctype/account_subtype/account_subtype.json delete mode 100644 erpnext/accounts/doctype/account_subtype/test_account_subtype.js delete mode 100644 erpnext/accounts/doctype/account_type/account_type.js delete mode 100644 erpnext/accounts/doctype/account_type/account_type.json delete mode 100644 erpnext/accounts/doctype/account_type/test_account_type.py rename erpnext/accounts/doctype/{account_subtype => bank_account_subtype}/__init__.py (100%) rename erpnext/accounts/doctype/{account_subtype/account_subtype.js => bank_account_subtype/bank_account_subtype.js} (77%) create mode 100644 erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.json rename erpnext/accounts/doctype/{account_type/account_type.py => bank_account_subtype/bank_account_subtype.py} (86%) rename erpnext/accounts/doctype/{account_type/test_account_type.js => bank_account_subtype/test_bank_account_subtype.js} (69%) rename erpnext/accounts/doctype/{account_subtype/test_account_subtype.py => bank_account_subtype/test_bank_account_subtype.py} (78%) rename erpnext/accounts/doctype/{account_type => bank_account_type}/__init__.py (100%) create mode 100644 erpnext/accounts/doctype/bank_account_type/bank_account_type.js create mode 100644 erpnext/accounts/doctype/bank_account_type/bank_account_type.json rename erpnext/accounts/doctype/{account_subtype/account_subtype.py => bank_account_type/bank_account_type.py} (60%) create mode 100644 erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py create mode 100644 erpnext/patches/v12_0/rename_account_type_doctype.py diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.json b/erpnext/accounts/doctype/account_subtype/account_subtype.json deleted file mode 100644 index 6b1f2a2526..0000000000 --- a/erpnext/accounts/doctype/account_subtype/account_subtype.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:account_subtype", - "beta": 0, - "creation": "2018-10-25 15:46:08.054586", - "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": "account_subtype", - "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": "Account Subtype", - "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": "2018-10-25 15:47:03.841390", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Account Subtype", - "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": "Accounts 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": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "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/accounts/doctype/account_subtype/test_account_subtype.js b/erpnext/accounts/doctype/account_subtype/test_account_subtype.js deleted file mode 100644 index 5646763bbd..0000000000 --- a/erpnext/accounts/doctype/account_subtype/test_account_subtype.js +++ /dev/null @@ -1,23 +0,0 @@ -/* eslint-disable */ -// rename this file from _test_[name] to test_[name] to activate -// and remove above this line - -QUnit.test("test: Account Subtype", function (assert) { - let done = assert.async(); - - // number of asserts - assert.expect(1); - - frappe.run_serially([ - // insert a new Account Subtype - () => frappe.tests.make('Account Subtype', [ - // values to be set - {key: 'value'} - ]), - () => { - assert.equal(cur_frm.doc.key, 'value'); - }, - () => done() - ]); - -}); diff --git a/erpnext/accounts/doctype/account_type/account_type.js b/erpnext/accounts/doctype/account_type/account_type.js deleted file mode 100644 index 858b56c077..0000000000 --- a/erpnext/accounts/doctype/account_type/account_type.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('Account Type', { - refresh: function() { - - } -}); diff --git a/erpnext/accounts/doctype/account_type/account_type.json b/erpnext/accounts/doctype/account_type/account_type.json deleted file mode 100644 index 6b8f724b40..0000000000 --- a/erpnext/accounts/doctype/account_type/account_type.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:account_type", - "beta": 0, - "creation": "2018-10-25 15:45:45.789963", - "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": "account_type", - "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": "Account Type", - "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": "2018-10-25 15:46:51.042604", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Account Type", - "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": "Accounts 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": "Accounts User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 1, - "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/accounts/doctype/account_type/test_account_type.py b/erpnext/accounts/doctype/account_type/test_account_type.py deleted file mode 100644 index 824c2f66ae..0000000000 --- a/erpnext/accounts/doctype/account_type/test_account_type.py +++ /dev/null @@ -1,9 +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 TestAccountType(unittest.TestCase): - pass diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index aa9c434db0..65a0a5138c 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -64,13 +64,13 @@ "fieldname": "account_type", "fieldtype": "Link", "label": "Account Type", - "options": "Account Type" + "options": "Bank Account Type" }, { "fieldname": "account_subtype", "fieldtype": "Link", "label": "Account Subtype", - "options": "Account Subtype" + "options": "Bank Account Subtype" }, { "fieldname": "column_break_7", @@ -200,7 +200,7 @@ } ], "links": [], - "modified": "2020-01-30 20:42:26.458316", + "modified": "2020-04-06 21:00:45.379804", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", diff --git a/erpnext/accounts/doctype/account_subtype/__init__.py b/erpnext/accounts/doctype/bank_account_subtype/__init__.py similarity index 100% rename from erpnext/accounts/doctype/account_subtype/__init__.py rename to erpnext/accounts/doctype/bank_account_subtype/__init__.py diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.js b/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.js similarity index 77% rename from erpnext/accounts/doctype/account_subtype/account_subtype.js rename to erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.js index 30144adeea..f0456651c8 100644 --- a/erpnext/accounts/doctype/account_subtype/account_subtype.js +++ b/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.js @@ -1,7 +1,7 @@ // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.ui.form.on('Account Subtype', { +frappe.ui.form.on('Bank Account Subtype', { refresh: function() { } diff --git a/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.json b/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.json new file mode 100644 index 0000000000..f875db8ca1 --- /dev/null +++ b/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.json @@ -0,0 +1,134 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:account_subtype", + "beta": 0, + "creation": "2018-10-25 15:46:08.054586", + "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": "account_subtype", + "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": "Account Subtype", + "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": "2018-10-25 15:47:03.841390", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Account Subtype", + "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": "Accounts 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": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "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/accounts/doctype/account_type/account_type.py b/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.py similarity index 86% rename from erpnext/accounts/doctype/account_type/account_type.py rename to erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.py index 3e6429318b..ab52c4af77 100644 --- a/erpnext/accounts/doctype/account_type/account_type.py +++ b/erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals from frappe.model.document import Document -class AccountType(Document): +class BankAccountSubtype(Document): pass diff --git a/erpnext/accounts/doctype/account_type/test_account_type.js b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js similarity index 69% rename from erpnext/accounts/doctype/account_type/test_account_type.js rename to erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js index 76e434f4ab..f59999845a 100644 --- a/erpnext/accounts/doctype/account_type/test_account_type.js +++ b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js @@ -2,15 +2,15 @@ // rename this file from _test_[name] to test_[name] to activate // and remove above this line -QUnit.test("test: Account Type", function (assert) { +QUnit.test("test: Bank Account Subtype", function (assert) { let done = assert.async(); // number of asserts assert.expect(1); frappe.run_serially([ - // insert a new Account Type - () => frappe.tests.make('Account Type', [ + // insert a new Bank Account Subtype + () => frappe.tests.make('Bank Account Subtype', [ // values to be set {key: 'value'} ]), diff --git a/erpnext/accounts/doctype/account_subtype/test_account_subtype.py b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py similarity index 78% rename from erpnext/accounts/doctype/account_subtype/test_account_subtype.py rename to erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py index c37b5b9db7..ca3addc979 100644 --- a/erpnext/accounts/doctype/account_subtype/test_account_subtype.py +++ b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py @@ -5,5 +5,5 @@ from __future__ import unicode_literals import unittest -class TestAccountSubtype(unittest.TestCase): +class TestBankAccountSubtype(unittest.TestCase): pass diff --git a/erpnext/accounts/doctype/account_type/__init__.py b/erpnext/accounts/doctype/bank_account_type/__init__.py similarity index 100% rename from erpnext/accounts/doctype/account_type/__init__.py rename to erpnext/accounts/doctype/bank_account_type/__init__.py diff --git a/erpnext/accounts/doctype/bank_account_type/bank_account_type.js b/erpnext/accounts/doctype/bank_account_type/bank_account_type.js new file mode 100644 index 0000000000..4cfabe3d1d --- /dev/null +++ b/erpnext/accounts/doctype/bank_account_type/bank_account_type.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Bank Account Type', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/accounts/doctype/bank_account_type/bank_account_type.json b/erpnext/accounts/doctype/bank_account_type/bank_account_type.json new file mode 100644 index 0000000000..5a297cc2f9 --- /dev/null +++ b/erpnext/accounts/doctype/bank_account_type/bank_account_type.json @@ -0,0 +1,68 @@ +{ + "actions": [], + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:account_type", + "creation": "2018-10-25 15:45:45.789963", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account_type" + ], + "fields": [ + { + "fieldname": "account_type", + "fieldtype": "Data", + "label": "Account Type", + "unique": 1 + } + ], + "links": [], + "modified": "2020-04-10 21:13:09.137898", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Bank Account 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": "Accounts Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account_subtype/account_subtype.py b/erpnext/accounts/doctype/bank_account_type/bank_account_type.py similarity index 60% rename from erpnext/accounts/doctype/account_subtype/account_subtype.py rename to erpnext/accounts/doctype/bank_account_type/bank_account_type.py index 46c45cc733..b7dc0e0dc3 100644 --- a/erpnext/accounts/doctype/account_subtype/account_subtype.py +++ b/erpnext/accounts/doctype/bank_account_type/bank_account_type.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# 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 AccountSubtype(Document): +class BankAccountType(Document): pass diff --git a/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py b/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py new file mode 100644 index 0000000000..f04725a2e5 --- /dev/null +++ b/erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestBankAccountType(unittest.TestCase): + pass diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 7083950c56..b4a5bd11a0 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -67,11 +67,11 @@ def add_bank_accounts(response, bank, company): frappe.throw(_("Please setup a default bank account for company {0}").format(company)) for account in response["accounts"]: - acc_type = frappe.db.get_value("Account Type", account["type"]) + acc_type = frappe.db.get_value("Bank Account Type", account["type"]) if not acc_type: add_account_type(account["type"]) - acc_subtype = frappe.db.get_value("Account Subtype", account["subtype"]) + acc_subtype = frappe.db.get_value("Bank Account Subtype", account["subtype"]) if not acc_subtype: add_account_subtype(account["subtype"]) @@ -106,7 +106,7 @@ def add_bank_accounts(response, bank, company): def add_account_type(account_type): try: frappe.get_doc({ - "doctype": "Account Type", + "doctype": "Bank Account Type", "account_type": account_type }).insert() except Exception: @@ -116,7 +116,7 @@ def add_account_type(account_type): def add_account_subtype(account_subtype): try: frappe.get_doc({ - "doctype": "Account Subtype", + "doctype": "Bank Account Subtype", "account_subtype": account_subtype }).insert() except Exception: diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py index 29e8fa4fec..1a063d6b6f 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py @@ -23,11 +23,11 @@ class TestPlaidSettings(unittest.TestCase): for ba in frappe.get_all("Bank Account"): frappe.get_doc("Bank Account", ba.name).delete() - for at in frappe.get_all("Account Type"): - frappe.get_doc("Account Type", at.name).delete() + for at in frappe.get_all("Bank Account Type"): + frappe.get_doc("Bank Account Type", at.name).delete() - for ast in frappe.get_all("Account Subtype"): - frappe.get_doc("Account Subtype", ast.name).delete() + for ast in frappe.get_all("Bank Account Subtype"): + frappe.get_doc("Bank Account Subtype", ast.name).delete() def test_plaid_disabled(self): frappe.db.set_value("Plaid Settings", None, "enabled", 0) @@ -35,11 +35,11 @@ class TestPlaidSettings(unittest.TestCase): def test_add_account_type(self): add_account_type("brokerage") - self.assertEqual(frappe.get_doc("Account Type", "brokerage").name, "brokerage") + self.assertEqual(frappe.get_doc("Bank Account Type", "brokerage").name, "brokerage") def test_add_account_subtype(self): add_account_subtype("loan") - self.assertEqual(frappe.get_doc("Account Subtype", "loan").name, "loan") + self.assertEqual(frappe.get_doc("Bank Account Subtype", "loan").name, "loan") def test_default_bank_account(self): if not frappe.db.exists("Bank", "Citi"): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8478c1020d..765f911bef 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -662,6 +662,7 @@ erpnext.patches.v12_0.create_irs_1099_field_united_states erpnext.patches.v12_0.move_bank_account_swift_number_to_bank erpnext.patches.v12_0.rename_bank_reconciliation_fields # 2020-01-22 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 diff --git a/erpnext/patches/v12_0/rename_account_type_doctype.py b/erpnext/patches/v12_0/rename_account_type_doctype.py new file mode 100644 index 0000000000..ffb4e937b1 --- /dev/null +++ b/erpnext/patches/v12_0/rename_account_type_doctype.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.rename_doc('DocType', 'Account Type', 'Bank Account Type', force=True) + frappe.rename_doc('DocType', 'Account Subtype', 'Bank Account Subtype', force=True) + frappe.reload_doc('accounts', 'doctype', 'bank_account') \ No newline at end of file From 2daf6dad2e17e201e5ca14df5b9cade1f4cd698c Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 23 Apr 2020 13:51:29 +0530 Subject: [PATCH 69/69] fix: Allow creation of loan security pledge from Loan Application for old prices --- .../doctype/loan_application/loan_application.js | 2 +- .../doctype/loan_security_pledge/loan_security_pledge.py | 3 ++- .../doctype/loan_security_price/loan_security_price.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js index aba5f4260c..6cf47bf85c 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.js +++ b/erpnext/loan_management/doctype/loan_application/loan_application.js @@ -31,7 +31,7 @@ frappe.ui.form.on('Loan Application', { add_toolbar_buttons: function(frm) { if (frm.doc.status == "Approved") { - if (frm.doc.is_secured) { + if (frm.doc.is_secured_loan) { frappe.db.get_value("Loan Security Pledge", {"loan_application": frm.doc.name, "docstatus": 1}, "name", (r) => { if (!r) { frm.add_custom_button(__('Loan Security Pledge'), function() { diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index b405ccae55..eb6135868d 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -30,7 +30,8 @@ class LoanSecurityPledge(Document): if not pledge.qty and not pledge.amount: frappe.throw(_("Qty or Amount is mandatroy for loan security")) - pledge.loan_security_price = get_loan_security_price(pledge.loan_security) + if not (self.loan_application and pledge.loan_security_price): + pledge.loan_security_price = get_loan_security_price(pledge.loan_security) if not pledge.qty: pledge.qty = cint(pledge.amount/pledge.loan_security_price) diff --git a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py index 2855b52610..32d81afed5 100644 --- a/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py +++ b/erpnext/loan_management/doctype/loan_security_price/loan_security_price.py @@ -37,7 +37,7 @@ def get_loan_security_price(loan_security, valid_time=None): }, 'loan_security_price') if not loan_security_price: - frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(loan_security))) + frappe.throw(_("No valid Loan Security Price found for {0}").format(frappe.bold(loan_security))) else: return loan_security_price