From 59239172a1ed352848fe06c705a68281c41e3106 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 14 Sep 2020 17:34:21 +0530 Subject: [PATCH 01/41] feat: Supplier Quotation Comparison - v3 --- .../quoted_item_comparison.json | 32 ------- .../__init__.py | 0 .../supplier_quotation_comparison.html} | 0 .../supplier_quotation_comparison.js} | 12 ++- .../supplier_quotation_comparison.json | 32 +++++++ .../supplier_quotation_comparison.py} | 83 ++++++++++++++----- 6 files changed, 106 insertions(+), 53 deletions(-) delete mode 100644 erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.json rename erpnext/buying/report/{quoted_item_comparison => supplier_quotation_comparison}/__init__.py (100%) rename erpnext/buying/report/{quoted_item_comparison/quoted_item_comparison.html => supplier_quotation_comparison/supplier_quotation_comparison.html} (100%) rename erpnext/buying/report/{quoted_item_comparison/quoted_item_comparison.js => supplier_quotation_comparison/supplier_quotation_comparison.js} (91%) create mode 100644 erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.json rename erpnext/buying/report/{quoted_item_comparison/quoted_item_comparison.py => supplier_quotation_comparison/supplier_quotation_comparison.py} (70%) diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.json b/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.json deleted file mode 100644 index 23b3ace49c..0000000000 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "add_total_row": 0, - "apply_user_permissions": 1, - "creation": "2016-07-21 08:31:05.890362", - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "idx": 2, - "is_standard": "Yes", - "modified": "2017-02-24 20:04:58.784351", - "modified_by": "Administrator", - "module": "Buying", - "name": "Quoted Item Comparison", - "owner": "Administrator", - "ref_doctype": "Supplier Quotation", - "report_name": "Quoted Item Comparison", - "report_type": "Script Report", - "roles": [ - { - "role": "Manufacturing Manager" - }, - { - "role": "Purchase Manager" - }, - { - "role": "Purchase User" - }, - { - "role": "Stock User" - } - ] -} \ No newline at end of file diff --git a/erpnext/buying/report/quoted_item_comparison/__init__.py b/erpnext/buying/report/supplier_quotation_comparison/__init__.py similarity index 100% rename from erpnext/buying/report/quoted_item_comparison/__init__.py rename to erpnext/buying/report/supplier_quotation_comparison/__init__.py diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.html b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html similarity index 100% rename from erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.html rename to erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js similarity index 91% rename from erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js rename to erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index 518d665e7e..80e521a8bf 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -1,7 +1,7 @@ // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // For license information, please see license.txt -frappe.query_reports["Quoted Item Comparison"] = { +frappe.query_reports["Supplier Quotation Comparison"] = { filters: [ { fieldtype: "Link", @@ -78,6 +78,13 @@ frappe.query_reports["Quoted Item Comparison"] = { return { filters: { "docstatus": ["<", 2] } } } }, + { + "fieldname":"group_by", + "label": __("Group by"), + "fieldtype": "Select", + "options": [__("Group by Supplier"), __("Group by Item")], + "default": __("Group by Supplier") + }, { fieldtype: "Check", label: __("Include Expired"), @@ -98,6 +105,9 @@ frappe.query_reports["Quoted Item Comparison"] = { } } + if(column.fieldname === "price_per_unit" && data.price_per_unit && data.min && data.min === 1){ + value = `
${value}
`; + } return value; }, diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.json b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.json new file mode 100644 index 0000000000..886e5b8757 --- /dev/null +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.json @@ -0,0 +1,32 @@ +{ + "add_total_row": 0, + "apply_user_permissions": 1, + "creation": "2016-07-21 08:31:05.890362", + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 2, + "is_standard": "Yes", + "modified": "2017-02-24 20:04:58.784351", + "modified_by": "Administrator", + "module": "Buying", + "name": "Supplier Quotation Comparison", + "owner": "Administrator", + "ref_doctype": "Supplier Quotation", + "report_name": "Supplier Quotation Comparison", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing Manager" + }, + { + "role": "Purchase Manager" + }, + { + "role": "Purchase User" + }, + { + "role": "Stock User" + } + ] +} \ No newline at end of file diff --git a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py similarity index 70% rename from erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py rename to erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 4426560c16..d2399b1ec9 100644 --- a/erpnext/buying/report/quoted_item_comparison/quoted_item_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -12,9 +12,9 @@ def execute(filters=None): if not filters: return [], [] + columns = get_columns(filters) conditions = get_conditions(filters) supplier_quotation_data = get_data(filters, conditions) - columns = get_columns() data, chart_data = prepare_data(supplier_quotation_data, filters) message = get_message() @@ -41,9 +41,13 @@ def get_conditions(filters): return conditions def get_data(filters, conditions): - supplier_quotation_data = frappe.db.sql("""SELECT - sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation, - sqi.lead_time_days, sq.supplier, sq.valid_till + supplier_quotation_data = frappe.db.sql(""" + SELECT + sqi.parent, sqi.item_code, + sqi.qty, sqi.stock_qty, sqi.rate, + sqi.uom, sqi.stock_uom, + sqi.request_for_quotation, + sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till FROM `tabSupplier Quotation Item` sqi, `tabSupplier Quotation` sq @@ -58,16 +62,18 @@ def get_data(filters, conditions): return supplier_quotation_data def prepare_data(supplier_quotation_data, filters): - out, suppliers, qty_list, chart_data = [], [], [], [] - supplier_wise_map = defaultdict(list) + out, groups, qty_list, suppliers, chart_data = [], [], [], [], [] + group_wise_map = defaultdict(list) supplier_qty_price_map = {} + group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code" company_currency = frappe.db.get_default("currency") float_precision = cint(frappe.db.get_default("float_precision")) or 2 for data in supplier_quotation_data: - supplier = data.get("supplier") - supplier_currency = frappe.db.get_value("Supplier", data.get("supplier"), "default_currency") + group = data.get(group_by_field) # get item or supplier value for this row + + supplier_currency = frappe.db.get_value("Supplier", data.get("supplier_name"), "default_currency") if supplier_currency: exchange_rate = get_exchange_rate(supplier_currency, company_currency) @@ -75,38 +81,55 @@ def prepare_data(supplier_quotation_data, filters): exchange_rate = 1 row = { - "item_code": data.get('item_code'), + "item_code": "" if group_by_field=="item_code" else data.get("item_code"), # leave blank if group by field + "supplier_name": "" if group_by_field=="supplier_name" else data.get("supplier_name"), "quotation": data.get("parent"), "qty": data.get("qty"), "price": flt(data.get("rate") * exchange_rate, float_precision), "uom": data.get("uom"), + "stock_uom": data.get('stock_uom'), "request_for_quotation": data.get("request_for_quotation"), "valid_till": data.get('valid_till'), "lead_time_days": data.get('lead_time_days') } + row["price_per_unit"] = flt(row["price"]) / (flt(data.get("stock_qty")) or 1) - # map for report view of form {'supplier1':[{},{},...]} - supplier_wise_map[supplier].append(row) + # map for report view of form {'supplier1'/'item1':[{},{},...]} + group_wise_map[group].append(row) # map for chart preparation of the form {'supplier1': {'qty': 'price'}} + supplier = data.get("supplier_name") if filters.get("item_code"): if not supplier in supplier_qty_price_map: supplier_qty_price_map[supplier] = {} supplier_qty_price_map[supplier][row["qty"]] = row["price"] + groups.append(group) suppliers.append(supplier) qty_list.append(data.get("qty")) + groups = list(set(groups)) suppliers = list(set(suppliers)) qty_list = list(set(qty_list)) + highlight_min_price = group_by_field == "item_code" + # final data format for report view - for supplier in suppliers: - supplier_wise_map[supplier][0].update({"supplier_name": supplier}) - for entry in supplier_wise_map[supplier]: + for group in groups: + group_entries = group_wise_map[group] # all entries pertaining to item/supplier + group_entries[0].update({group_by_field : group}) + + if highlight_min_price: + prices = [group_entry["price_per_unit"] for group_entry in group_entries] + min_price = min(prices) + + for entry in group_entries: + if highlight_min_price and entry["price_per_unit"] == min_price: + entry["min"] = 1 out.append(entry) if filters.get("item_code"): + # render chart only for one item comparison chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map) return out, chart_data @@ -145,8 +168,9 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map): return chart_data -def get_columns(): - columns = [{ +def get_columns(filters): + group_by_columns = [ + { "fieldname": "supplier_name", "label": _("Supplier"), "fieldtype": "Link", @@ -158,8 +182,10 @@ def get_columns(): "label": _("Item"), "fieldtype": "Link", "options": "Item", - "width": 200 - }, + "width": 150 + }] + + columns = [ { "fieldname": "uom", "label": _("UOM"), @@ -180,6 +206,20 @@ def get_columns(): "options": "Company:company:default_currency", "width": 110 }, + { + "fieldname": "stock_uom", + "label": _("Stock UOM"), + "fieldtype": "Link", + "options": "UOM", + "width": 90 + }, + { + "fieldname": "price_per_unit", + "label": _("Price per Unit (Stock UOM)"), + "fieldtype": "Currency", + "options": "Company:company:default_currency", + "width": 120 + }, { "fieldname": "quotation", "label": _("Supplier Quotation"), @@ -205,9 +245,12 @@ def get_columns(): "fieldtype": "Link", "options": "Request for Quotation", "width": 150 - } - ] + }] + if filters.get("group_by") == "Group by Item": + group_by_columns.reverse() + + columns[0:0] = group_by_columns # add positioned group by columns to the report return columns def get_message(): From 90d9d80ef98fffaa17e4511a9b076d723a5b6fe3 Mon Sep 17 00:00:00 2001 From: marination Date: Sat, 19 Sep 2020 00:43:53 +0530 Subject: [PATCH 02/41] fix: More MR UX fixes --- .../production_plan/production_plan.py | 1 - .../doctype/sales_order/sales_order.py | 1 - .../material_request/material_request.js | 3 +- .../material_request/material_request.json | 47 +++++---------- .../material_request_dashboard.py | 2 +- .../material_request_item.json | 59 ++++++++----------- 6 files changed, 44 insertions(+), 69 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index c8892376b7..5b14d054af 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -381,7 +381,6 @@ class ProductionPlan(Document): "transaction_date": nowdate(), "status": "Draft", "company": self.company, - "requested_by": frappe.session.user, 'material_request_type': material_request_type, 'customer': item_doc.customer or '' }) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index f88289871e..62a5d4e68f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -989,7 +989,6 @@ def make_raw_material_request(items, company, sales_order, project=None): doctype = 'Material Request', transaction_date = nowdate(), company = company, - requested_by = frappe.session.user, material_request_type = 'Purchase' )) for item in raw_materials: diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 3c4e35349e..8c470988c8 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -280,7 +280,8 @@ frappe.ui.form.on('Material Request', { fieldname:'default_supplier', fieldtype: 'Link', options: 'Supplier', - description: __('Select a Supplier from the Default Supplier List of the items below.'), + description: __('Select a Supplier from the Default Suppliers of the items below. \ + On selection, a Purchase Order will be made against items belonging to the selected Supplier only.'), get_query: () => { return{ query: "erpnext.stock.doctype.material_request.material_request.get_default_supplier_query", diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index 44503d22a3..da73bc8015 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -11,9 +11,10 @@ "naming_series", "title", "material_request_type", - "transfer_status", "customer", + "status", "column_break_2", + "transaction_date", "schedule_date", "company", "amended_from", @@ -25,11 +26,8 @@ "scan_barcode", "items", "more_info", - "requested_by", - "transaction_date", - "column_break2", - "status", "per_ordered", + "column_break2", "per_received", "printing_details", "letter_head", @@ -82,7 +80,8 @@ "fieldname": "customer", "fieldtype": "Link", "label": "Customer", - "options": "Customer" + "options": "Customer", + "print_hide": 1 }, { "fieldname": "column_break_2", @@ -92,12 +91,12 @@ "allow_on_submit": 1, "fieldname": "schedule_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Required By" }, { "fieldname": "company", "fieldtype": "Link", - "in_list_view": 1, "in_standard_filter": 1, "label": "Company", "oldfieldname": "company", @@ -153,18 +152,10 @@ "oldfieldtype": "Section Break", "options": "fa fa-file-text" }, - { - "fieldname": "requested_by", - "fieldtype": "Data", - "in_standard_filter": 1, - "label": "Requested For", - "options": "Email" - }, { "default": "Today", "fieldname": "transaction_date", "fieldtype": "Date", - "in_list_view": 1, "label": "Transaction Date", "no_copy": 1, "oldfieldname": "transaction_date", @@ -197,7 +188,7 @@ "width": "100px" }, { - "depends_on": "eval:doc.docstatus==1", + "depends_on": "eval:doc.per_ordered > 0", "fieldname": "per_ordered", "fieldtype": "Percent", "label": "% Ordered", @@ -208,7 +199,7 @@ "read_only": 1 }, { - "depends_on": "eval:doc.docstatus==1", + "depends_on": "eval:doc.per_received > 0", "fieldname": "per_received", "fieldtype": "Percent", "label": "% Received", @@ -282,13 +273,15 @@ }, { "fieldname": "warehouse_section", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "label": "Warehouse" }, { - "description": "Sets 'For Warehouse' in each row of the Items table.", + "description": "Sets 'Target Warehouse' in each row of the Items table.", "fieldname": "set_warehouse", "fieldtype": "Link", - "label": "Set Warehouse", + "in_list_view": 1, + "label": "Set Target Warehouse", "options": "Warehouse" }, { @@ -300,26 +293,18 @@ }, { "depends_on": "eval:doc.material_request_type == 'Material Transfer'", + "description": "Sets 'Source Warehouse' in each row of the Items table.", "fieldname": "set_from_warehouse", "fieldtype": "Link", - "label": "Set From Warehouse", + "label": "Set Source Warehouse", "options": "Warehouse" - }, - { - "allow_on_submit": 1, - "depends_on": "eval:doc.add_to_transit == 1", - "fieldname": "transfer_status", - "fieldtype": "Select", - "label": "Transfer Status", - "options": "\nNot Started\nIn Transit\nCompleted", - "read_only": 1 } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, "links": [], - "modified": "2020-08-10 13:27:54.891058", + "modified": "2020-09-19 00:36:00.306761", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py index 0e4fb7a6dd..f3e5e5db25 100644 --- a/erpnext/stock/doctype/material_request/material_request_dashboard.py +++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py @@ -7,7 +7,7 @@ def get_data(): 'fieldname': 'material_request', 'transactions': [ { - 'label': _('Related'), + 'label': _('Reference'), 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order'] }, { diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 32bd4a0a57..08c273838f 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -13,12 +13,10 @@ "schedule_date", "section_break_4", "description", + "column_break_12", "item_group", "brand", - "image_section", "image", - "column_break_12", - "manufacturer_part_no", "quantity_and_warehouse", "qty", "stock_uom", @@ -34,26 +32,26 @@ "amount", "manufacture_details", "manufacturer", + "manufacturer_part_no", + "col_break_mfg", + "bom_no", + "accounting_dimensions_section", + "project", + "dimension_col_break", + "cost_center", "more_info", "lead_time_date", "sales_order", "sales_order_item", "production_plan", "material_request_plan_item", + "expense_account", "col_break4", "min_order_qty", "projected_qty", "actual_qty", "ordered_qty", "received_qty", - "accounting_details", - "expense_account", - "accounting_dimensions_section", - "project", - "dimension_col_break", - "cost_center", - "section_break_37", - "bom_no", "section_break_46", "page_break" ], @@ -164,7 +162,7 @@ "fieldname": "warehouse", "fieldtype": "Link", "in_list_view": 1, - "label": "For Warehouse", + "label": "Target Warehouse", "oldfieldname": "warehouse", "oldfieldtype": "Link", "options": "Warehouse", @@ -191,12 +189,14 @@ { "fieldname": "rate", "fieldtype": "Currency", - "label": "Rate" + "label": "Rate", + "print_hide": 1 }, { "fieldname": "amount", "fieldtype": "Currency", "label": "Amount", + "print_hide": 1, "read_only": 1 }, { @@ -326,6 +326,7 @@ "report_hide": 1 }, { + "depends_on": "eval:doc.docstatus==1", "fieldname": "ordered_qty", "fieldtype": "Float", "label": "Completed Qty", @@ -335,12 +336,6 @@ "print_hide": 1, "read_only": 1 }, - { - "collapsible": 1, - "fieldname": "accounting_details", - "fieldtype": "Section Break", - "label": "Accounting Details" - }, { "fieldname": "expense_account", "fieldtype": "Link", @@ -367,21 +362,10 @@ "print_hide": 1 }, { - "collapsible": 1, - "fieldname": "image_section", - "fieldtype": "Section Break", - "label": "Image" - }, - { - "depends_on": "eval:parent.material_request_type == \"Manufacture\"", - "fieldname": "section_break_37", - "fieldtype": "Section Break", - "label": "Manufacturing" - }, - { + "depends_on": "eval:doc.docstatus==1", "fieldname": "received_qty", "fieldtype": "Float", - "label": "Received Quantity", + "label": "Received Qty", "no_copy": 1, "print_hide": 1, "read_only": 1 @@ -398,6 +382,7 @@ }, { "collapsible": 1, + "depends_on": "eval:in_list([\"Manufacture\", \"Purchase\"], parent.material_request_type)", "fieldname": "manufacture_details", "fieldtype": "Section Break", "label": "Manufacture" @@ -430,10 +415,11 @@ "depends_on": "eval:parent.material_request_type == \"Material Transfer\"", "fieldname": "from_warehouse", "fieldtype": "Link", - "label": "Source Warehouse (Material Transfer)", + "label": "Source Warehouse", "options": "Warehouse" }, { + "allow_on_submit": 1, "fieldname": "bom_no", "fieldtype": "Link", "label": "BOM No", @@ -444,12 +430,17 @@ { "fieldname": "section_break_46", "fieldtype": "Section Break" + }, + { + "fieldname": "col_break_mfg", + "fieldtype": "Column Break" } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-05-15 09:00:00.992835", + "modified": "2020-09-18 20:52:33.265074", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", From b782d2a99d74576494d38f7abb02762a7b84d98c Mon Sep 17 00:00:00 2001 From: marination Date: Sat, 19 Sep 2020 01:02:34 +0530 Subject: [PATCH 03/41] fix: Added back accidentally deleted field --- .../doctype/material_request/material_request.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json index da73bc8015..d73349dd39 100644 --- a/erpnext/stock/doctype/material_request/material_request.json +++ b/erpnext/stock/doctype/material_request/material_request.json @@ -11,6 +11,7 @@ "naming_series", "title", "material_request_type", + "transfer_status", "customer", "status", "column_break_2", @@ -298,13 +299,22 @@ "fieldtype": "Link", "label": "Set Source Warehouse", "options": "Warehouse" + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.add_to_transit == 1", + "fieldname": "transfer_status", + "fieldtype": "Select", + "label": "Transfer Status", + "options": "\nNot Started\nIn Transit\nCompleted", + "read_only": 1 } ], "icon": "fa fa-ticket", "idx": 70, "is_submittable": 1, "links": [], - "modified": "2020-09-19 00:36:00.306761", + "modified": "2020-09-19 01:04:09.285862", "modified_by": "Administrator", "module": "Stock", "name": "Material Request", From 3aedeb642f8e18dc4f3a7a61f7932beb46ce7bc1 Mon Sep 17 00:00:00 2001 From: michellealva Date: Tue, 29 Sep 2020 12:54:55 +0530 Subject: [PATCH 04/41] fix: Change Error Message in Work Order --- erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index b7d968e974..3173b6c483 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -434,7 +434,7 @@ class WorkOrder(Document): elif flt(d.completed_qty) <= max_allowed_qty_for_wo: d.status = "Completed" else: - frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) + frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'")) def set_actual_dates(self): if self.get("operations"): From 81ad90c1d466b6927affddf01ba9027dc0e40611 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 29 Sep 2020 11:54:57 +0200 Subject: [PATCH 05/41] feat: validate sales invoice for germany --- .../doctype/sales_invoice/sales_invoice.json | 10 +++- erpnext/hooks.py | 3 + .../regional/germany/accounts_controller.py | 57 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 erpnext/regional/germany/accounts_controller.py diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 2397b7d0cb..ed1ed4393d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -19,6 +19,7 @@ "is_return", "column_break1", "company", + "company_tax_id", "posting_date", "posting_time", "set_posting_time", @@ -1940,13 +1941,20 @@ "hide_seconds": 1, "label": "Is Internal Customer", "read_only": 1 + }, + { + "fetch_from": "company.tax_id", + "fieldname": "company_tax_id", + "fieldtype": "Data", + "label": "Company Tax ID", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, "links": [], - "modified": "2020-08-27 01:56:28.532140", + "modified": "2020-09-29 10:00:41.470858", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 4e05076a3d..d9a639a395 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -393,6 +393,9 @@ regional_overrides = { 'Italy': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.italy.utils.update_itemised_tax_data', 'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.italy.utils.sales_invoice_validate', + }, + 'Germany': { + 'erpnext.controllers.accounts_controller.validate_regional': 'erpnext.regional.germany.accounts_controller.validate_regional', } } user_privacy_documents = [ diff --git a/erpnext/regional/germany/accounts_controller.py b/erpnext/regional/germany/accounts_controller.py new file mode 100644 index 0000000000..193c8e14a3 --- /dev/null +++ b/erpnext/regional/germany/accounts_controller.py @@ -0,0 +1,57 @@ +import frappe +from frappe import _ +from frappe import msgprint + + +REQUIRED_FIELDS = { + "Sales Invoice": [ + { + "field_name": "company_address", + "regulation": "§ 14 Abs. 4 Nr. 1 UStG" + }, + { + "field_name": "company_tax_id", + "regulation": "§ 14 Abs. 4 Nr. 2 UStG" + }, + { + "field_name": "taxes", + "regulation": "§ 14 Abs. 4 Nr. 8 UStG", + "condition": "not exempt_from_sales_tax" + }, + { + "field_name": "customer_address", + "regulation": "§ 14 Abs. 4 Nr. 1 UStG", + "condition": "base_grand_total > 250" + } + ] +} + + +def validate_regional(doc): + """Check if required fields for this document are present.""" + required_fields = REQUIRED_FIELDS.get(doc.doctype) + if not required_fields: + return + + meta = frappe.get_meta(doc.doctype) + field_map = {field.fieldname: field.label for field in meta.fields} + + for field in required_fields: + condition = field.get("condition") + if condition and not frappe.safe_eval(condition, doc.as_dict()): + continue + + field_name = field.get("field_name") + regulation = field.get("regulation") + if field_name and not doc.get(field_name): + missing(field_map.get(field_name), regulation) + + +def missing(field_label, regulation): + """Notify the user that a required field is missing.""" + context = 'Specific for Germany. Example: Remember to set Company Tax ID. It is required by § 14 Abs. 4 Nr. 2 UStG.' + msgprint(_('Remember to set {field_label}. It is required by {regulation}.', context=context).format( + field_label=frappe.bold(_(field_label)), + regulation=regulation + ) + ) From f90c3602a73786e101ae7a2fcf1bfb1e4551b743 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Oct 2020 14:38:17 +0530 Subject: [PATCH 06/41] fix: Price should be total amount, modified deskpage --- erpnext/buying/desk_page/buying/buying.json | 4 ++-- .../supplier_quotation_comparison.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/buying/desk_page/buying/buying.json b/erpnext/buying/desk_page/buying/buying.json index 565d39c3c8..2e870fea82 100644 --- a/erpnext/buying/desk_page/buying/buying.json +++ b/erpnext/buying/desk_page/buying/buying.json @@ -33,7 +33,7 @@ { "hidden": 0, "label": "Other Reports", - "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Quoted Item Comparison\",\n \"name\": \"Quoted Item Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" + "links": "[\n {\n \"is_query_report\": true,\n \"label\": \"Items To Be Requested\",\n \"name\": \"Items To Be Requested\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Item-wise Purchase History\",\n \"name\": \"Item-wise Purchase History\",\n \"onboard\": 1,\n \"reference_doctype\": \"Item\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Receipt Trends\",\n \"name\": \"Purchase Receipt Trends\",\n \"reference_doctype\": \"Purchase Receipt\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Purchase Invoice Trends\",\n \"name\": \"Purchase Invoice Trends\",\n \"reference_doctype\": \"Purchase Invoice\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Raw Materials To Be Transferred\",\n \"name\": \"Subcontracted Raw Materials To Be Transferred\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Subcontracted Item To Be Received\",\n \"name\": \"Subcontracted Item To Be Received\",\n \"reference_doctype\": \"Purchase Order\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Quotation Comparison\",\n \"name\": \"Supplier Quotation Comparison\",\n \"onboard\": 1,\n \"reference_doctype\": \"Supplier Quotation\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Material Requests for which Supplier Quotations are not created\",\n \"name\": \"Material Requests for which Supplier Quotations are not created\",\n \"reference_doctype\": \"Material Request\",\n \"type\": \"report\"\n },\n {\n \"is_query_report\": true,\n \"label\": \"Supplier Addresses And Contacts\",\n \"name\": \"Address And Contacts\",\n \"reference_doctype\": \"Address\",\n \"route_options\": {\n \"party_type\": \"Supplier\"\n },\n \"type\": \"report\"\n }\n]" }, { "hidden": 0, @@ -60,7 +60,7 @@ "idx": 0, "is_standard": 1, "label": "Buying", - "modified": "2020-06-29 19:30:24.983050", + "modified": "2020-09-30 14:40:55.638458", "modified_by": "Administrator", "module": "Buying", "name": "Buying", diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index d2399b1ec9..2b371915f3 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -44,7 +44,7 @@ def get_data(filters, conditions): supplier_quotation_data = frappe.db.sql(""" SELECT sqi.parent, sqi.item_code, - sqi.qty, sqi.stock_qty, sqi.rate, + sqi.qty, sqi.stock_qty, sqi.amount, sqi.uom, sqi.stock_uom, sqi.request_for_quotation, sqi.lead_time_days, sq.supplier as supplier_name, sq.valid_till @@ -85,7 +85,7 @@ def prepare_data(supplier_quotation_data, filters): "supplier_name": "" if group_by_field=="supplier_name" else data.get("supplier_name"), "quotation": data.get("parent"), "qty": data.get("qty"), - "price": flt(data.get("rate") * exchange_rate, float_precision), + "price": flt(data.get("amount") * exchange_rate, float_precision), "uom": data.get("uom"), "stock_uom": data.get('stock_uom'), "request_for_quotation": data.get("request_for_quotation"), @@ -112,12 +112,12 @@ def prepare_data(supplier_quotation_data, filters): suppliers = list(set(suppliers)) qty_list = list(set(qty_list)) - highlight_min_price = group_by_field == "item_code" + highlight_min_price = group_by_field == "item_code" or filters.get("item_code") # final data format for report view for group in groups: group_entries = group_wise_map[group] # all entries pertaining to item/supplier - group_entries[0].update({group_by_field : group}) + group_entries[0].update({group_by_field : group}) # Add item/supplier name in first group row if highlight_min_price: prices = [group_entry["price_per_unit"] for group_entry in group_entries] From 4993ef19d6fe4280ea7b95c2e8ee7e1905a90c5d Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Oct 2020 16:35:09 +0530 Subject: [PATCH 07/41] feat: Supplier Quotation UX fixes --- .../request_for_quotation.js | 2 +- .../request_for_quotation.py | 12 ++-- .../supplier_quotation/supplier_quotation.js | 28 +++++--- .../supplier_quotation_item.json | 69 +++++++++++++------ 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 4a937f7f0d..cf5d7cd4c5 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -179,7 +179,7 @@ frappe.ui.form.on("Request for Quotation",{ dialog.hide(); return frappe.call({ type: "GET", - method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation", + method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq", args: { "source_name": doc.name, "for_supplier": args.supplier diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index b54a585b97..fd04b0d2a6 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -214,14 +214,14 @@ def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters): and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent limit %(start)s, %(page_len)s""", {"start": start, "page_len":page_len, "txt": "%%%s%%" % txt, "name": filters.get('supplier')}) -# This method is used to make supplier quotation from material request form. @frappe.whitelist() -def make_supplier_quotation(source_name, for_supplier, target_doc=None): +def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None): def postprocess(source, target_doc): - target_doc.supplier = for_supplier - args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True) - target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company) - target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list') + if for_supplier: + target_doc.supplier = for_supplier + args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True) + target_doc.currency = args.currency or get_party_account_currency('Supplier', for_supplier, source.company) + target_doc.buying_price_list = args.buying_price_list or frappe.db.get_value('Buying Settings', None, 'buying_price_list') set_missing_values(source, target_doc) doclist = get_mapped_doc("Request for Quotation", source_name, { diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index 1b8b40459f..4a2a0786f3 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -8,8 +8,7 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext setup: function() { this.frm.custom_make_buttons = { 'Purchase Order': 'Purchase Order', - 'Quotation': 'Quotation', - 'Subscription': 'Subscription' + 'Quotation': 'Quotation' } this._super(); @@ -28,12 +27,6 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext cur_frm.page.set_inner_btn_group_as_primary(__('Create')); cur_frm.add_custom_button(__("Quotation"), this.make_quotation, __('Create')); - - if(!this.frm.doc.auto_repeat) { - cur_frm.add_custom_button(__('Subscription'), function() { - erpnext.utils.make_subscription(me.frm.doc.doctype, me.frm.doc.name) - }, __('Create')) - } } else if (this.frm.doc.docstatus===0) { @@ -54,6 +47,25 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext } }) }, __("Get items from")); + + this.frm.add_custom_button(__("Request for Quotation"), + function() { + if (!me.frm.doc.supplier) { + frappe.throw({message:__("Please select a Supplier"), title:__("Mandatory")}) + } + erpnext.utils.map_current_doc({ + method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.make_supplier_quotation_from_rfq", + source_doctype: "Request for Quotation", + target: me.frm, + setters: { + company: me.frm.doc.company, + transaction_date: null + }, + get_query_filters: { + docstatus: 1, + } + }) + }, __("Get items from")); } }, diff --git a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json index b50e834ec7..7d624357f3 100644 --- a/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json +++ b/erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json @@ -12,6 +12,8 @@ "item_name", "column_break_3", "lead_time_days", + "expected_delivery_date", + "is_free_item", "section_break_5", "description", "item_group", @@ -19,20 +21,18 @@ "col_break1", "image", "image_view", - "manufacture_details", - "manufacturer", - "column_break_15", - "manufacturer_part_no", "quantity_and_rate", "qty", "stock_uom", - "price_list_rate", - "discount_percentage", - "discount_amount", "col_break2", "uom", "conversion_factor", "stock_qty", + "sec_break_price_list", + "price_list_rate", + "discount_percentage", + "discount_amount", + "col_break_price_list", "base_price_list_rate", "sec_break1", "rate", @@ -42,7 +42,6 @@ "base_rate", "base_amount", "pricing_rules", - "is_free_item", "section_break_24", "net_rate", "net_amount", @@ -56,7 +55,6 @@ "weight_uom", "warehouse_and_reference", "warehouse", - "project", "prevdoc_doctype", "material_request", "sales_order", @@ -65,13 +63,19 @@ "material_request_item", "request_for_quotation_item", "item_tax_rate", + "manufacture_details", + "manufacturer", + "column_break_15", + "manufacturer_part_no", + "ad_sec_break", + "project", "section_break_44", "page_break" ], "fields": [ { "bold": 1, - "columns": 4, + "columns": 2, "fieldname": "item_code", "fieldtype": "Link", "in_list_view": 1, @@ -107,7 +111,7 @@ { "fieldname": "lead_time_days", "fieldtype": "Int", - "label": "Lead Time in days" + "label": "Supplier Lead Time (days)" }, { "collapsible": 1, @@ -162,7 +166,6 @@ { "fieldname": "stock_uom", "fieldtype": "Link", - "in_list_view": 1, "label": "Stock UOM", "options": "UOM", "print_hide": 1, @@ -196,6 +199,7 @@ { "fieldname": "uom", "fieldtype": "Link", + "in_list_view": 1, "label": "UOM", "options": "UOM", "print_hide": 1, @@ -289,14 +293,6 @@ "print_hide": 1, "read_only": 1 }, - { - "default": "0", - "fieldname": "is_free_item", - "fieldtype": "Check", - "label": "Is Free Item", - "print_hide": 1, - "read_only": 1 - }, { "fieldname": "section_break_24", "fieldtype": "Section Break" @@ -528,12 +524,43 @@ { "fieldname": "column_break_15", "fieldtype": "Column Break" + }, + { + "fieldname": "sec_break_price_list", + "fieldtype": "Section Break" + }, + { + "fieldname": "col_break_price_list", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "ad_sec_break", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "default": "0", + "depends_on": "is_free_item", + "fieldname": "is_free_item", + "fieldtype": "Check", + "label": "Is Free Item", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "bold": 1, + "fieldname": "expected_delivery_date", + "fieldtype": "Date", + "label": "Expected Delivery Date" } ], "idx": 1, + "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-04-07 18:35:51.175947", + "modified": "2020-10-01 16:34:39.703033", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation Item", From 5006345a0a55ff4bb01cf431af6a8b8b900e5428 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Oct 2020 20:41:05 +0530 Subject: [PATCH 08/41] chore: Fetch RFQs in Get Items from RFQ based on Supplier selected - Rfq will appear only if selected supplier is in the RFQ --- .../request_for_quotation.py | 28 +++++++++++++++++++ .../supplier_quotation/supplier_quotation.js | 6 ++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index fd04b0d2a6..76775343e4 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -354,3 +354,31 @@ def get_supplier_tag(): frappe.cache().hset("Supplier", "Tags", tags) return frappe.cache().hget("Supplier", "Tags") + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters): + conditions = "" + if txt: + conditions += "and rfq.name like '%%"+txt+"%%' " + + if filters.get("transaction_date"): + conditions += "and rfq.transaction_date = '{0}'".format(filters.get("transaction_date")) + + rfq_data = frappe.db.sql(""" + select + distinct rfq.name, rfq.transaction_date, + rfq.company + from + `tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier + where + rfq.name = rfq_supplier.parent + and rfq_supplier.supplier = '{0}' + and rfq.docstatus = 1 + and rfq.company = '{1}' + {2} + order by rfq.transaction_date ASC + limit {3} offset {4} """ \ + .format(filters.get("supplier"), filters.get("company"), conditions, page_len, start), as_dict=1) + + return rfq_data \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index 4a2a0786f3..3376e82956 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -62,8 +62,10 @@ erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.ext transaction_date: null }, get_query_filters: { - docstatus: 1, - } + supplier: me.frm.doc.supplier + }, + get_query_method: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_rfq_containing_supplier" + }) }, __("Get items from")); } From f57ba86bce5ad0b1ec647f90a8d8d0d42fd21a31 Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Oct 2020 21:10:00 +0530 Subject: [PATCH 09/41] chore: Add Date and Valid Till to List View --- .../buying/doctype/supplier_quotation/supplier_quotation.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 660dcff34b..9a092ca5c3 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -159,6 +159,7 @@ "default": "Today", "fieldname": "transaction_date", "fieldtype": "Date", + "in_list_view": 1, "label": "Date", "oldfieldname": "transaction_date", "oldfieldtype": "Date", @@ -798,6 +799,7 @@ { "fieldname": "valid_till", "fieldtype": "Date", + "in_list_view": 1, "label": "Valid Till" } ], @@ -805,7 +807,7 @@ "idx": 29, "is_submittable": 1, "links": [], - "modified": "2020-07-18 05:10:45.556792", + "modified": "2020-10-01 20:56:17.932007", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", From b9e2f17f4f427dc8726c348ddc2577c7d8b50f7b Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 1 Oct 2020 21:31:51 +0530 Subject: [PATCH 10/41] fix: More secure query --- .../doctype/request_for_quotation/request_for_quotation.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index 76775343e4..361ccdf7f3 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -378,7 +378,8 @@ def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filt and rfq.company = '{1}' {2} order by rfq.transaction_date ASC - limit {3} offset {4} """ \ - .format(filters.get("supplier"), filters.get("company"), conditions, page_len, start), as_dict=1) + limit %(page_len)s offset %(start)s """ \ + .format(filters.get("supplier"), filters.get("company"), conditions), + {"page_len": page_len, "start": start}, as_dict=1) return rfq_data \ No newline at end of file From 0bc61b31f7f7d0eee17be43c26ee6c5048ed050b Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 2 Oct 2020 11:54:54 +0530 Subject: [PATCH 11/41] chore: Move Qty fields (received qty,etc.) close to Qty/Stock UOM --- .../material_request_item.json | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 08c273838f..25bbbbd4b3 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -26,6 +26,13 @@ "uom", "conversion_factor", "stock_qty", + "qty_info_sec_break", + "min_order_qty", + "projected_qty", + "qty_info_col_break", + "actual_qty", + "ordered_qty", + "received_qty", "rate_and_amount_section_break", "rate", "col_break3", @@ -45,13 +52,8 @@ "sales_order_item", "production_plan", "material_request_plan_item", - "expense_account", "col_break4", - "min_order_qty", - "projected_qty", - "actual_qty", - "ordered_qty", - "received_qty", + "expense_account", "section_break_46", "page_break" ], @@ -434,13 +436,21 @@ { "fieldname": "col_break_mfg", "fieldtype": "Column Break" + }, + { + "fieldname": "qty_info_sec_break", + "fieldtype": "Section Break" + }, + { + "fieldname": "qty_info_col_break", + "fieldtype": "Column Break" } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-18 20:52:33.265074", + "modified": "2020-10-02 11:44:36.553064", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", From 203585f29250522bf559b96e75ebc34b07ce89ac Mon Sep 17 00:00:00 2001 From: michellealva Date: Tue, 6 Oct 2020 16:00:39 +0530 Subject: [PATCH 12/41] fix(Employee): Field level change in Exit section --- erpnext/hr/doctype/employee/employee.json | 33 ++++++----------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 8c02e4f1d6..da789198e5 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -109,7 +109,6 @@ "encashment_date", "exit_interview_details", "held_on", - "reason_for_resignation", "new_workplace", "feedback", "lft", @@ -682,7 +681,7 @@ }, { "fieldname": "reason_for_leaving", - "fieldtype": "Data", + "fieldtype": "Small Text", "label": "Reason for Leaving", "oldfieldname": "reason_for_leaving", "oldfieldtype": "Data" @@ -696,6 +695,7 @@ "options": "\nYes\nNo" }, { + "depends_on": "eval:doc.leave_encashed ==\"Yes\"", "fieldname": "encashment_date", "fieldtype": "Date", "label": "Encashment Date", @@ -705,7 +705,6 @@ { "fieldname": "exit_interview_details", "fieldtype": "Column Break", - "label": "Exit Interview Details", "oldfieldname": "col_brk6", "oldfieldtype": "Column Break", "width": "50%" @@ -713,18 +712,10 @@ { "fieldname": "held_on", "fieldtype": "Date", - "label": "Held On", + "label": "Exit Interview Held On", "oldfieldname": "held_on", "oldfieldtype": "Date" }, - { - "fieldname": "reason_for_resignation", - "fieldtype": "Select", - "label": "Reason for Resignation", - "oldfieldname": "reason_for_resignation", - "oldfieldtype": "Select", - "options": "\nBetter Prospects\nHealth Concerns" - }, { "fieldname": "new_workplace", "fieldtype": "Data", @@ -809,37 +800,29 @@ "fieldname": "expense_approver", "fieldtype": "Link", "label": "Expense Approver", - "options": "User", - "show_days": 1, - "show_seconds": 1 + "options": "User" }, { "fieldname": "approvers_section", "fieldtype": "Section Break", - "label": "Approvers", - "show_days": 1, - "show_seconds": 1 + "label": "Approvers" }, { "fieldname": "column_break_45", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "shift_request_approver", "fieldtype": "Link", "label": "Shift Request Approver", - "options": "User", - "show_days": 1, - "show_seconds": 1 + "options": "User" } ], "icon": "fa fa-user", "idx": 24, "image_field": "image", "links": [], - "modified": "2020-07-28 01:36:04.109189", + "modified": "2020-10-06 15:58:23.805489", "modified_by": "Administrator", "module": "HR", "name": "Employee", From be0ebc50855e3f3404b6f8d5bbc78df6ed3a50ce Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 7 Oct 2020 20:47:12 +0530 Subject: [PATCH 13/41] spell fixes for Condition --- .../salary_component/salary_component.json | 9 ++++--- .../doctype/salary_detail/salary_detail.json | 24 ++++++------------- .../doctype/quotation/tests/test_quotation.js | 2 +- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/erpnext/payroll/doctype/salary_component/salary_component.json b/erpnext/payroll/doctype/salary_component/salary_component.json index 225b048293..c97e45cd53 100644 --- a/erpnext/payroll/doctype/salary_component/salary_component.json +++ b/erpnext/payroll/doctype/salary_component/salary_component.json @@ -217,7 +217,7 @@ "fieldname": "help", "fieldtype": "HTML", "label": "Help", - "options": "

Help

\n\n

Notes:

\n\n
    \n
  1. Use field base for using base salary of the Employee
  2. \n
  3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
  4. \n
  5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
  6. \n
  7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n
  9. Direct Amount can also be entered based on Condtion. See example 3
\n\n

Examples

\n
    \n
  1. Calculating Basic Salary based on base\n
    Condition: base < 10000
    \n
    Formula: base * .2
  2. \n
  3. Calculating HRA based on Basic SalaryBS \n
    Condition: BS > 2000
    \n
    Formula: BS * .1
  4. \n
  5. Calculating TDS based on Employment Typeemployment_type \n
    Condition: employment_type==\"Intern\"
    \n
    Amount: 1000
  6. \n
" + "options": "

Help

\n\n

Notes:

\n\n
    \n
  1. Use field base for using base salary of the Employee
  2. \n
  3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
  4. \n
  5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
  6. \n
  7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n
  9. Direct Amount can also be entered based on Condition. See example 3
\n\n

Examples

\n
    \n
  1. Calculating Basic Salary based on base\n
    Condition: base < 10000
    \n
    Formula: base * .2
  2. \n
  3. Calculating HRA based on Basic SalaryBS \n
    Condition: BS > 2000
    \n
    Formula: BS * .1
  4. \n
  5. Calculating TDS based on Employment Typeemployment_type \n
    Condition: employment_type==\"Intern\"
    \n
    Amount: 1000
  6. \n
" }, { "default": "0", @@ -238,14 +238,13 @@ "depends_on": "eval:doc.type == \"Deduction\"", "fieldname": "is_income_tax_component", "fieldtype": "Check", - "label": "Is Income Tax Component", - "show_days": 1, - "show_seconds": 1 + "label": "Is Income Tax Component" } ], "icon": "fa fa-flag", + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-06-22 15:39:20.826565", + "modified": "2020-10-07 20:38:33.795853", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Component", diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json index cc87caeae1..eedb56ec08 100644 --- a/erpnext/payroll/doctype/salary_detail/salary_detail.json +++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json @@ -117,7 +117,7 @@ "depends_on": "eval:doc.is_flexible_benefit != 1", "fieldname": "section_break_2", "fieldtype": "Section Break", - "label": "Condtion and formula" + "label": "Condition and formula" }, { "allow_on_submit": 1, @@ -206,38 +206,28 @@ "collapsible": 1, "fieldname": "section_break_5", "fieldtype": "Section Break", - "label": "Component properties and references ", - "show_days": 1, - "show_seconds": 1 + "label": "Component properties and references " }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "section_break_19", - "fieldtype": "Section Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Section Break" }, { "fieldname": "column_break_18", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "fieldname": "column_break_24", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2020-07-01 12:13:41.956495", + "modified": "2020-10-07 20:39:41.619283", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Detail", diff --git a/erpnext/selling/doctype/quotation/tests/test_quotation.js b/erpnext/selling/doctype/quotation/tests/test_quotation.js index d69d799d0d..ad942fe497 100644 --- a/erpnext/selling/doctype/quotation/tests/test_quotation.js +++ b/erpnext/selling/doctype/quotation/tests/test_quotation.js @@ -46,7 +46,7 @@ QUnit.test("test: quotation", function (assert) { assert.ok(cur_frm.doc.items[0].rate == 200, "Price Changed Manually"); assert.equal(cur_frm.doc.total, 1000, "New Total Calculated"); - // Check Terms and Condtions + // Check Terms and Conditions assert.ok(cur_frm.doc.tc_name == "Test Term 1", "Terms and Conditions Checked"); assert.ok(cur_frm.doc.payment_terms_template, "Payment Terms Template is correct"); From a86cab0bb2f3088237335e682603a87da50cc3ec Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 8 Oct 2020 16:41:23 +0530 Subject: [PATCH 14/41] fix: added patch to make custom field 'Print UOM after Quantity' --- erpnext/patches.txt | 1 + .../patches/v13_0/print_uom_after_quantity_patch.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 erpnext/patches/v13_0/print_uom_after_quantity_patch.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6087ce29aa..77310cd70e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -729,3 +729,4 @@ erpnext.patches.v13_0.setting_custom_roles_for_some_regional_reports erpnext.patches.v13_0.rename_issue_doctype_fields erpnext.patches.v13_0.change_default_pos_print_format erpnext.patches.v13_0.set_youtube_video_id +erpnext.patches.v13_0.print_uom_after_quantity_patch \ No newline at end of file diff --git a/erpnext/patches/v13_0/print_uom_after_quantity_patch.py b/erpnext/patches/v13_0/print_uom_after_quantity_patch.py new file mode 100644 index 0000000000..0de3728f5c --- /dev/null +++ b/erpnext/patches/v13_0/print_uom_after_quantity_patch.py @@ -0,0 +1,10 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals + +import frappe +from erpnext.setup.install import create_print_uom_after_qty_custom_field + +def execute(): + create_print_uom_after_qty_custom_field() From 22dc42efb98192a80372374cfc37cad2c6c79d08 Mon Sep 17 00:00:00 2001 From: Anupam Date: Wed, 7 Oct 2020 18:16:18 +0530 Subject: [PATCH 15/41] fix: error log while creating company --- erpnext/setup/doctype/company/company.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 8e707fe3f4..4a438f70e6 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -442,7 +442,7 @@ def install_country_fixtures(company): module_name = "erpnext.regional.{0}.setup.setup".format(frappe.scrub(company_doc.country)) frappe.get_attr(module_name)(company_doc, False) except Exception as e: - frappe.log_error(str(e), frappe.get_traceback()) + frappe.log_error() frappe.throw(_("Failed to setup defaults for country {0}. Please contact support@erpnext.com").format(frappe.bold(company_doc.country))) From 776d8f72cf0b0ff2a5e19d8cafe6cffcadac57b5 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 9 Oct 2020 15:36:11 +0530 Subject: [PATCH 16/41] chore: do not allow future transactions --- erpnext/utilities/transaction_base.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index c8e3330908..7f27ab6511 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import frappe.share from frappe import _ -from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_datetime, get_link_to_form +from frappe.utils import cstr, now_datetime, cint, flt, get_time, get_datetime, get_link_to_form, date_diff, nowdate from erpnext.controllers.status_updater import StatusUpdater from erpnext.accounts.utils import get_fiscal_year @@ -29,7 +29,13 @@ class TransactionBase(StatusUpdater): except ValueError: frappe.throw(_('Invalid Posting Time')) + self.validate_future_posting() self.validate_with_last_transaction_posting_time() + + def validate_future_posting(self): + if getattr(self, 'set_posting_time', None) and date_diff(self.posting_date, nowdate()) > 0: + msg = _("Posting future transactions are not allowed due to Immutable Ledger") + frappe.throw(msg, title=_("Future Posting Not Allowed")) def add_calendar_event(self, opts, force=False): if cstr(self.contact_by) != cstr(self._prev.contact_by) or \ From 3d91d48b7786e5bc37fa82bd24f4e76512ef2e4b Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 10 Oct 2020 21:56:50 +0530 Subject: [PATCH 17/41] fix: validate only for stock transactions --- erpnext/utilities/transaction_base.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py index 7f27ab6511..298e1110d3 100644 --- a/erpnext/utilities/transaction_base.py +++ b/erpnext/utilities/transaction_base.py @@ -32,7 +32,21 @@ class TransactionBase(StatusUpdater): self.validate_future_posting() self.validate_with_last_transaction_posting_time() + def is_stock_transaction(self): + if self.doctype not in ["Sales Invoice", "Purchase Invoice", "Stock Entry", "Stock Reconciliation", + "Delivery Note", "Purchase Receipt", "Fees"]: + return False + + if self.doctype in ["Sales Invoice", "Purchase Invoice"]: + if not (self.get("update_stock") or self.get("is_pos")): + return False + + return True + def validate_future_posting(self): + if not self.is_stock_transaction(): + return + if getattr(self, 'set_posting_time', None) and date_diff(self.posting_date, nowdate()) > 0: msg = _("Posting future transactions are not allowed due to Immutable Ledger") frappe.throw(msg, title=_("Future Posting Not Allowed")) @@ -168,13 +182,8 @@ class TransactionBase(StatusUpdater): def validate_with_last_transaction_posting_time(self): - if self.doctype not in ["Sales Invoice", "Purchase Invoice", "Stock Entry", "Stock Reconciliation", - "Delivery Note", "Purchase Receipt", "Fees"]: - return - - if self.doctype in ["Sales Invoice", "Purchase Invoice"]: - if not (self.get("update_stock") or self.get("is_pos")): - return + if not self.is_stock_transaction(): + return for item in self.get('items'): last_transaction_time = frappe.db.sql(""" From 2be23309bb703edfb1c356b900ecef943ac7f247 Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 11 Oct 2020 14:14:50 +0530 Subject: [PATCH 18/41] fix: can't add child table in item price --- erpnext/stock/doctype/item_price/item_price.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 8e39eb5037..51b47c50a3 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -50,16 +50,18 @@ class ItemPrice(Document): def check_duplicates(self): conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s" + condition_data_dict = dict(item_code=self.item_code, price_list=self.price_list, name=self.name) for field in ['uom', 'valid_from', 'valid_upto', 'packing_unit', 'customer', 'supplier']: if self.get(field): conditions += " and {0} = %({1})s".format(field, field) + condition_data_dict[field] = self.get(field) price_list_rate = frappe.db.sql(""" SELECT price_list_rate FROM `tabItem Price` - {conditions} """.format(conditions=conditions), self.as_dict()) + {conditions} """.format(conditions=conditions), condition_data_dict) if price_list_rate : frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, UOM, Qty and Dates."), ItemPriceDuplicateItem) From 7a86a77322122efa0565f00d21fb6a8f46a1158e Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Mon, 12 Oct 2020 11:30:36 +0800 Subject: [PATCH 19/41] fix: Asset status after maintenance or repair Fix a bug where an **Asset**'s status is not being set to what it was previously after an **Asset Repair** or **Asset Maintenance**. --- erpnext/assets/doctype/asset/asset.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index efdbdb1fbf..45fdc9316e 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -131,7 +131,7 @@ class Asset(AccountsController): def validate_gross_and_purchase_amount(self): if self.is_existing_asset: return - + if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount: frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\ Please do not book expense of multiple assets against one single Asset.") @@ -561,14 +561,18 @@ class Asset(AccountsController): return 100 * (1 - flt(depreciation_rate, float_precision)) def update_maintenance_status(): - assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1}) + assets = frappe.get_all( + "Asset", filters={"docstatus": 1, "maintenance_required": 1} + ) for asset in assets: asset = frappe.get_doc("Asset", asset.name) - if frappe.db.exists('Asset Maintenance Task', {'parent': asset.name, 'next_due_date': today()}): - asset.set_status('In Maintenance') - if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}): - asset.set_status('Out of Order') + if frappe.db.exists("Asset Repair", {"asset_name": asset.name, "repair_status": "Pending"}): + asset.set_status("Out of Order") + elif frappe.db.exists("Asset Maintenance Task", {"parent": asset.name, "next_due_date": today()}): + asset.set_status("In Maintenance") + else: + asset.set_status() def make_post_gl_entry(): From 03975a692e579656195ff16e2f31c1395bef60fa Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 12 Oct 2020 12:03:07 +0530 Subject: [PATCH 20/41] fix: Perform Item Attribute Value Validation on Variants Only --- .../doctype/item_attribute/item_attribute.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py index 7f00201587..3764738e83 100644 --- a/erpnext/stock/doctype/item_attribute/item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/item_attribute.py @@ -29,9 +29,18 @@ class ItemAttribute(Document): '''Validate that if there are existing items with attributes, they are valid''' attributes_list = [d.attribute_value for d in self.item_attribute_values] - for item in frappe.db.sql('''select i.name, iva.attribute_value as value - from `tabItem Variant Attribute` iva, `tabItem` i where iva.attribute = %s - and iva.parent = i.name and i.has_variants = 0''', self.name, as_dict=1): + # Get Item Variant Attribute details of variant items + items = frappe.db.sql(""" + select + i.name, iva.attribute_value as value + from + `tabItem Variant Attribute` iva, `tabItem` i + where + iva.attribute = %(attribute)s + and iva.parent = i.name and + i.variant_of is not null and i.variant_of != ''""", {"attribute" : self.name}, as_dict=1) + + for item in items: if self.numeric_values: validate_is_incremental(self, self.name, item.value, item.name) else: From efc3e1fa6212911563445b8bf1e1fd756fd974f3 Mon Sep 17 00:00:00 2001 From: Anupam Date: Thu, 1 Oct 2020 15:55:04 +0530 Subject: [PATCH 21/41] fix: Payment Schedule not fetching --- .../accounts/doctype/subscription/subscription.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 07525317aa..552a5d476b 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -345,13 +345,14 @@ class Subscription(Document): invoice.set_taxes() # Due date - invoice.append( - 'payment_schedule', - { - 'due_date': add_days(invoice.posting_date, cint(self.days_until_due)), - 'invoice_portion': 100 - } - ) + if self.days_until_due: + invoice.append( + 'payment_schedule', + { + 'due_date': add_days(invoice.posting_date, cint(self.days_until_due)), + 'invoice_portion': 100 + } + ) # Discounts if self.additional_discount_percentage: From 15d46040ff1546e205f3e1044aefa15642fa225a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 12 Oct 2020 12:39:08 +0530 Subject: [PATCH 22/41] fix: Do not consider opening entries for TDS calculation (#23597) --- .../tax_withholding_category/tax_withholding_category.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 83d7967f79..8b5e68b359 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -106,6 +106,7 @@ def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_detai from `tabGL Entry` where company = %s and party in %s and fiscal_year=%s and credit > 0 + and is_opening = 'No' """, (company, tuple(suppliers), fiscal_year), as_dict=1) vouchers = [d.voucher_no for d in entries] @@ -192,6 +193,7 @@ def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=No select distinct voucher_no from `tabGL Entry` where party in %s and %s and debit > 0 + and is_opening = 'No' """, (tuple(suppliers), condition)) or [] def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None): From 6ec3dc545392e013bb5fce737837d46ed8a934a6 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Mon, 12 Oct 2020 02:26:55 -0700 Subject: [PATCH 23/41] fix(asset): cannot create asset if cwip disabled and account not set (#23580) * Never add an asset GL entry if CWIP is not enabled * fix: asset purchase with purchase invoice * chore: allow enable cwip accounting only if cwip account is set * fix: cannot create asset if cwip disabled and account not set Co-authored-by: Saqib Ansari --- .../purchase_invoice/purchase_invoice.py | 3 +- .../purchase_invoice/test_purchase_invoice.py | 3 +- erpnext/assets/doctype/asset/asset.py | 75 +++++---- erpnext/assets/doctype/asset/test_asset.py | 144 +++++++++--------- .../doctype/asset_category/asset_category.py | 18 ++- .../asset_category/test_asset_category.py | 20 ++- 6 files changed, 153 insertions(+), 110 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 079f599706..c5260a1239 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -711,7 +711,8 @@ class PurchaseInvoice(BuyingController): item.item_tax_amount / self.conversion_rate) }, item=item)) else: - cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) + cwip_account = get_asset_account("capital_work_in_progress_account", + asset_category=item.asset_category,company=self.company) cwip_account_currency = get_account_currency(cwip_account) gl_entries.append(self.get_gl_dict({ diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 9a666bf9f8..2e5a7142a3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1002,7 +1002,8 @@ def make_purchase_invoice(**args): "cost_center": args.cost_center or "_Test Cost Center - _TC", "project": args.project, "rejected_warehouse": args.rejected_warehouse or "", - "rejected_serial_no": args.rejected_serial_no or "" + "rejected_serial_no": args.rejected_serial_no or "", + "asset_location": args.location or "" }) if args.get_taxes_and_charges: diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index efdbdb1fbf..fca9559504 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -466,50 +466,63 @@ class Asset(AccountsController): def validate_make_gl_entry(self): purchase_document = self.get_purchase_document() - asset_bought_with_invoice = purchase_document == self.purchase_invoice - fixed_asset_account, cwip_account = self.get_asset_accounts() - cwip_enabled = is_cwip_accounting_enabled(self.asset_category) - # check if expense already has been booked in case of cwip was enabled after purchasing asset - expense_booked = False - cwip_booked = False - - if asset_bought_with_invoice: - expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""", - (purchase_document, fixed_asset_account), as_dict=1) - else: - cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""", - (purchase_document, cwip_account), as_dict=1) - - if cwip_enabled and (expense_booked or not cwip_booked): - # if expense has already booked from invoice or cwip is booked from receipt + if not purchase_document: return False - elif not cwip_enabled and (not expense_booked or cwip_booked): - # if cwip is disabled but expense hasn't been booked yet - return True - elif cwip_enabled: - # default condition - return True + + asset_bought_with_invoice = (purchase_document == self.purchase_invoice) + fixed_asset_account = self.get_fixed_asset_account() + + cwip_enabled = is_cwip_accounting_enabled(self.asset_category) + cwip_account = self.get_cwip_account(cwip_enabled=cwip_enabled) + + query = """SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""" + if asset_bought_with_invoice: + # with invoice purchase either expense or cwip has been booked + expense_booked = frappe.db.sql(query, (purchase_document, fixed_asset_account), as_dict=1) + if expense_booked: + # if expense is already booked from invoice then do not make gl entries regardless of cwip enabled/disabled + return False + + cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1) + if cwip_booked: + # if cwip is booked from invoice then make gl entries regardless of cwip enabled/disabled + return True + else: + # with receipt purchase either cwip has been booked or no entries have been made + if not cwip_account: + # if cwip account isn't available do not make gl entries + return False + + cwip_booked = frappe.db.sql(query, (purchase_document, cwip_account), as_dict=1) + # if cwip is not booked from receipt then do not make gl entries + # if cwip is booked from receipt then make gl entries + return cwip_booked def get_purchase_document(self): asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock') purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt return purchase_document + + def get_fixed_asset_account(self): + return get_asset_category_account('fixed_asset_account', None, self.name, None, self.asset_category, self.company) + + def get_cwip_account(self, cwip_enabled=False): + cwip_account = None + try: + cwip_account = get_asset_account("capital_work_in_progress_account", self.name, self.asset_category, self.company) + except: + # if no cwip account found in category or company and "cwip is enabled" then raise else silently pass + if cwip_enabled: + raise - def get_asset_accounts(self): - fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name, - asset_category = self.asset_category, company = self.company) - - cwip_account = get_asset_account("capital_work_in_progress_account", - self.name, self.asset_category, self.company) - - return fixed_asset_account, cwip_account + return cwip_account def make_gl_entries(self): gl_entries = [] purchase_document = self.get_purchase_document() - fixed_asset_account, cwip_account = self.get_asset_accounts() + fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account() if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()): diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 52039c183b..a0d76031fc 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -9,6 +9,7 @@ from frappe.utils import cstr, nowdate, getdate, flt, get_last_day, add_days, ad from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset, restore_asset from erpnext.assets.doctype.asset.asset import make_sales_invoice from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt +from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_invoice class TestAsset(unittest.TestCase): @@ -558,81 +559,6 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) - def test_gle_with_cwip_toggling(self): - # TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset - frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) - - pr = make_purchase_receipt(item_code="Macbook Pro", - qty=1, rate=5000, do_not_submit=True, location="Test Location") - pr.set('taxes', [{ - 'category': 'Total', - 'add_deduct_tax': 'Add', - 'charge_type': 'On Net Total', - 'account_head': '_Test Account Service Tax - _TC', - 'description': '_Test Account Service Tax', - 'cost_center': 'Main - _TC', - 'rate': 5.0 - }, { - 'category': 'Valuation and Total', - 'add_deduct_tax': 'Add', - 'charge_type': 'On Net Total', - 'account_head': '_Test Account Shipping Charges - _TC', - 'description': '_Test Account Shipping Charges', - 'cost_center': 'Main - _TC', - 'rate': 5.0 - }]) - pr.submit() - expected_gle = ( - ("Asset Received But Not Billed - _TC", 0.0, 5250.0), - ("CWIP Account - _TC", 5250.0, 0.0) - ) - pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Receipt' and voucher_no = %s - order by account""", pr.name) - self.assertEqual(pr_gle, expected_gle) - - pi = make_invoice(pr.name) - pi.submit() - expected_gle = ( - ("_Test Account Service Tax - _TC", 250.0, 0.0), - ("_Test Account Shipping Charges - _TC", 250.0, 0.0), - ("Asset Received But Not Billed - _TC", 5250.0, 0.0), - ("Creditors - _TC", 0.0, 5500.0), - ("Expenses Included In Asset Valuation - _TC", 0.0, 250.0), - ) - pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Purchase Invoice' and voucher_no = %s - order by account""", pi.name) - self.assertEqual(pi_gle, expected_gle) - - asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') - asset_doc = frappe.get_doc('Asset', asset) - month_end_date = get_last_day(nowdate()) - asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15) - self.assertEqual(asset_doc.gross_purchase_amount, 5250.0) - asset_doc.append("finance_books", { - "expected_value_after_useful_life": 200, - "depreciation_method": "Straight Line", - "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": month_end_date - }) - - # disable cwip and try submitting - frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) - asset_doc.submit() - # asset should have gl entries even if cwip is disabled - expected_gle = ( - ("_Test Fixed Asset - _TC", 5250.0, 0.0), - ("CWIP Account - _TC", 0.0, 5250.0) - ) - gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` - where voucher_type='Asset' and voucher_no = %s - order by account""", asset_doc.name) - self.assertEqual(gle, expected_gle) - - frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) - def test_expense_head(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location") @@ -640,6 +566,74 @@ class TestAsset(unittest.TestCase): doc = make_invoice(pr.name) self.assertEquals('Asset Received But Not Billed - _TC', doc.items[0].expense_account) + + def test_asset_cwip_toggling_cases(self): + cwip = frappe.db.get_value("Asset Category", "Computers", "enable_cwip_accounting") + name = frappe.db.get_value("Asset Category Account", filters={"parent": "Computers"}, fieldname=["name"]) + cwip_acc = "CWIP Account - _TC" + + frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) + frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "") + frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", "") + + # case 0 -- PI with cwip disable, Asset with cwip disabled, No cwip account set + pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1) + asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name') + asset_doc = frappe.get_doc('Asset', asset) + asset_doc.available_for_use_date = nowdate() + asset_doc.calculate_depreciation = 0 + asset_doc.submit() + gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) + self.assertFalse(gle) + + # case 1 -- PR with cwip disabled, Asset with cwip enabled + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location") + frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) + frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) + asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') + asset_doc = frappe.get_doc('Asset', asset) + asset_doc.available_for_use_date = nowdate() + asset_doc.calculate_depreciation = 0 + asset_doc.submit() + gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) + self.assertFalse(gle) + + # case 2 -- PR with cwip enabled, Asset with cwip disabled + pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location") + frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) + asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name') + asset_doc = frappe.get_doc('Asset', asset) + asset_doc.available_for_use_date = nowdate() + asset_doc.calculate_depreciation = 0 + asset_doc.submit() + gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) + self.assertTrue(gle) + + # case 3 -- PI with cwip disabled, Asset with cwip enabled + pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1) + frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1) + asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name') + asset_doc = frappe.get_doc('Asset', asset) + asset_doc.available_for_use_date = nowdate() + asset_doc.calculate_depreciation = 0 + asset_doc.submit() + gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) + self.assertFalse(gle) + + # case 4 -- PI with cwip enabled, Asset with cwip disabled + pi = make_purchase_invoice(item_code="Macbook Pro", qty=1, rate=200000.0, location="Test Location", update_stock=1) + frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0) + asset = frappe.db.get_value('Asset', {'purchase_invoice': pi.name, 'docstatus': 0}, 'name') + asset_doc = frappe.get_doc('Asset', asset) + asset_doc.available_for_use_date = nowdate() + asset_doc.calculate_depreciation = 0 + asset_doc.submit() + gle = frappe.db.sql("""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""", asset_doc.name) + self.assertTrue(gle) + + frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip) + frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", cwip_acc) + frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account", cwip_acc) def create_asset_data(): if not frappe.db.exists("Asset Category", "Computers"): diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 9a33fc14ac..46620d56e9 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cint +from frappe.utils import cint, get_link_to_form from frappe.model.document import Document class AssetCategory(Document): @@ -13,6 +13,7 @@ class AssetCategory(Document): self.validate_finance_books() self.validate_account_types() self.validate_account_currency() + self.valide_cwip_account() def validate_finance_books(self): for d in self.finance_books: @@ -58,6 +59,21 @@ class AssetCategory(Document): frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.") .format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)), title=_("Invalid Account")) + + def valide_cwip_account(self): + if self.enable_cwip_accounting: + missing_cwip_accounts_for_company = [] + for d in self.accounts: + if (not d.capital_work_in_progress_account and + not frappe.db.get_value("Company", d.company_name, "capital_work_in_progress_account")): + missing_cwip_accounts_for_company.append(get_link_to_form("Company", d.company_name)) + + if missing_cwip_accounts_for_company: + msg = _("""To enable Capital Work in Progress Accounting, """) + msg += _("""you must select Capital Work in Progress Account in accounts table""") + msg += "

