diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 0d5ac9d5126..4197d547340 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '10.1.74' +__version__ = '10.1.76' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index 5162c29604c..13d53d1f6ab 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -37,8 +37,8 @@ frappe.ui.form.on('POS Profile', { return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} }; }); - frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'use_pos_in_offline_mode', (r) => { - is_offline = r && cint(r.use_pos_in_offline_mode) + frappe.db.get_value('POS Settings', 'POS Settings', 'use_pos_in_offline_mode', (r) => { + const is_offline = r && cint(r.use_pos_in_offline_mode) frm.toggle_display('offline_pos_section', is_offline); frm.toggle_display('print_format_for_online', !is_offline); }); diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index a089b4c91b9..077c396c71b 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -4,7 +4,7 @@ "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 1, - "autoname": "field:pos_profile_name", + "autoname": "Prompt", "beta": 0, "creation": "2013-05-24 12:15:51", "custom": 0, @@ -52,6 +52,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "", "fieldname": "section_break_2", "fieldtype": "Section Break", "hidden": 0, @@ -76,38 +77,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pos_profile_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": "POS Profile Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 1 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -142,6 +111,240 @@ "translatable": 0, "unique": 0 }, + { + "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, + "oldfieldname": "customer_account", + "oldfieldtype": "Link", + "options": "Customer", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_from": "company.country", + "fieldname": "country", + "fieldtype": "Read Only", + "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": "Country", + "length": 0, + "no_copy": 0, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "update_stock", + "fieldname": "warehouse", + "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": "Warehouse", + "length": 0, + "no_copy": 0, + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "campaign", + "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": "Campaign", + "length": 0, + "no_copy": 0, + "options": "Campaign", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "company_address", + "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": "Company Address", + "length": 0, + "no_copy": 0, + "options": "Address", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "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 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -374,207 +577,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_4", - "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 - }, - { - "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, - "oldfieldname": "customer_account", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "company.country", - "fieldname": "country", - "fieldtype": "Read Only", - "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": "Country", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "update_stock", - "fieldname": "warehouse", - "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": "Warehouse", - "length": 0, - "no_copy": 0, - "oldfieldname": "warehouse", - "oldfieldtype": "Link", - "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "campaign", - "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": "Campaign", - "length": 0, - "no_copy": 0, - "options": "Campaign", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "depends_on": "", "fieldname": "section_break_15", "fieldtype": "Section Break", "hidden": 0, @@ -649,6 +652,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Mode of Payment", "length": 0, "no_copy": 0, "permlevel": 0, @@ -714,6 +718,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "", "length": 0, "no_copy": 0, "permlevel": 0, @@ -736,6 +741,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "description": "Only show Items from these Item Groups", "fieldname": "item_groups", "fieldtype": "Table", "hidden": 0, @@ -800,6 +806,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "description": "Only show Customer of these Customer Groups", "fieldname": "customer_groups", "fieldtype": "Table", "hidden": 0, @@ -843,6 +850,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Print Settings", "length": 0, "no_copy": 0, "permlevel": 0, @@ -925,40 +933,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "select_print_heading", - "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": "Print Heading", - "length": 0, - "no_copy": 0, - "oldfieldname": "select_print_heading", - "oldfieldtype": "Select", - "options": "Print Heading", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -990,40 +964,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "selling_price_list", - "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": "Price List", - "length": 0, - "no_copy": 0, - "oldfieldname": "price_list_name", - "oldfieldtype": "Select", - "options": "Price List", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1061,110 +1001,11 @@ { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "apply_discount", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Discount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Grand Total", - "depends_on": "", - "fieldname": "apply_discount_on", - "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": "Apply Discount On", - "length": 0, - "no_copy": 0, - "options": "Grand Total\nNet Total", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company_address_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": "Company Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company_address", + "fieldname": "select_print_heading", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -1173,12 +1014,13 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Company Address Name", + "label": "Print Heading", "length": 0, "no_copy": 0, - "options": "Address", + "oldfieldname": "select_print_heading", + "oldfieldtype": "Select", + "options": "Print Heading", "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -1207,7 +1049,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Offline POS Section", + "label": "Offline POS Settings", "length": 0, "no_copy": 0, "permlevel": 0, @@ -1389,6 +1231,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "selling_price_list", + "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": "Price List", + "length": 0, + "no_copy": 0, + "oldfieldname": "price_list_name", + "oldfieldtype": "Select", + "options": "Price List", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1688,6 +1564,41 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Grand Total", + "depends_on": "", + "fieldname": "apply_discount_on", + "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": "Apply Discount On", + "length": 0, + "no_copy": 0, + "options": "Grand Total\nNet Total", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -1701,7 +1612,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-12-03 14:16:08.589778", + "modified": "2018-12-13 13:36:22.045519", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", @@ -1749,11 +1660,11 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, - "search_fields": "pos_profile_name", + "search_fields": "", "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "pos_profile_name", + "title_field": "", "track_changes": 0, "track_seen": 0, "track_views": 0 diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index bf2e20c248a..723ef4366ad 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -127,25 +127,26 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): 'txt': '%%%s%%' % txt } - pos_profile = frappe.db.sql("""select pf.name, pf.pos_profile_name + pos_profile = frappe.db.sql("""select pf.name from `tabPOS Profile` pf, `tabPOS Profile User` pfu where pfu.parent = pf.name and pfu.user = %(user)s and pf.company = %(company)s - and (pf.name like %(txt)s or pf.pos_profile_name like %(txt)s) + and (pf.name like %(txt)s) and pf.disabled = 0 limit %(start)s, %(page_len)s""", args) if not pos_profile: del args['user'] - pos_profile = frappe.db.sql("""select pf.name, pf.pos_profile_name + pos_profile = frappe.db.sql("""select pf.name from `tabPOS Profile` pf left join `tabPOS Profile User` pfu on pf.name = pfu.parent where - ifnull(pfu.user, '') = '' and pf.company = %(company)s and - (pf.name like %(txt)s or pf.pos_profile_name like %(txt)s) + ifnull(pfu.user, '') = '' + and pf.company = %(company)s + and pf.name like %(txt)s and pf.disabled = 0""", args) return pos_profile diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py index c1b033cddab..58f12162d14 100644 --- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py @@ -40,7 +40,6 @@ def make_pos_profile(): "expense_account": "_Test Account Cost for Goods Sold - _TC", "income_account": "Sales - _TC", "name": "_Test POS Profile", - "pos_profile_name": "_Test POS Profile", "naming_series": "_T-POS Profile-", "selling_price_list": "_Test Price List", "territory": "_Test Territory", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 1bb7c97b0de..bfdf451f440 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -830,6 +830,10 @@ class PurchaseInvoice(BuyingController): return tax_withholding_details = get_party_tax_withholding_details(self) + + if not tax_withholding_details: + return + accounts = [] for d in self.taxes: if d.account_head == tax_withholding_details.get("account_head"): @@ -839,6 +843,12 @@ class PurchaseInvoice(BuyingController): if not accounts or tax_withholding_details.get("account_head") not in accounts: self.append("taxes", tax_withholding_details) + to_remove = [d for d in self.taxes + if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")] + + for d in to_remove: + self.remove(d) + # calculate totals again after applying TDS self.calculate_taxes_and_totals() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f3ec775d549..6072fb895cd 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -324,7 +324,8 @@ class SalesInvoice(SellingController): return { "print_format": print_format, "allow_edit_rate": pos.get("allow_user_to_edit_rate"), - "allow_edit_discount": pos.get("allow_user_to_edit_discount") + "allow_edit_discount": pos.get("allow_user_to_edit_discount"), + "campaign": pos.get("campaign") } def update_time_sheet(self, sales_invoice): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 94037c7d0a0..f9364e2d847 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals @@ -375,7 +375,7 @@ class TestSalesInvoice(unittest.TestCase): si.insert() self.assertEqual(si.net_total, 4600) - + self.assertEqual(si.get("taxes")[0].tax_amount, 874.0) self.assertEqual(si.get("taxes")[0].total, 5474.0) @@ -405,12 +405,12 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.total, 975) self.assertEqual(si.net_total, 900) - + self.assertEqual(si.get("taxes")[0].tax_amount, 216.0) self.assertEqual(si.get("taxes")[0].total, 1116.0) self.assertEqual(si.grand_total, 1116.0) - + def test_inclusive_rate_validations(self): si = frappe.copy_doc(test_records[2]) for i, tax in enumerate(si.get("taxes")): @@ -552,7 +552,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(si.grand_total, 1215.90) self.assertEqual(si.rounding_adjustment, 0.01) self.assertEqual(si.base_rounding_adjustment, 0.50) - + def test_outstanding(self): w = self.make() @@ -762,6 +762,20 @@ class TestSalesInvoice(unittest.TestCase): set_perpetual_inventory(0) frappe.db.sql("delete from `tabPOS Profile`") + + def test_pos_si_without_payment(self): + set_perpetual_inventory() + make_pos_profile() + + pos = copy.deepcopy(test_records[1]) + pos["is_pos"] = 1 + pos["update_stock"] = 1 + + si = frappe.copy_doc(pos) + si.insert() + + # Check that the invoice cannot be submitted without payments + self.assertRaises(frappe.ValidationError, si.submit) def test_sales_invoice_gl_entry_with_perpetual_inventory_no_item_code(self): set_perpetual_inventory() @@ -923,7 +937,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertRaises(SerialNoWarehouseError, si.submit) def test_serial_numbers_against_delivery_note(self): - """ + """ check if the sales invoice item serial numbers and the delivery note items serial numbers are same """ @@ -1238,7 +1252,7 @@ class TestSalesInvoice(unittest.TestCase): def test_item_wise_tax_breakup_india(self): frappe.flags.country = "India" - + si = self.create_si_to_test_tax_breakup() itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) @@ -1256,12 +1270,12 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(itemised_tax, expected_itemised_tax) self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) - + frappe.flags.country = None def test_item_wise_tax_breakup_outside_india(self): frappe.flags.country = "United States" - + si = self.create_si_to_test_tax_breakup() itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si) @@ -1287,7 +1301,7 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(itemised_tax, expected_itemised_tax) self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount) - + frappe.flags.country = None def create_si_to_test_tax_breakup(self): @@ -1375,7 +1389,7 @@ class TestSalesInvoice(unittest.TestCase): shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test") si = frappe.copy_doc(test_records[2]) - + si.shipping_rule = shipping_rule.name si.insert() @@ -1392,14 +1406,14 @@ class TestSalesInvoice(unittest.TestCase): "cost_center": shipping_rule.cost_center, "tax_amount": shipping_amount, "description": shipping_rule.name - } + } si.append("taxes", shipping_charge) si.save() self.assertEqual(si.net_total, 1250) self.assertEqual(si.total_taxes_and_charges, 577.05) - self.assertEqual(si.grand_total, 1827.05) + self.assertEqual(si.grand_total, 1827.05) def test_create_invoice_without_terms(self): si = create_sales_invoice(do_not_save=1) @@ -1496,7 +1510,7 @@ class TestSalesInvoice(unittest.TestCase): for gle in gl_entries: self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) - + accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 accounts_settings.save() @@ -1524,9 +1538,9 @@ def create_sales_invoice(**args): "warehouse": args.warehouse or "_Test Warehouse - _TC", "qty": args.qty or 1, "rate": args.rate or 100, - "income_account": "Sales - _TC", - "expense_account": "Cost of Goods Sold - _TC", - "cost_center": "_Test Cost Center - _TC", + "income_account": args.income_account or "Sales - _TC", + "expense_account": args.expense_account or "Cost of Goods Sold - _TC", + "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no }) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index f553cc09eca..6c31e9efed1 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -24,6 +24,7 @@ def get_party_tax_withholding_details(ref_doc): .format(tax_withholding_category, ref_doc.company)) tds_amount = get_tds_amount(ref_doc, tax_details, fy) tax_row = get_tax_row(tax_details, tds_amount) + return tax_row def get_tax_withholding_details(tax_withholding_category, fiscal_year, company): @@ -62,46 +63,64 @@ def get_tax_row(tax_details, tds_amount): def get_tds_amount(ref_doc, tax_details, fiscal_year_details): fiscal_year, year_start_date, year_end_date = fiscal_year_details tds_amount = 0 + tds_deducted = 0 - def _get_tds(): - tds_amount = 0 - if not tax_details.threshold or ref_doc.net_total >= tax_details.threshold: - tds_amount = ref_doc.net_total * tax_details.rate / 100 - return tds_amount + def _get_tds(amount): + if amount <= 0: + return 0 - if tax_details.cumulative_threshold: - entries = frappe.db.sql(""" + return amount * tax_details.rate / 100 + + entries = frappe.db.sql(""" select voucher_no, credit from `tabGL Entry` where party=%s and fiscal_year=%s and credit > 0 """, (ref_doc.supplier, fiscal_year), as_dict=1) - supplier_credit_amount = flt(sum([d.credit for d in entries])) + vouchers = [d.voucher_no for d in entries] + advance_vouchers = get_advance_vouchers(ref_doc.supplier, fiscal_year) - vouchers = [d.voucher_no for d in entries] - vouchers += get_advance_vouchers(ref_doc.supplier, fiscal_year) + tds_vouchers = vouchers + advance_vouchers - tds_deducted = 0 - if vouchers: - tds_deducted = flt(frappe.db.sql(""" - select sum(credit) - from `tabGL Entry` - where account=%s and fiscal_year=%s and credit > 0 - and voucher_no in ({0}) - """.format(', '.join(["'%s'" % d for d in vouchers])), - (tax_details.account_head, fiscal_year))[0][0]) + if tds_vouchers: + tds_deducted = frappe.db.sql(""" + SELECT sum(credit) FROM `tabGL Entry` + WHERE + account=%s and fiscal_year=%s and credit > 0 + and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))), + ((tax_details.account_head, fiscal_year) + tuple(tds_vouchers))) + + tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0 + + if tds_deducted: + tds_amount = _get_tds(ref_doc.net_total) + else: + supplier_credit_amount = frappe.get_all('Purchase Invoice Item', + fields = ['sum(net_amount)'], + filters = {'parent': ('in', vouchers), 'docstatus': 1}, as_list=1) + + supplier_credit_amount = (supplier_credit_amount[0][0] + if supplier_credit_amount and supplier_credit_amount[0][0] else 0) + + jv_supplier_credit_amt = frappe.get_all('Journal Entry Account', + fields = ['sum(credit_in_account_currency)'], + filters = { + 'parent': ('in', vouchers), 'docstatus': 1, + 'party': ref_doc.supplier, + 'reference_type': ('not in', ['Purchase Invoice']) + }, as_list=1) + + supplier_credit_amount += (jv_supplier_credit_amt[0][0] + if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0) + + supplier_credit_amount += ref_doc.net_total debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date) + supplier_credit_amount -= debit_note_amount - total_invoiced_amount = supplier_credit_amount + tds_deducted \ - + flt(ref_doc.net_total) - debit_note_amount - if total_invoiced_amount >= tax_details.cumulative_threshold: - total_applicable_tds = total_invoiced_amount * tax_details.rate / 100 - tds_amount = min(total_applicable_tds - tds_deducted, ref_doc.net_total) - else: - tds_amount = _get_tds() - else: - tds_amount = _get_tds() + if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold) + or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)): + tds_amount = _get_tds(supplier_credit_amount) return tds_amount @@ -114,7 +133,7 @@ def get_advance_vouchers(supplier, fiscal_year=None, company=None, from_date=Non select distinct voucher_no from `tabGL Entry` where party=%s and %s and debit > 0 - """, (supplier, condition)) + """, (supplier, condition)) or [] def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None): condition = "" @@ -126,4 +145,4 @@ def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None from `tabPurchase Invoice` where supplier=%s %s and is_return=1 and docstatus=1 and posting_date between %s and %s - """, (supplier, condition, year_start_date, year_end_date))) + """, (supplier, condition, year_start_date, year_end_date))) \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 20e1746e36a..25301967080 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe import unittest from frappe.utils import today +from erpnext.accounts.utils import get_fiscal_year test_dependencies = ["Supplier Group"] @@ -14,65 +15,105 @@ class TestTaxWithholdingCategory(unittest.TestCase): def setUpClass(self): # create relevant supplier, etc create_records() + create_tax_with_holding_category() - def test_single_threshold_tds(self): - frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194D - Individual") - pi = create_purchase_invoice() + def test_cumulative_threshold_tds(self): + frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS") + invoices = [] + + # create invoices for lower than single threshold tax rate + for _ in xrange(2): + pi = create_purchase_invoice(supplier = "Test TDS Supplier") + pi.submit() + invoices.append(pi) + + # create another invoice whose total when added to previously created invoice, + # surpasses cumulative threshhold + pi = create_purchase_invoice(supplier = "Test TDS Supplier") pi.submit() - self.assertEqual(pi.taxes_and_charges_deducted, 800) - self.assertEqual(pi.grand_total, 15200) + # assert equal tax deduction on total invoice amount uptil now + self.assertEqual(pi.taxes_and_charges_deducted, 3000) + self.assertEqual(pi.grand_total, 7000) + invoices.append(pi) + + # TDS is already deducted, so from onward system will deduct the TDS on every invoice + pi = create_purchase_invoice(supplier = "Test TDS Supplier", rate=5000) + pi.submit() + + # assert equal tax deduction on total invoice amount uptil now + self.assertEqual(pi.taxes_and_charges_deducted, 500) + invoices.append(pi) + + #delete invoices to avoid clashing + for d in invoices: + d.cancel() + frappe.delete_doc("Purchase Invoice", d.name) + + def test_single_threshold_tds(self): + invoices = [] + frappe.db.set_value("Supplier", "Test TDS Supplier1", "tax_withholding_category", "Single Threshold TDS") + pi = create_purchase_invoice(supplier = "Test TDS Supplier1", rate = 20000) + pi.submit() + invoices.append(pi) + + self.assertEqual(pi.taxes_and_charges_deducted, 2000) + self.assertEqual(pi.grand_total, 18000) # check gl entry for the purchase invoice gl_entries = frappe.db.get_all('GL Entry', filters={'voucher_no': pi.name}, fields=["*"]) self.assertEqual(len(gl_entries), 3) for d in gl_entries: if d.account == pi.credit_to: - self.assertEqual(d.credit, 15200) + self.assertEqual(d.credit, 18000) elif d.account == pi.items[0].get("expense_account"): - self.assertEqual(d.debit, 16000) + self.assertEqual(d.debit, 20000) elif d.account == pi.taxes[0].get("account_head"): - self.assertEqual(d.credit, 800) + self.assertEqual(d.credit, 2000) else: raise ValueError("Account head does not match.") - # delete purchase invoice to avoid it interefering in other tests - pi.cancel() - frappe.delete_doc('Purchase Invoice', pi.name) - - def test_cumulative_threshold_tds(self): - frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "TDS - 194C - Individual") - invoices = [] - - # create invoices for lower than single threshold tax rate - for _ in xrange(6): - pi = create_purchase_invoice() - pi.submit() - invoices.append(pi) - - # create another invoice whose total when added to previously created invoice, - # surpasses cumulative threshhold - pi = create_purchase_invoice() + pi = create_purchase_invoice(supplier = "Test TDS Supplier1") pi.submit() - - # assert equal tax deduction on total invoice amount uptil now - self.assertEqual(pi.taxes_and_charges_deducted, 1120) - self.assertEqual(pi.grand_total, 14880) invoices.append(pi) + # TDS amount is 1000 because in previous invoices it's already deducted + self.assertEqual(pi.taxes_and_charges_deducted, 1000) + # delete invoices to avoid clashing for d in invoices: d.cancel() frappe.delete_doc("Purchase Invoice", d.name) -def create_purchase_invoice(qty=1): + def test_single_threshold_tds_with_previous_vouchers(self): + invoices = [] + frappe.db.set_value("Supplier", "Test TDS Supplier2", "tax_withholding_category", "Single Threshold TDS") + pi = create_purchase_invoice(supplier="Test TDS Supplier2") + pi.submit() + invoices.append(pi) + + pi = create_purchase_invoice(supplier="Test TDS Supplier2") + pi.submit() + invoices.append(pi) + + self.assertEqual(pi.taxes_and_charges_deducted, 2000) + self.assertEqual(pi.grand_total, 8000) + + # delete invoices to avoid clashing + for d in invoices: + d.cancel() + frappe.delete_doc("Purchase Invoice", d.name) + +def create_purchase_invoice(**args): # return sales invoice doc object item = frappe.get_doc('Item', {'item_name': 'TDS Item'}) + + args = frappe._dict(args) pi = frappe.get_doc({ "doctype": "Purchase Invoice", "posting_date": today(), "apply_tds": 1, - "supplier": frappe.get_doc('Supplier', {"supplier_name": "Test TDS Supplier"}).name, + "supplier": args.supplier, "company": '_Test Company', "taxes_and_charges": "", "currency": "INR", @@ -81,8 +122,8 @@ def create_purchase_invoice(qty=1): "items": [{ 'doctype': 'Purchase Invoice Item', 'item_code': item.name, - 'qty': qty, - 'rate': 16000, + 'qty': args.qty or 1, + 'rate': args.rate or 10000, 'cost_center': 'Main - _TC', 'expense_account': 'Stock Received But Not Billed - _TC' }] @@ -92,20 +133,73 @@ def create_purchase_invoice(qty=1): return pi def create_records(): - # create a new supplier - frappe.get_doc({ - "supplier_group": "_Test Supplier Group", - "supplier_name": "Test TDS Supplier", - "doctype": "Supplier", - "tax_withholding_category": "TDS - 194D - Individual" - }).insert() + # create a new suppliers + for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']: + if frappe.db.exists('Supplier', name): + continue + + frappe.get_doc({ + "supplier_group": "_Test Supplier Group", + "supplier_name": name, + "doctype": "Supplier", + }).insert() # create an item - frappe.get_doc({ - "doctype": "Item", - "item_code": "TDS Item", - "item_name": "TDS Item", - "item_group": "All Item Groups", - "company": "_Test Company", - "is_stock_item": 0, - }).insert() \ No newline at end of file + if not frappe.db.exists('Item', "TDS Item"): + frappe.get_doc({ + "doctype": "Item", + "item_code": "TDS Item", + "item_name": "TDS Item", + "item_group": "All Item Groups", + "is_stock_item": 0, + }).insert() + + # create an account + if not frappe.db.exists("Account", "TDS - _TC"): + frappe.get_doc({ + 'doctype': 'Account', + 'company': '_Test Company', + 'account_name': 'TDS', + 'parent_account': 'Tax Assets - _TC', + 'report_type': 'Balance Sheet', + 'root_type': 'Asset' + }).insert() + +def create_tax_with_holding_category(): + fiscal_year = get_fiscal_year(today(), company="_Test Company")[0] + + # Cummulative thresold + if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Cumulative Threshold TDS", + "category_name": "10% TDS", + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 0, + 'cumulative_threshold': 30000.00 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() + + # Single thresold + if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"): + frappe.get_doc({ + "doctype": "Tax Withholding Category", + "name": "Single Threshold TDS", + "category_name": "10% TDS", + "rates": [{ + 'fiscal_year': fiscal_year, + 'tax_withholding_rate': 10, + 'single_threshold': 20000.00, + 'cumulative_threshold': 0 + }], + "accounts": [{ + 'company': '_Test Company', + 'account': 'TDS - _TC' + }] + }).insert() \ No newline at end of file diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 91f3711f8d9..04c8718ad9a 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -1135,16 +1135,18 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }, apply_category: function() { - var me = this; - category = this.selected_item_group || "All Item Groups"; + frappe.db.get_value("Item Group", {lft: 1, is_group: 1}, "name", (r) => { + category = this.selected_item_group || r.name; - if(category == 'All Item Groups') { - return this.item_data - } else { - return this.item_data.filter(function(element, index, array){ - return element.item_group == category; - }); - } + if(category == r.name) { + return this.item_data + } else { + return this.item_data.filter(function(element, index, array){ + return element.item_group == category; + }); + } + }) + }, bind_items_event: function() { diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7a91bd2dc5e..1085cddab5f 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, msgprint, scrub -from frappe.defaults import get_user_permissions +from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.model.utils import get_fetch_values from frappe.utils import (add_days, getdate, formatdate, date_diff, add_years, get_timestamp, nowdate, flt, add_months, get_last_day) @@ -151,10 +151,7 @@ def get_default_price_list(party): def set_price_list(out, party, party_type, given_price_list): # price list - price_list = filter(None, get_user_permissions() - .get("Price List", {}) - .get("docs", [])) - price_list = list(price_list) + price_list = get_permitted_documents('Price List') if price_list: price_list = price_list[0] diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index b1bdce95c84..bbfee1112f6 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -107,6 +107,11 @@ frappe.query_reports["Accounts Receivable"] = { "label": __("Show PDC in Print"), "fieldtype": "Check", }, + { + "fieldname":"based_on_payment_terms", + "label": __("Based On Payment Terms"), + "fieldtype": "Check", + }, { "fieldname":"tax_id", "label": __("Tax Id"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 8e05a087af7..121d5b02133 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, scrub -from frappe.utils import getdate, nowdate, flt, cint +from frappe.utils import getdate, nowdate, flt, cint, formatdate, cstr class ReceivablePayableReport(object): def __init__(self, filters=None): @@ -57,6 +57,21 @@ class ReceivablePayableReport(object): credit_or_debit_note = "Credit Note" if args.get("party_type") == "Customer" else "Debit Note" + if self.filters.based_on_payment_terms: + columns.append({ + "label": "Payment Term", + "fieldname": "payment_term", + "fieldtype": "Data", + "width": 120 + }) + columns.append({ + "label": "Invoice Grand Total", + "fieldname": "invoice_grand_total", + "fieldtype": "Currency", + "options": "currency", + "width": 120 + }) + for label in ("Invoiced Amount", "Paid Amount", credit_or_debit_note, "Outstanding Amount"): columns.append({ "label": label, @@ -97,12 +112,6 @@ class ReceivablePayableReport(object): "options": "Currency", "width": 100 }, - { - "fieldname": "pdc/lc_date", - "label": _("PDC/LC Date"), - "fieldtype": "Date", - "width": 110 - }, { "fieldname": "pdc/lc_ref", "label": _("PDC/LC Ref"), @@ -113,14 +122,14 @@ class ReceivablePayableReport(object): "fieldname": "pdc/lc_amount", "label": _("PDC/LC Amount"), "fieldtype": "Currency", - "options": "Currency", + "options": "currency", "width": 130 }, { "fieldname": "remaining_balance", "label": _("Remaining Balance"), "fieldtype": "Currency", - "options": "Currency", + "options": "currency", "width": 130 }] @@ -151,108 +160,203 @@ class ReceivablePayableReport(object): def get_data(self, party_naming_by, args): from erpnext.accounts.utils import get_currency_precision - currency_precision = get_currency_precision() or 2 - dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" + self.currency_precision = get_currency_precision() or 2 + self.dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type")) if not self.filters.get("company"): self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') - company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") + self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") return_entries = self.get_return_entries(args.get("party_type")) data = [] - pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date) + self.pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date) gl_entries_data = self.get_entries_till(self.filters.report_date, args.get("party_type")) if gl_entries_data: voucher_nos = [d.voucher_no for d in gl_entries_data] or [] dn_details = get_dn_details(args.get("party_type"), voucher_nos) - voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) + self.voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) + + if self.filters.based_on_payment_terms: + self.payment_term_map = self.get_payment_term_detail(voucher_nos) for gle in gl_entries_data: - if self.is_receivable_or_payable(gle, dr_or_cr, future_vouchers): - outstanding_amount, credit_note_amount = self.get_outstanding_amount(gle, - self.filters.report_date, dr_or_cr, return_entries, currency_precision) - if abs(outstanding_amount) > 0.1/10**currency_precision: - row = [gle.posting_date, gle.party] + if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers): + outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( + gle,self.filters.report_date, self.dr_or_cr, return_entries) - # customer / supplier name - if party_naming_by == "Naming Series": - row += [self.get_party_name(gle.party_type, gle.party)] + temp_outstanding_amt = outstanding_amount + temp_credit_note_amt = credit_note_amount - # get due date - due_date = voucher_details.get(gle.voucher_no, {}).get("due_date", "") - bill_date = voucher_details.get(gle.voucher_no, {}).get("bill_date", "") + if abs(outstanding_amount) > 0.1/10**self.currency_precision: + if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): + for d in self.payment_term_map.get(gle.voucher_no): + # Allocate payment amount based on payment terms(FIFO order) + payment_amount, d.payment_amount = self.allocate_based_on_fifo(payment_amount, d.payment_term_amount) - row += [gle.voucher_type, gle.voucher_no, due_date] + term_outstanding_amount = d.payment_term_amount - d.payment_amount - # get supplier bill details - if args.get("party_type") == "Supplier": - row += [ - voucher_details.get(gle.voucher_no, {}).get("bill_no", ""), - voucher_details.get(gle.voucher_no, {}).get("bill_date", "") - ] + # Allocate credit note based on payment terms(FIFO order) + credit_note_amount, d.credit_note_amount = self.allocate_based_on_fifo(credit_note_amount, term_outstanding_amount) - # invoiced and paid amounts - invoiced_amount = gle.get(dr_or_cr) if (gle.get(dr_or_cr) > 0) else 0 - paid_amt = invoiced_amount - outstanding_amount - credit_note_amount - row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] + term_outstanding_amount -= d.credit_note_amount + + row_outstanding = term_outstanding_amount + # Allocate PDC based on payment terms(FIFO order) + d.pdc_details, d.pdc_amount = self.allocate_pdc_amount_in_fifo(gle, row_outstanding) + + if term_outstanding_amount > 0: + row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount, + d.credit_note_amount, d.due_date, d.payment_amount , d.payment_term_amount, + d.description, d.pdc_amount, d.pdc_details) + data.append(row) + + if credit_note_amount: + row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, temp_outstanding_amt, + temp_credit_note_amt) + data.append(row) - # ageing data - if self.filters.ageing_based_on == "Due Date": - entry_date = due_date - elif self.filters.ageing_based_on == "Supplier Invoice Date": - entry_date = bill_date else: - entry_date = gle.posting_date - - row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), - cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount) - - - # issue 6371-Ageing buckets should not have amounts if due date is not reached - if self.filters.ageing_based_on == "Due Date" \ - and getdate(due_date) > getdate(self.filters.report_date): - row[-1]=row[-2]=row[-3]=row[-4]=0 - - if self.filters.ageing_based_on == "Supplier Invoice Date" \ - and getdate(bill_date) > getdate(self.filters.report_date): - - row[-1]=row[-2]=row[-3]=row[-4]=0 - - if self.filters.get(scrub(args.get("party_type"))): - row.append(gle.account_currency) - else: - row.append(company_currency) - - pdc = pdc_details.get((gle.voucher_no, gle.party), {}) - - remaining_balance = outstanding_amount - flt(pdc.get("pdc_amount")) - row += [pdc.get("pdc_date"), pdc.get("pdc_ref"), - flt(pdc.get("pdc_amount")), remaining_balance] - - if args.get('party_type') == 'Customer': - # customer LPO - row += [voucher_details.get(gle.voucher_no, {}).get("po_no")] - - # Delivery Note - row += [voucher_details.get(gle.voucher_no, {}).get("delivery_note")] - - # customer territory / supplier group - if args.get("party_type") == "Customer": - row += [self.get_territory(gle.party), self.get_customer_group(gle.party), - voucher_details.get(gle.voucher_no, {}).get("sales_person")] - if args.get("party_type") == "Supplier": - row += [self.get_supplier_group(gle.party)] - - row.append(gle.remarks) - data.append(row) - + row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, outstanding_amount, + credit_note_amount) + data.append(row) return data + def allocate_pdc_amount_in_fifo(self, gle, row_outstanding): + pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) + + pdc_details = [] + pdc_amount = 0 + for pdc in pdc_list: + if row_outstanding <= pdc.pdc_amount: + pdc_amount += row_outstanding + pdc.pdc_amount -= row_outstanding + if row_outstanding and pdc.pdc_ref and pdc.pdc_date: + pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date)) + row_outstanding = 0 + + else: + pdc_amount = pdc.pdc_amount + if pdc.pdc_amount and pdc.pdc_ref and pdc.pdc_date: + pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date)) + pdc.pdc_amount = 0 + row_outstanding -= pdc_amount + + return pdc_details, pdc_amount + + def prepare_row_without_payment_terms(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount): + pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) + pdc_amount = 0 + pdc_details = [] + for d in pdc_list: + pdc_amount += flt(d.pdc_amount) + if pdc_amount and d.pdc_ref and d.pdc_date: + pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) + + row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, + credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) + + return row + + + def allocate_based_on_fifo(self, total_amount, row_amount): + allocated_amount = 0 + if row_amount <= total_amount: + allocated_amount = row_amount + total_amount -= row_amount + else: + allocated_amount = total_amount + total_amount = 0 + + return total_amount, allocated_amount + + def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, + due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None, pdc_details=None): + row = [gle.posting_date, gle.party] + + # customer / supplier name + if party_naming_by == "Naming Series": + row += [self.get_party_name(gle.party_type, gle.party)] + + # get due date + if not due_date: + due_date = self.voucher_details.get(gle.voucher_no, {}).get("due_date", "") + bill_date = self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "") + + row += [gle.voucher_type, gle.voucher_no, due_date] + + # get supplier bill details + if args.get("party_type") == "Supplier": + row += [ + self.voucher_details.get(gle.voucher_no, {}).get("bill_no", ""), + self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "") + ] + + # invoiced and paid amounts + invoiced_amount = gle.get(self.dr_or_cr) if (gle.get(self.dr_or_cr) > 0) else 0 + + if self.filters.based_on_payment_terms: + row+=[payment_term, invoiced_amount] + if payment_term_amount: + invoiced_amount = payment_term_amount + + if not payment_term_amount: + paid_amt = invoiced_amount - outstanding_amount - credit_note_amount + row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] + + # ageing data + if self.filters.ageing_based_on == "Due Date": + entry_date = due_date + elif self.filters.ageing_based_on == "Supplier Invoice Date": + entry_date = bill_date + else: + entry_date = gle.posting_date + + row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), + cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount) + + + # issue 6371-Ageing buckets should not have amounts if due date is not reached + if self.filters.ageing_based_on == "Due Date" \ + and getdate(due_date) > getdate(self.filters.report_date): + row[-1]=row[-2]=row[-3]=row[-4]=0 + + if self.filters.ageing_based_on == "Supplier Invoice Date" \ + and getdate(bill_date) > getdate(self.filters.report_date): + + row[-1]=row[-2]=row[-3]=row[-4]=0 + + if self.filters.get(scrub(args.get("party_type"))): + row.append(gle.account_currency) + else: + row.append(self.company_currency) + + remaining_balance = outstanding_amount - flt(pdc_amount) + pdc_details = ", ".join(pdc_details) + row += [pdc_details, pdc_amount, remaining_balance] + + if args.get('party_type') == 'Customer': + # customer LPO + row += [self.voucher_details.get(gle.voucher_no, {}).get("po_no")] + + # Delivery Note + row += [self.voucher_details.get(gle.voucher_no, {}).get("delivery_note")] + + # customer territory / supplier group + if args.get("party_type") == "Customer": + row += [self.get_territory(gle.party), self.get_customer_group(gle.party), + self.voucher_details.get(gle.voucher_no, {}).get("sales_person")] + if args.get("party_type") == "Supplier": + row += [self.get_supplier_group(gle.party)] + + row.append(gle.remarks) + + return row + def get_entries_after(self, report_date, party_type): # returns a distinct list return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries(party_type, report_date, for_future=True)])) @@ -280,25 +384,25 @@ class ReceivablePayableReport(object): doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice" return [d.name for d in frappe.get_all(doctype, filters={"is_return": 1, "docstatus": 1})] - def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries, currency_precision): + def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries): payment_amount, credit_note_amount = 0.0, 0.0 reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit" for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no): if getdate(e.posting_date) <= report_date and e.name!=gle.name: - amount = flt(e.get(reverse_dr_or_cr), currency_precision) - flt(e.get(dr_or_cr), currency_precision) + amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision) if e.voucher_no not in return_entries: payment_amount += amount else: credit_note_amount += amount - outstanding_amount = (flt((flt(gle.get(dr_or_cr), currency_precision) - - flt(gle.get(reverse_dr_or_cr), currency_precision) - - payment_amount - credit_note_amount), currency_precision)) + outstanding_amount = (flt((flt(gle.get(dr_or_cr), self.currency_precision) + - flt(gle.get(reverse_dr_or_cr), self.currency_precision) + - payment_amount - credit_note_amount), self.currency_precision)) - credit_note_amount = flt(credit_note_amount, currency_precision) + credit_note_amount = flt(credit_note_amount, self.currency_precision) - return outstanding_amount, credit_note_amount + return outstanding_amount, credit_note_amount, payment_amount def get_party_name(self, party_type, party_name): return self.get_party_map(party_type).get(party_name, {}).get("customer_name" if party_type == "Customer" else "supplier_name") or "" @@ -383,7 +487,7 @@ class ReceivablePayableReport(object): conditions.append("""party in (select name from tabCustomer where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1} and name=tabCustomer.customer_group))""".format(lft, rgt)) - + if self.filters.get("territory"): lft, rgt = frappe.db.get_value("Territory", self.filters.get("territory"), ["lft", "rgt"]) @@ -415,7 +519,7 @@ class ReceivablePayableReport(object): conditions.append("""party in (select name from tabSupplier where supplier_group=%s)""") values.append(self.filters.get("supplier_group")) - + return " and ".join(conditions), values def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher): @@ -432,6 +536,31 @@ class ReceivablePayableReport(object): .get(against_voucher_type, {})\ .get(against_voucher, []) + def get_payment_term_detail(self, voucher_nos): + payment_term_map = frappe._dict() + payment_terms_details = frappe.db.sql(""" select si.name, + party_account_currency, currency, si.conversion_rate, + ps.due_date, ps.payment_amount, ps.description + from `tabSales Invoice` si, `tabPayment Schedule` ps + where si.name = ps.parent and + si.docstatus = 1 and si.company = '%s' and + si.name in (%s) order by ps.due_date + """ % (frappe.db.escape(self.filters.company), ','.join(['%s'] *len(voucher_nos))), + (tuple(voucher_nos)), as_dict = 1) + + for d in payment_terms_details: + if self.filters.get("customer") and d.currency == d.party_account_currency: + payment_term_amount = d.payment_amount + else: + payment_term_amount = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + + payment_term_map.setdefault(d.name, []).append(frappe._dict({ + "due_date": d.due_date, + "payment_term_amount": payment_term_amount, + "description": d.description + })) + return payment_term_map + def get_chart_data(self, columns, data): ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4] @@ -479,12 +608,11 @@ def get_ageing_data(first_range, second_range, third_range, age_as_on, entry_dat def get_pdc_details(party_type, report_date): pdc_details = frappe._dict() - - for pdc in frappe.db.sql(""" + pdc_via_pe = frappe.db.sql(""" select pref.reference_name as invoice_no, pent.party, pent.party_type, - max(pent.posting_date) as pdc_date, sum(ifnull(pref.allocated_amount,0)) as pdc_amount, - GROUP_CONCAT(pent.reference_no SEPARATOR ', ') as pdc_ref + pent.posting_date as pdc_date, ifnull(pref.allocated_amount,0) as pdc_amount, + pent.reference_no as pdc_ref from `tabPayment Entry` as pent inner join `tabPayment Entry Reference` as pref on @@ -492,19 +620,22 @@ def get_pdc_details(party_type, report_date): where pent.docstatus < 2 and pent.posting_date > %s and pent.party_type = %s - group by pent.party, pref.reference_name""", (report_date, party_type), as_dict=1): - pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc) + """, (report_date, party_type), as_dict=1) + + for pdc in pdc_via_pe: + pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc) + if scrub(party_type): amount_field = ("jea.debit_in_account_currency" if party_type == 'Supplier' else "jea.credit_in_account_currency") else: amount_field = "jea.debit + jea.credit" - for pdc in frappe.db.sql(""" + pdc_via_je = frappe.db.sql(""" select jea.reference_name as invoice_no, jea.party, jea.party_type, - max(je.posting_date) as pdc_date, sum(ifnull({0},0)) as pdc_amount, - GROUP_CONCAT(je.cheque_no SEPARATOR ', ') as pdc_ref + je.posting_date as pdc_date, ifnull({0},0) as pdc_amount, + je.cheque_no as pdc_ref from `tabJournal Entry` as je inner join `tabJournal Entry Account` as jea on @@ -512,16 +643,10 @@ def get_pdc_details(party_type, report_date): where je.docstatus < 2 and je.posting_date > %s and jea.party_type = %s - group by jea.party, jea.reference_name""".format(amount_field), (report_date, party_type), as_dict=1): - if (pdc.invoice_no, pdc.party) in pdc_details: - key = (pdc.invoice_no, pdc.party) - pdc_details[key]["pdc_amount"] += pdc.pdc_amount - if pdc.pdc_ref: - pdc_details[key]["pdc_ref"] += ", " + pdc.pdc_ref - if pdc.pdc_date: - pdc_details[key]["pdc_date"] = max(pdc_details[key]["pdc_date"], pdc.pdc_date) - else: - pdc_details.setdefault((pdc.invoice_no, pdc.party), pdc) + """.format(amount_field), (report_date, party_type), as_dict=1) + + for pdc in pdc_via_je: + pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc) return pdc_details diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py new file mode 100644 index 00000000000..34e6c83e016 --- /dev/null +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -0,0 +1,84 @@ +import frappe +import frappe.defaults +import unittest +from frappe.utils import today, getdate, add_days +from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + +class TestAccountsReceivable(unittest.TestCase): + def test_accounts_receivable(self): + frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'") + frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") + + filters = { + 'company': '_Test Company 2', + 'based_on_payment_terms': 1 + } + + name = make_sales_invoice() + report = execute(filters) + + expected_data = [[100,30], [100,50], [100,20]] + + self.assertEqual(expected_data[0], report[1][0][6:8]) + self.assertEqual(expected_data[1], report[1][1][6:8]) + self.assertEqual(expected_data[2], report[1][2][6:8]) + + make_payment(name) + report = execute(filters) + + expected_data_after_payment = [[100,50], [100,20]] + + self.assertEqual(expected_data_after_payment[0], report[1][0][6:8]) + self.assertEqual(expected_data_after_payment[1], report[1][1][6:8]) + + make_credit_note(name) + report = execute(filters) + + expected_data_after_credit_note = [[100,100,30,100,-30]] + + self.assertEqual(expected_data_after_credit_note[0], report[1][0][6:11]) + + +def make_sales_invoice(): + frappe.set_user("Administrator") + + si = create_sales_invoice(company="_Test Company 2", + customer = '_Test Customer 2', + currency = 'EUR', + warehouse = 'Finished Goods - _TC2', + debit_to = 'Debtors - _TC2', + income_account = 'Sales - _TC2', + expense_account = 'Cost of Goods Sold - _TC2', + cost_center = '_Test Company 2 - _TC2', + do_not_save=1) + + si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30)) + si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50)) + si.append('payment_schedule', dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20)) + + si.submit() + + return si.name + +def make_payment(docname): + pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30) + pe.paid_from = "Debtors - _TC2" + pe.insert() + pe.submit() + + +def make_credit_note(docname): + create_sales_invoice(company="_Test Company 2", + customer = '_Test Customer 2', + currency = 'EUR', + qty = -1, + warehouse = 'Finished Goods - _TC2', + debit_to = 'Debtors - _TC2', + income_account = 'Sales - _TC2', + expense_account = 'Cost of Goods Sold - _TC2', + cost_center = '_Test Company 2 - _TC2', + is_return = 1, + return_against = docname) + diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index db733dd1e10..09cf5b1d2fc 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -213,7 +213,7 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency): total = 0 row = frappe._dict({ "account": _(d.name), - "parent_account": _(d.parent_account), + "parent_account": _(d.parent_account) if d.parent_account else '', "indent": flt(d.indent), "year_start_date": year_start_date, "year_end_date": year_end_date, diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 7e50d9be61d..8c3deaff484 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -25,7 +25,7 @@ def execute(filters=None): account_details.setdefault(acc.name, acc) if filters.get('party'): - parties = str(filters.get("party")).strip() + parties = cstr(filters.get("party")).strip() filters.party = [d.strip() for d in parties.split(',') if d] validate_filters(filters, account_details) @@ -60,11 +60,11 @@ def validate_filters(filters, account_details): frappe.throw(_("From Date must be before To Date")) if filters.get('project'): - projects = str(filters.get("project")).strip() + projects = cstr(filters.get("project")).strip() filters.project = [d.strip() for d in projects.split(',') if d] if filters.get('cost_center'): - cost_centers = str(filters.get("cost_center")).strip() + cost_centers = cstr(filters.get("cost_center")).strip() filters.cost_center = [d.strip() for d in cost_centers.split(',') if d] diff --git a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py index 05c8fb78de1..c234da0fe39 100644 --- a/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py +++ b/erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py @@ -108,33 +108,33 @@ def get_conditions(filters): def get_pos_invoice_data(filters): conditions = get_conditions(filters) result = frappe.db.sql('' - 'SELECT ' - 'posting_date, owner, sum(net_total) as "net_total", sum(total_taxes) as "total_taxes", ' - 'sum(paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount", ' - 'mode_of_payment, warehouse, cost_center ' - 'FROM (' - 'SELECT ' - 'parent, item_code, sum(amount) as "base_total", warehouse, cost_center ' - 'from `tabSales Invoice Item` group by parent' - ') t1 ' - 'left join ' - '(select parent, mode_of_payment from `tabSales Invoice Payment` group by parent) t3 ' - 'on (t3.parent = t1.parent) ' - 'JOIN (' - 'SELECT ' - 'docstatus, company, is_pos, name, posting_date, owner, sum(base_total) as "base_total", ' - 'sum(net_total) as "net_total", sum(total_taxes_and_charges) as "total_taxes", ' - 'sum(base_paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount" ' - 'FROM `tabSales Invoice` ' - 'GROUP BY name' - ') a ' - 'ON (' - 't1.parent = a.name and t1.base_total = a.base_total) ' - 'WHERE a.docstatus = 1' - ' AND {conditions} ' - 'GROUP BY ' - 'owner, posting_date, warehouse'.format(conditions=conditions), filters, as_dict=1 - ) + 'SELECT ' + 'posting_date, owner, sum(net_total) as "net_total", sum(total_taxes) as "total_taxes", ' + 'sum(paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount", ' + 'mode_of_payment, warehouse, cost_center ' + 'FROM (' + 'SELECT ' + 'parent, item_code, sum(amount) as "base_total", warehouse, cost_center ' + 'from `tabSales Invoice Item` group by parent' + ') t1 ' + 'left join ' + '(select parent, mode_of_payment from `tabSales Invoice Payment` group by parent) t3 ' + 'on (t3.parent = t1.parent) ' + 'JOIN (' + 'SELECT ' + 'docstatus, company, is_pos, name, posting_date, owner, sum(base_total) as "base_total", ' + 'sum(net_total) as "net_total", sum(total_taxes_and_charges) as "total_taxes", ' + 'sum(base_paid_amount) as "paid_amount", sum(outstanding_amount) as "outstanding_amount" ' + 'FROM `tabSales Invoice` ' + 'GROUP BY name' + ') a ' + 'ON (' + 't1.parent = a.name and t1.base_total = a.base_total) ' + 'WHERE a.docstatus = 1' + ' AND {conditions} ' + 'GROUP BY ' + 'owner, posting_date, warehouse'.format(conditions=conditions), filters, as_dict=1 + ) return result @@ -156,7 +156,6 @@ def get_sales_invoice_data(filters): def get_mode_of_payments(filters): - frappe.log_error(filters, 'filters') mode_of_payments = {} invoice_list = get_invoices(filters) invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) @@ -164,12 +163,14 @@ def get_mode_of_payments(filters): inv_mop = frappe.db.sql("""select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment from `tabSales Invoice` a, `tabSales Invoice Payment` b where a.name = b.parent + and a.docstatus = 1 and a.name in ({invoice_list_names}) union select a.owner,a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c where a.name = c.reference_name and b.name = c.parent + and b.docstatus = 1 and a.name in ({invoice_list_names}) union select a.owner, a.posting_date, @@ -196,13 +197,13 @@ def get_invoices(filters): def get_mode_of_payment_details(filters): mode_of_payment_details = {} invoice_list = get_invoices(filters) - frappe.log_error(invoice_list, 'invoice_list') invoice_list_names = ",".join(['"' + invoice['name'] + '"' for invoice in invoice_list]) if invoice_list: inv_mop_detail = frappe.db.sql("""select a.owner, a.posting_date, ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount from `tabSales Invoice` a, `tabSales Invoice Payment` b where a.name = b.parent + and a.docstatus = 1 and a.name in ({invoice_list_names}) group by a.owner, a.posting_date, mode_of_payment union @@ -211,6 +212,7 @@ def get_mode_of_payment_details(filters): from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c where a.name = c.reference_name and b.name = c.parent + and b.docstatus = 1 and a.name in ({invoice_list_names}) group by a.owner, a.posting_date, mode_of_payment union diff --git a/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py new file mode 100644 index 00000000000..62843e74efc --- /dev/null +++ b/erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py @@ -0,0 +1,165 @@ +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import unittest +import frappe +from erpnext.accounts.report.sales_payment_summary.sales_payment_summary import get_mode_of_payments, get_mode_of_payment_details +from frappe.utils import today +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry + +test_dependencies = ["Sales Invoice"] + +class TestSalesPaymentSummary(unittest.TestCase): + @classmethod + def setUpClass(self): + create_records() + pes = frappe.get_all("Payment Entry") + jes = frappe.get_all("Journal Entry") + sis = frappe.get_all("Sales Invoice") + for pe in pes: + frappe.db.set_value("Payment Entry", pe.name, "docstatus", 2) + for je in jes: + frappe.db.set_value("Journal Entry", je.name, "docstatus", 2) + for si in sis: + frappe.db.set_value("Sales Invoice", si.name, "docstatus", 2) + + def test_get_mode_of_payments(self): + filters = get_filters() + + for dummy in range(2): + si = create_sales_invoice_record() + si.insert() + si.submit() + + if int(si.name[-3:])%2 == 0: + bank_account = "_Test Cash - _TC" + mode_of_payment = "Cash" + else: + bank_account = "_Test Bank - _TC" + mode_of_payment = "Credit Card" + + pe = get_payment_entry("Sales Invoice", si.name, bank_account=bank_account) + pe.reference_no = "_Test" + pe.reference_date = today() + pe.mode_of_payment = mode_of_payment + pe.insert() + pe.submit() + + mop = get_mode_of_payments(filters) + self.assertTrue('Credit Card' in mop.values()[0]) + self.assertTrue('Cash' in mop.values()[0]) + + # Cancel all Cash payment entry and check if this mode of payment is still fetched. + payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Cash", "docstatus": 1}, fields=["name", "docstatus"]) + for payment_entry in payment_entries: + pe = frappe.get_doc("Payment Entry", payment_entry.name) + pe.cancel() + + mop = get_mode_of_payments(filters) + self.assertTrue('Credit Card' in mop.values()[0]) + self.assertTrue('Cash' not in mop.values()[0]) + + def test_get_mode_of_payments_details(self): + filters = get_filters() + + for dummy in range(2): + si = create_sales_invoice_record() + si.insert() + si.submit() + + if int(si.name[-3:])%2 == 0: + bank_account = "_Test Cash - _TC" + mode_of_payment = "Cash" + else: + bank_account = "_Test Bank - _TC" + mode_of_payment = "Credit Card" + + pe = get_payment_entry("Sales Invoice", si.name, bank_account=bank_account) + pe.reference_no = "_Test" + pe.reference_date = today() + pe.mode_of_payment = mode_of_payment + pe.insert() + pe.submit() + + mopd = get_mode_of_payment_details(filters) + + mopd_values = mopd.values()[0] + for mopd_value in mopd_values: + if mopd_value[0] == "Credit Card": + cc_init_amount = mopd_value[1] + + # Cancel one Credit Card Payment Entry and check that it is not fetched in mode of payment details. + payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Credit Card", "docstatus": 1}, fields=["name", "docstatus"]) + for payment_entry in payment_entries[:1]: + pe = frappe.get_doc("Payment Entry", payment_entry.name) + pe.cancel() + + mopd = get_mode_of_payment_details(filters) + mopd_values = mopd.values()[0] + for mopd_value in mopd_values: + if mopd_value[0] == "Credit Card": + cc_final_amount = mopd_value[1] + + self.assertTrue(cc_init_amount > cc_final_amount) + +def get_filters(): + return { + "from_date": "1900-01-01", + "to_date": today(), + "company": "_Test Company" + } + +def create_sales_invoice_record(qty=1): + # return sales invoice doc object + return frappe.get_doc({ + "doctype": "Sales Invoice", + "customer": frappe.get_doc('Customer', {"customer_name": "Prestiga-Biz"}).name, + "company": '_Test Company', + "due_date": today(), + "posting_date": today(), + "currency": "INR", + "taxes_and_charges": "", + "debit_to": "Debtors - _TC", + "taxes": [], + "items": [{ + 'doctype': 'Sales Invoice Item', + 'item_code': frappe.get_doc('Item', {'item_name': 'Consulting'}).name, + 'qty': qty, + "rate": 10000, + 'income_account': 'Sales - _TC', + 'cost_center': 'Main - _TC', + 'expense_account': 'Cost of Goods Sold - _TC' + }] + }) + +def create_records(): + if frappe.db.exists("Customer", "Prestiga-Biz"): + return + + #customer + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "Prestiga-Biz", + "customer_type": "Company", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + # item + item = frappe.get_doc({ + "doctype": "Item", + "item_code": "Consulting", + "item_name": "Consulting", + "item_group": "All Item Groups", + "company": "_Test Company", + "is_stock_item": 0 + }).insert() + + # item price + frappe.get_doc({ + "doctype": "Item Price", + "price_list": "Standard Selling", + "item_code": item.item_code, + "price_list_rate": 10000 + }).insert() \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index a505e4976a5..019d0de4708 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -315,7 +315,6 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order", source_doctype: "Material Request", target: me.frm, - args: args, setters: { company: me.frm.doc.company }, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index ed761ceb908..3c4ef2b17fe 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -91,6 +91,7 @@ class PurchaseOrder(BuyingController): self.party_account_currency = get_party_account_currency("Supplier", self.supplier, self.company) def validate_minimum_order_qty(self): + if not self.get("items"): return items = list(set([d.item_code for d in self.get("items")])) itemwise_min_order_qty = frappe._dict(frappe.db.sql("""select name, min_order_qty diff --git a/erpnext/buying/report/purchase_analytics/purchase_analytics.js b/erpnext/buying/report/purchase_analytics/purchase_analytics.js index b55046e0655..e17973c337b 100644 --- a/erpnext/buying/report/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/report/purchase_analytics/purchase_analytics.js @@ -77,9 +77,20 @@ frappe.query_reports["Purchase Analytics"] = { events: { onCheckRow: function(data) { row_name = data[2].content; - row_values = data.slice(5).map(function (column) { - return column.content; - }) + length = data.length; + + var tree_type = frappe.query_report.filters[0].value; + + if(tree_type == "Supplier" || tree_type == "Item") { + row_values = data.slice(4,length-1).map(function (column) { + return column.content; + }) + } + else { + row_values = data.slice(3,length-1).map(function (column) { + return column.content; + }) + } entry = { 'name':row_name, diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py index c399f3eee27..5aff6bacaea 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py @@ -65,8 +65,8 @@ def get_quantity_list(item): if item: qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` - where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1) - qty_list.sort(reverse=False) + where ifnull(item_code,'')=%s and docstatus < 2 order by qty""", item, as_dict=1) + for qt in qty_list: col = frappe._dict({ "key": str(qt.qty), diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index 15996c3a44b..9d8e1bf3c34 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -561,10 +561,6 @@ def get_data(): "label": _("Subscription Management"), "icon": "fa fa-microchip ", "items": [ - { - "type": "doctype", - "name": "Subscriber", - }, { "type": "doctype", "name": "Subscription Plan", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index ee6dc2a473a..541e56d781c 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -678,7 +678,7 @@ class BuyingController(StockController): frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) def validate_schedule_date(self): - if not self.schedule_date: + if not self.schedule_date and self.get("items"): self.schedule_date = min([d.schedule_date for d in self.get("items")]) if self.schedule_date: diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 7b3f7404626..63e89ab6e35 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -12,6 +12,9 @@ from erpnext.controllers.accounts_controller import AccountsController from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock import get_warehouse_account_map +class QualityInspectionRequiredError(frappe.ValidationError): pass +class QualityInspectionRejectedError(frappe.ValidationError): pass + class StockController(AccountsController): def validate(self): super(StockController, self).validate() @@ -317,7 +320,6 @@ class StockController(AccountsController): def validate_inspection(self): '''Checks if quality inspection is set for Items that require inspection. On submit, throw an exception''' - inspection_required_fieldname = None if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: inspection_required_fieldname = "inspection_required_before_purchase" @@ -330,17 +332,25 @@ class StockController(AccountsController): return for d in self.get('items'): - raise_exception = False + qa_required = False if (inspection_required_fieldname and not d.quality_inspection and frappe.db.get_value("Item", d.item_code, inspection_required_fieldname)): - raise_exception = True + qa_required = True elif self.doctype == "Stock Entry" and not d.quality_inspection and d.t_warehouse: - raise_exception = True + qa_required = True - if raise_exception: + if qa_required: frappe.msgprint(_("Quality Inspection required for Item {0}").format(d.item_code)) if self.docstatus==1: - raise frappe.ValidationError + raise QualityInspectionRequiredError + elif self.docstatus == 1: + if d.quality_inspection: + qa_doc = frappe.get_doc("Quality Inspection", d.quality_inspection) + qa_failed = any([r.status=="Rejected" for r in qa_doc.readings]) + if qa_failed: + frappe.throw(_("Row {0}: Quality Inspection rejected for item {1}") + .format(d.idx, d.item_code), QualityInspectionRejectedError) + def update_blanket_order(self): blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order])) diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index 0fd7bb7ac8a..48dcdbe8638 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -376,7 +376,7 @@ def setup_pos_profile(): company_abbr = frappe.get_cached_value('Company', erpnext.get_default_company(), "abbr") pos = frappe.new_doc('POS Profile') pos.user = frappe.db.get_global('demo_accounts_user') - pos.pos_profile_name = "Demo POS Profile" + pos.name = "Demo POS Profile" pos.naming_series = 'SINV-' pos.update_stock = 0 pos.write_off_account = 'Cost of Goods Sold - '+ company_abbr diff --git a/erpnext/education/report/student_fee_collection/student_fee_collection.json b/erpnext/education/report/student_fee_collection/student_fee_collection.json index 07fc27c7a08..eb945cfffb8 100644 --- a/erpnext/education/report/student_fee_collection/student_fee_collection.json +++ b/erpnext/education/report/student_fee_collection/student_fee_collection.json @@ -1,18 +1,18 @@ { "add_total_row": 0, - "apply_user_permissions": 1, "creation": "2016-06-22 02:58:41.024538", "disabled": 0, "docstatus": 0, "doctype": "Report", "idx": 3, "is_standard": "Yes", - "modified": "2017-11-10 19:41:37.320224", + "modified": "2018-12-17 16:46:46.176620", "modified_by": "Administrator", "module": "Education", "name": "Student Fee Collection", "owner": "Administrator", - "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(paid_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student", + "prepared_report": 0, + "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(grand_total) - sum(outstanding_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student", "ref_doctype": "Fees", "report_name": "Student Fee Collection", "report_type": "Query Report", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index cf8c06d700e..3ab6752d8cd 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)" source_link = "https://github.com/frappe/erpnext" develop_version = '12.x.x-develop' -staging_version = '11.0.3-beta.30' +staging_version = '11.0.3-beta.33' error_report_email = "support@erpnext.com" diff --git a/erpnext/hr/doctype/department/test_department.py b/erpnext/hr/doctype/department/test_department.py index da696613f91..2eeca26e303 100644 --- a/erpnext/hr/doctype/department/test_department.py +++ b/erpnext/hr/doctype/department/test_department.py @@ -1,9 +1,24 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import frappe +import unittest test_ignore = ["Leave Block List"] +class TestDepartment(unittest.TestCase): + def test_remove_department_data(self): + doc = create_department("Test Department") + frappe.delete_doc('Department', doc.name) +def create_department(department_name, parent_department=None): + doc = frappe.get_doc({ + 'doctype': 'Department', + 'is_group': 0, + 'parent_department': parent_department, + 'department_name': department_name, + 'company': frappe.defaults.get_defaults().company + }).insert() + + return doc -import frappe test_records = frappe.get_test_records('Department') \ No newline at end of file diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index cb4c1908fb3..d518cd89957 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -13,8 +13,8 @@ from frappe.model.document import Document from erpnext.utilities.transaction_base import delete_events from frappe.utils.nestedset import NestedSet -class EmployeeUserDisabledError(frappe.ValidationError): - pass +class EmployeeUserDisabledError(frappe.ValidationError): pass +class EmployeeLeftValidationError(frappe.ValidationError): pass class Employee(NestedSet): nsm_parent_field = 'reports_to' @@ -62,8 +62,8 @@ class Employee(NestedSet): def validate_user_details(self): data = frappe.db.get_value('User', self.user_id, ['enabled', 'user_image'], as_dict=1) - - self.image = data.get("user_image") + if data.get("user_image"): + self.image = data.get("user_image") self.validate_for_enabled_user_id(data.get("enabled", 0)) self.validate_duplicate_user_id() @@ -147,8 +147,16 @@ class Employee(NestedSet): validate_email_add(self.personal_email, True) def validate_status(self): - if self.status == 'Left' and not self.relieving_date: - throw(_("Please enter relieving date.")) + if self.status == 'Left': + reports_to = frappe.db.get_all('Employee', + filters={'reports_to': self.name} + ) + if reports_to: + link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name) for employee in reports_to] + throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ") + + ', '.join(link_to_employees), EmployeeLeftValidationError) + if not self.relieving_date: + throw(_("Please enter relieving date.")) def validate_for_enabled_user_id(self, enabled): if not self.status == 'Active': diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py index 1afb8f40b47..5a63beb2836 100644 --- a/erpnext/hr/doctype/employee/test_employee.py +++ b/erpnext/hr/doctype/employee/test_employee.py @@ -7,6 +7,7 @@ import frappe import erpnext import unittest import frappe.utils +from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError test_records = frappe.get_test_records('Employee') @@ -32,6 +33,18 @@ class TestEmployee(unittest.TestCase): email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True) self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message) + def test_employee_status_left(self): + employee1 = make_employee("test_employee_1@company.com") + employee2 = make_employee("test_employee_2@company.com") + employee1_doc = frappe.get_doc("Employee", employee1) + employee2_doc = frappe.get_doc("Employee", employee2) + employee2_doc.reload() + employee2_doc.reports_to = employee1_doc.name + employee2_doc.save() + employee1_doc.reload() + employee1_doc.status = 'Left' + self.assertRaises(EmployeeLeftValidationError, employee1_doc.save) + def make_employee(user): if not frappe.db.get_value("User", user): frappe.get_doc({ diff --git a/erpnext/hr/doctype/employee_separation/employee_separation_list.js b/erpnext/hr/doctype/employee_separation/employee_separation_list.js index 11487cc6f00..76c58f56327 100644 --- a/erpnext/hr/doctype/employee_separation/employee_separation_list.js +++ b/erpnext/hr/doctype/employee_separation/employee_separation_list.js @@ -1,5 +1,5 @@ frappe.listview_settings['Employee Separation'] = { - add_fields: ["boarding_status", "employee_name", "date_of_joining", "department"], + add_fields: ["boarding_status", "employee_name", "department"], filters:[["boarding_status","=", "Pending"]], get_indicator: function(doc) { return [__(doc.boarding_status), frappe.utils.guess_colour(doc.boarding_status), "status,=," + doc.boarding_status]; diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index a77dd32c49c..5bce348489e 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -14,7 +14,7 @@ frappe.ui.form.on("Leave Application", { doctype: frm.doc.doctype } }; - }); + }); frm.set_query("employee", erpnext.queries.employee); }, @@ -83,7 +83,7 @@ frappe.ui.form.on("Leave Application", { if (!frm.doc.employee && frappe.defaults.get_user_permissions()) { const perm = frappe.defaults.get_user_permissions(); if (perm && perm['Employee']) { - frm.set_value('employee', perm['Employee']["docs"][0]) + frm.set_value('employee', perm['Employee'].map(perm_doc => perm_doc.doc)[0]); } } }, diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index aca277effdf..6b7c0f7e79d 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -187,7 +187,7 @@ class LeaveApplication(Document): self.total_leave_days = get_number_of_leave_days(self.employee, self.leave_type, self.from_date, self.to_date, self.half_day, self.half_day_date) - if self.total_leave_days == 0: + if self.total_leave_days <= 0: frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave.")) if not is_lwp(self.leave_type): diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 4748011b626..a682e8b76f5 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -457,7 +457,7 @@ def get_leave_period(): return frappe.get_doc(dict( name = 'Test Leave Period', doctype = 'Leave Period', - from_date = "{0}-01-01".format(now_datetime().year), + from_date = "{0}-12-01".format(now_datetime().year - 1), to_date = "{0}-12-31".format(now_datetime().year), company = "_Test Company", is_active = 1 diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index 033938de361..79d75bfdf6e 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -124,6 +124,7 @@ frappe.ui.form.on('Salary Structure', { "label":__("Employee"), "fieldname":"employee", "fieldtype":"Select", + "reqd": true, options: employees }, { fieldname:"fetch", diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.py b/erpnext/hr/doctype/salary_structure/salary_structure.py index 7ead14030f2..202ae9bcfe5 100644 --- a/erpnext/hr/doctype/salary_structure/salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/salary_structure.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, cint +from frappe.utils import flt, cint, cstr from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document @@ -22,7 +22,7 @@ class SalaryStructure(Document): overwritten_fields_if_missing = ["amount_based_on_formula", "formula", "amount"] for table in ["earnings", "deductions"]: for d in self.get(table): - component_default_value = frappe.db.get_value("Salary Component", str(d.salary_component), + component_default_value = frappe.db.get_value("Salary Component", cstr(d.salary_component), overwritten_fields + overwritten_fields_if_missing, as_dict=1) if component_default_value: for fieldname in overwritten_fields: diff --git a/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py new file mode 100644 index 00000000000..6e151d0e3c5 --- /dev/null +++ b/erpnext/hr/doctype/upload_attendance/test_upload_attendance.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, 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.hr.doctype.upload_attendance.upload_attendance import get_data +from erpnext.hr.doctype.employee.test_employee import make_employee + +class TestUploadAttendance(unittest.TestCase): + def test_date_range(self): + employee = make_employee("test_employee@company.com") + employee_doc = frappe.get_doc("Employee", employee) + date_of_joining = "2018-01-02" + relieving_date = "2018-01-03" + from_date = "2018-01-01" + to_date = "2018-01-04" + employee_doc.date_of_joining = date_of_joining + employee_doc.relieving_date = relieving_date + employee_doc.save() + args = { + "from_date": from_date, + "to_date": to_date + } + data = get_data(args) + filtered_data = [] + for row in data: + if row[1] == employee: + filtered_data.append(row) + for row in filtered_data: + self.assertTrue(getdate(row[3]) >= getdate(date_of_joining) and getdate(row[3]) <= getdate(relieving_date)) diff --git a/erpnext/hr/doctype/upload_attendance/upload_attendance.py b/erpnext/hr/doctype/upload_attendance/upload_attendance.py index 3d080a725eb..db74b102a70 100644 --- a/erpnext/hr/doctype/upload_attendance/upload_attendance.py +++ b/erpnext/hr/doctype/upload_attendance/upload_attendance.py @@ -41,16 +41,28 @@ def add_header(w): return w def add_data(w, args): + data = get_data(args) + writedata(w, data) + return w + +def get_data(args): dates = get_dates(args) employees = get_active_employees() existing_attendance_records = get_existing_attendance_records(args) + data = [] for date in dates: for employee in employees: + if getdate(date) < getdate(employee.date_of_joining): + continue + if employee.relieving_date: + if getdate(date) > getdate(employee.relieving_date): + continue existing_attendance = {} if existing_attendance_records \ - and tuple([getdate(date), employee.name]) in existing_attendance_records: + and tuple([getdate(date), employee.name]) in existing_attendance_records \ + and getdate(employee.date_of_joining) >= getdate(date) \ + and getdate(employee.relieving_date) <= getdate(date): existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])] - row = [ existing_attendance and existing_attendance.name or "", employee.name, employee.employee_name, date, @@ -58,8 +70,12 @@ def add_data(w, args): existing_attendance and existing_attendance.leave_type or "", employee.company, existing_attendance and existing_attendance.naming_series or get_naming_series(), ] - w.writerow(row) - return w + data.append(row) + return data + +def writedata(w, data): + for row in data: + w.writerow(row) def get_dates(args): """get list of dates in between from date and to date""" @@ -68,8 +84,13 @@ def get_dates(args): return dates def get_active_employees(): - employees = frappe.db.sql("""select name, employee_name, company - from tabEmployee where docstatus < 2 and status = 'Active'""", as_dict=1) + employees = frappe.db.get_all('Employee', + fields=['name', 'employee_name', 'date_of_joining', 'company', 'relieving_date'], + filters={ + 'docstatus': ['<', 2], + 'status': 'Active' + } + ) return employees def get_existing_attendance_records(args): diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index a01011a178d..2615b31782c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -402,6 +402,8 @@ frappe.ui.form.on("BOM Item", "items_remove", function(frm) { var toggle_operations = function(frm) { frm.toggle_display("operations_section", cint(frm.doc.with_operations) == 1); + frm.toggle_display("transfer_material_against", cint(frm.doc.with_operations) == 1); + frm.toggle_reqd("transfer_material_against", cint(frm.doc.with_operations) == 1); }; frappe.ui.form.on("BOM", "with_operations", function(frm) { diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 8c5f2af96a8..0cf7dc4f816 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -80,41 +80,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials", - "fieldname": "quantity", - "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": "Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "quantity", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -154,8 +119,10 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "inspection_required", - "fieldtype": "Check", + "default": "1", + "description": "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials", + "fieldname": "quantity", + "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -163,51 +130,18 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Inspection Required", + "label": "Quantity", "length": 0, "no_copy": 0, + "oldfieldname": "quantity", + "oldfieldtype": "Currency", "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "inspection_required", - "fieldname": "quality_inspection_template", - "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": "Quality Inspection Template", - "length": 0, - "no_copy": 0, - "options": "Quality Inspection Template", - "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, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -346,77 +280,11 @@ { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, - "allow_on_submit": 1, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "rm_cost_as_per", - "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": "Rate Of Materials Based On", - "length": 0, - "no_copy": 0, - "options": "Valuation Rate\nLast Purchase Rate\nPrice List", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.rm_cost_as_per===\"Price List\"", - "fieldname": "buying_price_list", - "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": "Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "set_rate_of_sub_assembly_item_based_on_bom", + "fieldname": "inspection_required", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -425,7 +293,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Set rate of sub-assembly item based on BOM", + "label": "Inspection Required", "length": 0, "no_copy": 0, "permlevel": 0, @@ -481,7 +349,7 @@ "collapsible": 0, "columns": 0, "fieldname": "allow_same_item_multiple_times", - "fieldtype": "Data", + "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -508,12 +376,12 @@ { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "with_operations", - "fieldname": "transfer_material_against_job_card", + "default": "1", + "fieldname": "set_rate_of_sub_assembly_item_based_on_bom", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -522,7 +390,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Transfer Material Against Job Card", + "label": "Set rate of sub-assembly item based on BOM", "length": 0, "no_copy": 0, "permlevel": 0, @@ -538,6 +406,40 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "inspection_required", + "fieldname": "quality_inspection_template", + "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": "Quality Inspection Template", + "length": 0, + "no_copy": 0, + "options": "Quality Inspection Template", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -603,6 +505,72 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "transfer_material_against", + "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": "Transfer Material Against", + "length": 0, + "no_copy": 0, + "options": "\nWork Order\nJob Card", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "conversion_rate", + "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": "Conversion Rate", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "9", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -670,12 +638,12 @@ { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "conversion_rate", - "fieldtype": "Float", + "fieldname": "rm_cost_as_per", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -683,17 +651,50 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Conversion Rate", + "label": "Rate Of Materials Based On", "length": 0, "no_copy": 0, + "options": "Valuation Rate\nLast Purchase Rate\nPrice List", "permlevel": 0, - "precision": "9", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.rm_cost_as_per===\"Price List\"", + "fieldname": "buying_price_list", + "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": "Price List", + "length": 0, + "no_copy": 0, + "options": "Price List", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -707,7 +708,7 @@ "collapsible": 0, "columns": 0, "depends_on": "", - "description": "Specify the operations, operating cost and give a unique Operation no to your operations.", + "description": "", "fieldname": "operations_section", "fieldtype": "Section Break", "hidden": 0, @@ -1976,7 +1977,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-10-24 02:07:21.618275", + "modified": "2018-12-13 17:45:44.843197", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index d72f00af8f0..54ffa069126 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -81,7 +81,7 @@ class BOM(WebsiteGenerator): def get_item_det(self, item_code): item = frappe.db.sql("""select name, item_name, docstatus, description, image, - is_sub_contracted_item, stock_uom, default_bom, last_purchase_rate, allow_transfer_for_manufacture + is_sub_contracted_item, stock_uom, default_bom, last_purchase_rate, include_item_in_manufacturing from `tabItem` where name=%s""", item_code, as_dict = 1) if not item: @@ -109,7 +109,7 @@ class BOM(WebsiteGenerator): "item_name": item.item_name, "bom_no": item.bom_no, "stock_qty": item.stock_qty, - "allow_transfer_for_manufacture": item.allow_transfer_for_manufacture + "include_item_in_manufacturing": item.include_item_in_manufacturing }) for r in ret: if not item.get(r): @@ -128,8 +128,8 @@ class BOM(WebsiteGenerator): self.validate_rm_item(item) args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' - args['transfer_for_manufacture'] = (cstr(args.get('allow_transfer_for_manufacture', '')) or - item and item[0].allow_transfer_for_manufacture or 0) + args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or + item and item[0].include_item_in_manufacturing or 0) args.update(item[0]) rate = self.get_rm_rate(args) @@ -145,7 +145,7 @@ class BOM(WebsiteGenerator): 'qty' : args.get("qty") or args.get("stock_qty") or 1, 'stock_qty' : args.get("qty") or args.get("stock_qty") or 1, 'base_rate' : rate, - 'allow_transfer_for_manufacture': cint(args['transfer_for_manufacture']) or 0 + 'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0 } return ret_item @@ -477,7 +477,7 @@ class BOM(WebsiteGenerator): 'stock_uom' : d.stock_uom, 'stock_qty' : flt(d.stock_qty), 'rate' : d.base_rate, - 'allow_transfer_for_manufacture': d.allow_transfer_for_manufacture + 'include_item_in_manufacturing': d.include_item_in_manufacturing })) def company_currency(self): @@ -494,7 +494,7 @@ class BOM(WebsiteGenerator): # Did not use qty_consumed_per_unit in the query, as it leads to rounding loss child_fb_items = frappe.db.sql("""select bom_item.item_code, bom_item.item_name, bom_item.description, bom_item.source_warehouse, bom_item.operation, - bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, bom_item.allow_transfer_for_manufacture, + bom_item.stock_uom, bom_item.stock_qty, bom_item.rate, bom_item.include_item_in_manufacturing, bom_item.stock_qty / ifnull(bom.quantity, 1) as qty_consumed_per_unit from `tabBOM Explosion Item` bom_item, tabBOM bom where bom_item.parent = bom.name and bom.name = %s and bom.docstatus = 1""", bom_no, as_dict = 1) @@ -509,7 +509,7 @@ class BOM(WebsiteGenerator): 'stock_uom' : d['stock_uom'], 'stock_qty' : d['qty_consumed_per_unit'] * stock_qty, 'rate' : flt(d['rate']), - 'allow_transfer_for_manufacture': d.get('allow_transfer_for_manufacture', 0) + 'include_item_in_manufacturing': d.get('include_item_in_manufacturing', 0) })) def add_exploded_items(self): @@ -585,7 +585,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite query = query.format(table="BOM Explosion Item", where_conditions="", is_stock_item=is_stock_item, - select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.allow_transfer_for_manufacture, + select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing, (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s ) as idx""") items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True) @@ -594,7 +594,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) else: query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item, - select_columns = ", bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.allow_transfer_for_manufacture") + select_columns = ", bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing") items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) for item in items: diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 1a7e594e87c..25730f9b9f4 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -11,7 +11,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 }, { "amount": 2000.0, @@ -23,7 +23,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 } ], "docstatus": 1, @@ -57,7 +57,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 }, { "amount": 2000.0, @@ -69,7 +69,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 } ], "docstatus": 1, @@ -102,7 +102,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 }, { "amount": 3000.0, @@ -115,7 +115,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 } ], "docstatus": 1, @@ -150,7 +150,7 @@ "uom": "_Test UOM", "stock_uom": "_Test UOM", "source_warehouse": "_Test Warehouse - _TC", - "allow_transfer_for_manufacture": 1 + "include_item_in_manufacturing": 1 } ], "docstatus": 1, diff --git a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json index ab3c5a12059..9fadbef0f54 100644 --- a/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json +++ b/erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -573,7 +574,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "allow_transfer_for_manufacture", + "fieldname": "include_item_in_manufacturing", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -582,7 +583,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Allow Transfer for Manufacture", + "label": "Include Item In Manufacturing", "length": 0, "no_copy": 0, "permlevel": 0, @@ -609,7 +610,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-08-27 16:32:35.152139", + "modified": "2018-11-20 19:04:59.813773", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Explosion Item", diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json index cc69471eba2..8d4d69b09a9 100644 --- a/erpnext/manufacturing/doctype/bom_item/bom_item.json +++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json @@ -78,6 +78,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "operation", + "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": "Item operation", + "length": 0, + "no_copy": 0, + "options": "Operation", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -932,8 +965,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fetch_from": "item_code.allow_transfer_for_manufacture", - "fieldname": "allow_transfer_for_manufacture", + "fetch_from": "item_code.include_item_in_manufacturing", + "fieldname": "include_item_in_manufacturing", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -942,7 +975,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Allow Transfer for Manufacture", + "label": "Include Item In Manufacturing", "length": 0, "no_copy": 0, "permlevel": 0, @@ -990,39 +1023,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "operation", - "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": "Item operation", - "length": 0, - "no_copy": 0, - "options": "Operation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 } ], "has_web_view": 0, @@ -1035,7 +1035,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-11-22 15:04:55.187136", + "modified": "2018-12-28 16:38:56.529079", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Item", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 6f5290e9ca2..3fe9b8af30d 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Job Card', { refresh: function(frm) { - if (frm.doc.items && frm.doc.docstatus==1) { + if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) { if (frm.doc.for_quantity != frm.doc.transferred_qty) { frm.add_custom_button(__("Material Request"), () => { frm.trigger("make_material_request"); @@ -31,6 +31,7 @@ frappe.ui.form.on('Job Card', { frm.add_custom_button(__("Complete Job"), () => { frm.set_value('actual_end_date', frappe.datetime.now_datetime()); frm.save(); + frm.savesubmit(); }); } } diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 443cad8666c..b020c89053c 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -46,6 +47,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bom_no", + "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": "BOM No", + "length": 0, + "no_copy": 0, + "options": "BOM", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -112,39 +146,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "wip_warehouse", - "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": "WIP Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -281,9 +282,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "0", - "fieldname": "transferred_qty", - "fieldtype": "Float", + "fieldname": "wip_warehouse", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -291,17 +291,18 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Transferred Qty", + "label": "WIP Warehouse", "length": 0, "no_copy": 0, + "options": "Warehouse", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -635,8 +636,9 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "bom_no", - "fieldtype": "Link", + "default": "0", + "fieldname": "transferred_qty", + "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -644,10 +646,42 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "BOM No", + "label": "Transferred Qty", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "requested_qty", + "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": "Requested Qty", "length": 0, "no_copy": 0, - "options": "BOM", "permlevel": 0, "precision": "", "print_hide": 0, @@ -701,8 +735,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_20", - "fieldtype": "Column Break", + "fieldname": "remarks", + "fieldtype": "Small Text", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -710,6 +744,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Remarks", "length": 0, "no_copy": 0, "permlevel": 0, @@ -732,8 +767,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "remarks", - "fieldtype": "Small Text", + "fieldname": "column_break_20", + "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -741,7 +776,6 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Remarks", "length": 0, "no_copy": 0, "permlevel": 0, @@ -776,13 +810,13 @@ "in_standard_filter": 0, "label": "Status", "length": 0, - "no_copy": 0, - "options": "Open\nWork In Progress\nCancelled\nCompleted", + "no_copy": 1, + "options": "Open\nWork In Progress\nMaterial Transferred\nSubmitted\nCancelled\nCompleted", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -834,7 +868,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-28 16:50:43.576151", + "modified": "2018-12-13 17:23:57.986381", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index bc745350a7e..5343a280ca7 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -11,9 +11,9 @@ from frappe.model.document import Document class JobCard(Document): def validate(self): - self.status = 'Open' self.validate_actual_dates() self.set_time_in_mins() + self.set_status() def validate_actual_dates(self): if get_datetime(self.actual_start_date) > get_datetime(self.actual_end_date): @@ -48,7 +48,7 @@ class JobCard(Document): return doc = frappe.get_doc('Work Order', self.get('work_order')) - if not doc.transfer_material_against_job_card and doc.skip_transfer: + if doc.transfer_material_against == 'Work Order' and doc.skip_transfer: return for d in doc.required_items: @@ -104,20 +104,23 @@ class JobCard(Document): wo.set_actual_dates() wo.save() - def set_transferred_qty(self): + def set_transferred_qty(self, update_status=False): if not self.items: self.transferred_qty = self.for_quantity if self.docstatus == 1 else 0 if self.items: - self.transferred_qty = frappe.db.get_value('Stock Entry', {'job_card': self.name, - 'work_order': self.work_order, 'docstatus': 1}, 'sum(fg_completed_qty)') or 0 + self.transferred_qty = frappe.db.get_value('Stock Entry', { + 'job_card': self.name, + 'work_order': self.work_order, + 'docstatus': 1 + }, 'sum(fg_completed_qty)') or 0 self.db_set("transferred_qty", self.transferred_qty) qty = 0 if self.work_order: doc = frappe.get_doc('Work Order', self.work_order) - if doc.transfer_material_against_job_card and not doc.skip_transfer: + if doc.transfer_material_against == 'Job Card' and not doc.skip_transfer: completed = True for d in doc.operations: if d.status != 'Completed': @@ -131,15 +134,28 @@ class JobCard(Document): doc.db_set('material_transferred_for_manufacturing', qty) - self.set_status() + self.set_status(update_status) - def set_status(self): - status = 'Cancelled' if self.docstatus == 2 else 'Work In Progress' + def set_status(self, update_status=False): + self.status = { + 0: "Open", + 1: "Submitted", + 2: "Cancelled" + }[self.docstatus or 0] - if self.for_quantity == self.transferred_qty: - status = 'Completed' + if self.actual_start_date: + self.status = 'Work In Progress' - self.db_set('status', status) + if (self.docstatus == 1 and + (self.for_quantity == self.transferred_qty or not self.items)): + self.status = 'Completed' + + if self.status != 'Completed': + if self.for_quantity == self.transferred_qty: + self.status = 'Material Transferred' + + if update_status: + self.db_set('status', self.status) @frappe.whitelist() def make_material_request(source_name, target_doc=None): diff --git a/erpnext/manufacturing/doctype/job_card/job_card_list.js b/erpnext/manufacturing/doctype/job_card/job_card_list.js index d40a9fa4958..ed851ebc83b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card_list.js +++ b/erpnext/manufacturing/doctype/job_card/job_card_list.js @@ -6,6 +6,8 @@ frappe.listview_settings['Job Card'] = { return [__("Completed"), "green", "status,=,Completed"]; } else if (doc.docstatus == 2) { return [__("Cancelled"), "red", "status,=,Cancelled"]; + } else if (doc.status === "Material Transferred") { + return [__('Material Transferred'), "blue", "status,=,Material Transferred"]; } else { return [__("Open"), "red", "status,=,Open"]; } diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index d4620cd952e..9bf38581679 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -515,7 +515,7 @@ def get_items_for_material_requests(doc, company=None): doc = frappe._dict(json.loads(doc)) doc['mr_items'] = [] - po_items = doc['po_items'] if doc.get('po_items') else doc['items'] + po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') for data in po_items: warehouse = None @@ -534,10 +534,10 @@ def get_items_for_material_requests(doc, company=None): else: planned_qty = data.get('planned_qty') bom_no = data.get('bom_no') - include_subcontracted_items = doc['include_subcontracted_items'] - company = doc['company'] - include_non_stock_items = doc['include_non_stock_items'] - ignore_existing_ordered_qty = doc['ignore_existing_ordered_qty'] + include_subcontracted_items = doc.get('include_subcontracted_items') + company = doc.get('company') + include_non_stock_items = doc.get('include_non_stock_items') + ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') if not planned_qty: frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get('idx'))) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 431ad325587..69381c53b3e 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -306,7 +306,7 @@ class TestWorkOrder(unittest.TestCase): items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0} for item, allow_transfer in items.items(): make_item(item, { - 'allow_transfer_for_manufacture': allow_transfer + 'include_item_in_manufacturing': allow_transfer }) fg_item = 'Finished Good Transfer Item' diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index e85b0a5411b..7b2f9a4bff3 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -112,11 +112,20 @@ frappe.ui.form.on("Work Order", { frm.trigger('show_progress'); } - if (frm.doc.docstatus === 1 && frm.doc.operations + if (frm.doc.docstatus === 1 + && frm.doc.operations && frm.doc.operations.length && frm.doc.qty != frm.doc.material_transferred_for_manufacturing) { - frm.add_custom_button(__('Make Job Card'), () => { - frm.trigger("make_job_card") - }).addClass('btn-primary'); + const not_completed = frm.doc.operations.filter(d => { + if(d.status != 'Completed') { + return true; + } + }); + + if(not_completed && not_completed.length) { + frm.add_custom_button(__('Make Job Card'), () => { + frm.trigger("make_job_card") + }).addClass('btn-primary'); + } } if(frm.doc.required_items && frm.doc.allow_alternative_item) { @@ -294,7 +303,7 @@ frappe.ui.form.on("Work Order", { frm.trigger('set_sales_order'); erpnext.in_production_item_onchange = true; $.each(["description", "stock_uom", "project", "bom_no", - "allow_alternative_item", "transfer_material_against_job_card"], function(i, field) { + "allow_alternative_item", "transfer_material_against"], function(i, field) { frm.set_value(field, r.message[field]); }); @@ -340,9 +349,8 @@ frappe.ui.form.on("Work Order", { before_submit: function(frm) { frm.toggle_reqd(["fg_warehouse", "wip_warehouse"], true); frm.fields_dict.required_items.grid.toggle_reqd("source_warehouse", true); - if (frm.doc.operations) { - frm.fields_dict.operations.grid.toggle_reqd("workstation", true); - } + frm.toggle_reqd("transfer_material_against", frm.doc.operations); + frm.fields_dict.operations.grid.toggle_reqd("workstation", frm.doc.operations); }, set_sales_order: function(frm) { @@ -425,7 +433,7 @@ erpnext.work_order = { } const show_start_btn = (frm.doc.skip_transfer - || frm.doc.transfer_material_against_job_card) ? 0 : 1; + || frm.doc.transfer_material_against == 'Job Card') ? 0 : 1; if (show_start_btn){ if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index df9dd83a704..a65d04f61b8 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, @@ -183,6 +184,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allow_alternative_item", + "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": "Allow Alternative Item", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -223,7 +256,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "allow_alternative_item", + "description": "Check if material transfer entry is not required", + "fieldname": "skip_transfer", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -232,7 +266,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Allow Alternative Item", + "label": "Skip Material Transfer", "length": 0, "no_copy": 0, "permlevel": 0, @@ -486,39 +520,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Check if material transfer entry is not required", - "fieldname": "skip_transfer", - "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": "Skip Material Transfer", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -552,39 +553,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "operations", - "fieldname": "transfer_material_against_job_card", - "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": "Transfer Material Against Job Card", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1070,6 +1038,41 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Work Order", + "depends_on": "operations", + "fieldname": "transfer_material_against", + "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": "Transfer Material Against", + "length": 0, + "no_copy": 0, + "options": "\nWork Order\nJob Card", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -1672,7 +1675,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-09-05 06:28:22.983369", + "modified": "2018-12-13 15:33:12.490710", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order", diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index e73328f10e1..9873efa124b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -191,7 +191,7 @@ class WorkOrder(Document): for purpose, fieldname in (("Manufacture", "produced_qty"), ("Material Transfer for Manufacture", "material_transferred_for_manufacturing")): if (purpose == 'Material Transfer for Manufacture' and - self.operations and self.transfer_material_against_job_card): + self.operations and self.transfer_material_against == 'Job Card'): continue qty = flt(frappe.db.sql("""select sum(fg_completed_qty) @@ -459,7 +459,7 @@ class WorkOrder(Document): 'allow_alternative_item': item.allow_alternative_item, 'required_qty': item.qty, 'source_warehouse': item.source_warehouse or item.default_warehouse, - 'allow_transfer_for_manufacture': item.allow_transfer_for_manufacture + 'include_item_in_manufacturing': item.include_item_in_manufacturing }) self.set_available_qty() @@ -564,11 +564,11 @@ def get_item_details(item, project = None): frappe.throw(_("Default BOM for {0} not found").format(item)) bom_data = frappe.db.get_value('BOM', res['bom_no'], - ['project', 'allow_alternative_item', 'transfer_material_against_job_card'], as_dict=1) + ['project', 'allow_alternative_item', 'transfer_material_against'], as_dict=1) res['project'] = project or bom_data.project res['allow_alternative_item'] = bom_data.allow_alternative_item - res['transfer_material_against_job_card'] = bom_data.transfer_material_against_job_card + res['transfer_material_against'] = bom_data.transfer_material_against res.update(check_if_scrap_warehouse_mandatory(res["bom_no"])) return res @@ -682,7 +682,7 @@ def create_job_card(work_order, row, qty=0, auto_create=False): 'wip_warehouse': work_order.wip_warehouse }) - if work_order.transfer_material_against_job_card and not work_order.skip_transfer: + if work_order.transfer_material_against == 'Job Card' and not work_order.skip_transfer: doc.get_required_items() if auto_create: diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json index 1db11f76910..44421626361 100644 --- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json +++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -342,7 +343,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "allow_transfer_for_manufacture", + "fieldname": "include_item_in_manufacturing", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -351,7 +352,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Allow Transfer for Manufacture", + "label": "Include Item In Manufacturing", "length": 0, "no_copy": 0, "permlevel": 0, @@ -506,7 +507,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-10-04 16:16:54.237829", + "modified": "2018-11-20 19:04:38.508839", "modified_by": "Administrator", "module": "Manufacturing", "name": "Work Order Item", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4a67eb4e477..9b8a69d2b21 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -495,7 +495,7 @@ erpnext.patches.v10_0.set_b2c_limit erpnext.patches.v10_0.update_translatable_fields erpnext.patches.v10_0.rename_offer_letter_to_job_offer execute:frappe.delete_doc('DocType', 'Production Planning Tool', ignore_missing=True) -erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_group +erpnext.patches.v10_0.migrate_daily_work_summary_settings_to_daily_work_summary_group # 24-12-2018 erpnext.patches.v10_0.add_default_cash_flow_mappers erpnext.patches.v11_0.make_quality_inspection_template erpnext.patches.v10_0.update_status_for_multiple_source_in_po @@ -579,3 +579,4 @@ erpnext.patches.v10_0.update_user_image_in_employee erpnext.patches.v11_0.update_delivery_trip_status erpnext.patches.v10_0.repost_gle_for_purchase_receipts_with_rejected_items erpnext.patches.v11_0.set_missing_gst_hsn_code +erpnext.patches.v11_0.rename_bom_wo_fields diff --git a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py index 3d15bbc7317..102b6da8757 100644 --- a/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py +++ b/erpnext/patches/v10_0/migrate_daily_work_summary_settings_to_daily_work_summary_group.py @@ -6,37 +6,36 @@ import frappe def execute(): - frappe.reload_doc("hr", "doctype", "daily_work_summary_group") - frappe.reload_doc("hr", "doctype", "daily_work_summary_group_user") + if not frappe.db.table_exists('Daily Work Summary Group'): + frappe.reload_doc("hr", "doctype", "daily_work_summary_group") + frappe.reload_doc("hr", "doctype", "daily_work_summary_group_user") - # check if Daily Work Summary Settings Company table exists - try: - frappe.db.sql('DESC `tabDaily Work Summary Settings Company`') - except Exception: - return + # check if Daily Work Summary Settings Company table exists + try: + frappe.db.sql('DESC `tabDaily Work Summary Settings Company`') + except Exception: + return - # get the previously saved settings - previous_setting = get_previous_setting() - if previous_setting["companies"]: - for d in previous_setting["companies"]: - users = frappe.get_list("Employee", dict( - company=d.company, user_id=("!=", " ")), "user_id as user") - if(len(users)): - # create new group entry for each company entry - new_group = frappe.get_doc(dict(doctype="Daily Work Summary Group", - name="Daily Work Summary for " + d.company, - users=users, - send_emails_at=d.send_emails_at, - subject=previous_setting["subject"], - message=previous_setting["message"])) - new_group.flags.ignore_permissions = True - new_group.flags.ignore_validate = True - new_group.insert(ignore_if_duplicate = True) - frappe.delete_doc("Daily Work Summary Settings") - frappe.delete_doc("Daily Work Summary Settings Company") + # get the previously saved settings + previous_setting = get_previous_setting() + if previous_setting["companies"]: + for d in previous_setting["companies"]: + users = frappe.get_list("Employee", dict( + company=d.company, user_id=("!=", " ")), "user_id as user") + if(len(users)): + # create new group entry for each company entry + new_group = frappe.get_doc(dict(doctype="Daily Work Summary Group", + name="Daily Work Summary for " + d.company, + users=users, + send_emails_at=d.send_emails_at, + subject=previous_setting["subject"], + message=previous_setting["message"])) + new_group.flags.ignore_permissions = True + new_group.flags.ignore_validate = True + new_group.insert(ignore_if_duplicate = True) -def get_setting_companies(): - return frappe.db.sql("select * from `tabDaily Work Summary Settings Company`", as_dict=True) + frappe.delete_doc("DocType", "Daily Work Summary Settings") + frappe.delete_doc("DocType", "Daily Work Summary Settings Company") def get_previous_setting(): @@ -47,3 +46,6 @@ def get_previous_setting(): obj[field] = value obj["companies"] = get_setting_companies() return obj + +def get_setting_companies(): + return frappe.db.sql("select * from `tabDaily Work Summary Settings Company`", as_dict=True) \ No newline at end of file diff --git a/erpnext/patches/v11_0/rename_bom_wo_fields.py b/erpnext/patches/v11_0/rename_bom_wo_fields.py new file mode 100644 index 00000000000..c8106a6bd59 --- /dev/null +++ b/erpnext/patches/v11_0/rename_bom_wo_fields.py @@ -0,0 +1,36 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + for doctype in ['BOM Explosion Item', 'BOM Item', 'Work Order Item', 'Item']: + if frappe.db.has_column(doctype, 'allow_transfer_for_manufacture'): + if doctype != 'Item': + frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype)) + else: + frappe.reload_doc('stock', 'doctype', frappe.scrub(doctype)) + + rename_field(doctype, "allow_transfer_for_manufacture", "include_item_in_manufacturing") + + if frappe.db.has_column('BOM', 'allow_same_item_multiple_times'): + frappe.db.sql(""" UPDATE tabBOM + SET + allow_same_item_multiple_times = 0 + WHERE + trim(coalesce(allow_same_item_multiple_times, '')) = '' """) + + for doctype in ['BOM', 'Work Order']: + frappe.reload_doc('manufacturing', 'doctype', frappe.scrub(doctype)) + + if frappe.db.has_column(doctype, 'transfer_material_against_job_card'): + frappe.db.sql(""" UPDATE `tab%s` + SET transfer_material_against = CASE WHEN + transfer_material_against_job_card = 1 then 'Job Card' Else 'Work Order' END + WHERE docstatus < 2""" % (doctype)) + else: + frappe.db.sql(""" UPDATE `tab%s` + SET transfer_material_against = 'Work Order' + WHERE docstatus < 2""" % (doctype)) \ No newline at end of file diff --git a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py index 123eed5affc..7f7cfc1327a 100644 --- a/erpnext/patches/v11_0/skip_user_permission_check_for_department.py +++ b/erpnext/patches/v11_0/skip_user_permission_check_for_department.py @@ -1,28 +1,60 @@ import frappe +from frappe.desk.form.linked_with import get_linked_doctypes # Skips user permission check for doctypes where department link field was recently added # https://github.com/frappe/erpnext/pull/14121 def execute(): - user_permissions = frappe.get_all("User Permission", - filters=[['allow', '=', 'Department']], - fields=['name', 'skip_for_doctype']) + doctypes_to_skip = [] + for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip', + 'Attendance', 'Training Feedback', 'Training Result Employee', + 'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee', + 'Timesheet', 'Sales Person', 'Payroll Employee Detail']: + if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue + doctypes_to_skip.append(doctype) - doctypes_to_skip = [] + frappe.reload_doctype('User Permission') - for doctype in ['Appraisal', 'Leave Allocation', 'Expense Claim', 'Instructor', 'Salary Slip', - 'Attendance', 'Training Feedback', 'Training Result Employee', - 'Leave Application', 'Employee Advance', 'Activity Cost', 'Training Event Employee', - 'Timesheet', 'Sales Person', 'Payroll Employee Detail']: - if frappe.db.exists('Custom Field', { 'dt': doctype, 'fieldname': 'department'}): continue - doctypes_to_skip.append(doctype) + user_permissions = frappe.get_all("User Permission", + filters=[['allow', '=', 'Department'], ['applicable_for', 'in', [None] + doctypes_to_skip]], + fields=['name', 'applicable_for']) - for perm in user_permissions: - skip_for_doctype = perm.get('skip_for_doctype') + user_permissions_to_delete = [] + new_user_permissions_list = [] - skip_for_doctype = skip_for_doctype.split('\n') + doctypes_to_skip - skip_for_doctype = set(skip_for_doctype) # to remove duplicates - skip_for_doctype = '\n'.join(skip_for_doctype) # convert back to string + for user_permission in user_permissions: + if user_permission.applicable_for: + # simply delete user permission record since it needs to be skipped. + user_permissions_to_delete.append(user_permission.name) + else: + # if applicable_for is `None` it means that user permission is applicable for every doctype + # to avoid this we need to create other user permission records and only skip the listed doctypes in this patch + linked_doctypes = get_linked_doctypes(user_permission.allow, True).keys() + applicable_for_doctypes = list(set(linked_doctypes) - set(doctypes_to_skip)) - frappe.set_value('User Permission', perm.name, 'skip_for_doctype', skip_for_doctype) + user_permissions_to_delete.append(user_permission.name) + for doctype in applicable_for_doctypes: + if doctype: + # Maintain sequence (name, user, allow, for_value, applicable_for, apply_to_all_doctypes) + new_user_permissions_list.append(( + frappe.generate_hash("", 10), + user_permission.user, + user_permission.allow, + user_permission.for_value, + doctype, + 0 + )) + + if new_user_permissions_list: + frappe.db.sql(''' + INSERT INTO `tabUser Permission` + (`name`, `user`, `allow`, `for_value`, `applicable_for`, `apply_to_all_doctypes`) + VALUES {}'''.format(', '.join(['%s'] * len(new_user_permissions_list))), # nosec + tuple(new_user_permissions_list) + ) + + if user_permissions_to_delete: + frappe.db.sql('DELETE FROM `tabUser Permission` WHERE `name` IN ({})'.format( # nosec + ','.join(['%s'] * len(user_permissions_to_delete)) + ), tuple(user_permissions_to_delete)) \ No newline at end of file diff --git a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py index 9c94deee4b0..1b58c97ea4d 100644 --- a/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py +++ b/erpnext/patches/v11_0/update_allow_transfer_for_manufacture.py @@ -6,7 +6,7 @@ import frappe def execute(): frappe.reload_doc('stock', 'doctype', 'item') - frappe.db.sql(""" update `tabItem` set allow_transfer_for_manufacture = 1 + frappe.db.sql(""" update `tabItem` set include_item_in_manufacturing = 1 where ifnull(is_stock_item, 0) = 1""") for doctype in ['BOM Item', 'Work Order Item', 'BOM Explosion Item']: @@ -14,7 +14,7 @@ def execute(): frappe.db.sql(""" update `tab{0}` child, tabItem item set - child.allow_transfer_for_manufacture = 1 + child.include_item_in_manufacturing = 1 where child.item_code = item.name and ifnull(item.is_stock_item, 0) = 1 """.format(doctype)) \ No newline at end of file diff --git a/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py b/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py index 942f089bce6..8a8c8064dde 100644 --- a/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py +++ b/erpnext/patches/v9_0/add_user_to_child_table_in_pos_profile.py @@ -32,7 +32,7 @@ def execute(): 'user': user, 'default': 1 }) - _doc.pos_profile_name = user + ' - ' + _doc.company + _doc.flags.ignore_validate = True _doc.flags.ignore_mandatory = True _doc.save() \ No newline at end of file diff --git a/erpnext/patches/v9_0/set_pos_profile_name.py b/erpnext/patches/v9_0/set_pos_profile_name.py index 1958e2c777a..a3a97352150 100644 --- a/erpnext/patches/v9_0/set_pos_profile_name.py +++ b/erpnext/patches/v9_0/set_pos_profile_name.py @@ -11,14 +11,14 @@ def execute(): for pos in frappe.get_all(doctype, filters={'disabled': 0}): doc = frappe.get_doc(doctype, pos.name) - if not doc.user or doc.pos_profile_name: continue + if not doc.user: continue try: - doc.pos_profile_name = doc.user + ' - ' + doc.company + pos_profile_name = doc.user + ' - ' + doc.company doc.flags.ignore_validate = True doc.flags.ignore_mandatory = True doc.save() - frappe.rename_doc(doctype, doc.name, doc.pos_profile_name, force=True) + frappe.rename_doc(doctype, doc.name, pos_profile_name, force=True) except frappe.LinkValidationError: frappe.db.set_value("POS Profile", doc.name, 'disabled', 1) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 942593a9484..de45ec30a61 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -16,7 +16,7 @@ from six import iteritems class Project(Document): def get_feed(self): - return '{0}: {1}'.format(_(self.status), self.project_name) + return '{0}: {1}'.format(_(self.status), frappe.safe_decode(self.project_name)) def onload(self): """Load project tasks for quick view""" @@ -76,7 +76,7 @@ class Project(Document): def validate_project_name(self): if self.get("__islocal") and frappe.db.exists("Project", self.project_name): - frappe.throw(_("Project {0} already exists").format(self.project_name)) + frappe.throw(_("Project {0} already exists").format(frappe.safe_decode(self.project_name))) def validate_dates(self): if self.expected_start_date and self.expected_end_date: @@ -258,13 +258,13 @@ class Project(Document): self.total_purchase_cost = total_purchase_cost and total_purchase_cost[0][0] or 0 def update_sales_amount(self): - total_sales_amount = frappe.db.sql("""select sum(base_grand_total) + total_sales_amount = frappe.db.sql("""select sum(base_net_total) from `tabSales Order` where project = %s and docstatus=1""", self.name) self.total_sales_amount = total_sales_amount and total_sales_amount[0][0] or 0 def update_billed_amount(self): - total_billed_amount = frappe.db.sql("""select sum(base_grand_total) + total_billed_amount = frappe.db.sql("""select sum(base_net_total) from `tabSales Invoice` where project = %s and docstatus=1""", self.name) self.total_billed_amount = total_billed_amount and total_billed_amount[0][0] or 0 diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 8c84c11ae9c..f1179033bed 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -186,6 +186,8 @@ def make_salary_structure_for_timesheet(employee): if not frappe.db.get_value("Salary Structure Assignment", {'employee':employee, 'docstatus': 1}): + frappe.db.set_value('Employee', employee, 'date_of_joining', + add_months(nowdate(), -5)) create_salary_structure_assignment(employee, salary_structure.name) return salary_structure diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 82d6f6e2269..9beba6adf8d 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -97,6 +97,9 @@ erpnext.setup.slides_settings = [ if (!this.values.company_abbr) { return false; } + if (this.values.company_abbr.length > 5) { + return false; + } return true; } }, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js old mode 100644 new mode 100755 index dc989e2fa96..adb7c6dd68d --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -126,7 +126,7 @@ $.extend(erpnext.utils, { '