From c0d8233a8b37a8b78306f531a94d5bcc21f5eedb Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 18 Nov 2019 15:45:53 +0530 Subject: [PATCH 01/27] fix: Raw material qty depending on the quantity of the parent BOM --- erpnext/manufacturing/report/bom_explorer/bom_explorer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py index 875d1152de..48907adc5f 100644 --- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py +++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py @@ -14,7 +14,7 @@ def execute(filters=None): def get_data(filters, data): get_exploded_items(filters.bom, data) -def get_exploded_items(bom, data, indent=0): +def get_exploded_items(bom, data, indent=0, qty=1): exploded_items = frappe.get_all("BOM Item", filters={"parent": bom}, fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom']) @@ -26,13 +26,13 @@ def get_exploded_items(bom, data, indent=0): 'item_name': item.item_name, 'indent': indent, 'bom': item.bom_no, - 'qty': item.qty, + 'qty': item.qty * qty, 'uom': item.uom, 'description': item.description, 'scrap': item.scrap }) if item.bom_no: - get_exploded_items(item.bom_no, data, indent=indent+1) + get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty) def get_columns(): return [ From aff678e376f4d5d9623b7c80eeb93b8b6e9e50d3 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 18:20:48 +0530 Subject: [PATCH 02/27] fix: Changed type of column 'serial_no' in Stock Ledger Entry (#19702) --- .../stock_ledger_entry.json | 653 ++---------------- 1 file changed, 39 insertions(+), 614 deletions(-) diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index 947f94853e..c9eba71b0d 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -1,874 +1,299 @@ { "allow_copy": 1, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, "autoname": "MAT-SLE-.YYYY.-.#####", - "beta": 0, "creation": "2013-01-29 19:25:42", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Other", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "item_code", + "serial_no", + "batch_no", + "warehouse", + "posting_date", + "posting_time", + "voucher_type", + "voucher_no", + "voucher_detail_no", + "actual_qty", + "incoming_rate", + "outgoing_rate", + "stock_uom", + "qty_after_transaction", + "valuation_rate", + "stock_value", + "stock_value_difference", + "stock_queue", + "project", + "company", + "fiscal_year", + "is_cancelled", + "to_rename" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "item_code", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Item Code", - "length": 0, - "no_copy": 0, "oldfieldname": "item_code", "oldfieldtype": "Link", "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "serial_no", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, + "fieldtype": "Long Text", "in_list_view": 1, - "in_standard_filter": 0, "label": "Serial No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "batch_no", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Batch No", - "length": 0, - "no_copy": 0, "oldfieldname": "batch_no", "oldfieldtype": "Data", - "permlevel": 0, - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "warehouse", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, "label": "Warehouse", - "length": 0, - "no_copy": 0, "oldfieldname": "warehouse", "oldfieldtype": "Link", "options": "Warehouse", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "posting_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Posting Date", - "length": 0, - "no_copy": 0, "oldfieldname": "posting_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "posting_time", "fieldtype": "Time", - "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": "Posting Time", - "length": 0, - "no_copy": 0, "oldfieldname": "posting_time", "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "100px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "100px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Voucher Type", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_type", "oldfieldtype": "Data", "options": "DocType", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_no", "fieldtype": "Dynamic Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, "in_standard_filter": 1, "label": "Voucher No", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_no", "oldfieldtype": "Data", "options": "voucher_type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "voucher_detail_no", "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": "Voucher Detail No", - "length": 0, - "no_copy": 0, "oldfieldname": "voucher_detail_no", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "actual_qty", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Quantity", - "length": 0, - "no_copy": 0, "oldfieldname": "actual_qty", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "incoming_rate", "fieldtype": "Currency", - "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": "Incoming Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "incoming_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "outgoing_rate", "fieldtype": "Currency", - "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": "Outgoing Rate", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_uom", "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": "Stock UOM", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_uom", "oldfieldtype": "Data", "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "qty_after_transaction", "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Actual Qty After Transaction", - "length": 0, - "no_copy": 0, "oldfieldname": "bin_aqat", "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "valuation_rate", "fieldtype": "Currency", - "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": "Valuation Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "valuation_rate", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_value", "fieldtype": "Currency", - "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": "Stock Value", - "length": 0, - "no_copy": 0, "oldfieldname": "stock_value", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_value_difference", "fieldtype": "Currency", - "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": "Stock Value Difference", - "length": 0, - "no_copy": 0, "options": "Company:company:default_currency", - "permlevel": 0, - "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 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "stock_queue", "fieldtype": "Text", "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": "Stock Queue (FIFO)", - "length": 0, - "no_copy": 0, "oldfieldname": "fcfs_stack", "oldfieldtype": "Text", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "project", "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": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "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 + "options": "Project" }, { - "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": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Company", - "length": 0, - "no_copy": 0, "oldfieldname": "company", "oldfieldtype": "Data", "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "fiscal_year", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Fiscal Year", - "length": 0, - "no_copy": 0, "oldfieldname": "fiscal_year", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "is_cancelled", "fieldtype": "Select", "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": "Is Cancelled", - "length": 0, - "no_copy": 0, "options": "\nNo\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "1", "fieldname": "to_rename", "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": "To Rename", - "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": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 } ], - "has_web_view": 0, - "hide_heading": 0, "hide_toolbar": 1, "icon": "fa fa-list", "idx": 1, - "image_view": 0, "in_create": 1, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-07 07:04:37.523024", + "modified": "2019-11-27 12:17:31.522675", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Accounts Manager" } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file From bcd02069ea2e7ee9d208ee7ac4aef6bcade16e5f Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 18:23:45 +0530 Subject: [PATCH 03/27] fix: Validation for Suppliers in SO to PO (#19686) - Check if there is a Supplier against atleast one item in Sales Order - Validation message earlier was vague --- erpnext/selling/doctype/sales_order/sales_order.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e12b359bdf..e97a4ee461 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -834,6 +834,10 @@ def make_purchase_order(source_name, for_supplier=None, selected_items=[], targe for item in sales_order.items: if item.supplier and item.supplier not in suppliers: suppliers.append(item.supplier) + + if not suppliers: + frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order.")) + for supplier in suppliers: po =frappe.get_list("Purchase Order", filters={"sales_order":source_name, "supplier":supplier, "docstatus": ("<", "2")}) if len(po) == 0: From 707b83940aeaa60289dc8f8f029faf9363234720 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 28 Nov 2019 18:27:17 +0530 Subject: [PATCH 04/27] fix: due date before posting date for items added to cart yesterday (#19681) --- erpnext/shopping_cart/cart.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 1236ade45f..813d0dd196 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -66,6 +66,7 @@ def place_order(): from erpnext.selling.doctype.quotation.quotation import _make_sales_order sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True)) + sales_order.payment_schedule = [] if not cint(cart_settings.allow_items_not_in_stock): for item in sales_order.get("items"): From 9f3276046ff584843cac3f1a140dbc17f9b45d8e Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:29:33 +0530 Subject: [PATCH 05/27] fix: Account type in Handling Difference in Inventory account (#19674) * fix: Account type in Handling Difference in Inventory account * fix: Add Stock Adjustment account * fix: Rename account to stock adjustment --- .../ae_uae_chart_template_standard.json | 435 +++++++++--------- 1 file changed, 218 insertions(+), 217 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json index 8856c8cc90..a8afb55df6 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json @@ -1,465 +1,466 @@ { - "country_code": "ae", - "name": "U.A.E - Chart of Accounts", + "country_code": "ae", + "name": "U.A.E - Chart of Accounts", "tree": { "Assets": { "Current Assets": { "Accounts Receivable": { "Corporate Credit Cards": { "account_type": "Receivable" - }, + }, "Other Receivable": { "Accrued Rebates Due from Suppliers": { "account_type": "Receivable" - }, + }, "Accrued Income from Suppliers": { "account_type": "Receivable" - }, + }, "Other Debtors": { "account_type": "Receivable" - }, + }, "account_type": "Receivable" - }, + }, "Post Dated Cheques Received": { "account_type": "Receivable" - }, + }, "Staff Receivable": { "account_type": "Receivable" - }, + }, "Trade Receivable": { "account_type": "Receivable" - }, + }, "Trade in Opening Fees": { "account_type": "Receivable" - }, + }, "account_type": "Receivable" - }, + }, "Cash in Hand & Banks": { "Banks": { - "Bank Margin On LC & LG": {}, - "Banks Blocked Deposits": {}, - "Banks Call Deposit Accounts": {}, + "Bank Margin On LC & LG": {}, + "Banks Blocked Deposits": {}, + "Banks Call Deposit Accounts": {}, "Banks Current Accounts": { "account_type": "Bank" - }, + }, "account_type": "Bank" - }, + }, "Cash in Hand": { "Cash in Safe": { "Main Safe": { "account_type": "Cash" - }, + }, "Main Safe - Foreign Currency": { "account_type": "Cash" } - }, + }, "Petty Cash": { "Petty Cash - Administration": { "account_type": "Cash" - }, + }, "Petty Cash - Others": { "account_type": "Cash" } - }, + }, "account_type": "Cash" - }, + }, "Cash in Transit": { "Credit Cards": { "Gateway Credit Cards": { "account_type": "Bank" - }, + }, "Manual Visa & Master Cards": { "account_type": "Bank" - }, + }, "PayPal Account": { "account_type": "Bank" - }, + }, "Visa & Master Credit Cards": { "account_type": "Bank" } } } - }, + }, "Inventory": { "Consigned Stock": { - "Handling Difference in Inventory": { - "account_type": "Stock Adjustment" - }, + "Handling Difference in Inventory": {}, "Items Delivered to Customs on temporary Base": {} - }, + }, "Stock in Hand": { "account_type": "Stock" } - }, + }, "Preliminary and Preoperating Expenses": { "Preoperating Expenses": {} - }, + }, "Prepayments & Deposits": { "Deposits": { - "Deposit - Office Rent": {}, - "Deposit Others": {}, - "Deposit to Immigration (Visa)": {}, + "Deposit - Office Rent": {}, + "Deposit Others": {}, + "Deposit to Immigration (Visa)": {}, "Deposits - Customs": {} - }, + }, "Prepaid Taxes": { - "Sales Taxes Receivables": {}, + "Sales Taxes Receivables": {}, "Withholding Tax Receivables": {} - }, + }, "Prepayments": { - "Other Prepayments": {}, - "PrePaid Advertisement Expenses": {}, - "Prepaid Bank Guarantee": {}, - "Prepaid Consultancy Fees": {}, - "Prepaid Employees Housing": {}, - "Prepaid Finance charge for Loans": {}, - "Prepaid Legal Fees": {}, - "Prepaid License Fees": {}, - "Prepaid Life Insurance": {}, - "Prepaid Maintenance": {}, - "Prepaid Medical Insurance": {}, - "Prepaid Office Rent": {}, - "Prepaid Other Insurance": {}, - "Prepaid Schooling Fees": {}, - "Prepaid Site Hosting Fees": {}, + "Other Prepayments": {}, + "PrePaid Advertisement Expenses": {}, + "Prepaid Bank Guarantee": {}, + "Prepaid Consultancy Fees": {}, + "Prepaid Employees Housing": {}, + "Prepaid Finance charge for Loans": {}, + "Prepaid Legal Fees": {}, + "Prepaid License Fees": {}, + "Prepaid Life Insurance": {}, + "Prepaid Maintenance": {}, + "Prepaid Medical Insurance": {}, + "Prepaid Office Rent": {}, + "Prepaid Other Insurance": {}, + "Prepaid Schooling Fees": {}, + "Prepaid Site Hosting Fees": {}, "Prepaid Sponsorship Fees": {} } } - }, + }, "Long Term Assets": { "Fixed Assets": { "Accumulated Depreciation": { "Acc. Depreciation of Motor Vehicles": { "account_type": "Accumulated Depreciation" - }, + }, "Acc. Deprn.Computer Hardware & Software": { "account_type": "Accumulated Depreciation" - }, + }, "Acc.Deprn.of Furniture & Office Equipment": { "account_type": "Accumulated Depreciation" - }, + }, "Amortisation on Leasehold Improvement": { "account_type": "Accumulated Depreciation" - }, + }, "account_type": "Accumulated Depreciation" - }, + }, "Fixed Assets (Cost Price)": { "Computer Hardware & Software": { "account_type": "Fixed Asset" - }, + }, "Furniture and Equipment": { "account_type": "Fixed Asset" - }, - "Leasehold Improvement": {}, + }, + "Leasehold Improvement": {}, "Motor Vehicles": { "account_type": "Fixed Asset" - }, - "Work In Progress": {}, + }, + "Work In Progress": {}, "account_type": "Fixed Asset" } - }, + }, "Intangible Assets": { - "Computer Card Renewal": {}, - "Disposal of Outlets": {}, + "Computer Card Renewal": {}, + "Disposal of Outlets": {}, "Registration of Trademarks": {} - }, - "Intercompany Accounts": {}, + }, + "Intercompany Accounts": {}, "Investments": { "Investments in Subsidiaries": {} } - }, + }, "root_type": "Asset" - }, + }, "Closing And Temporary Accounts": { "Closing Accounts": { "Closing Account": {} - }, + }, "root_type": "Liability" - }, + }, "Expenses": { "Commercial Expenses": { - "Consultancy Fees": {}, + "Consultancy Fees": {}, "Provision for Doubtful Debts": {} - }, + }, "Cost of Sale": { "Cost Of Goods Sold": { - "Cost Of Goods Sold I/C Sales": {}, + "Cost Of Goods Sold I/C Sales": {}, "Cost of Goods Sold in Trading": { "account_type": "Cost of Goods Sold" - }, + }, "account_type": "Cost of Goods Sold" - }, + }, "Expenses Included In Valuation": { "account_type": "Expenses Included In Valuation" + }, + "Stock Adjustment": { + "account_type": "Stock Adjustment" } - }, + }, "Depreciation": { "Depreciation & Amortization": { - "Amortization on Leasehold Improvement": {}, + "Amortization on Leasehold Improvement": {}, "Depreciation Of Computer Hard & Soft": { "account_type": "Depreciation" - }, + }, "Depreciation Of Furniture & Office Equipment\n\t\t\t": { "account_type": "Depreciation" - }, + }, "Depreciation Of Motor Vehicles": { "account_type": "Depreciation" } } - }, + }, "Direct Expenses": { "Financial Charges": { - "Air Miles Card Charges": {}, - "Amex Credit Cards Charges": {}, - "Bank Finance & Loan Charges": {}, - "Credit Card Charges": {}, - "Credit Card Swipe Charges": {}, + "Air Miles Card Charges": {}, + "Amex Credit Cards Charges": {}, + "Bank Finance & Loan Charges": {}, + "Credit Card Charges": {}, + "Credit Card Swipe Charges": {}, "PayPal Charges": {} } - }, + }, "MISC Charges": { "Other Charges": { "Capital Loss": { - "Disposal of Business Branch": {}, - "Loss On Fixed Assets Disposal": {}, + "Disposal of Business Branch": {}, + "Loss On Fixed Assets Disposal": {}, "Loss on Difference on Exchange": {} - }, + }, "Other Non Operating Exp": { "Other Non Operating Expenses": {} - }, + }, "Previous Year Adjustments": { "Previous Year Adjustments Account": {} - }, + }, "Royalty Fees": { "Royalty to Parent Co.": {} - }, + }, "Tax / Zakat Expenses": { "Income Tax": { "account_type": "Tax" - }, - "Zakat": {}, + }, + "Zakat": {}, "account_type": "Tax" } } - }, + }, "Share Resources": { "Share Resource Expenses Account": {} - }, + }, "Store Operating Expenses": { "Selling, General & Admin Expenses": { "Advertising Expenses": { "Other - Advertising Expenses": {} - }, + }, "Bank & Finance Charges": { "Other Bank Charges": {} - }, + }, "Communications": { - "Courier": {}, - "Others - Communication": {}, - "Telephone": {}, + "Courier": {}, + "Others - Communication": {}, + "Telephone": {}, "Web Site Hosting Fees": {} - }, + }, "Office & Various Expenses": { - "Cleaning": {}, - "Conveyance Expenses": {}, - "Gifts & Donations": {}, - "Insurance": {}, - "Kitchen and Buffet Expenses": {}, - "Maintenance": {}, - "Others - Office Various Expenses": {}, - "Security & Guard": {}, - "Stationary From Suppliers": {}, - "Stationary Out Of Stock": {}, - "Subscriptions": {}, - "Training": {}, + "Cleaning": {}, + "Conveyance Expenses": {}, + "Gifts & Donations": {}, + "Insurance": {}, + "Kitchen and Buffet Expenses": {}, + "Maintenance": {}, + "Others - Office Various Expenses": {}, + "Security & Guard": {}, + "Stationary From Suppliers": {}, + "Stationary Out Of Stock": {}, + "Subscriptions": {}, + "Training": {}, "Vehicle Expenses": {} - }, + }, "Personnel Cost": { - "Basic Salary": {}, - "End Of Service Indemnity": {}, - "Housing Allowance": {}, - "Leave Salary": {}, - "Leave Ticket": {}, - "Life Insurance": {}, - "Medical Insurance": {}, - "Personnel Cost Others": {}, - "Sales Commission": {}, - "Staff School Allowances": {}, - "Transportation Allowance": {}, - "Uniform": {}, + "Basic Salary": {}, + "End Of Service Indemnity": {}, + "Housing Allowance": {}, + "Leave Salary": {}, + "Leave Ticket": {}, + "Life Insurance": {}, + "Medical Insurance": {}, + "Personnel Cost Others": {}, + "Sales Commission": {}, + "Staff School Allowances": {}, + "Transportation Allowance": {}, + "Uniform": {}, "Visa Expenses": {} - }, + }, "Professional & Legal Fees": { - "Audit Fees": {}, - "Legal fees": {}, - "Others - Professional Fees": {}, - "Sponsorship Fees": {}, + "Audit Fees": {}, + "Legal fees": {}, + "Others - Professional Fees": {}, + "Sponsorship Fees": {}, "Trade License Fees": {} - }, + }, "Provision & Write Off": { - "Amortisation of Preoperating Expenses": {}, - "Cash Shortage": {}, - "Others - Provision & Write off": {}, - "Write Off Inventory": {}, + "Amortisation of Preoperating Expenses": {}, + "Cash Shortage": {}, + "Others - Provision & Write off": {}, + "Write Off Inventory": {}, "Write Off Receivables & Payables": {} - }, + }, "Rent Expenses": { - "Office Rent": {}, + "Office Rent": {}, "Warehouse Rent": {} - }, + }, "Travel Expenses": { - "Air tickets": {}, - "Hotel": {}, - "Meals": {}, - "Others": {}, + "Air tickets": {}, + "Hotel": {}, + "Meals": {}, + "Others": {}, "Per Diem": {} - }, + }, "Utilities": { - "Other Utility Cahrges": {}, + "Other Utility Cahrges": {}, "Water & Electricity": {} } } - }, + }, "root_type": "Expense" - }, + }, "Liabilities": { "Current Liabilities": { "Accounts Payable": { "Payables": { "Advance Payable to Suppliers": { "account_type": "Payable" - }, + }, "Consigned Payable": { "account_type": "Payable" - }, + }, "Other Payable": { "account_type": "Payable" - }, + }, "Post Dated Cheques Paid": { "account_type": "Payable" - }, - "Staff Payable": {}, + }, + "Staff Payable": {}, "Suppliers Price Protection": { "account_type": "Payable" - }, + }, "Trade Payable": { "account_type": "Payable" - }, + }, "account_type": "Payable" } - }, + }, "Accruals & Provisions": { "Accruals": { "Accrued Personnel Cost": { - "Accrued - Commissions": {}, - "Accrued - Leave Salary": {}, - "Accrued - Leave Tickets": {}, - "Accrued - Salaries": {}, - "Accrued Other Personnel Cost": {}, - "Accrued Salaries Increment": {}, + "Accrued - Commissions": {}, + "Accrued - Leave Salary": {}, + "Accrued - Leave Tickets": {}, + "Accrued - Salaries": {}, + "Accrued Other Personnel Cost": {}, + "Accrued Salaries Increment": {}, "Accrued-Staff Bonus": {} } - }, + }, "Accrued Expenses": { "Accrued Other Expenses": { - "Accrued - Audit Fees": {}, - "Accrued - Office Rent": {}, - "Accrued - Sponsorship": {}, - "Accrued - Telephone": {}, - "Accrued - Utilities": {}, + "Accrued - Audit Fees": {}, + "Accrued - Office Rent": {}, + "Accrued - Sponsorship": {}, + "Accrued - Telephone": {}, + "Accrued - Utilities": {}, "Accrued Others": {} } - }, + }, "Other Current Liabilities": { - "Accrued Dubai Customs": {}, - "Deferred income": {}, + "Accrued Dubai Customs": {}, + "Deferred income": {}, "Shipping & Handling": {} - }, + }, "Provisions": { "Tax Payables": { - "Income Tax Payable": {}, - "Sales Tax Payable": {}, + "Income Tax Payable": {}, + "Sales Tax Payable": {}, "Withholding Tax Payable": {} } - }, + }, "Short Term Loan": {} - }, + }, "Duties and Taxes": { - "account_type": "Tax", + "account_type": "Tax", "is_group": 1 - }, + }, "Reservations & Credit Notes": { "Credit Notes": { - "Credit Notes to Customers": {}, + "Credit Notes to Customers": {}, "Reservations": {} } - }, + }, "Stock Liabilities": { "Stock Received But Not Billed": { "account_type": "Stock Received But Not Billed" } - }, + }, "Unearned Income": {} - }, + }, "Long Term Liabilities": { "Long Term Loans & Provisions": {} - }, + }, "root_type": "Liability" - }, + }, "Revenue": { "Direct Revenue": { "Other Direct Revenue": { "Other Revenue - Operating": { - "Advertising Income": {}, - "Branding Income": {}, - "Early Setmt Margin from Suppliers": {}, - "Marketing Rebate from Suppliers": {}, - "Rebate from Suppliers": {}, - "Service Income": {}, + "Advertising Income": {}, + "Branding Income": {}, + "Early Setmt Margin from Suppliers": {}, + "Marketing Rebate from Suppliers": {}, + "Rebate from Suppliers": {}, + "Service Income": {}, "Space Rental Income": {} } } - }, + }, "Indirect Revenue": { "Other Indirect Revenue": { - "Capital Gain": {}, - "Excess In Till": {}, - "Gain On Difference Of Exchange": {}, - "Management Consultancy Fees": {}, + "Capital Gain": {}, + "Excess In Till": {}, + "Gain On Difference Of Exchange": {}, + "Management Consultancy Fees": {}, "Other Income": {} - }, + }, "Other Revenue - Non Operating": { - "Interest Revenue": {}, - "Interest from FD": {}, - "Products Listing Fees from Suppliers": {}, + "Interest Revenue": {}, + "Interest from FD": {}, + "Products Listing Fees from Suppliers": {}, "Trade Opening Fees from suppliers": {} } - }, + }, "Sales": { "Sales from Other Regions": { "Sales from Other Region": {} - }, + }, "Sales of same region": { - "Management Consultancy Fees 1": {}, - "Sales Account": {}, + "Management Consultancy Fees 1": {}, + "Sales Account": {}, "Sales of I/C": {} } - }, + }, "root_type": "Income" - }, + }, "Share Holder Equity": { "Capital": { - "Contributed Capital": {}, - "Share Capital": {}, - "Shareholders Current A/c": {}, - "Sub Ordinated Loan": {}, + "Contributed Capital": {}, + "Share Capital": {}, + "Shareholders Current A/c": {}, + "Sub Ordinated Loan": {}, "Treasury Stocks": {} - }, + }, "Retained Earnings": { - "Current Year Results": {}, - "Dividends Paid": {}, + "Current Year Results": {}, + "Dividends Paid": {}, "Previous Years Results": {} - }, - "account_type": "Equity", + }, + "account_type": "Equity", "root_type": "Equity" } } From 5c2ba0ef9c6e6424af95b841dade3e0b873184aa Mon Sep 17 00:00:00 2001 From: Joseph Marie Alba <54699674+erpjosephalba@users.noreply.github.com> Date: Thu, 28 Nov 2019 21:03:20 +0800 Subject: [PATCH 06/27] Fix: Logic bug (#19692) account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)] - if accounts[d]['is_group'] not in ('', 1) is wrong because it returns false even if the account is a group. - should be if accounts[d]['is_group'] not in ('', 0) However, the correction provided here: account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] is more consistent with the prior statement that extracts ledger (and not group) accounts. account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] --- .../chart_of_accounts_importer/chart_of_accounts_importer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 9bf5887b38..34070b01ae 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -185,7 +185,8 @@ def validate_account_types(accounts): return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)) account_types_for_group = ["Bank", "Cash", "Stock"] - account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] not in ('', 1)] + # fix logic bug + account_groups = [accounts[d]["account_type"] for d in accounts if accounts[d]['is_group'] == 1] missing = list(set(account_types_for_group) - set(account_groups)) if missing: From 7cdde9364502a8bd28a744119a96b48a494d05b6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:37:51 +0530 Subject: [PATCH 07/27] fix: Validation for parent cost center (#19664) * fix: Validation for parent cost center * fix: Minor modification in condition * fix: Update test cases for invalid cost center creation --- .../doctype/cost_center/cost_center.py | 7 +++++++ .../doctype/cost_center/test_cost_center.py | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 584e11c53f..0294e78111 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -18,6 +18,7 @@ class CostCenter(NestedSet): def validate(self): self.validate_mandatory() + self.validate_parent_cost_center() def validate_mandatory(self): if self.cost_center_name != self.company and not self.parent_cost_center: @@ -25,6 +26,12 @@ class CostCenter(NestedSet): elif self.cost_center_name == self.company and self.parent_cost_center: frappe.throw(_("Root cannot have a parent cost center")) + def validate_parent_cost_center(self): + if self.parent_cost_center: + if not frappe.db.get_value('Cost Center', self.parent_cost_center, 'is_group'): + frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format( + frappe.bold(self.parent_cost_center))) + def convert_group_to_ledger(self): if self.check_if_child_exists(): frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes")) diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py index c4fad75375..8f23d90676 100644 --- a/erpnext/accounts/doctype/cost_center/test_cost_center.py +++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py @@ -1,12 +1,26 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - - +import unittest import frappe + test_records = frappe.get_test_records('Cost Center') +class TestCostCenter(unittest.TestCase): + def test_cost_center_creation_against_child_node(self): + if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}): + frappe.get_doc(test_records[1]).insert() + + cost_center = frappe.get_doc({ + 'doctype': 'Cost Center', + 'cost_center_name': '_Test Cost Center 3', + 'parent_cost_center': '_Test Cost Center 2 - _TC', + 'is_group': 0, + 'company': '_Test Company' + }) + + self.assertRaises(frappe.ValidationError, cost_center.save) def create_cost_center(**args): args = frappe._dict(args) From f3b393f5e0e4cd7e0864be1c9791301c980bd841 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 28 Nov 2019 18:52:16 +0530 Subject: [PATCH 08/27] fix: UOM was not fetching in purchase invoice (#19732) * fix: UOM was not fetching in purchase invoice * fix: Changes requested Co-authored-by: Marica --- .../purchase_invoice/purchase_invoice.js | 17 ----------------- .../doctype/asset_category/asset_category.py | 3 ++- erpnext/stock/get_item_details.py | 8 +++++++- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e4e2c7b10f..d7e64cf36f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -330,23 +330,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ frm: cur_frm }) }, - - item_code: function(frm, cdt, cdn) { - var row = locals[cdt][cdn]; - if(row.item_code) { - frappe.call({ - method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", - args: { - "item": row.item_code, - "fieldname": "fixed_asset_account", - "company": frm.doc.company - }, - callback: function(r, rt) { - frappe.model.set_value(cdt, cdn, "expense_account", r.message); - } - }) - } - } }); cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice); diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 2a42894623..fc08841be9 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -29,7 +29,8 @@ def get_asset_category_account(fieldname, item=None, asset=None, account=None, a account=None if not account: - asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) + asset_details = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) + asset_category, company = asset_details or [None, None] account = frappe.db.get_value("Asset Category Account", filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 9f47edc774..55f4be136b 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -254,6 +254,12 @@ def get_basic_details(args, item, overwrite_warehouse=True): args['material_request_type'] = frappe.db.get_value('Material Request', args.get('name'), 'material_request_type', cache=True) + expense_account = None + + if args.get('doctype') == 'Purchase Invoice' and item.is_fixed_asset: + from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account + expense_account = get_asset_category_account(fieldname = "fixed_asset_account", item = args.item_code, company= args.company) + #Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master if not args.uom: if args.get('doctype') in sales_doctypes: @@ -271,7 +277,7 @@ def get_basic_details(args, item, overwrite_warehouse=True): "image": cstr(item.image).strip(), "warehouse": warehouse, "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), - "expense_account": get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults), + "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no, From 8f48896261615f2ea9880e3ccb9d6d538e59d053 Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 28 Nov 2019 19:39:49 +0530 Subject: [PATCH 09/27] fix: Permission issue in Stock Entry (#19738) --- erpnext/accounts/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 94697be02f..89c8467da2 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -569,7 +569,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None) warehouse_account = get_warehouse_account_map(company) - account_balance = get_balance_on(account, posting_date, in_account_currency=False) + account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True) related_warehouses = [wh for wh, wh_details in warehouse_account.items() if wh_details.account == account and not wh_details.is_group] From 9d6d95c4a20f5ef6ef6a366473c461de934a91d7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 29 Nov 2019 13:26:52 +0530 Subject: [PATCH 10/27] fix: valuation of "finished good" item in purchase receipt (#19268) * fix: Remove redundant purchase orders and unwanted condition * fix: [WIP] Purchase receipt value * fix: Add raw material cost based on transfered raw material * fix: get_qty_to_be_received * fix: Remove debugger statement * fix: Reset rm_supp_cost before setting subcontracted raw_materials * test: Fix and modify tests for backflush_based_on_stock_entry * fix: Add non stock items to Purchase Receipt from Purchase Order * fix: Ignore valuation rate check for non stock raw material * fix: Rename check all rows * fix: Remove amount from test * test: Fix item rate error * fix: handling of serial nos in backflush * fix: Add serial no. of raw materials * fix: [WIP] Handle Batch nos for purchase reciept backflushed raw material * fix: Raw material batch number selection in purchase receipt * Update test_purchase_order.py --- .../doctype/purchase_order/purchase_order.js | 3 + .../purchase_order/test_purchase_order.py | 53 ++- erpnext/controllers/buying_controller.py | 336 +++++++++++++++--- .../bom_update_tool/bom_update_tool.py | 4 +- ..._order_items_to_be_received_or_billed.json | 2 +- .../stock/report/stock_ledger/stock_ledger.py | 4 +- 6 files changed, 322 insertions(+), 80 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index c5fa98da09..7b5e5c5cca 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", { return { filters: { "company": frm.doc.company, + "name": ['!=', frm.doc.supplier_warehouse], "is_group": 0 } } @@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( }) } + me.dialog.get_field('sub_con_rm_items').check_all_rows() + me.dialog.show() this.dialog.set_primary_action(__('Transfer'), function() { me.values = me.dialog.get_values(); diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 4506db6405..a0a1e8ed5c 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -519,47 +519,62 @@ class TestPurchaseOrder(unittest.TestCase): def test_backflush_based_on_stock_entry(self): item_code = "_Test Subcontracted FG Item 1" make_subcontracted_item(item_code) + make_item('Sub Contracted Raw Material 1', { + 'is_stock_item': 1, + 'is_sub_contracted_item': 1 + }) update_backflush_based_on("Material Transferred for Subcontract") - po = create_purchase_order(item_code=item_code, qty=1, + + order_qty = 5 + po = create_purchase_order(item_code=item_code, qty=order_qty, is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") - make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 1", qty=100, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 2", qty=10, basic_rate=100) + make_stock_entry(target="_Test Warehouse - _TC", + item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100) - rm_item = [ - {"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item", - "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"}, + rm_items = [ + {"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item", + "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, {"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100", - "qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}, + "qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, {"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1", - "qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}] + "qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}, + {'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos', + 'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}] - rm_item_string = json.dumps(rm_item) + rm_item_string = json.dumps(rm_items) se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) - se.append('items', { - 'item_code': "Test Extra Item 2", - "qty": 1, - "rate": 100, - "s_warehouse": "_Test Warehouse - _TC", - "t_warehouse": "_Test Warehouse 1 - _TC" - }) - se.set_missing_values() se.submit() pr = make_purchase_receipt(po.name) + + received_qty = 2 + # partial receipt + pr.get('items')[0].qty = received_qty pr.save() pr.submit() - se_items = sorted([d.item_code for d in se.get('items')]) - supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) + transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name]) + issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')]) + + self.assertEquals(transferred_items, issued_items) + self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000) + + + transferred_rm_map = frappe._dict() + for item in rm_items: + transferred_rm_map[item.get('rm_item_code')] = item + + for item in pr.get('supplied_items'): + self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty) - self.assertEquals(se_items, supplied_items) update_backflush_based_on("BOM") def test_advance_payment_entry_unlink_against_purchase_order(self): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index d12643af82..3ec7aff9cb 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -221,7 +221,7 @@ class BuyingController(StockController): "backflush_raw_materials_of_subcontract_based_on") if (self.doctype == 'Purchase Receipt' and backflush_raw_materials_based_on != 'BOM'): - self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table) + self.update_raw_materials_supplied_based_on_stock_entries() else: for item in self.get("items"): if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: @@ -241,41 +241,95 @@ class BuyingController(StockController): if self.is_subcontracted == "No" and self.get("supplied_items"): self.set('supplied_items', []) - def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table): - self.set(raw_material_table, []) - purchase_orders = [d.purchase_order for d in self.items] - if purchase_orders: - items = get_subcontracted_raw_materials_from_se(purchase_orders) - backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name) + def update_raw_materials_supplied_based_on_stock_entries(self): + self.set('supplied_items', []) - for d in items: - qty = d.qty - backflushed_raw_materials.get(d.item_code, 0) - rm = self.append(raw_material_table, {}) - rm.rm_item_code = d.item_code - rm.item_name = d.item_name - rm.main_item_code = d.main_item_code - rm.description = d.description - rm.stock_uom = d.stock_uom - rm.required_qty = qty - rm.consumed_qty = qty - rm.serial_no = d.serial_no - rm.batch_no = d.batch_no + purchase_orders = set([d.purchase_order for d in self.items]) - # get raw materials rate - from erpnext.stock.utils import get_incoming_rate - rm.rate = get_incoming_rate({ - "item_code": d.item_code, - "warehouse": self.supplier_warehouse, - "posting_date": self.posting_date, - "posting_time": self.posting_time, - "qty": -1 * qty, - "serial_no": rm.serial_no - }) - if not rm.rate: - rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse, - self.doctype, self.name, currency=self.company_currency, company = self.company) + # qty of raw materials backflushed (for each item per purchase order) + backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders) - rm.amount = qty * flt(rm.rate) + # qty of "finished good" item yet to be received + qty_to_be_received_map = get_qty_to_be_received(purchase_orders) + + for item in self.get('items'): + # reset raw_material cost + item.rm_supp_cost = 0 + + # qty of raw materials transferred to the supplier + transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code) + + non_stock_items = get_non_stock_items(item.purchase_order, item.item_code) + + item_key = '{}{}'.format(item.item_code, item.purchase_order) + + fg_yet_to_be_received = qty_to_be_received_map.get(item_key) + + raw_material_data = backflushed_raw_materials_map.get(item_key, {}) + + consumed_qty = raw_material_data.get('qty', 0) + consumed_serial_nos = raw_material_data.get('serial_nos', '') + consumed_batch_nos = raw_material_data.get('batch_nos', '') + + transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code) + backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) + + for raw_material in transferred_raw_materials + non_stock_items: + transferred_qty = raw_material.qty + + rm_qty_to_be_consumed = transferred_qty - consumed_qty + + # backflush all remaining transferred qty in the last Purchase Receipt + if fg_yet_to_be_received == item.qty: + qty = rm_qty_to_be_consumed + else: + qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty + + if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'): + qty = frappe.utils.ceil(qty) + + if qty > rm_qty_to_be_consumed: + qty = rm_qty_to_be_consumed + + if not qty: continue + + if raw_material.serial_nos: + set_serial_nos(raw_material, consumed_serial_nos, qty) + + if raw_material.batch_nos: + batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code, + qty, transferred_batch_qty_map, backflushed_batch_qty_map) + for batch_data in batches_qty: + qty = batch_data['qty'] + raw_material.batch_no = batch_data['batch'] + self.append_raw_material_to_be_backflushed(item, raw_material, qty) + else: + self.append_raw_material_to_be_backflushed(item, raw_material, qty) + + def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty): + rm = self.append('supplied_items', {}) + rm.update(raw_material_data) + + rm.required_qty = qty + rm.consumed_qty = qty + + if not raw_material_data.get('non_stock_item'): + from erpnext.stock.utils import get_incoming_rate + rm.rate = get_incoming_rate({ + "item_code": raw_material_data.rm_item_code, + "warehouse": self.supplier_warehouse, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + "qty": -1 * qty, + "serial_no": rm.serial_no + }) + + if not rm.rate: + rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse, + self.doctype, self.name, currency=self.company_currency, company=self.company) + + rm.amount = qty * flt(rm.rate) + fg_item_doc.rm_supp_cost += rm.amount def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table): exploded_item = 1 @@ -387,9 +441,11 @@ class BuyingController(StockController): item_codes = list(set(item.item_code for item in self.get("items"))) if item_codes: - self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name - from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \ - (", ".join((["%s"]*len(item_codes))),), item_codes)] + items = frappe.get_all('Item', filters={ + 'name': ['in', item_codes], + 'is_sub_contracted_item': 1 + }) + self._sub_contracted_items = [item.name for item in items] return self._sub_contracted_items @@ -722,28 +778,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1): return bom_items -def get_subcontracted_raw_materials_from_se(purchase_orders): - return frappe.db.sql(""" - select - sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description, - sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no - from `tabStock Entry` se,`tabStock Entry Detail` sed - where - se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor' - and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != '' - group by sed.item_code, sed.t_warehouse - """ % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1) +def get_subcontracted_raw_materials_from_se(purchase_order, fg_item): + common_query = """ + SELECT + sed.item_code AS rm_item_code, + SUM(sed.qty) AS qty, + sed.description, + sed.stock_uom, + sed.subcontracted_item AS main_item_code, + {serial_no_concat_syntax} AS serial_nos, + {batch_no_concat_syntax} AS batch_nos + FROM `tabStock Entry` se,`tabStock Entry Detail` sed + WHERE + se.name = sed.parent + AND se.docstatus=1 + AND se.purpose='Send to Subcontractor' + AND se.purchase_order = %s + AND IFNULL(sed.t_warehouse, '') != '' + AND sed.subcontracted_item = %s + GROUP BY sed.item_code, sed.subcontracted_item + """ + raw_materials = frappe.db.multisql({ + 'mariadb': common_query.format( + serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)", + batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)" + ), + 'postgres': common_query.format( + serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')", + batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')" + ) + }, (purchase_order, fg_item), as_dict=1) -def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt): - return frappe._dict(frappe.db.sql(""" - select - prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi - where - pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s) - and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1 - group by prsi.rm_item_code - """ % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders))) + return raw_materials + +def get_backflushed_subcontracted_raw_materials(purchase_orders): + common_query = """ + SELECT + CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key, + SUM(prsi.consumed_qty) AS qty, + {serial_no_concat_syntax} AS serial_nos, + {batch_no_concat_syntax} AS batch_nos + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi + WHERE + pr.name = pri.parent + AND pr.name = prsi.parent + AND pri.purchase_order IN %s + AND pri.item_code = prsi.main_item_code + AND pr.docstatus = 1 + GROUP BY prsi.rm_item_code, pri.purchase_order + """ + + backflushed_raw_materials = frappe.db.multisql({ + 'mariadb': common_query.format( + serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)", + batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)" + ), + 'postgres': common_query.format( + serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')", + batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')" + ) + }, (purchase_orders, ), as_dict=1) + + backflushed_raw_materials_map = frappe._dict() + for item in backflushed_raw_materials: + backflushed_raw_materials_map.setdefault(item.item_key, item) + + return backflushed_raw_materials_map def get_asset_item_details(asset_items): asset_items_data = {} @@ -776,3 +876,125 @@ def validate_item_type(doc, fieldname, message): error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) frappe.throw(error_message) + +def get_qty_to_be_received(purchase_orders): + return frappe._dict(frappe.db.sql(""" + SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key, + SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received + FROM `tabPurchase Order Item` poi + WHERE + poi.`parent` in %s + GROUP BY poi.`item_code`, poi.`parent` + HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`) + """, (purchase_orders))) + +def get_non_stock_items(purchase_order, fg_item_code): + return frappe.db.sql(""" + SELECT + pois.main_item_code, + pois.rm_item_code, + item.description, + pois.required_qty AS qty, + pois.rate, + 1 as non_stock_item, + pois.stock_uom + FROM `tabPurchase Order Item Supplied` pois, `tabItem` item + WHERE + pois.`rm_item_code` = item.`name` + AND item.is_stock_item = 0 + AND pois.`parent` = %s + AND pois.`main_item_code` = %s + """, (purchase_order, fg_item_code), as_dict=1) + + +def set_serial_nos(raw_material, consumed_serial_nos, qty): + serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \ + set(get_serial_nos(consumed_serial_nos)) + if serial_nos and qty <= len(serial_nos): + raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)]) + +def get_transferred_batch_qty_map(purchase_order, fg_item): + # returns + # { + # (item_code, fg_code): { + # batch1: 10, # qty + # batch2: 16 + # }, + # } + transferred_batch_qty_map = {} + transferred_batches = frappe.db.sql(""" + SELECT + sed.batch_no, + SUM(sed.qty) AS qty, + sed.item_code + FROM `tabStock Entry` se,`tabStock Entry Detail` sed + WHERE + se.name = sed.parent + AND se.docstatus=1 + AND se.purpose='Send to Subcontractor' + AND se.purchase_order = %s + AND sed.subcontracted_item = %s + AND sed.batch_no IS NOT NULL + GROUP BY + sed.batch_no, + sed.item_code + """, (purchase_order, fg_item), as_dict=1) + + for batch_data in transferred_batches: + transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) + transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty + + return transferred_batch_qty_map + +def get_backflushed_batch_qty_map(purchase_order, fg_item): + # returns + # { + # (item_code, fg_code): { + # batch1: 10, # qty + # batch2: 16 + # }, + # } + backflushed_batch_qty_map = {} + backflushed_batches = frappe.db.sql(""" + SELECT + pris.batch_no, + SUM(pris.consumed_qty) AS qty, + pris.rm_item_code AS item_code + FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris + WHERE + pr.name = pri.parent + AND pri.parent = pris.parent + AND pri.purchase_order = %s + AND pri.item_code = pris.main_item_code + AND pr.docstatus = 1 + AND pris.main_item_code = %s + AND pris.batch_no IS NOT NULL + GROUP BY + pris.rm_item_code, pris.batch_no + """, (purchase_order, fg_item), as_dict=1) + + for batch_data in backflushed_batches: + backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {}) + backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty + + return backflushed_batch_qty_map + +def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map): + # Returns available batches to be backflushed based on requirements + transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {}) + backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {}) + + available_batches = [] + + for (batch, transferred_qty) in transferred_batches.items(): + backflushed_qty = backflushed_batches.get(batch, 0) + available_qty = transferred_qty - backflushed_qty + + if available_qty >= required_qty: + available_batches.append({'batch': batch, 'qty': required_qty}) + break + else: + available_batches.append({'batch': batch, 'qty': available_qty}) + required_qty -= available_qty + + return available_batches \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py index 2ca4d16a07..31a9fdb28a 100644 --- a/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py +++ b/erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.py @@ -9,6 +9,7 @@ from frappe import _ from six import string_types from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order from frappe.model.document import Document +import click class BOMUpdateTool(Document): def replace_bom(self): @@ -17,7 +18,8 @@ class BOMUpdateTool(Document): frappe.cache().delete_key('bom_children') bom_list = self.get_parent_boms(self.new_bom) updated_bom = [] - + with click.progressbar(bom_list) as bom_list: + pass for bom in bom_list: try: bom_obj = frappe.get_cached_doc('BOM', bom) diff --git a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json index caf7eb8863..48c0f423fd 100644 --- a/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json +++ b/erpnext/stock/report/purchase_order_items_to_be_received_or_billed/purchase_order_items_to_be_received_or_billed.json @@ -15,7 +15,7 @@ "prepared_report": 0, "query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`", "ref_doctype": "Purchase Order", - "report_name": "Purchase Order Items To Be Received or Billed1", + "report_name": "Purchase Order Items To Be Received or Billed", "report_type": "Query Report", "roles": [ { diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index db7f6ad1b9..d757ecb293 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -122,8 +122,8 @@ def get_item_details(items, sl_entries, include_uom): cf_field = cf_join = "" if include_uom: cf_field = ", ucd.conversion_factor" - cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \ - % (include_uom) + cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \ + % frappe.db.escape(include_uom) res = frappe.db.sql(""" select From bf8fe06f86e513edc52a59da851180c7e48ebeb3 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 29 Nov 2019 14:14:11 +0530 Subject: [PATCH 11/27] fix: please specify customer error in sales invoice if patient is blank --- .../doctype/sales_invoice/sales_invoice.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 3c85210663..2ea74f6d85 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -789,22 +789,21 @@ frappe.ui.form.on('Sales Invoice', { method: "frappe.client.get_value", args:{ doctype: "Patient", - filters: {"name": frm.doc.patient}, + filters: { + "name": frm.doc.patient + }, fieldname: "customer" }, - callback:function(patient_customer) { - if(patient_customer){ - frm.set_value("customer", patient_customer.message.customer); - frm.refresh_fields(); + callback:function(r) { + if(r && r.message.customer){ + frm.set_value("customer", r.message.customer); } } }); } - else{ - frm.set_value("customer", ''); - } } }, + refresh: function(frm) { if (frappe.boot.active_domains.includes("Healthcare")){ frm.set_df_property("patient", "hidden", 0); From 45f57f273ca8d1367686cbbfeeb3d57669a9629d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 26 Nov 2019 15:13:23 +0530 Subject: [PATCH 12/27] fix: Serial no validation against sales invoice --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 70a80ca184..9d2f133da4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1048,9 +1048,14 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no, - ["sales_invoice", "item_code"]) - if sales_invoice and item_code == item.item_code and self.name != sales_invoice: + serial_no_details = frappe.db.get_value("Serial No", serial_no, + ["sales_invoice", "item_code"], as_dict=1) + + if not serial_no_details: + continue + + if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ + and self.name != serial_no_details.sales_invoice: sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" From c51dd2989eb6319bddd9a1c0e0750b1905da6a2b Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 26 Nov 2019 16:12:29 +0530 Subject: [PATCH 13/27] fix: Validation msg --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9d2f133da4..def671c19b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1056,10 +1056,10 @@ class SalesInvoice(SellingController): if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \ and self.name != serial_no_details.sales_invoice: - sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") + sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" - .format(serial_no, sales_invoice))) + .format(serial_no, serial_no_details.sales_invoice))) def update_project(self): if self.project: From 20c31dd579f369b260777574994ad06a4f6c8d56 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 29 Nov 2019 16:55:10 +0530 Subject: [PATCH 14/27] fix: tax templates from all companies fetching in receipt (#19682) * fix: tax templates from all companies fetching in receipt * Update purchase_receipt.js --- .../stock/doctype/purchase_receipt/purchase_receipt.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index d5914f9b28..6b5e40e628 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -34,6 +34,12 @@ frappe.ui.form.on("Purchase Receipt", { filters: {'company': frm.doc.company } } }); + + frm.set_query("taxes_and_charges", function() { + return { + filters: {'company': frm.doc.company } + } + }); }, onload: function(frm) { @@ -296,4 +302,4 @@ var validate_sample_quantity = function(frm, cdt, cdn) { } }); } -}; \ No newline at end of file +}; From 06a6fa4cfc88cbe6c05abe0000826b78ddecdfbf Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 29 Nov 2019 16:56:27 +0530 Subject: [PATCH 15/27] feat: Accounts Payable report based on payment terms (#19672) --- erpnext/accounts/report/accounts_payable/accounts_payable.js | 5 +++++ .../report/accounts_receivable/accounts_receivable.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 8eb670de51..b1f427ca7f 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Link", "options": "Supplier Group" }, + { + "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 14906f2c2e..41989bf863 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -318,7 +318,7 @@ class ReceivablePayableReport(object): self.append_payment_term(row, d, term) def append_payment_term(self, row, d, term): - if self.filters.get("customer") and d.currency == d.party_account_currency: + if (self.filters.get("customer") or self.filters.get("supplier")) and d.currency == d.party_account_currency: invoiced = d.payment_amount else: invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) From e3c05290c8f2aa58bfa7ab83723b1967dffbf1e2 Mon Sep 17 00:00:00 2001 From: Khushal Trivedi Date: Fri, 29 Nov 2019 17:00:09 +0530 Subject: [PATCH 16/27] fix: date validation on inpatient record, else condition removing on clinical prcd templ which is not req (#19691) --- .../clinical_procedure_template.py | 5 +++-- .../doctype/inpatient_record/inpatient_record.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py index 141329b3db..7cec362200 100644 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py +++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py @@ -63,10 +63,11 @@ def updating_rate(self): item_code=%s""",(self.template, self.rate, self.item)) def create_item_from_template(doc): + disabled = 1 + if(doc.is_billable == 1): disabled = 0 - else: - disabled = 1 + #insert item item = frappe.get_doc({ "doctype": "Item", diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py index c107cd7335..835b38bedf 100644 --- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py +++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import today, now_datetime +from frappe.utils import today, now_datetime, getdate from frappe.model.document import Document from frappe.desk.reportview import get_match_cond @@ -15,11 +15,20 @@ class InpatientRecord(Document): frappe.db.set_value("Patient", self.patient, "inpatient_record", self.name) def validate(self): + self.validate_dates() self.validate_already_scheduled_or_admitted() if self.status == "Discharged": frappe.db.set_value("Patient", self.patient, "inpatient_status", None) frappe.db.set_value("Patient", self.patient, "inpatient_record", None) + def validate_dates(self): + if (getdate(self.scheduled_date) < getdate(today())) or \ + (getdate(self.admitted_datetime) < getdate(today())): + frappe.throw(_("Scheduled and Admitted dates can not be less than today")) + if (getdate(self.expected_discharge) < getdate(self.scheduled_date)) or \ + (getdate(self.discharge_date) < getdate(self.scheduled_date)): + frappe.throw(_("Expected and Discharge dates cannot be less than Admission Schedule date")) + def validate_already_scheduled_or_admitted(self): query = """ select name, status From 98627f46f69df70e727c79b4b34169c62ded03b3 Mon Sep 17 00:00:00 2001 From: Saqib Date: Fri, 29 Nov 2019 17:29:38 +0530 Subject: [PATCH 17/27] fix: remove asset movement mandatory fields (#19670) * fix: pending on review date (#19609) * fix: On Specific case if no item code in name * fix: pending on review date * feat: fetch leave approver from both employee and department approvers (#19613) * fix: fetch leave approvers from both department and employee master * fix: creaate a set of approvers * fix(expense claim): fetch outstanding documents based on party account type * fix: not able to select item in sales order * fix: code cleanup * fix: asset movement ux fixes (#19641) * fix: Multiple fixes related to landed cost accounting (#19656) * fix: last purchase rate greater than selling price * Fixed Asset Refactor Review fixes (#19665) * fix: fixed asset item creation ux fixes * fix: auto creation of asset ux fixes * fix: [LCV] incorrect condition when checking assets linked with PR * fix: bulk update assets * refac: remove company level cwip enabling * cwip can be enabled only on category level * fix: #19649 * fix: remove asset movement mandatory fields * fix: label for reference doctype From 750b3a594671151d7edeba770a9a5d5906085a73 Mon Sep 17 00:00:00 2001 From: Prasad Ramesh Date: Fri, 29 Nov 2019 17:30:47 +0530 Subject: [PATCH 18/27] fix: Button label case (#19706) Json -> JSON --- erpnext/regional/report/gstr_1/gstr_1.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 9682768280..ce559218cb 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -52,7 +52,7 @@ frappe.query_reports["GSTR-1"] = { ], onload: function (report) { - report.page.add_inner_button(__("Download as Json"), function () { + report.page.add_inner_button(__("Download as JSON"), function () { var filters = report.get_values(); const args = { From 0de066c3b190be6f8eac06f547feba0c0750226a Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Fri, 29 Nov 2019 13:02:17 +0100 Subject: [PATCH 19/27] feat(regional): Add master data to DATEV Export (#18755) * Add master data to export * add SQL statements to get customers and suppliers * make data category a string * fix SQL error * fix SQL errors * unique column names * add encoding of constants * get customer primary address and contact * fix typo * fix typo * binary response * add filename * add filecontent * rename account columns * exclude account groups * use compression, close file before transfer * fix StringIO * add basic tests * fix assertion, merge test methods * fix indentation * relative import of constants * fix path * import os * Add default currency to test company * root accounts with parent = null * move account-related things to setup() * add: test headers * company and filters become class properties * add: test csv creation * (fix): add missing account * (fix): remove wrong space * add items to sales invoice * refactor: create test data * fix: create cost center * fix: doctype Accoutn * fix: make sure account belongs to company * fix: remove customer group and territory, save on a new line * create default warehouses * fix: make Item myself * fix: item defaults are a list * fix: use my own warehouse * fix: use my own expense account * fix: let you take care of the Sales Invoice Item * fix: import zipfile * add TODOs * fix: workaround for pandas bug * SQL: utf-8 everywhere to make conversion in tests unnecessary * tests: zipfile must be encoded string * fix(tests): invalid start byte * fix(test): give is_zipfile() the file-like object it expects * fix(test): fix encoding of colums * fix(get_transactions): as_dict is 1 by default * fix(tests): allow empty data * refactor: rename columns in get_account_names * fix(pandas): keep sorting columns * fix: "lineterminator" must be a string * fix(test): check if cost center exists * fix: credit limit became a child table * fix: save company after creation * insert instead of save * tests: setup_fiscal_year * fix(test): import cstr * fix(tests): fiscal year * fix: can't concat str to bytes * fix: make csv-encoding work for py2 and py3 * fix(test): use frappe.as_unicode instead of unicode * fix: use BytesIO instead of StringIO for py3 compatibility * fix(tests): use BytesIO instead of StringIO for py3 compatibility --- erpnext/regional/report/datev/datev.py | 470 +++++++--------- .../regional/report/datev/datev_constants.py | 512 ++++++++++++++++++ erpnext/regional/report/datev/test_datev.py | 244 +++++++++ 3 files changed, 961 insertions(+), 265 deletions(-) create mode 100644 erpnext/regional/report/datev/datev_constants.py create mode 100644 erpnext/regional/report/datev/test_datev.py diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index ee8735fb1f..bd70639ef2 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -10,17 +10,26 @@ Provide a report and downloadable CSV according to the German DATEV format. from __future__ import unicode_literals import datetime import json +import zlib +import zipfile +import six +from six import BytesIO from six import string_types import frappe from frappe import _ import pandas as pd +from .datev_constants import DataCategory +from .datev_constants import Transactions +from .datev_constants import DebtorsCreditors +from .datev_constants import AccountNames +from .datev_constants import QUERY_REPORT_COLUMNS def execute(filters=None): """Entry point for frappe.""" validate(filters) - result = get_gl_entries(filters, as_dict=0) - columns = get_columns() + result = get_transactions(filters, as_dict=0) + columns = QUERY_REPORT_COLUMNS return columns, result @@ -41,65 +50,8 @@ def validate(filters): except frappe.DoesNotExistError: frappe.throw(_('Please create DATEV Settings for Company {}.').format(filters.get('company'))) -def get_columns(): - """Return the list of columns that will be shown in query report.""" - columns = [ - { - "label": "Umsatz (ohne Soll/Haben-Kz)", - "fieldname": "Umsatz (ohne Soll/Haben-Kz)", - "fieldtype": "Currency", - }, - { - "label": "Soll/Haben-Kennzeichen", - "fieldname": "Soll/Haben-Kennzeichen", - "fieldtype": "Data", - }, - { - "label": "Kontonummer", - "fieldname": "Kontonummer", - "fieldtype": "Data", - }, - { - "label": "Gegenkonto (ohne BU-Schlüssel)", - "fieldname": "Gegenkonto (ohne BU-Schlüssel)", - "fieldtype": "Data", - }, - { - "label": "Belegdatum", - "fieldname": "Belegdatum", - "fieldtype": "Date", - }, - { - "label": "Buchungstext", - "fieldname": "Buchungstext", - "fieldtype": "Text", - }, - { - "label": "Beleginfo - Art 1", - "fieldname": "Beleginfo - Art 1", - "fieldtype": "Data", - }, - { - "label": "Beleginfo - Inhalt 1", - "fieldname": "Beleginfo - Inhalt 1", - "fieldtype": "Data", - }, - { - "label": "Beleginfo - Art 2", - "fieldname": "Beleginfo - Art 2", - "fieldtype": "Data", - }, - { - "label": "Beleginfo - Inhalt 2", - "fieldname": "Beleginfo - Inhalt 2", - "fieldtype": "Data", - } - ] - return columns - - -def get_gl_entries(filters, as_dict): +def get_transactions(filters, as_dict=1): """ Get a list of accounting entries. @@ -111,7 +63,7 @@ def get_gl_entries(filters, as_dict): as_dict -- return as list of dicts [0,1] """ gl_entries = frappe.db.sql(""" - select + SELECT /* either debit or credit amount; always positive */ case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)', @@ -132,7 +84,7 @@ def get_gl_entries(filters, as_dict): gl.against_voucher_type as 'Beleginfo - Art 2', gl.against_voucher as 'Beleginfo - Inhalt 2' - from `tabGL Entry` gl + FROM `tabGL Entry` gl /* Statistisches Konto (Debitoren/Kreditoren) */ left join `tabParty Account` pa @@ -155,15 +107,127 @@ def get_gl_entries(filters, as_dict): left join `tabAccount` acc_against_pa on pa.account = acc_against_pa.name - where gl.company = %(company)s - and DATE(gl.posting_date) >= %(from_date)s - and DATE(gl.posting_date) <= %(to_date)s - order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) + WHERE gl.company = %(company)s + AND DATE(gl.posting_date) >= %(from_date)s + AND DATE(gl.posting_date) <= %(to_date)s + ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1) return gl_entries -def get_datev_csv(data, filters): +def get_customers(filters): + """ + Get a list of Customers. + + Arguments: + filters -- dict of filters to be passed to the sql query + """ + return frappe.db.sql(""" + SELECT + + acc.account_number as 'Konto', + cus.customer_name as 'Name (Adressatentyp Unternehmen)', + case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp', + adr.address_line1 as 'Straße', + adr.pincode as 'Postleitzahl', + adr.city as 'Ort', + UPPER(country.code) as 'Land', + adr.address_line2 as 'Adresszusatz', + con.email_id as 'E-Mail', + coalesce(con.mobile_no, con.phone) as 'Telefon', + cus.website as 'Internet', + cus.tax_id as 'Steuernummer', + ccl.credit_limit as 'Kreditlimit (Debitor)' + + FROM `tabParty Account` par + + left join `tabAccount` acc + on acc.name = par.account + + left join `tabCustomer` cus + on cus.name = par.parent + + left join `tabAddress` adr + on adr.name = cus.customer_primary_address + + left join `tabCountry` country + on country.name = adr.country + + left join `tabContact` con + on con.name = cus.customer_primary_contact + + left join `tabCustomer Credit Limit` ccl + on ccl.parent = cus.name + and ccl.company = par.company + + WHERE par.company = %(company)s + AND par.parenttype = 'Customer'""", filters, as_dict=1, as_utf8=1) + + +def get_suppliers(filters): + """ + Get a list of Suppliers. + + Arguments: + filters -- dict of filters to be passed to the sql query + """ + return frappe.db.sql(""" + SELECT + + acc.account_number as 'Konto', + sup.supplier_name as 'Name (Adressatentyp Unternehmen)', + case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp', + adr.address_line1 as 'Straße', + adr.pincode as 'Postleitzahl', + adr.city as 'Ort', + UPPER(country.code) as 'Land', + adr.address_line2 as 'Adresszusatz', + con.email_id as 'E-Mail', + coalesce(con.mobile_no, con.phone) as 'Telefon', + sup.website as 'Internet', + sup.tax_id as 'Steuernummer', + case sup.on_hold when 1 then sup.release_date else null end as 'Zahlungssperre bis' + + FROM `tabParty Account` par + + left join `tabAccount` acc + on acc.name = par.account + + left join `tabSupplier` sup + on sup.name = par.parent + + left join `tabDynamic Link` dyn_adr + on dyn_adr.link_name = sup.name + and dyn_adr.link_doctype = 'Supplier' + and dyn_adr.parenttype = 'Address' + + left join `tabAddress` adr + on adr.name = dyn_adr.parent + and adr.is_primary_address = '1' + + left join `tabCountry` country + on country.name = adr.country + + left join `tabDynamic Link` dyn_con + on dyn_con.link_name = sup.name + and dyn_con.link_doctype = 'Supplier' + and dyn_con.parenttype = 'Contact' + + left join `tabContact` con + on con.name = dyn_con.parent + and con.is_primary_contact = '1' + + WHERE par.company = %(company)s + AND par.parenttype = 'Supplier'""", filters, as_dict=1, as_utf8=1) + + +def get_account_names(filters): + return frappe.get_list("Account", + fields=["account_number as Konto", "name as Kontenbeschriftung"], + filters={"company": filters.get("company"), "is_group": "0"}) + + +def get_datev_csv(data, filters, csv_class): """ Fill in missing columns and return a CSV in DATEV Format. @@ -174,7 +238,46 @@ def get_datev_csv(data, filters): Arguments: data -- array of dictionaries filters -- dict + csv_class -- defines DATA_CATEGORY, FORMAT_NAME and COLUMNS """ + header = get_header(filters, csv_class) + + empty_df = pd.DataFrame(columns=csv_class.COLUMNS) + data_df = pd.DataFrame.from_records(data) + + result = empty_df.append(data_df, sort=True) + + if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS: + result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) + + if csv_class.DATA_CATEGORY == DataCategory.ACCOUNT_NAMES: + result['Sprach-ID'] = 'de-DE' + + header = ';'.join(header).encode('latin_1') + data = result.to_csv( + # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035 + sep=str(';'), + # European decimal seperator + decimal=',', + # Windows "ANSI" encoding + encoding='latin_1', + # format date as DDMM + date_format='%d%m', + # Windows line terminator + line_terminator='\r\n', + # Do not number rows + index=False, + # Use all columns defined above + columns=csv_class.COLUMNS + ) + + if not six.PY2: + data = data.encode('latin_1') + + return header + b'\r\n' + data + + +def get_header(filters, csv_class): header = [ # A = DATEV format # DTVF = created by DATEV software, @@ -185,18 +288,8 @@ def get_datev_csv(data, filters): # 510 = 5.10, # 720 = 7.20 "510", - # C = Data category - # 21 = Transaction batch (Buchungsstapel), - # 67 = Buchungstextkonstanten, - # 16 = Debitors/Creditors, - # 20 = Account names (Kontenbeschriftungen) - "21", - # D = Format name - # Buchungsstapel, - # Buchungstextkonstanten, - # Debitoren/Kreditoren, - # Kontenbeschriftungen - "Buchungsstapel", + csv_class.DATA_CATEGORY, + csv_class.FORMAT_NAME, # E = Format version (regarding format name) "", # F = Generated on @@ -224,16 +317,17 @@ def get_datev_csv(data, filters): # P = Transaction batch end date (YYYYMMDD) frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), # Q = Description (for example, "January - February 2019 Transactions") - "{} - {} Buchungsstapel".format( - frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), - frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy") + "{} - {} {}".format( + frappe.utils.formatdate(filters.get('from_date'), "MMMM yyyy"), + frappe.utils.formatdate(filters.get('to_date'), "MMMM yyyy"), + csv_class.FORMAT_NAME ), # R = Diktatkürzel "", # S = Buchungstyp # 1 = Transaction batch (Buchungsstapel), # 2 = Annual financial statement (Jahresabschluss) - "1", + "1" if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else "", # T = Rechnungslegungszweck "", # U = Festschreibung @@ -241,185 +335,8 @@ def get_datev_csv(data, filters): # V = Kontoführungs-Währungskennzeichen des Geldkontos frappe.get_value("Company", filters.get("company"), "default_currency") ] - columns = [ - # All possible columns must tbe listed here, because DATEV requires them to - # be present in the CSV. - # --- - # Umsatz - "Umsatz (ohne Soll/Haben-Kz)", - "Soll/Haben-Kennzeichen", - "WKZ Umsatz", - "Kurs", - "Basis-Umsatz", - "WKZ Basis-Umsatz", - # Konto/Gegenkonto - "Kontonummer", - "Gegenkonto (ohne BU-Schlüssel)", - "BU-Schlüssel", - # Datum - "Belegdatum", - # Belegfelder - "Belegfeld 1", - "Belegfeld 2", - # Weitere Felder - "Skonto", - "Buchungstext", - # OPOS-Informationen - "Postensperre", - "Diverse Adressnummer", - "Geschäftspartnerbank", - "Sachverhalt", - "Zinssperre", - # Digitaler Beleg - "Beleglink", - # Beleginfo - "Beleginfo - Art 1", - "Beleginfo - Inhalt 1", - "Beleginfo - Art 2", - "Beleginfo - Inhalt 2", - "Beleginfo - Art 3", - "Beleginfo - Inhalt 3", - "Beleginfo - Art 4", - "Beleginfo - Inhalt 4", - "Beleginfo - Art 5", - "Beleginfo - Inhalt 5", - "Beleginfo - Art 6", - "Beleginfo - Inhalt 6", - "Beleginfo - Art 7", - "Beleginfo - Inhalt 7", - "Beleginfo - Art 8", - "Beleginfo - Inhalt 8", - # Kostenrechnung - "Kost 1 - Kostenstelle", - "Kost 2 - Kostenstelle", - "Kost-Menge", - # Steuerrechnung - "EU-Land u. UStID", - "EU-Steuersatz", - "Abw. Versteuerungsart", - # L+L Sachverhalt - "Sachverhalt L+L", - "Funktionsergänzung L+L", - # Funktion Steuerschlüssel 49 - "BU 49 Hauptfunktionstyp", - "BU 49 Hauptfunktionsnummer", - "BU 49 Funktionsergänzung", - # Zusatzinformationen - "Zusatzinformation - Art 1", - "Zusatzinformation - Inhalt 1", - "Zusatzinformation - Art 2", - "Zusatzinformation - Inhalt 2", - "Zusatzinformation - Art 3", - "Zusatzinformation - Inhalt 3", - "Zusatzinformation - Art 4", - "Zusatzinformation - Inhalt 4", - "Zusatzinformation - Art 5", - "Zusatzinformation - Inhalt 5", - "Zusatzinformation - Art 6", - "Zusatzinformation - Inhalt 6", - "Zusatzinformation - Art 7", - "Zusatzinformation - Inhalt 7", - "Zusatzinformation - Art 8", - "Zusatzinformation - Inhalt 8", - "Zusatzinformation - Art 9", - "Zusatzinformation - Inhalt 9", - "Zusatzinformation - Art 10", - "Zusatzinformation - Inhalt 10", - "Zusatzinformation - Art 11", - "Zusatzinformation - Inhalt 11", - "Zusatzinformation - Art 12", - "Zusatzinformation - Inhalt 12", - "Zusatzinformation - Art 13", - "Zusatzinformation - Inhalt 13", - "Zusatzinformation - Art 14", - "Zusatzinformation - Inhalt 14", - "Zusatzinformation - Art 15", - "Zusatzinformation - Inhalt 15", - "Zusatzinformation - Art 16", - "Zusatzinformation - Inhalt 16", - "Zusatzinformation - Art 17", - "Zusatzinformation - Inhalt 17", - "Zusatzinformation - Art 18", - "Zusatzinformation - Inhalt 18", - "Zusatzinformation - Art 19", - "Zusatzinformation - Inhalt 19", - "Zusatzinformation - Art 20", - "Zusatzinformation - Inhalt 20", - # Mengenfelder LuF - "Stück", - "Gewicht", - # Forderungsart - "Zahlweise", - "Forderungsart", - "Veranlagungsjahr", - "Zugeordnete Fälligkeit", - # Weitere Felder - "Skontotyp", - # Anzahlungen - "Auftragsnummer", - "Buchungstyp", - "USt-Schlüssel (Anzahlungen)", - "EU-Land (Anzahlungen)", - "Sachverhalt L+L (Anzahlungen)", - "EU-Steuersatz (Anzahlungen)", - "Erlöskonto (Anzahlungen)", - # Stapelinformationen - "Herkunft-Kz", - # Technische Identifikation - "Buchungs GUID", - # Kostenrechnung - "Kost-Datum", - # OPOS-Informationen - "SEPA-Mandatsreferenz", - "Skontosperre", - # Gesellschafter und Sonderbilanzsachverhalt - "Gesellschaftername", - "Beteiligtennummer", - "Identifikationsnummer", - "Zeichnernummer", - # OPOS-Informationen - "Postensperre bis", - # Gesellschafter und Sonderbilanzsachverhalt - "Bezeichnung SoBil-Sachverhalt", - "Kennzeichen SoBil-Buchung", - # Stapelinformationen - "Festschreibung", - # Datum - "Leistungsdatum", - "Datum Zuord. Steuerperiode", - # OPOS-Informationen - "Fälligkeit", - # Konto/Gegenkonto - "Generalumkehr (GU)", - # Steuersatz für Steuerschlüssel - "Steuersatz", - "Land" - ] + return header - empty_df = pd.DataFrame(columns=columns) - data_df = pd.DataFrame.from_records(data) - - result = empty_df.append(data_df) - result['Belegdatum'] = pd.to_datetime(result['Belegdatum']) - - header = ';'.join(header).encode('latin_1') - data = result.to_csv( - sep=b';', - # European decimal seperator - decimal=',', - # Windows "ANSI" encoding - encoding='latin_1', - # format date as DDMM - date_format='%d%m', - # Windows line terminator - line_terminator=b'\r\n', - # Do not number rows - index=False, - # Use all columns defined above - columns=columns - ) - - return header + b'\r\n' + data @frappe.whitelist() def download_datev_csv(filters=None): @@ -438,8 +355,31 @@ def download_datev_csv(filters=None): filters = json.loads(filters) validate(filters) - data = get_gl_entries(filters, as_dict=1) - frappe.response['result'] = get_datev_csv(data, filters) - frappe.response['doctype'] = 'EXTF_Buchungsstapel' - frappe.response['type'] = 'csv' + # This is where my zip will be written + zip_buffer = BytesIO() + # This is my zip file + datev_zip = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED) + + transactions = get_transactions(filters) + transactions_csv = get_datev_csv(transactions, filters, csv_class=Transactions) + datev_zip.writestr('EXTF_Buchungsstapel.csv', transactions_csv) + + account_names = get_account_names(filters) + account_names_csv = get_datev_csv(account_names, filters, csv_class=AccountNames) + datev_zip.writestr('EXTF_Kontenbeschriftungen.csv', account_names_csv) + + customers = get_customers(filters) + customers_csv = get_datev_csv(customers, filters, csv_class=DebtorsCreditors) + datev_zip.writestr('EXTF_Kunden.csv', customers_csv) + + suppliers = get_suppliers(filters) + suppliers_csv = get_datev_csv(suppliers, filters, csv_class=DebtorsCreditors) + datev_zip.writestr('EXTF_Lieferanten.csv', suppliers_csv) + + # You must call close() before exiting your program or essential records will not be written. + datev_zip.close() + + frappe.response['filecontent'] = zip_buffer.getvalue() + frappe.response['filename'] = 'DATEV.zip' + frappe.response['type'] = 'binary' diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py new file mode 100644 index 0000000000..1c9bd23ee1 --- /dev/null +++ b/erpnext/regional/report/datev/datev_constants.py @@ -0,0 +1,512 @@ +# coding: utf-8 +"""Constants used in datev.py.""" + +TRANSACTION_COLUMNS = [ + # All possible columns must tbe listed here, because DATEV requires them to + # be present in the CSV. + # --- + # Umsatz + "Umsatz (ohne Soll/Haben-Kz)", + "Soll/Haben-Kennzeichen", + "WKZ Umsatz", + "Kurs", + "Basis-Umsatz", + "WKZ Basis-Umsatz", + # Konto/Gegenkonto + "Kontonummer", + "Gegenkonto (ohne BU-Schlüssel)", + "BU-Schlüssel", + # Datum + "Belegdatum", + # Belegfelder + "Belegfeld 1", + "Belegfeld 2", + # Weitere Felder + "Skonto", + "Buchungstext", + # OPOS-Informationen + "Postensperre", + "Diverse Adressnummer", + "Geschäftspartnerbank", + "Sachverhalt", + "Zinssperre", + # Digitaler Beleg + "Beleglink", + # Beleginfo + "Beleginfo - Art 1", + "Beleginfo - Inhalt 1", + "Beleginfo - Art 2", + "Beleginfo - Inhalt 2", + "Beleginfo - Art 3", + "Beleginfo - Inhalt 3", + "Beleginfo - Art 4", + "Beleginfo - Inhalt 4", + "Beleginfo - Art 5", + "Beleginfo - Inhalt 5", + "Beleginfo - Art 6", + "Beleginfo - Inhalt 6", + "Beleginfo - Art 7", + "Beleginfo - Inhalt 7", + "Beleginfo - Art 8", + "Beleginfo - Inhalt 8", + # Kostenrechnung + "Kost 1 - Kostenstelle", + "Kost 2 - Kostenstelle", + "Kost-Menge", + # Steuerrechnung + "EU-Land u. UStID", + "EU-Steuersatz", + "Abw. Versteuerungsart", + # L+L Sachverhalt + "Sachverhalt L+L", + "Funktionsergänzung L+L", + # Funktion Steuerschlüssel 49 + "BU 49 Hauptfunktionstyp", + "BU 49 Hauptfunktionsnummer", + "BU 49 Funktionsergänzung", + # Zusatzinformationen + "Zusatzinformation - Art 1", + "Zusatzinformation - Inhalt 1", + "Zusatzinformation - Art 2", + "Zusatzinformation - Inhalt 2", + "Zusatzinformation - Art 3", + "Zusatzinformation - Inhalt 3", + "Zusatzinformation - Art 4", + "Zusatzinformation - Inhalt 4", + "Zusatzinformation - Art 5", + "Zusatzinformation - Inhalt 5", + "Zusatzinformation - Art 6", + "Zusatzinformation - Inhalt 6", + "Zusatzinformation - Art 7", + "Zusatzinformation - Inhalt 7", + "Zusatzinformation - Art 8", + "Zusatzinformation - Inhalt 8", + "Zusatzinformation - Art 9", + "Zusatzinformation - Inhalt 9", + "Zusatzinformation - Art 10", + "Zusatzinformation - Inhalt 10", + "Zusatzinformation - Art 11", + "Zusatzinformation - Inhalt 11", + "Zusatzinformation - Art 12", + "Zusatzinformation - Inhalt 12", + "Zusatzinformation - Art 13", + "Zusatzinformation - Inhalt 13", + "Zusatzinformation - Art 14", + "Zusatzinformation - Inhalt 14", + "Zusatzinformation - Art 15", + "Zusatzinformation - Inhalt 15", + "Zusatzinformation - Art 16", + "Zusatzinformation - Inhalt 16", + "Zusatzinformation - Art 17", + "Zusatzinformation - Inhalt 17", + "Zusatzinformation - Art 18", + "Zusatzinformation - Inhalt 18", + "Zusatzinformation - Art 19", + "Zusatzinformation - Inhalt 19", + "Zusatzinformation - Art 20", + "Zusatzinformation - Inhalt 20", + # Mengenfelder LuF + "Stück", + "Gewicht", + # Forderungsart + "Zahlweise", + "Forderungsart", + "Veranlagungsjahr", + "Zugeordnete Fälligkeit", + # Weitere Felder + "Skontotyp", + # Anzahlungen + "Auftragsnummer", + "Buchungstyp", + "USt-Schlüssel (Anzahlungen)", + "EU-Land (Anzahlungen)", + "Sachverhalt L+L (Anzahlungen)", + "EU-Steuersatz (Anzahlungen)", + "Erlöskonto (Anzahlungen)", + # Stapelinformationen + "Herkunft-Kz", + # Technische Identifikation + "Buchungs GUID", + # Kostenrechnung + "Kost-Datum", + # OPOS-Informationen + "SEPA-Mandatsreferenz", + "Skontosperre", + # Gesellschafter und Sonderbilanzsachverhalt + "Gesellschaftername", + "Beteiligtennummer", + "Identifikationsnummer", + "Zeichnernummer", + # OPOS-Informationen + "Postensperre bis", + # Gesellschafter und Sonderbilanzsachverhalt + "Bezeichnung SoBil-Sachverhalt", + "Kennzeichen SoBil-Buchung", + # Stapelinformationen + "Festschreibung", + # Datum + "Leistungsdatum", + "Datum Zuord. Steuerperiode", + # OPOS-Informationen + "Fälligkeit", + # Konto/Gegenkonto + "Generalumkehr (GU)", + # Steuersatz für Steuerschlüssel + "Steuersatz", + "Land" +] + +DEBTOR_CREDITOR_COLUMNS = [ + # All possible columns must tbe listed here, because DATEV requires them to + # be present in the CSV. + # Columns "Leerfeld" have been replaced with "Leerfeld #" to not confuse pandas + # --- + "Konto", + "Name (Adressatentyp Unternehmen)", + "Unternehmensgegenstand", + "Name (Adressatentyp natürl. Person)", + "Vorname (Adressatentyp natürl. Person)", + "Name (Adressatentyp keine Angabe)", + "Adressatentyp", + "Kurzbezeichnung", + "EU-Land", + "EU-USt-IdNr.", + "Anrede", + "Titel/Akad. Grad", + "Adelstitel", + "Namensvorsatz", + "Adressart", + "Straße", + "Postfach", + "Postleitzahl", + "Ort", + "Land", + "Versandzusatz", + "Adresszusatz", + "Abweichende Anrede", + "Abw. Zustellbezeichnung 1", + "Abw. Zustellbezeichnung 2", + "Kennz. Korrespondenzadresse", + "Adresse gültig von", + "Adresse gültig bis", + "Telefon", + "Bemerkung (Telefon)", + "Telefon Geschäftsleitung", + "Bemerkung (Telefon GL)", + "E-Mail", + "Bemerkung (E-Mail)", + "Internet", + "Bemerkung (Internet)", + "Fax", + "Bemerkung (Fax)", + "Sonstige", + "Bemerkung (Sonstige)", + "Bankleitzahl 1", + "Bankbezeichnung 1", + "Bankkonto-Nummer 1", + "Länderkennzeichen 1", + "IBAN 1", + "Leerfeld 1", + "SWIFT-Code 1", + "Abw. Kontoinhaber 1", + "Kennz. Haupt-Bankverb. 1", + "Bankverb. 1 Gültig von", + "Bankverb. 1 Gültig bis", + "Bankleitzahl 2", + "Bankbezeichnung 2", + "Bankkonto-Nummer 2", + "Länderkennzeichen 2", + "IBAN 2", + "Leerfeld 2", + "SWIFT-Code 2", + "Abw. Kontoinhaber 2", + "Kennz. Haupt-Bankverb. 2", + "Bankverb. 2 gültig von", + "Bankverb. 2 gültig bis", + "Bankleitzahl 3", + "Bankbezeichnung 3", + "Bankkonto-Nummer 3", + "Länderkennzeichen 3", + "IBAN 3", + "Leerfeld 3", + "SWIFT-Code 3", + "Abw. Kontoinhaber 3", + "Kennz. Haupt-Bankverb. 3", + "Bankverb. 3 gültig von", + "Bankverb. 3 gültig bis", + "Bankleitzahl 4", + "Bankbezeichnung 4", + "Bankkonto-Nummer 4", + "Länderkennzeichen 4", + "IBAN 4", + "Leerfeld 4", + "SWIFT-Code 4", + "Abw. Kontoinhaber 4", + "Kennz. Haupt-Bankverb. 4", + "Bankverb. 4 Gültig von", + "Bankverb. 4 Gültig bis", + "Bankleitzahl 5", + "Bankbezeichnung 5", + "Bankkonto-Nummer 5", + "Länderkennzeichen 5", + "IBAN 5", + "Leerfeld 5", + "SWIFT-Code 5", + "Abw. Kontoinhaber 5", + "Kennz. Haupt-Bankverb. 5", + "Bankverb. 5 gültig von", + "Bankverb. 5 gültig bis", + "Leerfeld 6", + "Briefanrede", + "Grußformel", + "Kundennummer", + "Steuernummer", + "Sprache", + "Ansprechpartner", + "Vertreter", + "Sachbearbeiter", + "Diverse-Konto", + "Ausgabeziel", + "Währungssteuerung", + "Kreditlimit (Debitor)", + "Zahlungsbedingung", + "Fälligkeit in Tagen (Debitor)", + "Skonto in Prozent (Debitor)", + "Kreditoren-Ziel 1 (Tage)", + "Kreditoren-Skonto 1 (%)", + "Kreditoren-Ziel 2 (Tage)", + "Kreditoren-Skonto 2 (%)", + "Kreditoren-Ziel 3 Brutto (Tage)", + "Kreditoren-Ziel 4 (Tage)", + "Kreditoren-Skonto 4 (%)", + "Kreditoren-Ziel 5 (Tage)", + "Kreditoren-Skonto 5 (%)", + "Mahnung", + "Kontoauszug", + "Mahntext 1", + "Mahntext 2", + "Mahntext 3", + "Kontoauszugstext", + "Mahnlimit Betrag", + "Mahnlimit %", + "Zinsberechnung", + "Mahnzinssatz 1", + "Mahnzinssatz 2", + "Mahnzinssatz 3", + "Lastschrift", + "Verfahren", + "Mandantenbank", + "Zahlungsträger", + "Indiv. Feld 1", + "Indiv. Feld 2", + "Indiv. Feld 3", + "Indiv. Feld 4", + "Indiv. Feld 5", + "Indiv. Feld 6", + "Indiv. Feld 7", + "Indiv. Feld 8", + "Indiv. Feld 9", + "Indiv. Feld 10", + "Indiv. Feld 11", + "Indiv. Feld 12", + "Indiv. Feld 13", + "Indiv. Feld 14", + "Indiv. Feld 15", + "Abweichende Anrede (Rechnungsadresse)", + "Adressart (Rechnungsadresse)", + "Straße (Rechnungsadresse)", + "Postfach (Rechnungsadresse)", + "Postleitzahl (Rechnungsadresse)", + "Ort (Rechnungsadresse)", + "Land (Rechnungsadresse)", + "Versandzusatz (Rechnungsadresse)", + "Adresszusatz (Rechnungsadresse)", + "Abw. Zustellbezeichnung 1 (Rechnungsadresse)", + "Abw. Zustellbezeichnung 2 (Rechnungsadresse)", + "Adresse Gültig von (Rechnungsadresse)", + "Adresse Gültig bis (Rechnungsadresse)", + "Bankleitzahl 6", + "Bankbezeichnung 6", + "Bankkonto-Nummer 6", + "Länderkennzeichen 6", + "IBAN 6", + "Leerfeld 7", + "SWIFT-Code 6", + "Abw. Kontoinhaber 6", + "Kennz. Haupt-Bankverb. 6", + "Bankverb 6 gültig von", + "Bankverb 6 gültig bis", + "Bankleitzahl 7", + "Bankbezeichnung 7", + "Bankkonto-Nummer 7", + "Länderkennzeichen 7", + "IBAN 7", + "Leerfeld 8", + "SWIFT-Code 7", + "Abw. Kontoinhaber 7", + "Kennz. Haupt-Bankverb. 7", + "Bankverb 7 gültig von", + "Bankverb 7 gültig bis", + "Bankleitzahl 8", + "Bankbezeichnung 8", + "Bankkonto-Nummer 8", + "Länderkennzeichen 8", + "IBAN 8", + "Leerfeld 9", + "SWIFT-Code 8", + "Abw. Kontoinhaber 8", + "Kennz. Haupt-Bankverb. 8", + "Bankverb 8 gültig von", + "Bankverb 8 gültig bis", + "Bankleitzahl 9", + "Bankbezeichnung 9", + "Bankkonto-Nummer 9", + "Länderkennzeichen 9", + "IBAN 9", + "Leerfeld 10", + "SWIFT-Code 9", + "Abw. Kontoinhaber 9", + "Kennz. Haupt-Bankverb. 9", + "Bankverb 9 gültig von", + "Bankverb 9 gültig bis", + "Bankleitzahl 10", + "Bankbezeichnung 10", + "Bankkonto-Nummer 10", + "Länderkennzeichen 10", + "IBAN 10", + "Leerfeld 11", + "SWIFT-Code 10", + "Abw. Kontoinhaber 10", + "Kennz. Haupt-Bankverb. 10", + "Bankverb 10 gültig von", + "Bankverb 10 gültig bis", + "Nummer Fremdsystem", + "Insolvent", + "SEPA-Mandatsreferenz 1", + "SEPA-Mandatsreferenz 2", + "SEPA-Mandatsreferenz 3", + "SEPA-Mandatsreferenz 4", + "SEPA-Mandatsreferenz 5", + "SEPA-Mandatsreferenz 6", + "SEPA-Mandatsreferenz 7", + "SEPA-Mandatsreferenz 8", + "SEPA-Mandatsreferenz 9", + "SEPA-Mandatsreferenz 10", + "Verknüpftes OPOS-Konto", + "Mahnsperre bis", + "Lastschriftsperre bis", + "Zahlungssperre bis", + "Gebührenberechnung", + "Mahngebühr 1", + "Mahngebühr 2", + "Mahngebühr 3", + "Pauschalberechnung", + "Verzugspauschale 1", + "Verzugspauschale 2", + "Verzugspauschale 3", + "Alternativer Suchname", + "Status", + "Anschrift manuell geändert (Korrespondenzadresse)", + "Anschrift individuell (Korrespondenzadresse)", + "Anschrift manuell geändert (Rechnungsadresse)", + "Anschrift individuell (Rechnungsadresse)", + "Fristberechnung bei Debitor", + "Mahnfrist 1", + "Mahnfrist 2", + "Mahnfrist 3", + "Letzte Frist" +] + +ACCOUNT_NAME_COLUMNS = [ + # Account number + "Konto", + # Account name + "Kontenbeschriftung", + # Language of the account name + # "de-DE" or "en-GB" + "Sprach-ID" +] + +QUERY_REPORT_COLUMNS = [ + { + "label": "Umsatz (ohne Soll/Haben-Kz)", + "fieldname": "Umsatz (ohne Soll/Haben-Kz)", + "fieldtype": "Currency", + }, + { + "label": "Soll/Haben-Kennzeichen", + "fieldname": "Soll/Haben-Kennzeichen", + "fieldtype": "Data", + }, + { + "label": "Kontonummer", + "fieldname": "Kontonummer", + "fieldtype": "Data", + }, + { + "label": "Gegenkonto (ohne BU-Schlüssel)", + "fieldname": "Gegenkonto (ohne BU-Schlüssel)", + "fieldtype": "Data", + }, + { + "label": "Belegdatum", + "fieldname": "Belegdatum", + "fieldtype": "Date", + }, + { + "label": "Buchungstext", + "fieldname": "Buchungstext", + "fieldtype": "Text", + }, + { + "label": "Beleginfo - Art 1", + "fieldname": "Beleginfo - Art 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 1", + "fieldname": "Beleginfo - Inhalt 1", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Art 2", + "fieldname": "Beleginfo - Art 2", + "fieldtype": "Data", + }, + { + "label": "Beleginfo - Inhalt 2", + "fieldname": "Beleginfo - Inhalt 2", + "fieldtype": "Data", + } +] + +class DataCategory(): + """Field of the CSV Header.""" + + DEBTORS_CREDITORS = "16" + ACCOUNT_NAMES = "20" + TRANSACTIONS = "21" + POSTING_TEXT_CONSTANTS = "67" + +class FormatName(): + """Field of the CSV Header, corresponds to DataCategory.""" + + DEBTORS_CREDITORS = "Debitoren/Kreditoren" + ACCOUNT_NAMES = "Kontenbeschriftungen" + TRANSACTIONS = "Buchungsstapel" + POSTING_TEXT_CONSTANTS = "Buchungstextkonstanten" + +class Transactions(): + DATA_CATEGORY = DataCategory.TRANSACTIONS + FORMAT_NAME = FormatName.TRANSACTIONS + COLUMNS = TRANSACTION_COLUMNS + +class DebtorsCreditors(): + DATA_CATEGORY = DataCategory.DEBTORS_CREDITORS + FORMAT_NAME = FormatName.DEBTORS_CREDITORS + COLUMNS = DEBTOR_CREDITOR_COLUMNS + +class AccountNames(): + DATA_CATEGORY = DataCategory.ACCOUNT_NAMES + FORMAT_NAME = FormatName.ACCOUNT_NAMES + COLUMNS = ACCOUNT_NAME_COLUMNS diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py new file mode 100644 index 0000000000..3cc65fe9d3 --- /dev/null +++ b/erpnext/regional/report/datev/test_datev.py @@ -0,0 +1,244 @@ +# coding=utf-8 +from __future__ import unicode_literals + +import os +import json +import zipfile +from six import BytesIO +from unittest import TestCase + +import frappe +from frappe.utils import getdate, today, now_datetime, cstr +from frappe.test_runner import make_test_objects +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts + +from erpnext.regional.report.datev.datev import validate +from erpnext.regional.report.datev.datev import get_transactions +from erpnext.regional.report.datev.datev import get_customers +from erpnext.regional.report.datev.datev import get_suppliers +from erpnext.regional.report.datev.datev import get_account_names +from erpnext.regional.report.datev.datev import get_datev_csv +from erpnext.regional.report.datev.datev import get_header +from erpnext.regional.report.datev.datev import download_datev_csv + +from erpnext.regional.report.datev.datev_constants import DataCategory +from erpnext.regional.report.datev.datev_constants import Transactions +from erpnext.regional.report.datev.datev_constants import DebtorsCreditors +from erpnext.regional.report.datev.datev_constants import AccountNames +from erpnext.regional.report.datev.datev_constants import QUERY_REPORT_COLUMNS + +def make_company(company_name, abbr): + if not frappe.db.exists("Company", company_name): + company = frappe.get_doc({ + "doctype": "Company", + "company_name": company_name, + "abbr": abbr, + "default_currency": "EUR", + "country": "Germany", + "create_chart_of_accounts_based_on": "Standard Template", + "chart_of_accounts": "SKR04 mit Kontonummern" + }) + company.insert() + else: + company = frappe.get_doc("Company", company_name) + + # indempotent + company.create_default_warehouses() + + if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}): + company.create_default_cost_center() + + company.save() + return company + +def setup_fiscal_year(): + fiscal_year = None + year = cstr(now_datetime().year) + if not frappe.db.get_value("Fiscal Year", {"year": year}, "name"): + try: + fiscal_year = frappe.get_doc({ + "doctype": "Fiscal Year", + "year": year, + "year_start_date": "{0}-01-01".format(year), + "year_end_date": "{0}-12-31".format(year) + }) + fiscal_year.insert() + except frappe.NameError: + pass + + if fiscal_year: + fiscal_year.set_as_default() + +def make_customer_with_account(customer_name, company): + acc_name = frappe.db.get_value("Account", { + "account_name": customer_name, + "company": company.name + }, "name") + + if not acc_name: + acc = frappe.get_doc({ + "doctype": "Account", + "parent_account": "1 - Forderungen aus Lieferungen und Leistungen - _TG", + "account_name": customer_name, + "company": company.name, + "account_type": "Receivable", + "account_number": "10001" + }) + acc.insert() + acc_name = acc.name + + if not frappe.db.exists("Customer", customer_name): + customer = frappe.get_doc({ + "doctype": "Customer", + "customer_name": customer_name, + "customer_type": "Company", + "accounts": [{ + "company": company.name, + "account": acc_name + }] + }) + customer.insert() + else: + customer = frappe.get_doc("Customer", customer_name) + + return customer + +def make_item(item_code, company): + warehouse_name = frappe.db.get_value("Warehouse", { + "warehouse_name": "Stores", + "company": company.name + }, "name") + + if not frappe.db.exists("Item", item_code): + item = frappe.get_doc({ + "doctype": "Item", + "item_code": item_code, + "item_name": item_code, + "description": item_code, + "item_group": "All Item Groups", + "is_stock_item": 0, + "is_purchase_item": 0, + "is_customer_provided_item": 0, + "item_defaults": [{ + "default_warehouse": warehouse_name, + "company": company.name + }] + }) + item.insert() + else: + item = frappe.get_doc("Item", item_code) + return item + +def make_datev_settings(company): + if not frappe.db.exists("DATEV Settings", company.name): + frappe.get_doc({ + "doctype": "DATEV Settings", + "client": company.name, + "client_number": "12345", + "consultant_number": "67890" + }).insert() + + +class TestDatev(TestCase): + def setUp(self): + self.company = make_company("_Test GmbH", "_TG") + self.customer = make_customer_with_account("_Test Kunde GmbH", self.company) + self.filters = { + "company": self.company.name, + "from_date": today(), + "to_date": today() + } + + make_datev_settings(self.company) + item = make_item("_Test Item", self.company) + setup_fiscal_year() + + warehouse = frappe.db.get_value("Item Default", { + "parent": item.name, + "company": self.company.name + }, "default_warehouse") + + income_account = frappe.db.get_value("Account", { + "account_number": "4200", + "company": self.company.name + }, "name") + + tax_account = frappe.db.get_value("Account", { + "account_number": "3806", + "company": self.company.name + }, "name") + + si = create_sales_invoice( + company=self.company.name, + customer=self.customer.name, + currency=self.company.default_currency, + debit_to=self.customer.accounts[0].account, + income_account="4200 - Erlöse - _TG", + expense_account="6990 - Herstellungskosten - _TG", + cost_center=self.company.cost_center, + warehouse=warehouse, + item=item.name, + do_not_save=1 + ) + + si.append("taxes", { + "charge_type": "On Net Total", + "account_head": tax_account, + "description": "Umsatzsteuer 19 %", + "rate": 19 + }) + + si.save() + si.submit() + + def test_columns(self): + def is_subset(get_data, allowed_keys): + """ + Validate that the dict contains only allowed keys. + + Params: + get_data -- Function that returns a list of dicts. + allowed_keys -- List of allowed keys + """ + data = get_data(self.filters) + if data == []: + # No data and, therefore, no columns is okay + return True + actual_set = set(data[0].keys()) + # allowed set must be interpreted as unicode to match the actual set + allowed_set = set({frappe.as_unicode(key) for key in allowed_keys}) + return actual_set.issubset(allowed_set) + + self.assertTrue(is_subset(get_transactions, Transactions.COLUMNS)) + self.assertTrue(is_subset(get_customers, DebtorsCreditors.COLUMNS)) + self.assertTrue(is_subset(get_suppliers, DebtorsCreditors.COLUMNS)) + self.assertTrue(is_subset(get_account_names, AccountNames.COLUMNS)) + + def test_header(self): + self.assertTrue(Transactions.DATA_CATEGORY in get_header(self.filters, Transactions)) + self.assertTrue(AccountNames.DATA_CATEGORY in get_header(self.filters, AccountNames)) + self.assertTrue(DebtorsCreditors.DATA_CATEGORY in get_header(self.filters, DebtorsCreditors)) + + def test_csv(self): + test_data = [{ + "Umsatz (ohne Soll/Haben-Kz)": 100, + "Soll/Haben-Kennzeichen": "H", + "Kontonummer": "4200", + "Gegenkonto (ohne BU-Schlüssel)": "10000", + "Belegdatum": today(), + "Buchungstext": "No remark", + "Beleginfo - Art 1": "Sales Invoice", + "Beleginfo - Inhalt 1": "SINV-0001" + }] + get_datev_csv(data=test_data, filters=self.filters, csv_class=Transactions) + + def test_download(self): + """Assert that the returned file is a ZIP file.""" + download_datev_csv(self.filters) + + # zipfile.is_zipfile() expects a file-like object + zip_buffer = BytesIO() + zip_buffer.write(frappe.response['filecontent']) + + self.assertTrue(zipfile.is_zipfile(zip_buffer)) From 10017c14f3c976fe1ef579c76289f1ee1cf4be21 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 29 Nov 2019 17:33:10 +0530 Subject: [PATCH 20/27] feat: HSN Code wise Item Tax (#19478) * feat: HSN Code wise Item Tax * feat: enqueued task of updating all items --- .../doctype/gst_hsn_code/gst_hsn_code.js | 24 ++- .../doctype/gst_hsn_code/gst_hsn_code.json | 138 +++++------------- .../doctype/gst_hsn_code/gst_hsn_code.py | 19 +++ erpnext/stock/doctype/item/item.js | 14 ++ 4 files changed, 95 insertions(+), 100 deletions(-) diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js index e7cc91952d..7ff4de4863 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.js @@ -3,6 +3,26 @@ frappe.ui.form.on('GST HSN Code', { refresh: function(frm) { - + if(! frm.doc.__islocal && frm.doc.taxes.length){ + frm.add_custom_button(__('Update Taxes for Items'), function(){ + frappe.confirm( + 'Are you sure? It will overwrite taxes for all items with HSN Code '+frm.doc.name+'.', + function(){ + frappe.call({ + args:{ + taxes: frm.doc.taxes, + hsn_code: frm.doc.name + }, + method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master', + callback: function(r) { + if(r.message){ + frappe.show_alert(__('Item taxes updated')); + } + } + }); + } + ); + }); + } } -}); +}); \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json index 2a2145c7f7..06dab3726d 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.json @@ -1,104 +1,46 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "field:hsn_code", - "beta": 0, - "creation": "2017-06-21 10:48:56.422086", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "field:hsn_code", + "creation": "2017-06-21 10:48:56.422086", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "hsn_code", + "description", + "taxes" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "hsn_code", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "HSN Code", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "hsn_code", + "fieldtype": "Data", + "in_list_view": 1, + "label": "HSN Code", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "description", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Description" + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Taxes", + "options": "Item Tax" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-29 14:38:52.220743", - "modified_by": "Administrator", - "module": "Regional", - "name": "GST HSN Code", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "hsn_code, description", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "hsn_code", - "track_changes": 1, - "track_seen": 0 + ], + "modified": "2019-11-01 11:18:59.556931", + "modified_by": "Administrator", + "module": "Regional", + "name": "GST HSN Code", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "hsn_code, description", + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "hsn_code", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py index 9637c2e502..fa2cb1299a 100644 --- a/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py +++ b/erpnext/regional/doctype/gst_hsn_code/gst_hsn_code.py @@ -8,3 +8,22 @@ from frappe.model.document import Document class GSTHSNCode(Document): pass + +@frappe.whitelist() +def update_taxes_in_item_master(taxes, hsn_code): + items = frappe.get_list("Item", filters={ + 'gst_hsn_code': hsn_code + }) + + taxes = frappe.parse_json(taxes) + frappe.enqueue(update_item_document, items=items, taxes=taxes) + return 1 + +def update_item_document(items, taxes): + for item in items: + item_to_be_updated=frappe.get_doc("Item", item.name) + item_to_be_updated.taxes = [] + for tax in taxes: + tax = frappe._dict(tax) + item_to_be_updated.append("taxes", {'item_tax_template': tax.item_tax_template, 'tax_category': tax.tax_category}) + item_to_be_updated.save() \ No newline at end of file diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 410d9f1b45..e3d356f93b 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -136,6 +136,20 @@ frappe.ui.form.on("Item", { frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0); }, + gst_hsn_code: function(frm){ + if(!frm.doc.taxes){ + frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc=>{ + frm.doc.taxes = []; + $.each(hsn_doc.taxes || [], function(i, tax) { + let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes'); + a.item_tax_template = tax.item_tax_template; + a.tax_category = tax.tax_category; + frm.refresh_field('taxes'); + }); + }); + } + }, + is_fixed_asset: function(frm) { // set serial no to false & toggles its visibility frm.set_value('has_serial_no', 0); From 611d2ccd04edf186718704b60f6f0f125c3f9e32 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 Nov 2019 10:11:03 +0530 Subject: [PATCH 21/27] chore: updated path for set_request in v13-develop --- erpnext/portal/doctype/homepage/test_homepage.py | 2 +- .../portal/doctype/homepage_section/test_homepage_section.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index b262c4640c..bf5c4025a0 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.tests.test_website import set_request +from frappe.utils import set_request from frappe.website.render import render class TestHomepage(unittest.TestCase): diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py index c52b7a96c6..5b3196def2 100644 --- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import unittest from bs4 import BeautifulSoup -from frappe.tests.test_website import set_request +from frappe.utils import set_request from frappe.website.render import render class TestHomepageSection(unittest.TestCase): From 54694dd8161b959cf558c10180b3564ac043477c Mon Sep 17 00:00:00 2001 From: Sammish Thundiyil Date: Fri, 29 Nov 2019 15:30:30 +0300 Subject: [PATCH 22/27] modified: erpnext/hr/doctype/repayment_schedule/repayment_schedule.json (#19470) --- .../repayment_schedule.json | 293 +++++------------- 1 file changed, 72 insertions(+), 221 deletions(-) diff --git a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json index a1161851d0..5bb2d370fa 100644 --- a/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json +++ b/erpnext/hr/doctype/repayment_schedule/repayment_schedule.json @@ -1,231 +1,82 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-12-20 15:32:25.078334", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2016-12-20 15:32:25.078334", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "payment_date", + "principal_amount", + "interest_amount", + "total_payment", + "balance_loan_amount", + "paid" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "payment_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Payment Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "columns": 2, + "fieldname": "payment_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Payment Date" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "principal_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Principal Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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 - }, + "columns": 2, + "fieldname": "principal_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Principal Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "interest_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Interest Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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 - }, + "columns": 2, + "fieldname": "interest_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Interest Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "total_payment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Total Payment", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, + "columns": 2, + "fieldname": "total_payment", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Payment", + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "balance_loan_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Balance Loan Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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 - }, + "columns": 2, + "fieldname": "balance_loan_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Balance Loan Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "paid", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Paid", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "paid", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Paid", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2018-03-30 17:37:31.834792", - "modified_by": "Administrator", - "module": "HR", - "name": "Repayment Schedule", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "istable": 1, + "modified": "2019-10-29 11:45:10.694557", + "modified_by": "Administrator", + "module": "HR", + "name": "Repayment Schedule", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file From 935517d914f5fe0fc594c838901975d804e62c36 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 29 Nov 2019 18:08:42 +0530 Subject: [PATCH 23/27] fix: added bank reconciliation page in the accounting module (#19719) * fix: added bank reconcilation page inaccounting module * Update accounts.py --- erpnext/config/accounts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/config/accounts.py b/erpnext/config/accounts.py index ab75f211c0..08711fc09e 100644 --- a/erpnext/config/accounts.py +++ b/erpnext/config/accounts.py @@ -197,6 +197,11 @@ def get_data(): "name": "Bank Reconciliation Statement", "is_query_report": True, "doctype": "Journal Entry" + },{ + "type": "page", + "name": "bank-reconciliation", + "label": _("Bank Reconciliation"), + "icon": "fa fa-bar-chart" }, { "type": "report", From b3af2adc4a145b5d540fbaef030c9a4a1a6ced47 Mon Sep 17 00:00:00 2001 From: Victor Munene Date: Fri, 29 Nov 2019 15:42:13 +0300 Subject: [PATCH 24/27] use program.courses instead of get_all_children to get course list (#19695) --- .../education/doctype/program_enrollment/program_enrollment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py index d5348ffd06..7536172891 100644 --- a/erpnext/education/doctype/program_enrollment/program_enrollment.py +++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py @@ -71,7 +71,7 @@ class ProgramEnrollment(Document): def create_course_enrollments(self): student = frappe.get_doc("Student", self.student) program = frappe.get_doc("Program", self.program) - course_list = [course.course for course in program.get_all_children()] + course_list = [course.course for course in program.courses] for course_name in course_list: student.enroll_in_course(course_name=course_name, program_enrollment=self.name) From 23b1dae6a7218df6a4220e905dbebcf00488aecb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 Nov 2019 20:22:06 +0530 Subject: [PATCH 25/27] chore: dropped dead setup_wizard test fix: fixed imports for teste utils --- .../test_product_configurator.py | 2 +- .../setup/setup_wizard/test_setup_wizard.py | 71 ------------------- 2 files changed, 1 insertion(+), 72 deletions(-) delete mode 100644 erpnext/setup/setup_wizard/test_setup_wizard.py diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index a534e5f838..97042dba92 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from bs4 import BeautifulSoup import frappe, unittest -from frappe.tests.test_website import set_request, get_html_for_route +from frappe.utils import set_request, get_html_for_route from frappe.website.render import render from erpnext.portal.product_configurator.utils import get_products_for_website from erpnext.stock.doctype.item.test_item import make_item_variant diff --git a/erpnext/setup/setup_wizard/test_setup_wizard.py b/erpnext/setup/setup_wizard/test_setup_wizard.py deleted file mode 100644 index a489133aba..0000000000 --- a/erpnext/setup/setup_wizard/test_setup_wizard.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -import frappe, time -from frappe.utils.selenium_testdriver import TestDriver - -def run_setup_wizard_test(): - driver = TestDriver() - frappe.db.set_default('in_selenium', '1') - frappe.db.commit() - - driver.login('#page-setup-wizard') - print('Running Setup Wizard Test...') - - # Language slide - driver.wait_for_ajax(True) - time.sleep(1) - - driver.set_select("language", "English (United States)") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - - # Region slide - driver.wait_for_ajax(True) - driver.set_select("country", "India") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - - # Profile slide - driver.set_field("full_name", "Great Tester") - driver.set_field("email", "great@example.com") - driver.set_field("password", "test") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - time.sleep(1) - - # domain slide - driver.set_multicheck("domains", ["Manufacturing"]) - time.sleep(1) - driver.click(".next-btn") - - # Org slide - driver.set_field("company_name", "For Testing") - time.sleep(1) - driver.print_console() - driver.click(".next-btn") - - driver.set_field("company_tagline", "Just for GST") - driver.set_field("bank_account", "HDFC") - time.sleep(3) - driver.click(".complete-btn") - - # Wait for desktop - driver.wait_for('#page-desktop', timeout=600) - - driver.print_console() - time.sleep(3) - - frappe.db.set_default('in_selenium', None) - frappe.db.set_value("Company", "For Testing", "write_off_account", "Write Off - FT") - frappe.db.set_value("Company", "For Testing", "exchange_gain_loss_account", "Exchange Gain/Loss - FT") - frappe.db.commit() - - driver.close() - - return True From 98a54355ae5c88fdfcec488a5c12eb58f52b721a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sun, 1 Dec 2019 10:06:16 +0530 Subject: [PATCH 26/27] fix: Post GL entry fix for asset (#19751) --- erpnext/assets/doctype/asset/asset.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 40f1e1efc9..d32f834f0f 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -517,15 +517,18 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): - if not is_cwip_accounting_enabled(self.asset_category): - return - assets = frappe.db.sql_list(""" select name from `tabAsset` - where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) + asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting']) - for asset in assets: - doc = frappe.get_doc('Asset', asset) - doc.make_gl_entries() + for asset_category in asset_categories: + if cint(asset_category.enable_cwip_accounting): + assets = frappe.db.sql_list(""" select name from `tabAsset` + where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0 + and available_for_use_date = %s""", (asset_category.name, nowdate())) + + for asset in assets: + doc = frappe.get_doc('Asset', asset) + doc.make_gl_entries() def get_asset_naming_series(): meta = frappe.get_meta('Asset') From 5249a6348fbbc861652b9a70c23dedd8502f1c00 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Sun, 1 Dec 2019 06:17:21 +0100 Subject: [PATCH 27/27] Update de.csv (#19755) Changed all translations from "Aufgabe" to "Vorgang" in singular and plural where the word "Task" is project related. Left all agricultural and others how they are (as Aufgabe). --- erpnext/translations/de.csv | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 2e25a127d8..cdff3ff422 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1329,7 +1329,7 @@ apps/erpnext/erpnext/hr/doctype/job_offer/job_offer.js,Create Employee,Mitarbeit apps/erpnext/erpnext/utilities/transaction_base.py,Invalid Posting Time,Ungültige Buchungszeit DocType: Salary Component,Condition and Formula,Zustand und Formel DocType: Lead,Campaign Name,Kampagnenname -apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss der Aufgabe +apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss des Vorgangs apps/erpnext/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py,There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1} DocType: Fee Validity,Healthcare Practitioner,praktischer Arzt DocType: Hotel Room,Capacity,Kapazität @@ -1353,7 +1353,7 @@ DocType: Payment Entry,Received Amount (Company Currency),Erhaltene Menge (Gesel apps/erpnext/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py,Payment Cancelled. Please check your GoCardless Account for more details,Zahlung abgebrochen. Bitte überprüfen Sie Ihr GoCardless Konto für weitere Details DocType: Work Order,Skip Material Transfer to WIP Warehouse,Überspringen Sie die Materialübertragung in das WIP-Lager DocType: Contract,N/A,nicht verfügbar -DocType: Task Type,Task Type,Aufgabentyp +DocType: Task Type,Task Type,Vorgangstyp DocType: Topic,Topic Content,Themeninhalt DocType: Delivery Settings,Send with Attachment,Senden mit Anhang DocType: Service Level,Priorities,Prioritäten @@ -2449,7 +2449,7 @@ apps/erpnext/erpnext/public/js/utils/serial_no_batch_selector.js,Please select b DocType: Asset,Depreciation Schedules,Abschreibungen Termine apps/erpnext/erpnext/projects/doctype/timesheet/timesheet.js,Create Sales Invoice,Verkaufsrechnung erstellen apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,Ineligible ITC,Nicht förderfähiges ITC -DocType: Task,Dependent Tasks,Abhängige Aufgaben +DocType: Task,Dependent Tasks,Abhängige Vorgänge apps/erpnext/erpnext/regional/report/gstr_1/gstr_1.py,Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden: apps/erpnext/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js,Quantity to Produce,Menge zu produzieren apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Application period cannot be outside leave allocation period,Beantragter Zeitraum kann nicht außerhalb der beantragten Urlaubszeit liegen @@ -2846,7 +2846,7 @@ DocType: Loan,Applicant Type,Bewerbertyp DocType: Purchase Invoice,03-Deficiency in services,03-Mangel an Dienstleistungen DocType: Healthcare Settings,Default Medical Code Standard,Default Medical Code Standard DocType: Purchase Invoice Item,HSN/SAC,HSN / SAC -DocType: Project Template Task,Project Template Task,Projektvorlagenaufgabe +DocType: Project Template Task,Project Template Task,Projektvorgangsvorlage DocType: Accounts Settings,Over Billing Allowance (%),Mehr als Abrechnungsbetrag (%) apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Purchase Receipt {0} is not submitted,Kaufbeleg {0} wurde nicht übertragen DocType: Company,Default Payable Account,Standard-Verbindlichkeitenkonto @@ -3323,7 +3323,7 @@ DocType: Soil Texture,Silt,Schlick ,Qty to Order,Zu bestellende Menge DocType: Period Closing Voucher,"The account head under Liability or Equity, in which Profit/Loss will be booked","Der Kontenkopf unter Eigen- oder Fremdkapital, in dem Gewinn / Verlust verbucht wird" apps/erpnext/erpnext/accounts/doctype/budget/budget.py,Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},Ein weiterer Budgeteintrag '{0}' existiert bereits für {1} '{2}' und für '{3}' für das Geschäftsjahr {4} -apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Aufgaben +apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Vorgänge DocType: Opportunity,Mins to First Response,Minuten zum First Response DocType: Pricing Rule,Margin Type,Margenart apps/erpnext/erpnext/projects/doctype/project/project_dashboard.html,{0} hours,{0} Stunden @@ -3961,7 +3961,7 @@ apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Maintenance Sche apps/erpnext/erpnext/education/doctype/student/student_dashboard.py,Student LMS Activity,Student LMS Aktivität DocType: POS Profile,Applicable for Users,Anwendbar für Benutzer DocType: Supplier Quotation,PUR-SQTN-.YYYY.-,PUR-SQTN-.JJJJ.- -apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Aufgaben auf Status {0} setzen? +apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Vorgänge auf Status {0} setzen? DocType: Purchase Invoice,Set Advances and Allocate (FIFO),Vorschüsse setzen und zuordnen (FIFO) apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.py,No Work Orders created,Keine Arbeitsaufträge erstellt apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py,Salary Slip of employee {0} already created for this period,Gehaltsabrechnung der Mitarbeiter {0} für diesen Zeitraum bereits erstellt @@ -4418,7 +4418,7 @@ DocType: Normal Test Items,Result Value,Ergebnis Wert DocType: Hotel Room,Hotels,Hotels apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center_tree.js,New Cost Center Name,Neuer Kostenstellenname DocType: Leave Control Panel,Leave Control Panel,Urlaubsverwaltung -DocType: Project,Task Completion,Aufgabenerledigung +DocType: Project,Task Completion,Vorgangserfüllung apps/erpnext/erpnext/templates/generators/item/item_add_to_cart.html,Not in Stock,Nicht lagernd DocType: Volunteer,Volunteer Skills,Freiwillige Fähigkeiten DocType: Additional Salary,HR User,Nutzer Personalabteilung @@ -5197,7 +5197,7 @@ DocType: Work Order,Material Transferred for Manufacturing,Material zur Herstell apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py,Account {0} does not exists,Konto {0} existiert nicht apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.js,Select Loyalty Program,Wählen Sie Treueprogramm DocType: Project,Project Type,Projekttyp -apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diese Aufgabe existiert eine untergeordnete Aufgabe. Sie können diese Aufgabe daher nicht löschen. +apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diesen Vorgang existiert ein untergeordneter Vorgang. Sie können diese Aufgabe daher nicht löschen. apps/erpnext/erpnext/setup/doctype/sales_person/sales_person.py,Either target qty or target amount is mandatory.,Entweder Zielstückzahl oder Zielmenge ist zwingend erforderlich. apps/erpnext/erpnext/config/projects.py,Cost of various activities,Aufwendungen für verschiedene Tätigkeiten apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py,"Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}","Einstellen Events auf {0}, da die Mitarbeiter auf die beigefügten unter Verkaufs Personen keine Benutzer-ID {1}" @@ -5597,7 +5597,7 @@ apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Paid apps/erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py,{0} is not a valid Batch Number for Item {1},{0} ist keine gültige Chargennummer für Artikel {1} apps/erpnext/erpnext/shopping_cart/cart.py,Please enter valid coupon code !!,Bitte geben Sie einen gültigen Gutscheincode ein !! apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Note: There is not enough leave balance for Leave Type {0},Hinweis: Es gibt nicht genügend Urlaubsguthaben für Abwesenheitstyp {0} -DocType: Task,Task Description,Aufgabenbeschreibung +DocType: Task,Task Description,Vorgangsbeschreibung DocType: Training Event,Seminar,Seminar DocType: Program Enrollment Fee,Program Enrollment Fee,Programm Einschreibegebühr DocType: Item,Supplier Items,Lieferantenartikel @@ -5754,7 +5754,7 @@ apps/erpnext/erpnext/accounts/doctype/sales_invoice/pos.py,All Territories,Alle DocType: Lost Reason Detail,Lost Reason Detail,Verlorene Begründung Detail apps/erpnext/erpnext/hr/utils.py,Please set leave policy for employee {0} in Employee / Grade record,Legen Sie die Abwesenheitsrichtlinie für den Mitarbeiter {0} im Mitarbeiter- / Notensatz fest apps/erpnext/erpnext/public/js/controllers/transaction.js,Invalid Blanket Order for the selected Customer and Item,Ungültiger Blankoauftrag für den ausgewählten Kunden und Artikel -apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Aufgaben hinzufügen +apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Vorgänge hinzufügen DocType: Purchase Invoice,Items,Artikel apps/erpnext/erpnext/crm/doctype/contract/contract.py,End Date cannot be before Start Date.,Das Enddatum darf nicht vor dem Startdatum liegen. apps/erpnext/erpnext/education/doctype/course_enrollment/course_enrollment.py,Student is already enrolled.,Student ist bereits eingetragen sind.