" + msg += _("You can also set default CWIP account in Company {}").format(", ".join(missing_cwip_accounts_for_company)) + frappe.throw(msg, title=_("Missing Account")) @frappe.whitelist() diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py index b32f9b5020..39b79d6c50 100644 --- a/erpnext/assets/doctype/asset_category/test_asset_category.py +++ b/erpnext/assets/doctype/asset_category/test_asset_category.py @@ -26,4 +26,22 @@ class TestAssetCategory(unittest.TestCase): asset_category.insert() except frappe.DuplicateEntryError: pass - \ No newline at end of file + + def test_cwip_accounting(self): + company_cwip_acc = frappe.db.get_value("Company", "_Test Company", "capital_work_in_progress_account") + frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "") + + asset_category = frappe.new_doc("Asset Category") + asset_category.asset_category_name = "Computers" + asset_category.enable_cwip_accounting = 1 + + asset_category.total_number_of_depreciations = 3 + asset_category.frequency_of_depreciation = 3 + asset_category.append("accounts", { + "company_name": "_Test Company", + "fixed_asset_account": "_Test Fixed Asset - _TC", + "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC", + "depreciation_expense_account": "_Test Depreciations - _TC" + }) + + self.assertRaises(frappe.ValidationError, asset_category.insert) \ No newline at end of file From ebbe2858729fc393797b4b378e1429f3d176bb51 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Oct 2020 17:02:31 +0530 Subject: [PATCH 24/41] feat: (report) POS Register (#23313) * feat: pos register report * feat: group by fields in pos register * chore: add paid amount column * fix: (minor) remove redundant group by label --- .../accounts/report/pos_register/__init__.py | 0 .../report/pos_register/pos_register.js | 76 ++++++ .../report/pos_register/pos_register.json | 30 +++ .../report/pos_register/pos_register.py | 222 ++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 erpnext/accounts/report/pos_register/__init__.py create mode 100644 erpnext/accounts/report/pos_register/pos_register.js create mode 100644 erpnext/accounts/report/pos_register/pos_register.json create mode 100644 erpnext/accounts/report/pos_register/pos_register.py diff --git a/erpnext/accounts/report/pos_register/__init__.py b/erpnext/accounts/report/pos_register/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/report/pos_register/pos_register.js b/erpnext/accounts/report/pos_register/pos_register.js new file mode 100644 index 0000000000..b8d48d92de --- /dev/null +++ b/erpnext/accounts/report/pos_register/pos_register.js @@ -0,0 +1,76 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["POS Register"] = { + "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 + }, + { + "fieldname":"from_date", + "label": __("From Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"to_date", + "label": __("To Date"), + "fieldtype": "Date", + "default": frappe.datetime.get_today(), + "reqd": 1, + "width": "60px" + }, + { + "fieldname":"pos_profile", + "label": __("POS Profile"), + "fieldtype": "Link", + "options": "POS Profile" + }, + { + "fieldname":"cashier", + "label": __("Cashier"), + "fieldtype": "Link", + "options": "User" + }, + { + "fieldname":"customer", + "label": __("Customer"), + "fieldtype": "Link", + "options": "Customer" + }, + { + "fieldname":"mode_of_payment", + "label": __("Payment Method"), + "fieldtype": "Link", + "options": "Mode of Payment" + }, + { + "fieldname":"group_by", + "label": __("Group by"), + "fieldtype": "Select", + "options": ["", "POS Profile", "Cashier", "Payment Method", "Customer"], + "default": "POS Profile" + }, + { + "fieldname":"is_return", + "label": __("Is Return"), + "fieldtype": "Check" + }, + ], + "formatter": function(value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (data && data.bold) { + value = value.bold(); + + } + return value; + } +}; diff --git a/erpnext/accounts/report/pos_register/pos_register.json b/erpnext/accounts/report/pos_register/pos_register.json new file mode 100644 index 0000000000..2398b10475 --- /dev/null +++ b/erpnext/accounts/report/pos_register/pos_register.json @@ -0,0 +1,30 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2020-09-10 19:25:03.766871", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "modified": "2020-09-10 19:25:15.851331", + "modified_by": "Administrator", + "module": "Accounts", + "name": "POS Register", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "POS Invoice", + "report_name": "POS Register", + "report_type": "Script Report", + "roles": [ + { + "role": "Accounts Manager" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py new file mode 100644 index 0000000000..0bcde64f7f --- /dev/null +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -0,0 +1,222 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _, _dict +from erpnext import get_company_currency, get_default_company +from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments + +def execute(filters=None): + if not filters: + return [], [] + + validate_filters(filters) + + columns = get_columns(filters) + + group_by_field = get_group_by_field(filters.get("group_by")) + + pos_entries = get_pos_entries(filters, group_by_field) + if group_by_field != "mode_of_payment": + concat_mode_of_payments(pos_entries) + + # return only entries if group by is unselected + if not group_by_field: + return columns, pos_entries + + # handle grouping + invoice_map, grouped_data = {}, [] + for d in pos_entries: + invoice_map.setdefault(d[group_by_field], []).append(d) + + for key in invoice_map: + invoices = invoice_map[key] + grouped_data += invoices + add_subtotal_row(grouped_data, invoices, group_by_field, key) + + # move group by column to first position + column_index = next((index for (index, d) in enumerate(columns) if d["fieldname"] == group_by_field), None) + columns.insert(0, columns.pop(column_index)) + + return columns, grouped_data + +def get_pos_entries(filters, group_by_field): + conditions = get_conditions(filters) + order_by = "p.posting_date" + select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", "" + if group_by_field == "mode_of_payment": + select_mop_field = ", sip.mode_of_payment" + from_sales_invoice_payment = ", `tabSales Invoice Payment` sip" + group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND" + order_by += ", sip.mode_of_payment" + + elif group_by_field: + order_by += ", p.{}".format(group_by_field) + + return frappe.db.sql( + """ + SELECT + p.posting_date, p.name as pos_invoice, p.pos_profile, + p.owner, p.base_grand_total as grand_total, p.base_paid_amount as paid_amount, + p.customer, p.is_return {select_mop_field} + FROM + `tabPOS Invoice` p {from_sales_invoice_payment} + WHERE + {group_by_mop_condition} + {conditions} + ORDER BY + {order_by} + """.format( + select_mop_field=select_mop_field, + from_sales_invoice_payment=from_sales_invoice_payment, + group_by_mop_condition=group_by_mop_condition, + conditions=conditions, + order_by=order_by + ), filters, as_dict=1) + +def concat_mode_of_payments(pos_entries): + mode_of_payments = get_mode_of_payments(set([d.pos_invoice for d in pos_entries])) + for entry in pos_entries: + if mode_of_payments.get(entry.pos_invoice): + entry.mode_of_payment = ", ".join(mode_of_payments.get(entry.pos_invoice, [])) + +def add_subtotal_row(data, group_invoices, group_by_field, group_by_value): + grand_total = sum([d.grand_total for d in group_invoices]) + paid_amount = sum([d.paid_amount for d in group_invoices]) + data.append({ + group_by_field: group_by_value, + "grand_total": grand_total, + "paid_amount": paid_amount, + "bold": 1 + }) + data.append({}) + +def validate_filters(filters): + if not filters.get("company"): + frappe.throw(_("{0} is mandatory").format(_("Company"))) + + if not filters.get("from_date") and not filters.get("to_date"): + frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date")))) + + if filters.from_date > filters.to_date: + frappe.throw(_("From Date must be before To Date")) + + if (filters.get("pos_profile") and filters.get("group_by") == _('POS Profile')): + frappe.throw(_("Can not filter based on POS Profile, if grouped by POS Profile")) + + if (filters.get("customer") and filters.get("group_by") == _('Customer')): + frappe.throw(_("Can not filter based on Customer, if grouped by Customer")) + + if (filters.get("owner") and filters.get("group_by") == _('Cashier')): + frappe.throw(_("Can not filter based on Cashier, if grouped by Cashier")) + + if (filters.get("mode_of_payment") and filters.get("group_by") == _('Payment Method')): + frappe.throw(_("Can not filter based on Payment Method, if grouped by Payment Method")) + +def get_conditions(filters): + conditions = "company = %(company)s AND posting_date >= %(from_date)s AND posting_date <= %(to_date)s".format( + company=filters.get("company"), + from_date=filters.get("from_date"), + to_date=filters.get("to_date")) + + if filters.get("pos_profile"): + conditions += " AND pos_profile = %(pos_profile)s".format(pos_profile=filters.get("pos_profile")) + + if filters.get("owner"): + conditions += " AND owner = %(owner)s".format(owner=filters.get("owner")) + + if filters.get("customer"): + conditions += " AND customer = %(customer)s".format(customer=filters.get("customer")) + + if filters.get("is_return"): + conditions += " AND is_return = %(is_return)s".format(is_return=filters.get("is_return")) + + if filters.get("mode_of_payment"): + conditions += """ + AND EXISTS( + SELECT name FROM `tabSales Invoice Payment` sip + WHERE parent=p.name AND ifnull(sip.mode_of_payment, '') = %(mode_of_payment)s + )""" + + return conditions + +def get_group_by_field(group_by): + group_by_field = "" + + if group_by == "POS Profile": + group_by_field = "pos_profile" + elif group_by == "Cashier": + group_by_field = "owner" + elif group_by == "Customer": + group_by_field = "customer" + elif group_by == "Payment Method": + group_by_field = "mode_of_payment" + + return group_by_field + +def get_columns(filters): + columns = [ + { + "label": _("Posting Date"), + "fieldname": "posting_date", + "fieldtype": "Date", + "width": 90 + }, + { + "label": _("POS Invoice"), + "fieldname": "pos_invoice", + "fieldtype": "Link", + "options": "POS Invoice", + "width": 120 + }, + { + "label": _("Customer"), + "fieldname": "customer", + "fieldtype": "Link", + "options": "Customer", + "width": 120 + }, + { + "label": _("POS Profile"), + "fieldname": "pos_profile", + "fieldtype": "Link", + "options": "POS Profile", + "width": 160 + }, + { + "label": _("Cashier"), + "fieldname": "owner", + "fieldtype": "Link", + "options": "User", + "width": 140 + }, + { + "label": _("Grand Total"), + "fieldname": "grand_total", + "fieldtype": "Currency", + "options": "company:currency", + "width": 120 + }, + { + "label": _("Paid Amount"), + "fieldname": "paid_amount", + "fieldtype": "Currency", + "options": "company:currency", + "width": 120 + }, + { + "label": _("Payment Method"), + "fieldname": "mode_of_payment", + "fieldtype": "Data", + "width": 150 + }, + { + "label": _("Is Return"), + "fieldname": "is_return", + "fieldtype": "Data", + "width": 80 + }, + ] + + return columns \ No newline at end of file From ebb0a6266a2e13307342857c75982a979ac86fab Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Oct 2020 17:09:09 +0530 Subject: [PATCH 25/41] fix: last purchase rate in item prices report (#23506) * fix: last purchase rate in item prices report * fix: last purchase rate in item prices report * fix: last purchase rate in item prices report * chore: fetch last purchase rate from update stock purchase invoices --- .../stock/report/item_prices/item_prices.py | 51 +++++++++---------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py index aa3ed92079..12f3297203 100644 --- a/erpnext/stock/report/item_prices/item_prices.py +++ b/erpnext/stock/report/item_prices/item_prices.py @@ -77,38 +77,33 @@ def get_price_list(): return item_rate_map def get_last_purchase_rate(): - item_last_purchase_rate_map = {} - query = """select * from (select - result.item_code, - result.base_rate - from ( - (select - po_item.item_code, - po_item.item_name, - po.transaction_date as posting_date, - po_item.base_price_list_rate, - po_item.discount_percentage, - po_item.base_rate - from `tabPurchase Order` po, `tabPurchase Order Item` po_item - where po.name = po_item.parent and po.docstatus = 1) - union - (select - pr_item.item_code, - pr_item.item_name, - pr.posting_date, - pr_item.base_price_list_rate, - pr_item.discount_percentage, - pr_item.base_rate - from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item - where pr.name = pr_item.parent and pr.docstatus = 1) - ) result - order by result.item_code asc, result.posting_date desc) result_wrapper - group by item_code""" + query = """select * from ( + (select + po_item.item_code, + po.transaction_date as posting_date, + po_item.base_rate + from `tabPurchase Order` po, `tabPurchase Order Item` po_item + where po.name = po_item.parent and po.docstatus = 1) + union + (select + pr_item.item_code, + pr.posting_date, + pr_item.base_rate + from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pr_item + where pr.name = pr_item.parent and pr.docstatus = 1) + union + (select + pi_item.item_code, + pi.posting_date, + pi_item.base_rate + from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item + where pi.name = pi_item.parent and pi.docstatus = 1 and pi.update_stock = 1) + ) result order by result.item_code asc, result.posting_date asc""" for d in frappe.db.sql(query, as_dict=1): - item_last_purchase_rate_map.setdefault(d.item_code, d.base_rate) + item_last_purchase_rate_map[d.item_code] = d.base_rate return item_last_purchase_rate_map From b332c5f06f73057091aaa95fcc17a6f3cc5320db Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Oct 2020 17:10:37 +0530 Subject: [PATCH 26/41] fix: sales invoice series gets overwritten with pos invoice series (#23479) * fix: sales invoice series gets overwritten with pos invoice series * chore: show is consolidated only if sales invoice is pos --- .../accounts/doctype/pos_profile/pos_profile.js | 9 --------- .../doctype/pos_profile/pos_profile.json | 16 ++-------------- .../accounts/doctype/pos_profile/pos_profile.py | 4 ---- .../doctype/sales_invoice/sales_invoice.json | 1 + .../doctype/sales_invoice/sales_invoice.py | 2 +- 5 files changed, 4 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js index 8ec6a53626..558e21c13a 100755 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.js +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js @@ -15,15 +15,6 @@ frappe.ui.form.on("POS Profile", "onload", function(frm) { erpnext.queries.setup_queries(frm, "Warehouse", function() { return erpnext.queries.warehouse(frm.doc); }); - - frm.call({ - method: "erpnext.accounts.doctype.pos_profile.pos_profile.get_series", - callback: function(r) { - if(!r.exc) { - set_field_options("naming_series", r.message); - } - } - }); }); frappe.ui.form.on('POS Profile', { diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index d4c1791789..999da75997 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -8,7 +8,6 @@ "field_order": [ "disabled", "section_break_2", - "naming_series", "customer", "company", "country", @@ -59,17 +58,6 @@ "fieldname": "section_break_2", "fieldtype": "Section Break" }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Series", - "no_copy": 1, - "oldfieldname": "naming_series", - "oldfieldtype": "Select", - "options": "[Select]", - "reqd": 1 - }, { "fieldname": "customer", "fieldtype": "Link", @@ -323,7 +311,7 @@ "icon": "icon-cog", "idx": 1, "links": [], - "modified": "2020-06-29 12:20:30.977272", + "modified": "2020-10-01 17:29:27.759088", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", @@ -350,4 +338,4 @@ ], "sort_field": "modified", "sort_order": "DESC" -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py index 1386b70f55..1d160a5aa7 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.py +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py @@ -109,10 +109,6 @@ def get_child_nodes(group_type, root): return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) -@frappe.whitelist() -def get_series(): - return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s" - @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index cd66f7a086..c7d791ef7a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1926,6 +1926,7 @@ }, { "default": "0", + "depends_on": "eval:(doc.is_pos && doc.is_consolidated)", "fieldname": "is_consolidated", "fieldtype": "Check", "label": "Is Consolidated", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 92e49d59da..206340b9a6 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -428,7 +428,7 @@ class SalesInvoice(SellingController): if pos.get('account_for_change_amount'): self.account_for_change_amount = pos.get('account_for_change_amount') - for fieldname in ('naming_series', 'currency', 'letter_head', 'tc_name', + for fieldname in ('currency', 'letter_head', 'tc_name', 'company', 'select_print_heading', 'write_off_account', 'taxes_and_charges', 'write_off_cost_center', 'apply_discount_on', 'cost_center'): if (not for_validate) or (for_validate and not self.get(fieldname)): From 3daad224adb02526d0276d7964054edd655aa0f5 Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 12 Oct 2020 18:31:23 +0530 Subject: [PATCH 27/41] fix: cannot merge pos invoice if validate selling price is checked (#23593) * fix: cannot merge pos invoice if validate selling price is checked * fix: validate selling price * fix: test * chore: add test * fix: error message --- .../doctype/pos_invoice/test_pos_invoice.py | 57 +++++++++++++++++-- erpnext/controllers/selling_controller.py | 22 ++++--- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 1a5920d8ab..e08af952dc 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -7,6 +7,7 @@ import frappe import unittest, copy, time from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry class TestPOSInvoice(unittest.TestCase): def test_timestamp_change(self): @@ -307,8 +308,9 @@ class TestPOSInvoice(unittest.TestCase): merge_pos_invoices() pos_inv.load_from_db() - sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice) - self.assertEqual(sales_invoice.grand_total, 3500) + rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") + self.assertEqual(rounded_total, 3500) + frappe.set_user("Administrator") def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile @@ -348,8 +350,55 @@ class TestPOSInvoice(unittest.TestCase): merge_pos_invoices() pos_inv.load_from_db() - sales_invoice = frappe.get_doc("Sales Invoice", pos_inv.consolidated_invoice) - self.assertEqual(sales_invoice.rounded_total, 840) + rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") + self.assertEqual(rounded_total, 840) + frappe.set_user("Administrator") + + def test_merging_with_validate_selling_price(self): + from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile + from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices + + if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): + frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1) + + make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=1, basic_rate=300) + frappe.db.sql("delete from `tabPOS Invoice`") + test_user, pos_profile = init_user_and_profile() + pos_inv = create_pos_invoice(rate=300, do_not_submit=1) + pos_inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 + }) + pos_inv.append('taxes', { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + 'included_in_print_rate': 1 + }) + self.assertRaises(frappe.ValidationError, pos_inv.submit) + + pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1) + pos_inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400 + }) + pos_inv2.append('taxes', { + "charge_type": "On Net Total", + "account_head": "_Test Account Service Tax - _TC", + "cost_center": "_Test Cost Center - _TC", + "description": "Service Tax", + "rate": 14, + 'included_in_print_rate': 1 + }) + pos_inv2.submit() + + merge_pos_invoices() + + pos_inv2.load_from_db() + rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total") + self.assertEqual(rounded_total, 400) + frappe.set_user("Administrator") + frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0) def create_pos_invoice(**args): args = frappe._dict(args) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 0ae175989e..58861715c2 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import cint, flt, cstr, comma_or +from frappe.utils import cint, flt, cstr, comma_or, get_link_to_form from frappe import _, throw from erpnext.stock.get_item_details import get_bin_details from erpnext.stock.utils import get_incoming_rate @@ -173,22 +173,26 @@ class SellingController(StockController): def validate_selling_price(self): def throw_message(idx, item_name, rate, ref_rate_field): - frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""") - .format(idx, item_name, ref_rate_field, rate)) + bold_net_rate = frappe.bold("net rate") + msg = (_("""Row #{}: Selling rate for item {} is lower than its {}. Selling {} should be atleast {}""") + .format(idx, frappe.bold(item_name), frappe.bold(ref_rate_field), bold_net_rate, frappe.bold(rate))) + msg += "

" + msg += (_("""You can alternatively disable selling price validation in {} to bypass this validation.""") + .format(get_link_to_form("Selling Settings", "Selling Settings"))) + frappe.throw(msg, title=_("Invalid Selling Price")) if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): return - if hasattr(self, "is_return") and self.is_return: return for it in self.get("items"): if not it.item_code: continue - + last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) - last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1) - if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom): + last_purchase_rate_in_sales_uom = last_purchase_rate * (it.conversion_factor or 1) + if flt(it.base_net_rate) < flt(last_purchase_rate_in_sales_uom): throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate") last_valuation_rate = frappe.db.sql(""" @@ -197,8 +201,8 @@ class SellingController(StockController): ORDER BY posting_date DESC, posting_time DESC, creation DESC LIMIT 1 """, (it.item_code, it.warehouse)) if last_valuation_rate: - last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1) - if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \ + last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] * (it.conversion_factor or 1) + if is_stock_item and flt(it.base_net_rate) < flt(last_valuation_rate_in_sales_uom) \ and not self.get('is_internal_customer'): throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate") From 824f48fd2336f5dc6ab36cb26d29e50140eca3ed Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 12 Oct 2020 20:08:03 +0530 Subject: [PATCH 28/41] fix: Item Link Formatter Behaviour --- erpnext/public/js/utils.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 6d73418821..ea2093eee1 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -703,9 +703,13 @@ erpnext.utils.map_current_doc = function(opts) { } frappe.form.link_formatters['Item'] = function(value, doc) { - if(doc && doc.item_name && doc.item_name !== value) { - return value? value + ': ' + doc.item_name: doc.item_name; + if (doc && value && doc.item_name && doc.item_name !== value) { + return value + ': ' + doc.item_name; + } else if (!value && doc.doctype && doc.item_name) { + // format blank value in child table + return doc.item_name; } else { + // if value is blank in report view or item code and name are the same, return as is return value; } } From 56d305b966b77ada7caa47ad78abdad2a356daa6 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Tue, 13 Oct 2020 00:07:52 +0530 Subject: [PATCH 29/41] fix: update italy fiscal year --- erpnext/public/js/setup_wizard.js | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js index 9beba6adf8..5d21190e37 100644 --- a/erpnext/public/js/setup_wizard.js +++ b/erpnext/public/js/setup_wizard.js @@ -309,7 +309,6 @@ erpnext.setup.fiscal_years = { "Hong Kong": ["04-01", "03-31"], "India": ["04-01", "03-31"], "Iran": ["06-23", "06-22"], - "Italy": ["07-01", "06-30"], "Myanmar": ["04-01", "03-31"], "New Zealand": ["04-01", "03-31"], "Pakistan": ["07-01", "06-30"], From 968f61d8bac65373d7659642e547e013a180f4b4 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Tue, 13 Oct 2020 00:25:46 +0530 Subject: [PATCH 30/41] fix: add multiple VAT rates for Italy --- .../setup_wizard/data/country_wise_tax.json | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 19318df38e..00fcaa8744 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -648,10 +648,19 @@ }, "Italy": { - "Italy Tax": { - "account_name": "VAT", - "tax_rate": 22.00 - } + "Italy VAT 22%": { + "account_name": "IVA 22%", + "tax_rate": 22.00, + "default": 1 + }, + "Italy VAT 10%":{ + "account_name": "IVA 10%", + "tax_rate": 10.00, + }, + "Italy VAT 4%":{ + "account_name": "IVA 4%", + "tax_rate": 4.00, + } }, "Ivory Coast": { From 5b790a00955a7b18fc85a0fd11829ff71d47232a Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 13 Oct 2020 02:37:55 +0530 Subject: [PATCH 31/41] fix: Convert dates to datetime.date before comparing in Holiday List --- erpnext/hr/doctype/holiday_list/holiday_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/holiday_list/holiday_list.py b/erpnext/hr/doctype/holiday_list/holiday_list.py index 76dc9429f1..6df7bc88c0 100644 --- a/erpnext/hr/doctype/holiday_list/holiday_list.py +++ b/erpnext/hr/doctype/holiday_list/holiday_list.py @@ -1,3 +1,4 @@ + # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt @@ -32,7 +33,7 @@ class HolidayList(Document): def validate_days(self): - if self.from_date > self.to_date: + if getdate(self.from_date) > getdate(self.to_date): throw(_("To Date cannot be before From Date")) for day in self.get("holidays"): From 617892db20de608b567afd825755762a6867e4e2 Mon Sep 17 00:00:00 2001 From: aakvatech <35020381+aakvatech@users.noreply.github.com> Date: Tue, 13 Oct 2020 08:18:59 +0300 Subject: [PATCH 32/41] feat: Add company and correct filter in bank statement reconciliation report filters If you have multi company scenario and many bank accounts that are no longer active, then it becomes difficult in bank statement reconciliation report to filter the account to reconcile and it also shows disabled accounts so futher confusion is created. https://github.com/frappe/erpnext/issues/23613 --- .../bank_reconciliation_statement.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js index 57fe4b05be..8f028496cd 100644 --- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js +++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js @@ -3,6 +3,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = { "filters": [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, { "fieldname":"account", "label": __("Bank Account"), @@ -12,11 +20,14 @@ frappe.query_reports["Bank Reconciliation Statement"] = { locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "", "reqd": 1, "get_query": function() { + var company = frappe.query_report.get_filter_value('company') return { "query": "erpnext.controllers.queries.get_account_list", "filters": [ ['Account', 'account_type', 'in', 'Bank, Cash'], ['Account', 'is_group', '=', 0], + ['Account', 'disabled', '=', 0], + ['Account', 'company', '=', company], ] } } @@ -34,4 +45,4 @@ frappe.query_reports["Bank Reconciliation Statement"] = { "fieldtype": "Check" }, ] -} \ No newline at end of file +} From ed779a416c8d49593ba8039a2a25bfd53e522c0d Mon Sep 17 00:00:00 2001 From: justmejust <71667386+justmejust@users.noreply.github.com> Date: Mon, 12 Oct 2020 23:16:02 -0700 Subject: [PATCH 33/41] Update ar.csv (#23490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit these changes made to the file is critical and important for Arabic user of the this software .the changes made to make appropriate and more correct meaning fro example in the Blanket Orders from Costumers the previous translation meaning امر بطانية) ) which mean you have an order to get ta Blanket which used when you want to sleep. so i decided to make this changes and will continue update this translation file in the future. --- erpnext/translations/ar.csv | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv index f586a44a46..01a135d60b 100644 --- a/erpnext/translations/ar.csv +++ b/erpnext/translations/ar.csv @@ -316,12 +316,12 @@ Authorized Signatory,المخول بالتوقيع, Auto Material Requests Generated,إنشاء طلب مواد تلقائي, Auto Repeat,تكرار تلقائي, Auto repeat document updated,تكرار تلقائي للمستندات المحدثة, -Automotive,سيارات, +Automotive,سيارات,متحرك بطاقة ذاتية Available,متاح, -Available Leaves,المغادارت المتوفرة, +Available Leaves,المغادارت والاجازات المتاحة, Available Qty,الكمية المتاحة, Available Selling,المبيعات المتاحة, -Available for use date is required,مطلوب متاح لتاريخ الاستخدام, +Available for use date is required,مطلوب تاريخ متاح للاستخدام, Available slots,الفتحات المتاحة, Available {0},متاح {0}, Available-for-use Date should be after purchase date,يجب أن يكون التاريخ متاحًا بعد تاريخ الشراء, @@ -331,16 +331,16 @@ Avg Daily Outgoing,متوسط الصادرات اليومية, Avg. Buying Price List Rate,متوسط قائمة أسعار الشراء, Avg. Selling Price List Rate,متوسط قائمة أسعار البيع, Avg. Selling Rate,متوسط معدل البيع, -BOM,فاتورة المواد, -BOM Browser,BOM متصفح, -BOM No,رقم قائمة المواد, -BOM Rate,سعر قائمة المواد, -BOM Stock Report,تقرير مخزون فاتورة المواد, +BOM,قائمة مكونات المواد, +BOM Browser,قائمة مكونات المواد متصفح, +BOM No,رقم قائمة مكونات المواد, +BOM Rate,سعر او معدل قائمة مكونات المواد, +BOM Stock Report,تقرير مخزون قائمة مكونات المواد, BOM and Manufacturing Quantity are required,مطلوب، قائمة مكونات المواد و كمية التصنيع, -BOM does not contain any stock item,فاتورة الموارد لا تحتوي على أي صنف مخزون, -BOM {0} does not belong to Item {1},قائمة المواد {0} لا تنتمي إلى الصنف {1}, -BOM {0} must be active,فاتورة المواد {0} يجب أن تكون نشطة\n
\nBOM {0} must be active, -BOM {0} must be submitted,فاتورة المواد {0} يجب أن تكون مسجلة\n
\nBOM {0} must be submitted, +BOM does not contain any stock item,قائمة مكونات المواد لا تحتوي على أي صنف مخزون, +BOM {0} does not belong to Item {1},قائمة مكونات المواد {0} لا تنتمي إلى الصنف {1}, +BOM {0} must be active,قائمة مكونات المواد {0} يجب أن تكون نشطة\n
\nBOM {0} must be active, +BOM {0} must be submitted,قائمة مكونات المواد {0} يجب أن تكون مسجلة\n
\nBOM {0} must be submitted, Balance,الموازنة, Balance (Dr - Cr),الرصيد (مدين - دائن), Balance ({0}),الرصيد ({0}), @@ -389,23 +389,23 @@ Bill Date,تاريخ الفاتورة, Bill No,رقم الفاتورة, Bill of Materials,فاتورة المواد, Bill of Materials (BOM),قوائم المواد, -Billable Hours,ساعات للفوترة, -Billed,توصف, +Billable Hours,ساعات قابلة للفوترة, +Billed,تمت الفوترة, Billed Amount,القيمة المقدم فاتورة بها, Billing,الفواتير, -Billing Address,عنوان تقديم الفواتير, +Billing Address,العنوان الذي ترسل به الفواتير, Billing Address is same as Shipping Address,عنوان الفواتير هو نفس عنوان الشحن, Billing Amount,قيمة الفواتير, -Billing Status,الحالة الفواتير, +Billing Status,حالة الفواتير, Billing currency must be equal to either default company's currency or party account currency,يجب أن تكون عملة الفوترة مساوية لعملة الشركة الافتراضية أو عملة حساب الطرف, Bills raised by Suppliers.,فواتير حولت من قبل الموردين., Bills raised to Customers.,فواتير حولت للزبائن., Biotechnology,التكنولوجيا الحيوية, Birthday Reminder,تذكير عيد ميلاد, Black,أسود, -Blanket Orders from Costumers.,أوامر بطانية من العملاء., +Blanket Orders from Costumers.,أوامر شراء شاملة من العملاء., Block Invoice,حظر الفاتورة, -Boms,قوائم المواد, +Boms,قوائم مكونات المواد, Bonus Payment Date cannot be a past date,لا يمكن أن يكون تاريخ الدفع المكافأ تاريخًا سابقًا, Both Trial Period Start Date and Trial Period End Date must be set,يجب تعيين كل من تاريخ بدء الفترة التجريبية وتاريخ انتهاء الفترة التجريبية, Both Warehouse must belong to same Company,يجب أن ينتمي المستودع إلى نفس الشركة\n
\nBoth Warehouse must belong to same Company, @@ -504,9 +504,9 @@ Cash In Hand,النقدية الحاضرة, Cash or Bank Account is mandatory for making payment entry,الحساب النقدي أو البنكي مطلوب لعمل مدخل بيع
Cash or Bank Account is mandatory for making payment entry, Cashier Closing,إغلاق أمين الصندوق, Casual Leave,أجازة عادية, -Category,فئة, +Category,فئة,صنف Category Name,اسم التصنيف, -Caution,الحذر, +Caution,الحذر,تحذير Central Tax,الضريبة المركزية, Certification,شهادة, Cess,سيس, @@ -627,7 +627,7 @@ Cost Center with existing transactions can not be converted to ledger,مركز Cost Centers,مراكز التكلفة, Cost Updated,تم تحديث التكلفة\n
\nCost Updated, Cost as on,التكلفة كما في, -Cost of Delivered Items,تكلفة البنود المسلمة, +Cost of Delivered Items,تكلفة السلع والمواد المسلمة, Cost of Goods Sold,تكلفة البضاعة المباعة, Cost of Issued Items,تكلفة المواد المصروفة, Cost of New Purchase,تكلفة الشراء الجديد, @@ -1300,7 +1300,7 @@ Insurance Start date should be less than Insurance End date,يجب أن يكون Integrated Tax,ضريبة متكاملة, Inter-State Supplies,اللوازم بين الدول, Interest Amount,مبلغ الفائدة, -Interests,الإهتمامات, +Interests,الإهتمامات او الفوائد, Intern,المتدرب, Internet Publishing,نشر على شبكة الإنترنت, Intra-State Supplies,اللوازم داخل الدولة, @@ -1421,13 +1421,13 @@ Lab Test UOM,اختبار مختبر أوم, Lab Tests and Vital Signs,اختبارات المختبر وعلامات حيوية, Lab result datetime cannot be before testing datetime,لا يمكن أن يكون تاريخ نتيجة المختبر سابقا لتاريخ الفحص, Lab testing datetime cannot be before collection datetime,لا يمكن أن يكون وقت اختبار المختبر قبل تاريخ جمع البيانات, -Label,ملصق, +Label,ملصق,'طابع Laboratory,مختبر, Language Name,اسم اللغة, Large,كبير, Last Communication,آخر الاتصالات, Last Communication Date,تاريخ الاتصال الأخير, -Last Name,اسم العائلة, +Last Name,اسم العائلة او اللقب, Last Order Amount,قيمة آخر طلب, Last Order Date,تاريخ أخر أمر بيع, Last Purchase Price,سعر الشراء الأخير, From 3245eaaeb0fc39067a5162215c2bd25a670dd2a7 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 13 Oct 2020 15:35:10 +0530 Subject: [PATCH 34/41] fix: displayings mandatory address field in Lead form --- erpnext/crm/doctype/lead/lead.json | 10 +++++++--- erpnext/crm/doctype/lead/lead.py | 14 +++----------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index f5f8b4efb3..2df1793fdb 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -241,6 +241,7 @@ }, { "depends_on": "eval: doc.__islocal", + "description": "Home, Work, etc.", "fieldname": "address_title", "fieldtype": "Data", "label": "Address Title" @@ -249,7 +250,8 @@ "depends_on": "eval: doc.__islocal", "fieldname": "address_line1", "fieldtype": "Data", - "label": "Address Line 1" + "label": "Address Line 1", + "mandatory_depends_on": "eval: doc.address_title && doc.address_type" }, { "depends_on": "eval: doc.__islocal", @@ -261,7 +263,8 @@ "depends_on": "eval: doc.__islocal", "fieldname": "city", "fieldtype": "Data", - "label": "City/Town" + "label": "City/Town", + "mandatory_depends_on": "eval: doc.address_title && doc.address_type" }, { "depends_on": "eval: doc.__islocal", @@ -280,6 +283,7 @@ "fieldname": "country", "fieldtype": "Link", "label": "Country", + "mandatory_depends_on": "eval: doc.address_title && doc.address_type", "options": "Country" }, { @@ -449,7 +453,7 @@ "idx": 5, "image_field": "image", "links": [], - "modified": "2020-06-18 14:39:41.835416", + "modified": "2020-10-13 15:24:00.094811", "modified_by": "Administrator", "module": "CRM", "name": "Lead", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 99fa703fee..282f30e9cd 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -22,7 +22,8 @@ class Lead(SellingController): load_address_and_contact(self) def before_insert(self): - self.address_doc = self.create_address() + if self.address_type and self.address_title and self.address_line1 and self.city and self.country: + self.address_doc = self.create_address() self.contact_doc = self.create_contact() def after_insert(self): @@ -133,15 +134,6 @@ class Lead(SellingController): # skipping country since the system auto-sets it from system defaults address = frappe.new_doc("Address") - mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ] - - if not all([self.get(field) for field in mandatory_fields]): - frappe.msgprint(_('Missing mandatory fields in address. \ - {0} to create address' ).format(" Click here "), - alert=True, indicator='yellow') - return - address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) address.update({info_field: self.get(info_field) for info_field in info_fields}) address.insert() @@ -190,7 +182,7 @@ class Lead(SellingController): def update_links(self): # update address links - if self.address_doc: + if hasattr(self, 'address_doc'): self.address_doc.append("links", { "link_doctype": "Lead", "link_name": self.name, From c5e139a5421bced4b2607e68126c635848c5b8ad Mon Sep 17 00:00:00 2001 From: Marica Date: Tue, 13 Oct 2020 16:53:10 +0530 Subject: [PATCH 35/41] fix: Travis (#23606) * fix: Rename `make_supplier_quotation` to `make_supplier_quotation_from_rfq` in missing places * fix: Item Barcode and Test * fix: Over Receipt on subcontracting test * fix: POS Invoice and Loyalty Program Tests * fix: POS serialized item test and subcontracting exploded items in PO * fix: Subcontracting test considering sourced_by_supplier items * fix: Make only one Additonal Salary List with overwrite set --- .../loyalty_program/test_loyalty_program.py | 157 +++++++++--------- .../test_pos_closing_entry.py | 4 +- .../doctype/pos_invoice/pos_invoice.py | 3 +- .../doctype/pos_invoice/test_pos_invoice.py | 35 ++-- .../purchase_order/test_purchase_order.py | 6 +- .../test_request_for_quotation.py | 10 +- erpnext/demo/user/purchase.py | 4 +- erpnext/manufacturing/doctype/bom/test_bom.py | 1 - .../doctype/salary_slip/test_salary_slip.py | 3 +- erpnext/stock/doctype/item/item.py | 3 +- erpnext/stock/doctype/item/test_item.py | 2 +- .../purchase_receipt/test_purchase_receipt.py | 15 +- 12 files changed, 118 insertions(+), 125 deletions(-) diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index ee73ccaa61..5278d8b241 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -195,88 +195,91 @@ def create_sales_invoice_record(qty=1): def create_records(): # create a new loyalty Account - if frappe.db.exists("Account", "Loyalty - _TC"): - return - - frappe.get_doc({ - "doctype": "Account", - "account_name": "Loyalty", - "parent_account": "Direct Expenses - _TC", - "company": "_Test Company", - "is_group": 0, - "account_type": "Expense Account", - }).insert() + if not frappe.db.exists("Account", "Loyalty - _TC"): + frappe.get_doc({ + "doctype": "Account", + "account_name": "Loyalty", + "parent_account": "Direct Expenses - _TC", + "company": "_Test Company", + "is_group": 0, + "account_type": "Expense Account", + }).insert() # create a new loyalty program Single tier - frappe.get_doc({ - "doctype": "Loyalty Program", - "loyalty_program_name": "Test Single Loyalty", - "auto_opt_in": 1, - "from_date": today(), - "loyalty_program_type": "Single Tier Program", - "conversion_factor": 1, - "expiry_duration": 10, - "company": "_Test Company", - "cost_center": "Main - _TC", - "expense_account": "Loyalty - _TC", - "collection_rules": [{ - 'tier_name': 'Silver', - 'collection_factor': 1000, - 'min_spent': 1000 - }] - }).insert() - - # create a new customer - frappe.get_doc({ - "customer_group": "_Test Customer Group", - "customer_name": "Test Loyalty Customer", - "customer_type": "Individual", - "doctype": "Customer", - "territory": "_Test Territory" - }).insert() - - # create a new loyalty program Multiple tier - frappe.get_doc({ - "doctype": "Loyalty Program", - "loyalty_program_name": "Test Multiple Loyalty", - "auto_opt_in": 1, - "from_date": today(), - "loyalty_program_type": "Multiple Tier Program", - "conversion_factor": 1, - "expiry_duration": 10, - "company": "_Test Company", - "cost_center": "Main - _TC", - "expense_account": "Loyalty - _TC", - "collection_rules": [ - { + if not frappe.db.exists("Loyalty Program","Test Single Loyalty"): + frappe.get_doc({ + "doctype": "Loyalty Program", + "loyalty_program_name": "Test Single Loyalty", + "auto_opt_in": 1, + "from_date": today(), + "loyalty_program_type": "Single Tier Program", + "conversion_factor": 1, + "expiry_duration": 10, + "company": "_Test Company", + "cost_center": "Main - _TC", + "expense_account": "Loyalty - _TC", + "collection_rules": [{ 'tier_name': 'Silver', 'collection_factor': 1000, - 'min_spent': 10000 - }, - { - 'tier_name': 'Gold', - 'collection_factor': 1000, - 'min_spent': 19000 - } - ] - }).insert() + 'min_spent': 1000 + }] + }).insert() + + # create a new customer + if not frappe.db.exists("Customer","Test Loyalty Customer"): + frappe.get_doc({ + "customer_group": "_Test Customer Group", + "customer_name": "Test Loyalty Customer", + "customer_type": "Individual", + "doctype": "Customer", + "territory": "_Test Territory" + }).insert() + + # create a new loyalty program Multiple tier + if not frappe.db.exists("Loyalty Program","Test Multiple Loyalty"): + frappe.get_doc({ + "doctype": "Loyalty Program", + "loyalty_program_name": "Test Multiple Loyalty", + "auto_opt_in": 1, + "from_date": today(), + "loyalty_program_type": "Multiple Tier Program", + "conversion_factor": 1, + "expiry_duration": 10, + "company": "_Test Company", + "cost_center": "Main - _TC", + "expense_account": "Loyalty - _TC", + "collection_rules": [ + { + 'tier_name': 'Silver', + 'collection_factor': 1000, + 'min_spent': 10000 + }, + { + 'tier_name': 'Gold', + 'collection_factor': 1000, + 'min_spent': 19000 + } + ] + }).insert() # create an item - item = frappe.get_doc({ - "doctype": "Item", - "item_code": "Loyal Item", - "item_name": "Loyal Item", - "item_group": "All Item Groups", - "company": "_Test Company", - "is_stock_item": 1, - "opening_stock": 100, - "valuation_rate": 10000, - }).insert() + if not frappe.db.exists("Item", "Loyal Item"): + frappe.get_doc({ + "doctype": "Item", + "item_code": "Loyal Item", + "item_name": "Loyal Item", + "item_group": "All Item Groups", + "company": "_Test Company", + "is_stock_item": 1, + "opening_stock": 100, + "valuation_rate": 10000, + }).insert() # create item price - frappe.get_doc({ - "doctype": "Item Price", - "price_list": "Standard Selling", - "item_code": item.item_code, - "price_list_rate": 10000 - }).insert() + if not frappe.db.exists("Item Price", {"price_list": "Standard Selling", "item_code": "Loyal Item"}): + frappe.get_doc({ + "doctype": "Item Price", + "price_list": "Standard Selling", + "item_code": "Loyal Item", + "price_list_rate": 10000 + }).insert() diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py index aa6a388df5..8de54d5bde 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py +++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py @@ -45,7 +45,7 @@ class TestPOSClosingEntry(unittest.TestCase): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") -def init_user_and_profile(): +def init_user_and_profile(**args): user = 'test@example.com' test_user = frappe.get_doc('User', user) @@ -53,7 +53,7 @@ def init_user_and_profile(): test_user.add_roles(*roles) frappe.set_user(user) - pos_profile = make_pos_profile() + pos_profile = make_pos_profile(**args) pos_profile.append('applicable_for_users', { 'default': 1, 'user': user diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index ba68df7673..1669ca4094 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -139,7 +139,8 @@ class POSInvoice(SalesInvoice): frappe.throw(_("At least one mode of payment is required for POS invoice.")) def validate_change_account(self): - if frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company: + if self.change_amount and self.account_for_change_amount and \ + frappe.db.get_value("Account", self.account_for_change_amount, "company") != self.company: frappe.throw(_("The selected change account {} doesn't belongs to Company {}.").format(self.account_for_change_amount, self.company)) def validate_change_amount(self): diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index e08af952dc..c179360b01 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -8,6 +8,7 @@ import unittest, copy, time from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt class TestPOSInvoice(unittest.TestCase): def test_timestamp_change(self): @@ -222,29 +223,29 @@ class TestPOSInvoice(unittest.TestCase): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos - se = make_serialized_item(company='_Test Company with perpetual inventory', - target_warehouse="Stores - TCP1", cost_center='Main - TCP1', expense_account='Cost of Goods Sold - TCP1') + se = make_serialized_item(company='_Test Company', + target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC') serial_nos = get_serial_nos(se.get("items")[0].serial_no) - pos = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', - account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', - expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', + pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', item=se.get("items")[0].item_code, rate=1000, do_not_save=1) pos.get("items")[0].serial_no = serial_nos[0] - pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) + pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) pos.insert() pos.submit() - pos2 = create_pos_invoice(company='_Test Company with perpetual inventory', debit_to='Debtors - TCP1', - account_for_change_amount='Cash - TCP1', warehouse='Stores - TCP1', income_account='Sales - TCP1', - expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1', + pos2 = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC', + account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC', + expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC', item=se.get("items")[0].item_code, rate=1000, do_not_save=1) pos2.get("items")[0].serial_no = serial_nos[0] - pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 1000}) + pos2.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 1000}) self.assertRaises(frappe.ValidationError, pos2.insert) @@ -286,7 +287,7 @@ class TestPOSInvoice(unittest.TestCase): after_redeem_lp_details = get_loyalty_program_details_with_points(inv.customer, company=inv.company, loyalty_program=inv.loyalty_program) self.assertEqual(after_redeem_lp_details.loyalty_points, 9) - + def test_merging_into_sales_invoice_with_discount(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices @@ -295,7 +296,7 @@ class TestPOSInvoice(unittest.TestCase): test_user, pos_profile = init_user_and_profile() pos_inv = create_pos_invoice(rate=300, additional_discount_percentage=10, do_not_submit=1) pos_inv.append('payments', { - 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300 + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 270 }) pos_inv.submit() @@ -309,9 +310,9 @@ class TestPOSInvoice(unittest.TestCase): pos_inv.load_from_db() rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total") - self.assertEqual(rounded_total, 3500) + self.assertEqual(rounded_total, 3470) frappe.set_user("Administrator") - + def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self): from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import merge_pos_invoices @@ -361,7 +362,7 @@ class TestPOSInvoice(unittest.TestCase): if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"): frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1) - make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=1, basic_rate=300) + make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300) frappe.db.sql("delete from `tabPOS Invoice`") test_user, pos_profile = init_user_and_profile() pos_inv = create_pos_invoice(rate=300, do_not_submit=1) @@ -413,8 +414,6 @@ def create_pos_invoice(**args): pos_inv.is_pos = 1 pos_inv.pos_profile = args.pos_profile or pos_profile.name - pos_inv.set_missing_values() - if args.posting_date: pos_inv.set_posting_time = 1 pos_inv.posting_date = args.posting_date or frappe.utils.nowdate() @@ -428,6 +427,8 @@ def create_pos_invoice(**args): pos_inv.conversion_rate = args.conversion_rate or 1 pos_inv.account_for_change_amount = args.account_for_change_amount or "Cash - _TC" + pos_inv.set_missing_values() + pos_inv.append("items", { "item_code": args.item or args.item_code or "_Test Item", "warehouse": args.warehouse or "_Test Warehouse - _TC", diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 158799ce63..7c8ae6cfb8 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -651,12 +651,12 @@ class TestPurchaseOrder(unittest.TestCase): make_subcontracted_item(item_code) po = create_purchase_order(item_code=item_code, qty=1, - is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") + is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=1) name = frappe.db.get_value('BOM', {'item': item_code}, 'name') bom = frappe.get_doc('BOM', name) - exploded_items = sorted([d.item_code for d in bom.exploded_items]) + exploded_items = sorted([d.item_code for d in bom.exploded_items if not d.get('sourced_by_supplier')]) supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) self.assertEquals(exploded_items, supplied_items) @@ -664,7 +664,7 @@ class TestPurchaseOrder(unittest.TestCase): is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC", include_exploded_items=0) supplied_items1 = sorted([d.rm_item_code for d in po1.supplied_items]) - bom_items = sorted([d.item_code for d in bom.items]) + bom_items = sorted([d.item_code for d in bom.items if not d.get('sourced_by_supplier')]) self.assertEquals(supplied_items1, bom_items) diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index 019cefc0bd..ea38129a70 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -9,7 +9,7 @@ import frappe from frappe.utils import nowdate from erpnext.stock.doctype.item.test_item import make_item from erpnext.templates.pages.rfq import check_supplier_has_docname_access -from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation +from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation_from_rfq from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq @@ -22,7 +22,7 @@ class TestRequestforQuotation(unittest.TestCase): self.assertEqual(rfq.get('suppliers')[1].quote_status, 'Pending') # Submit the first supplier quotation - sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) + sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier) sq.submit() # No Quote first supplier quotation @@ -37,10 +37,10 @@ class TestRequestforQuotation(unittest.TestCase): def test_make_supplier_quotation(self): rfq = make_request_for_quotation() - sq = make_supplier_quotation(rfq.name, rfq.get('suppliers')[0].supplier) + sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[0].supplier) sq.submit() - sq1 = make_supplier_quotation(rfq.name, rfq.get('suppliers')[1].supplier) + sq1 = make_supplier_quotation_from_rfq(rfq.name, for_supplier=rfq.get('suppliers')[1].supplier) sq1.submit() self.assertEqual(sq.supplier, rfq.get('suppliers')[0].supplier) @@ -62,7 +62,7 @@ class TestRequestforQuotation(unittest.TestCase): rfq = make_request_for_quotation(supplier_data=supplier_wt_appos) - sq = make_supplier_quotation(rfq.name, supplier_wt_appos[0].get("supplier")) + sq = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier_wt_appos[0].get("supplier")) sq.submit() frappe.form_dict = frappe.local("form_dict") diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py index 86757dfaaa..b7aca79cf9 100644 --- a/erpnext/demo/user/purchase.py +++ b/erpnext/demo/user/purchase.py @@ -11,7 +11,7 @@ from erpnext.accounts.party import get_party_account_currency from erpnext.exceptions import InvalidCurrency from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation from erpnext.buying.doctype.request_for_quotation.request_for_quotation import \ - make_supplier_quotation as make_quotation_from_rfq + make_supplier_quotation_from_rfq def work(): frappe.set_user(frappe.db.get_global('demo_purchase_user')) @@ -44,7 +44,7 @@ def work(): rfq = frappe.get_doc('Request for Quotation', rfq.name) for supplier in rfq.suppliers: - supplier_quotation = make_quotation_from_rfq(rfq.name, supplier.supplier) + supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier) supplier_quotation.save() supplier_quotation.submit() diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index eface4d624..3239478872 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -207,7 +207,6 @@ class TestBOM(unittest.TestCase): supplied_items = sorted([d.rm_item_code for d in po.supplied_items]) self.assertEquals(bom_items, supplied_items) - def get_default_bom(item_code="_Test FG Item 2"): return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py index 37cd89a734..7fe4165362 100644 --- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py @@ -347,8 +347,7 @@ class TestSalarySlip(unittest.TestCase): # create additional salary of 150000 frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""", (employee)) - data["additional-1"] = create_additional_salary(employee, payroll_period, 50000) - data["additional-2"] = create_additional_salary(employee, payroll_period, 100000) + data["additional-1"] = create_additional_salary(employee, payroll_period, 150000) data["deducted_dates"] = create_salary_slips_for_payroll_period(employee, salary_structure.name, payroll_period) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index bfdddbbb60..a094e6c9ed 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -577,8 +577,9 @@ class Item(WebsiteGenerator): # if barcode is getting updated , the row name has to reset. # Delete previous old row doc and re-enter row as if new to reset name in db. item_barcode.set("__islocal", True) + item_barcode_entry_name = item_barcode.name item_barcode.name = None - frappe.delete_doc("Item Barcode", item_barcode.name) + frappe.delete_doc("Item Barcode", item_barcode_entry_name) def validate_warehouse_for_reorder(self): '''Validate Reorder level table for duplicate and conditional mandatory''' diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index cbd5e33b14..109731abb5 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -471,7 +471,7 @@ class TestItem(unittest.TestCase): item_doc = frappe.get_doc('Item', item_code) new_barcode = item_doc.append('barcodes') new_barcode.update(barcode_properties_list[0]) - self.assertRaises(frappe.DuplicateEntryError, item_doc.save) + self.assertRaises(frappe.UniqueValidationError, item_doc.save) # Add invalid barcode - should cause InvalidBarcode item_doc = frappe.get_doc('Item', item_code) diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 1e7153e774..74a06d8585 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -180,18 +180,15 @@ class TestPurchaseReceipt(unittest.TestCase): is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC") #stock raw materials in a warehouse before transfer - make_stock_entry(target="_Test Warehouse - _TC", - item_code="_Test Item Home Desktop 100", qty=1, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", item_code = "Test Extra Item 1", qty=1, basic_rate=100) make_stock_entry(target="_Test Warehouse - _TC", - item_code = "_Test Item", qty=1, basic_rate=100) - + item_code = "_Test FG Item", qty=1, basic_rate=100) rm_items = [ { "item_code": item_code, "rm_item_code": po.supplied_items[0].rm_item_code, - "item_name": "_Test Item", + "item_name": "_Test FG Item", "qty": po.supplied_items[0].required_qty, "warehouse": "_Test Warehouse - _TC", "stock_uom": "Nos" @@ -203,14 +200,6 @@ class TestPurchaseReceipt(unittest.TestCase): "qty": po.supplied_items[1].required_qty, "warehouse": "_Test Warehouse - _TC", "stock_uom": "Nos" - }, - { - "item_code": item_code, - "rm_item_code": po.supplied_items[2].rm_item_code, - "item_name": "_Test Item Home Desktop 100", - "qty": po.supplied_items[2].required_qty, - "warehouse": "_Test Warehouse - _TC", - "stock_uom": "Nos" } ] rm_item_string = json.dumps(rm_items) From 9aa3e7a37d4e89725a2f28738c078059862d3ee5 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 13 Oct 2020 13:35:33 +0200 Subject: [PATCH 36/41] feat: address template for luxembourg --- erpnext/regional/address_template/templates/luxembourg.html | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 erpnext/regional/address_template/templates/luxembourg.html diff --git a/erpnext/regional/address_template/templates/luxembourg.html b/erpnext/regional/address_template/templates/luxembourg.html new file mode 100644 index 0000000000..75075e7f1a --- /dev/null +++ b/erpnext/regional/address_template/templates/luxembourg.html @@ -0,0 +1,4 @@ +{% if address_line1 %}{{ address_line1 }}
{% endif -%} +{% if address_line2 %}{{ address_line2 }}
{% endif -%} +{% if pincode %}L-{{ pincode }}{% endif -%}{% if city %} {{ city }}{% endif %}
+{% if country %}{{ country | upper }}{% endif %} From 7db38058f3b568c7bf4b2e4c3d1cbfeb7b5f09f1 Mon Sep 17 00:00:00 2001 From: Anupam Date: Tue, 13 Oct 2020 17:33:58 +0530 Subject: [PATCH 37/41] fix: displayings mandatory address field in Lead form --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 282f30e9cd..1439adb015 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -22,7 +22,7 @@ class Lead(SellingController): load_address_and_contact(self) def before_insert(self): - if self.address_type and self.address_title and self.address_line1 and self.city and self.country: + if self.address_title and self.address_type: self.address_doc = self.create_address() self.contact_doc = self.create_contact() From c359d3528f073eaddc246bc4e1ce54cca393cf93 Mon Sep 17 00:00:00 2001 From: Afshan Date: Tue, 13 Oct 2020 18:54:54 +0530 Subject: [PATCH 38/41] fix: setting user precision instead of default --- erpnext/projects/report/billing_summary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index b808268d1b..0b44e9d112 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import time_diff_in_hours, flt +from frappe.model.meta import get_field_precision def get_columns(): return [ @@ -136,6 +137,7 @@ def get_timesheet_details(filters, timesheet_list): return timesheet_details_map def get_billable_and_total_duration(activity, start_time, end_time): + precision = frappe.get_precision("Timesheet Detail", "hours") activity_duration = time_diff_in_hours(end_time, start_time) billing_duration = 0.0 if activity.billable: @@ -143,4 +145,4 @@ def get_billable_and_total_duration(activity, start_time, end_time): if activity_duration != activity.billing_hours: billing_duration = activity_duration * activity.billing_hours / activity.hours - return flt(activity_duration, 2), flt(billing_duration, 2) \ No newline at end of file + return flt(activity_duration, precision), flt(billing_duration, precision) \ No newline at end of file From 1b329aaa7b08a906cb97dba697fc84a352c4cb82 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Tue, 13 Oct 2020 19:10:09 +0530 Subject: [PATCH 39/41] fix: json format fixes --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 00fcaa8744..2e45b6927e 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -655,11 +655,11 @@ }, "Italy VAT 10%":{ "account_name": "IVA 10%", - "tax_rate": 10.00, + "tax_rate": 10.00 }, "Italy VAT 4%":{ "account_name": "IVA 4%", - "tax_rate": 4.00, + "tax_rate": 4.00 } }, From 3eec4e61ab8255be6426ed9ce807c3f22896461c Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Tue, 13 Oct 2020 19:23:48 +0530 Subject: [PATCH 40/41] fix: GST rates for Australia --- erpnext/setup/setup_wizard/data/country_wise_tax.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index 2e45b6927e..beddaeed79 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -60,14 +60,10 @@ }, "Australia": { - "Australia GST1": { + "Australia GST": { "account_name": "GST 10%", "tax_rate": 10.00, "default": 1 - }, - "Australia GST 2%": { - "account_name": "GST 2%", - "tax_rate": 2 } }, From 93da52941e54f15b05959d84e06f2e10c1c30c6b Mon Sep 17 00:00:00 2001 From: Sun Howwrongbum Date: Tue, 13 Oct 2020 21:23:26 +0530 Subject: [PATCH 41/41] fix: consider rounded_total in returns (#23609) Co-authored-by: Marica --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 206340b9a6..801e688deb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -572,7 +572,8 @@ class SalesInvoice(SellingController): def validate_pos(self): if self.is_return: - if flt(self.paid_amount) + flt(self.write_off_amount) - flt(self.grand_total) > \ + invoice_total = self.rounded_total or self.grand_total + if flt(self.paid_amount) + flt(self.write_off_amount) - flt(invoice_total) > \ 1.0/(10.0**(self.precision("grand_total") + 1.0)): frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))