From 075f6ea0d56fe6a41fb5c5e50679390994796903 Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 4 Nov 2019 19:41:04 +0530 Subject: [PATCH 001/389] feat: Provision to fetch items from BOM in Stock Entry Items are populated in child table and amounts are calculated Warehouse Fields in popup toggle based on Stock Entry Type --- erpnext/manufacturing/doctype/bom/bom.py | 1 + .../stock/doctype/stock_entry/stock_entry.js | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 225ae29429..d02dd59d13 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -599,6 +599,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite item.image, bom.project, item.stock_uom, + item.item_group, item.allow_alternative_item, item_default.default_warehouse, item_default.expense_account as expense_account, diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 0b023024f9..cee09e78c6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -181,6 +181,12 @@ frappe.ui.form.on('Stock Entry', { } } + if (frm.doc.docstatus === 0) { + frm.add_custom_button(__('Bill of Materials'), function(){ + frm.events.get_items_from_bom(frm); + }, __("Get items from")); + } + if (frm.doc.docstatus===0) { frm.add_custom_button(__('Purchase Invoice'), function() { erpnext.utils.map_current_doc({ @@ -387,6 +393,73 @@ frappe.ui.form.on('Stock Entry', { } }, + get_items_from_bom: function(frm) { + let filters = function(){ + return {filters: { docstatus:1 }}; + } + + let fields = [ + {"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"), + options:"BOM", reqd: 1, get_query: filters()}, + {"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"), + options:"Warehouse"}, + {"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"), + options:"Warehouse"}, + {"fieldname":"qty", "fieldtype":"Float", "label":__("Quantity"), + reqd: 1, "default": 1}, + {"fieldname":"fetch_exploded", "fieldtype":"Check", + "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, + {"fieldname":"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} + ] + if (frm.doc.stock_entry_type == 'Material Issue'){ + fields.splice(2,1); + } + else if(frm.doc.stock_entry_type == 'Material Receipt'){ + fields.splice(1,1); + } + + let d = new frappe.ui.Dialog({ + title: __("Get Items from BOM"), + fields: fields + }); + d.get_input("fetch").on("click", function() { + let values = d.get_values(); + if(!values) return; + values["company"] = frm.doc.company; + if(!frm.doc.company) frappe.throw(__("Company field is required")); + frappe.call({ + method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", + args: values, + callback: function(r) { + if (!r.message) { + frappe.throw(__("BOM does not contain any stock item")); + } else { + erpnext.utils.remove_empty_first_row(frm, "items"); + $.each(r.message, function(i, item) { + let d = frappe.model.add_child(cur_frm.doc, "Stock Entry Detail", "items"); + d.item_code = item.item_code; + d.item_name = item.item_name; + d.item_group = item.item_group; + d.s_warehouse = values.source_warehouse; + d.t_warehouse = values.target_warehouse; + d.uom = item.stock_uom; + d.stock_uom = item.stock_uom; + d.conversion_factor = 1; + d.qty = item.qty; + d.expense_account = item.expense_account; + d.project = item.project; + frm.events.set_basic_rate(frm, d.doctype, d.name); + }); + } + d.hide(); + refresh_field("items"); + } + }); + + }); + d.show(); + }, + calculate_basic_amount: function(frm, item) { item.basic_amount = flt(flt(item.transfer_qty) * flt(item.basic_rate), precision("basic_amount", item)); From 5a31e228923d3ec2fe387e3e83eca3920cee6da4 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 6 Nov 2019 17:03:12 +0530 Subject: [PATCH 002/389] chore: frappe.rename_doc update --- .../clinical_procedure_template.py | 3 ++- .../healthcare_service_unit_type.py | 3 ++- .../doctype/lab_test_template/lab_test_template.py | 5 +++-- erpnext/patches/v9_2/rename_translated_domains_in_en.py | 5 +++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py index 141329b3db..a8a9037e4a 100644 --- a/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py +++ b/erpnext/healthcare/doctype/clinical_procedure_template/clinical_procedure_template.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe, json from frappe import _ from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc from frappe.utils import nowdate class ClinicalProcedureTemplate(Document): @@ -115,7 +116,7 @@ def change_item_code_from_template(item_code, doc): "item_code": item_code})): frappe.throw(_("Code {0} already exist").format(item_code)) else: - frappe.rename_doc("Item", doc.item_code, item_code, ignore_permissions = True) + rename_doc("Item", doc.item_code, item_code, ignore_permissions=True) frappe.db.set_value("Clinical Procedure Template", doc.name, "item_code", item_code) return diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py index 650499454b..43f01c86b9 100644 --- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py +++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc class HealthcareServiceUnitType(Document): def validate(self): @@ -107,7 +108,7 @@ def change_item_code(item, item_code, doc_name): if(item_exist): frappe.throw(_("Code {0} already exist").format(item_code)) else: - frappe.rename_doc("Item", item, item_code, ignore_permissions = True) + rename_doc("Item", item, item_code, ignore_permissions=True) frappe.db.set_value("Healthcare Service Unit Type", doc_name, "item_code", item_code) @frappe.whitelist() diff --git a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py index 101e143c12..91488e3533 100644 --- a/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py +++ b/erpnext/healthcare/doctype/lab_test_template/lab_test_template.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe, json from frappe.model.document import Document +from frappe.model.rename_doc import rename_doc from frappe import _ class LabTestTemplate(Document): @@ -112,9 +113,9 @@ def change_test_code_from_template(lab_test_code, doc): if(item_exist): frappe.throw(_("Code {0} already exist").format(lab_test_code)) else: - frappe.rename_doc("Item", doc.name, lab_test_code, ignore_permissions = True) + rename_doc("Item", doc.name, lab_test_code, ignore_permissions=True) frappe.db.set_value("Lab Test Template",doc.name,"lab_test_code",lab_test_code) - frappe.rename_doc("Lab Test Template", doc.name, lab_test_code, ignore_permissions = True) + rename_doc("Lab Test Template", doc.name, lab_test_code, ignore_permissions=True) return lab_test_code @frappe.whitelist() diff --git a/erpnext/patches/v9_2/rename_translated_domains_in_en.py b/erpnext/patches/v9_2/rename_translated_domains_in_en.py index aec5d438ec..e5a9e2461f 100644 --- a/erpnext/patches/v9_2/rename_translated_domains_in_en.py +++ b/erpnext/patches/v9_2/rename_translated_domains_in_en.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.model.rename_doc import rename_doc def execute(): frappe.reload_doc('stock', 'doctype', 'item') @@ -20,11 +21,11 @@ def execute(): if frappe.db.exists("Domain", domain): merge=True - frappe.rename_doc("Domain", translated_domain, domain, ignore_permissions=True, merge=merge) + rename_doc("Domain", translated_domain, domain, ignore_permissions=True, merge=merge) domain_settings = frappe.get_single("Domain Settings") active_domains = [d.domain for d in domain_settings.active_domains] - + try: for domain in active_domains: domain = frappe.get_doc("Domain", domain) From a076bddd833e6ec82fbfdf87cbbe0bbee3147368 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 15 Nov 2019 12:03:06 +0530 Subject: [PATCH 003/389] fix: Show 'Bill of Materials' custom button conditionally --- .../stock/doctype/stock_entry/stock_entry.js | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index cee09e78c6..bb85ef0c48 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -181,12 +181,6 @@ frappe.ui.form.on('Stock Entry', { } } - if (frm.doc.docstatus === 0) { - frm.add_custom_button(__('Bill of Materials'), function(){ - frm.events.get_items_from_bom(frm); - }, __("Get items from")); - } - if (frm.doc.docstatus===0) { frm.add_custom_button(__('Purchase Invoice'), function() { erpnext.utils.map_current_doc({ @@ -257,6 +251,17 @@ frappe.ui.form.on('Stock Entry', { frm.trigger("setup_quality_inspection"); }, + stock_entry_type: function(frm){ + frm.remove_custom_button('Bill of Materials', "Get items from"); + + if (frm.doc.docstatus === 0 && ['Material Issue','Material Receipt', + 'Material Transfer','Send to Subcontractor'].includes(frm.doc.purpose)) { + frm.add_custom_button(__('Bill of Materials'), function(){ + frm.events.get_items_from_bom(frm); + }, __("Get items from")); + } + }, + purpose: function(frm) { frm.trigger('validate_purpose_consumption'); frm.fields_dict.items.grid.refresh(); @@ -411,10 +416,10 @@ frappe.ui.form.on('Stock Entry', { "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, {"fieldname":"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} ] - if (frm.doc.stock_entry_type == 'Material Issue'){ + if (frm.doc.purpose == 'Material Issue'){ fields.splice(2,1); } - else if(frm.doc.stock_entry_type == 'Material Receipt'){ + else if(frm.doc.purpose == 'Material Receipt'){ fields.splice(1,1); } From e537cda0620f1be820afc9328857160693332d1a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 20 Nov 2019 12:38:58 +0530 Subject: [PATCH 004/389] fix: Added conditional check for conversion factor --- erpnext/stock/doctype/stock_entry/stock_entry.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index bb85ef0c48..6c82c8b6b2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -254,11 +254,11 @@ frappe.ui.form.on('Stock Entry', { stock_entry_type: function(frm){ frm.remove_custom_button('Bill of Materials', "Get items from"); - if (frm.doc.docstatus === 0 && ['Material Issue','Material Receipt', - 'Material Transfer','Send to Subcontractor'].includes(frm.doc.purpose)) { - frm.add_custom_button(__('Bill of Materials'), function(){ - frm.events.get_items_from_bom(frm); - }, __("Get items from")); + if (frm.doc.docstatus === 0 && + ['Material Issue', 'Material Receipt', 'Material Transfer', 'Send to Subcontractor'].includes(frm.doc.purpose)) { + frm.add_custom_button(__('Bill of Materials'), function() { + frm.events.get_items_from_bom(frm); + }, __("Get items from")); } }, @@ -449,7 +449,7 @@ frappe.ui.form.on('Stock Entry', { d.t_warehouse = values.target_warehouse; d.uom = item.stock_uom; d.stock_uom = item.stock_uom; - d.conversion_factor = 1; + d.conversion_factor = item.conversion_factor ? item.conversion_factor : 1; d.qty = item.qty; d.expense_account = item.expense_account; d.project = item.project; From 737d8a1e260027a16cc1e6bb5d42717372dbec1d Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 25 Nov 2019 09:42:08 +0530 Subject: [PATCH 005/389] fix: Added comments --- erpnext/stock/doctype/stock_entry/stock_entry.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 6c82c8b6b2..96e74ccca1 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -416,9 +416,12 @@ frappe.ui.form.on('Stock Entry', { "label":__("Fetch exploded BOM (including sub-assemblies)"), "default":1}, {"fieldname":"fetch", "label":__("Get Items from BOM"), "fieldtype":"Button"} ] + + //Exclude field 'Target Warehouse' in case of Material Issue if (frm.doc.purpose == 'Material Issue'){ fields.splice(2,1); } + //Exclude field 'Source Warehouse' in case of Material Receipt else if(frm.doc.purpose == 'Material Receipt'){ fields.splice(1,1); } From 647331bbd70a8d1b7c1b47c974175bbcbb70d15e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 25 Nov 2019 13:56:00 +0530 Subject: [PATCH 006/389] feat: add search in category --- erpnext/public/js/hub/pages/Category.vue | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue index 3a0e6bfab8..a1d5d729cc 100644 --- a/erpnext/public/js/hub/pages/Category.vue +++ b/erpnext/public/js/hub/pages/Category.vue @@ -3,6 +3,12 @@ class="marketplace-page" :data-page-name="page_name" > + +
{{ page_title }}
From c7cfc726d7ba28cc2d78668de7e019222a1882c9 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 27 Nov 2019 16:58:06 +0530 Subject: [PATCH 007/389] feat: navigate to stock ledger from batch report --- .../batch_wise_balance_history.js | 25 +++- .../batch_wise_balance_history.py | 37 +++--- .../stock/report/stock_ledger/stock_ledger.js | 10 +- .../stock/report/stock_ledger/stock_ledger.py | 110 +++++++++++++----- 4 files changed, 133 insertions(+), 49 deletions(-) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js index b23c908e07..23700c94ee 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.js @@ -16,6 +16,29 @@ frappe.query_reports["Batch-Wise Balance History"] = { "fieldtype": "Date", "width": "80", "default": frappe.datetime.get_today() + }, + { + "fieldname": "item", + "label": __("Item"), + "fieldtype": "Link", + "options": "Item", + "width": "80" } - ] + ], + "formatter": function (value, row, column, data, default_formatter) { + if (column.fieldname == "Batch" && data && !!data["Batch"]) { + value = data["Batch"]; + column.link_onclick = "frappe.query_reports['Batch-Wise Balance History'].set_batch_route_to_stock_ledger(" + JSON.stringify(data) + ")"; + } + + value = default_formatter(value, row, column, data); + return value; + }, + "set_batch_route_to_stock_ledger": function (data) { + frappe.route_options = { + "batch_no": data["Batch"] + }; + + frappe.set_route("query-report", "Stock Ledger"); + } } \ No newline at end of file diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index 7f7835f74e..2c95084b81 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -2,9 +2,11 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals + import frappe from frappe import _ -from frappe.utils import flt, cint, getdate +from frappe.utils import cint, flt, getdate + def execute(filters=None): if not filters: filters = {} @@ -17,29 +19,31 @@ def execute(filters=None): data = [] for item in sorted(iwb_map): - for wh in sorted(iwb_map[item]): - for batch in sorted(iwb_map[item][wh]): - qty_dict = iwb_map[item][wh][batch] - if qty_dict.opening_qty or qty_dict.in_qty or qty_dict.out_qty or qty_dict.bal_qty: - data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch, - flt(qty_dict.opening_qty, float_precision), flt(qty_dict.in_qty, float_precision), - flt(qty_dict.out_qty, float_precision), flt(qty_dict.bal_qty, float_precision), - item_map[item]["stock_uom"] - ]) + if not filters.get("item") or filters.get("item") == item: + for wh in sorted(iwb_map[item]): + for batch in sorted(iwb_map[item][wh]): + qty_dict = iwb_map[item][wh][batch] + if qty_dict.opening_qty or qty_dict.in_qty or qty_dict.out_qty or qty_dict.bal_qty: + data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch, + flt(qty_dict.opening_qty, float_precision), flt(qty_dict.in_qty, float_precision), + flt(qty_dict.out_qty, float_precision), flt(qty_dict.bal_qty, float_precision), + item_map[item]["stock_uom"] + ]) return columns, data + def get_columns(filters): """return columns based on filters""" columns = [_("Item") + ":Link/Item:100"] + [_("Item Name") + "::150"] + [_("Description") + "::150"] + \ - [_("Warehouse") + ":Link/Warehouse:100"] + [_("Batch") + ":Link/Batch:100"] + [_("Opening Qty") + ":Float:90"] + \ - [_("In Qty") + ":Float:80"] + [_("Out Qty") + ":Float:80"] + [_("Balance Qty") + ":Float:90"] + \ - [_("UOM") + "::90"] - + [_("Warehouse") + ":Link/Warehouse:100"] + [_("Batch") + ":Link/Batch:100"] + [_("Opening Qty") + ":Float:90"] + \ + [_("In Qty") + ":Float:80"] + [_("Out Qty") + ":Float:80"] + [_("Balance Qty") + ":Float:90"] + \ + [_("UOM") + "::90"] return columns + def get_conditions(filters): conditions = "" if not filters.get("from_date"): @@ -52,7 +56,8 @@ def get_conditions(filters): return conditions -#get all details + +# get all details def get_stock_ledger_entries(filters): conditions = get_conditions(filters) return frappe.db.sql(""" @@ -63,6 +68,7 @@ def get_stock_ledger_entries(filters): order by item_code, warehouse""" % conditions, as_dict=1) + def get_item_warehouse_batch_map(filters, float_precision): sle = get_stock_ledger_entries(filters) iwb_map = {} @@ -90,6 +96,7 @@ def get_item_warehouse_batch_map(filters, float_precision): return iwb_map + def get_item_details(filters): item_map = {} for d in frappe.db.sql("select name, item_name, description, stock_uom from tabItem", as_dict=1): diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js index 3fab3273b9..df3bba5e40 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.js +++ b/erpnext/stock/report/stock_ledger/stock_ledger.js @@ -77,7 +77,15 @@ frappe.query_reports["Stock Ledger"] = { "fieldtype": "Link", "options": "UOM" } - ] + ], + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + if (column.fieldname == "out_qty" && data.out_qty < 0) { + value = "" + value + ""; + } + + return value; + }, } // $(function() { diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index db7f6ad1b9..dd53a006b5 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -2,9 +2,11 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals + import frappe -from frappe import _ from erpnext.stock.utils import update_included_uom_in_report +from frappe import _ + def execute(filters=None): include_uom = filters.get("include_uom") @@ -36,7 +38,22 @@ def execute(filters=None): sle.update({ "qty_after_transaction": actual_qty, - "stock_value": stock_value + "stock_value": stock_value, + "in_qty": max(sle.actual_qty, 0), + "out_qty": min(sle.actual_qty, 0) + }) + + # get the name of the item that was produced using this item + if sle.voucher_type == "Stock Entry": + purpose, work_order, fg_completed_qty = frappe.db.get_value(sle.voucher_type, sle.voucher_no, ["purpose", "work_order", "fg_completed_qty"]) + + if purpose == "Manufacture" and work_order: + finished_product = frappe.db.get_value("Work Order", work_order, "item_name") + finished_qty = fg_completed_qty + + sle.update({ + "finished_product": finished_product, + "finished_qty": finished_qty, }) data.append(sle) @@ -47,53 +64,74 @@ def execute(filters=None): update_included_uom_in_report(columns, data, include_uom, conversion_factors) return columns, data + def get_columns(): columns = [ - {"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 95}, - {"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 130}, + {"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 150}, + {"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100}, {"label": _("Item Name"), "fieldname": "item_name", "width": 100}, + {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90}, + {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, + {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, + {"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"}, + {"label": _("Finished Product"), "fieldname": "finished_product", "width": 100}, + {"label": _("Finished Qty"), "fieldname": "finished_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, + {"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 150}, + {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 150}, {"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100}, {"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100}, {"label": _("Description"), "fieldname": "description", "width": 200}, - {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100}, - {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100}, - {"label": _("Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 50, "convertible": "qty"}, - {"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, - "options": "Company:company:default_currency", "convertible": "rate"}, - {"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, - "options": "Company:company:default_currency", "convertible": "rate"}, - {"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, - "options": "Company:company:default_currency"}, + {"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"}, + {"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"}, + {"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"}, {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110}, {"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100}, {"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100}, - {"label": _("Serial #"), "fieldname": "serial_no", "width": 100}, + {"label": _("Serial #"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100}, {"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100}, {"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110} ] return columns + def get_stock_ledger_entries(filters, items): item_conditions_sql = '' if items: item_conditions_sql = 'and sle.item_code in ({})'\ .format(', '.join([frappe.db.escape(i) for i in items])) - return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date, - item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate, - stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project, stock_value_difference - from `tabStock Ledger Entry` sle - where company = %(company)s and - posting_date between %(from_date)s and %(to_date)s - {sle_conditions} - {item_conditions_sql} - order by posting_date asc, posting_time asc, creation asc"""\ - .format( - sle_conditions=get_sle_conditions(filters), - item_conditions_sql = item_conditions_sql - ), filters, as_dict=1) + sl_entries = frappe.db.sql(""" + SELECT + concat_ws(" ", posting_date, posting_time) AS date, + item_code, + warehouse, + actual_qty, + qty_after_transaction, + incoming_rate, + valuation_rate, + stock_value, + voucher_type, + voucher_no, + batch_no, + serial_no, + company, + project, + stock_value_difference + FROM + `tabStock Ledger Entry` sle + WHERE + company = %(company)s + AND posting_date BETWEEN %(from_date)s AND %(to_date)s + {sle_conditions} + {item_conditions_sql} + ORDER BY + posting_date asc, posting_time asc, creation asc + """.format(sle_conditions=get_sle_conditions(filters), item_conditions_sql=item_conditions_sql), + filters, as_dict=1) + + return sl_entries + def get_items(filters): conditions = [] @@ -111,6 +149,7 @@ def get_items(filters): .format(" and ".join(conditions)), filters) return items + def get_item_details(items, sl_entries, include_uom): item_details = {} if not items: @@ -140,6 +179,7 @@ def get_item_details(items, sl_entries, include_uom): return item_details + def get_sle_conditions(filters): conditions = [] if filters.get("warehouse"): @@ -155,6 +195,7 @@ def get_sle_conditions(filters): return "and {}".format(" and ".join(conditions)) if conditions else "" + def get_opening_balance(filters, columns): if not (filters.item_code and filters.warehouse and filters.from_date): return @@ -166,13 +207,17 @@ def get_opening_balance(filters, columns): "posting_date": filters.from_date, "posting_time": "00:00:00" }) - row = {} - row["item_code"] = _("'Opening'") - for dummy, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')): - row[v] = last_entry.get(v, 0) + + row = { + "item_code": _("'Opening'"), + "qty_after_transaction": last_entry.get("qty_after_transaction", 0), + "valuation_rate": last_entry.get("valuation_rate", 0), + "stock_value": last_entry.get("stock_value", 0) + } return row + def get_warehouse_condition(warehouse): warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1) if warehouse_details: @@ -182,6 +227,7 @@ def get_warehouse_condition(warehouse): return '' + def get_item_group_condition(item_group): item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1) if item_group_details: From 312210c2b0bf2dff44c006b26ca1866ae2fe2b5b Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Tue, 13 Aug 2019 17:15:44 +0530 Subject: [PATCH 008/389] feat: create address and contact after lead creation --- erpnext/crm/doctype/lead/lead.js | 55 +++++++------- erpnext/crm/doctype/lead/lead.json | 95 +++++++++++++++++------- erpnext/crm/doctype/lead/lead.py | 115 +++++++++++++++++++++++------ 3 files changed, 187 insertions(+), 78 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js index 122e2b4eee..0c88d2826f 100644 --- a/erpnext/crm/doctype/lead/lead.js +++ b/erpnext/crm/doctype/lead/lead.js @@ -1,4 +1,4 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt frappe.provide("erpnext"); @@ -7,57 +7,54 @@ cur_frm.email_field = "email_id"; erpnext.LeadController = frappe.ui.form.Controller.extend({ setup: function () { this.frm.make_methods = { + 'Customer': this.make_customer, 'Quotation': this.make_quotation, - 'Opportunity': this.create_opportunity - } - - this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) { - return { query: "erpnext.controllers.queries.customer_query" } - } + 'Opportunity': this.make_opportunity + }; this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead); }, onload: function () { - if (cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) { - cur_frm.fields_dict.lead_owner.get_query = function (doc, cdt, cdn) { - return { query: "frappe.core.doctype.user.user.user_query" } - } - } + this.frm.set_query("customer", function (doc, cdt, cdn) { + return { query: "erpnext.controllers.queries.customer_query" } + }); - if (cur_frm.fields_dict.contact_by.df.options.match(/^User/)) { - cur_frm.fields_dict.contact_by.get_query = function (doc, cdt, cdn) { - return { query: "frappe.core.doctype.user.user.user_query" } - } - } + this.frm.set_query("lead_owner", function (doc, cdt, cdn) { + return { query: "frappe.core.doctype.user.user.user_query" } + }); + + this.frm.set_query("contact_by", function (doc, cdt, cdn) { + return { query: "frappe.core.doctype.user.user.user_query" } + }); }, refresh: function () { - var doc = this.frm.doc; + let doc = this.frm.doc; erpnext.toggle_naming_series(); frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' } - if(!doc.__islocal && doc.__onload && !doc.__onload.is_customer) { - this.frm.add_custom_button(__("Customer"), this.create_customer, __('Create')); - this.frm.add_custom_button(__("Opportunity"), this.create_opportunity, __('Create')); - this.frm.add_custom_button(__("Quotation"), this.make_quotation, __('Create')); + if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) { + this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create")); + this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create")); + this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create")); } - if (!this.frm.doc.__islocal) { - frappe.contacts.render_address_and_contact(cur_frm); + if (!this.frm.is_new()) { + frappe.contacts.render_address_and_contact(this.frm); } else { - frappe.contacts.clear_address_and_contact(cur_frm); + frappe.contacts.clear_address_and_contact(this.frm); } }, - create_customer: function () { + make_customer: function () { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_customer", frm: cur_frm }) }, - create_opportunity: function () { + make_opportunity: function () { frappe.model.open_mapped_doc({ method: "erpnext.crm.doctype.lead.lead.make_opportunity", frm: cur_frm @@ -77,7 +74,7 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({ }, company_name: function () { - if (this.frm.doc.organization_lead == 1) { + if (this.frm.doc.organization_lead && !this.frm.doc.lead_name) { this.frm.set_value("lead_name", this.frm.doc.company_name); } }, @@ -85,7 +82,7 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({ contact_date: function () { if (this.frm.doc.contact_date) { let d = moment(this.frm.doc.contact_date); - d.add(1, "hours"); + d.add(1, "day"); this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat)); } } diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index eb68c679ba..d2a98b609a 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -16,6 +16,7 @@ "col_break123", "lead_owner", "status", + "salutation", "gender", "source", "customer", @@ -29,16 +30,20 @@ "notes_section", "notes", "contact_info", - "address_desc", "address_html", + "address_title", + "address_line1", + "address_line2", + "city", + "county", + "state", + "country", + "pincode", "column_break2", "contact_html", "phone", - "salutation", "mobile_no", "fax", - "website", - "territory", "more_info", "type", "market_segment", @@ -46,6 +51,8 @@ "request_type", "column_break3", "company", + "website", + "territory", "unsubscribed", "blog_subscriber" ], @@ -73,7 +80,6 @@ "set_only_once": 1 }, { - "depends_on": "eval:!doc.organization_lead", "fieldname": "lead_name", "fieldtype": "Data", "in_global_search": 1, @@ -130,7 +136,6 @@ "search_index": 1 }, { - "depends_on": "eval:!doc.organization_lead", "fieldname": "gender", "fieldtype": "Link", "label": "Gender", @@ -218,19 +223,13 @@ }, { "collapsible": 1, + "collapsible_depends_on": "eval: doc.__islocal", "fieldname": "contact_info", "fieldtype": "Section Break", "label": "Address & Contact", "oldfieldtype": "Column Break", "options": "fa fa-map-marker" }, - { - "depends_on": "eval:doc.__islocal", - "fieldname": "address_desc", - "fieldtype": "HTML", - "label": "Address Desc", - "print_hide": 1 - }, { "fieldname": "address_html", "fieldtype": "HTML", @@ -242,14 +241,13 @@ "fieldtype": "Column Break" }, { - "depends_on": "eval:doc.organization_lead", "fieldname": "contact_html", "fieldtype": "HTML", "label": "Contact HTML", "read_only": 1 }, { - "depends_on": "eval:!doc.organization_lead", + "depends_on": "eval: doc.__islocal", "fieldname": "phone", "fieldtype": "Data", "label": "Phone", @@ -257,14 +255,14 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.organization_lead", + "depends_on": "eval: doc.__islocal", "fieldname": "salutation", "fieldtype": "Link", "label": "Salutation", "options": "Salutation" }, { - "depends_on": "eval:!doc.organization_lead", + "depends_on": "eval: doc.__islocal", "fieldname": "mobile_no", "fieldtype": "Data", "label": "Mobile No.", @@ -272,7 +270,7 @@ "oldfieldtype": "Data" }, { - "depends_on": "eval:!doc.organization_lead", + "depends_on": "eval: doc.__islocal", "fieldname": "fax", "fieldtype": "Data", "label": "Fax", @@ -361,12 +359,62 @@ "fieldname": "blog_subscriber", "fieldtype": "Check", "label": "Blog Subscriber" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "address_title", + "fieldtype": "Data", + "label": "Address Title" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "address_line1", + "fieldtype": "Data", + "label": "Address Line 1" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "address_line2", + "fieldtype": "Data", + "label": "Address Line 2" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "city", + "fieldtype": "Data", + "label": "City/Town" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "county", + "fieldtype": "Data", + "label": "County" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "country", + "fieldtype": "Link", + "label": "Country", + "options": "Country" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "pincode", + "fieldtype": "Data", + "label": "Postal Code", + "options": "Country" } ], "icon": "fa fa-user", "idx": 5, "image_field": "image", - "modified": "2019-09-19 12:49:02.536647", + "modified": "2019-09-20 12:49:02.536647", "modified_by": "Administrator", "module": "CRM", "name": "Lead", @@ -423,15 +471,6 @@ "read": 1, "report": 1, "role": "Sales User" - }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Guest", - "share": 1 } ], "search_fields": "lead_name,lead_owner,status", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 1dae4b9fc1..cc2badf984 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -2,18 +2,19 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils import (cstr, validate_email_address, cint, comma_and, has_gravatar, now, getdate, nowdate) -from frappe.model.mapper import get_mapped_doc -from erpnext.controllers.selling_controller import SellingController -from frappe.contacts.address_and_contact import load_address_and_contact +import frappe from erpnext.accounts.party import set_taxes +from erpnext.controllers.selling_controller import SellingController +from frappe import _ +from frappe.contacts.address_and_contact import load_address_and_contact from frappe.email.inbox import link_communication_to_document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import cint, comma_and, cstr, getdate, has_gravatar, nowdate, validate_email_address sender_field = "email_id" + class Lead(SellingController): def get_feed(self): return '{0}: {1}'.format(_(self.status), self.lead_name) @@ -23,15 +24,22 @@ class Lead(SellingController): self.get("__onload").is_customer = customer load_address_and_contact(self) + def before_insert(self): + self.address_doc = self.create_address() + self.contact_doc = self.create_contact() + + def after_insert(self): + self.update_links() + # after the address and contact are created, flush the field values + # to avoid inconsistent reporting in case the documents are changed + self.flush_address_and_contact_fields() + def validate(self): self.set_lead_name() self._prev = frappe._dict({ - "contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if \ - (not cint(self.get("__islocal"))) else None, - "ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if \ - (not cint(self.get("__islocal"))) else None, - "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if \ - (not cint(self.get("__islocal"))) else None, + "contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None, + "ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None, + "contact_by": frappe.db.get_value("Lead", self.name, "contact_by") if (not cint(self.is_new())) else None, }) self.set_status() @@ -39,7 +47,7 @@ class Lead(SellingController): if self.email_id: if not self.flags.ignore_email_validation: - validate_email_address(self.email_id, True) + validate_email_address(self.email_id, throw=True) if self.email_id == self.lead_owner: frappe.throw(_("Lead Owner cannot be same as the Lead")) @@ -53,8 +61,7 @@ class Lead(SellingController): if self.contact_date and getdate(self.contact_date) < getdate(nowdate()): frappe.throw(_("Next Contact Date cannot be in the past")) - if self.ends_on and self.contact_date and\ - (self.ends_on < self.contact_date): + if self.ends_on and self.contact_date and (self.ends_on < self.contact_date): frappe.throw(_("Ends On date cannot be before Next Contact Date.")) def on_update(self): @@ -66,8 +73,7 @@ class Lead(SellingController): "starts_on": self.contact_date, "ends_on": self.ends_on or "", "subject": ('Contact ' + cstr(self.lead_name)), - "description": ('Contact ' + cstr(self.lead_name)) + \ - (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '') + "description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '') }, force) def check_email_id_is_unique(self): @@ -81,8 +87,7 @@ class Lead(SellingController): .format(comma_and(duplicate_leads)), frappe.DuplicateEntryError) def on_trash(self): - frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", - self.name) + frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name) self.delete_events() @@ -115,10 +120,74 @@ class Lead(SellingController): self.lead_name = self.company_name + def create_address(self): + address_fields = ["address_title", "address_line1", "address_line2", + "city", "county", "state", "country", "pincode"] + info_fields = ["email_id", "phone", "fax"] + + # do not create an address if no fields are available, + # skipping country since the system auto-sets it from system defaults + if not any([self.get(field) for field in address_fields if field != "country"]): + return + + address = frappe.new_doc("Address") + 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() + + return address + + def create_contact(self): + names = self.lead_name.split(" ") + if len(names) > 1: + first_name, last_name = names[0], " ".join(names[1:]) + else: + first_name, last_name = self.lead_name, None + + contact_fields = ["email_id", "salutation", "gender", "phone", "mobile_no"] + + contact = frappe.new_doc("Contact") + contact.update({contact_field: self.get(contact_field) for contact_field in contact_fields}) + contact.update({ + "first_name": first_name, + "last_name": last_name + }) + contact.insert() + + return contact + + def update_links(self): + # update address links + if self.address_doc: + self.address_doc.append("links", { + "link_doctype": "Lead", + "link_name": self.name, + "link_title": self.lead_name + }) + self.address_doc.save() + + # update contact links + if self.contact_doc: + self.contact_doc.append("links", { + "link_doctype": "Lead", + "link_name": self.name, + "link_title": self.lead_name + }) + self.contact_doc.save() + + def flush_address_and_contact_fields(self): + fields = ['address_line1', 'address_line2', 'address_title', 'city', 'country', + 'county', 'fax', 'mobile_no', 'phone', 'pincode', 'salutation', 'state'] + + for field in fields: + self.set(field, None) + + @frappe.whitelist() def make_customer(source_name, target_doc=None): return _make_customer(source_name, target_doc) + def _make_customer(source_name, target_doc=None, ignore_permissions=False): def set_missing_values(source, target): if source.company_name: @@ -143,6 +212,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False): return doclist + @frappe.whitelist() def make_opportunity(source_name, target_doc=None): def set_missing_values(source, target): @@ -164,6 +234,7 @@ def make_opportunity(source_name, target_doc=None): return target_doc + @frappe.whitelist() def make_quotation(source_name, target_doc=None): def set_missing_values(source, target): @@ -205,7 +276,8 @@ def _set_missing_values(source, target): @frappe.whitelist() def get_lead_details(lead, posting_date=None, company=None): - if not lead: return {} + if not lead: + return {} from erpnext.accounts.party import set_address_details out = frappe._dict() @@ -231,6 +303,7 @@ def get_lead_details(lead, posting_date=None, company=None): return out + @frappe.whitelist() def make_lead_from_communication(communication, ignore_communication_links=False): """ raise a issue from email """ @@ -267,4 +340,4 @@ def get_lead_with_phone_number(number): lead = leads[0].name if leads else None - return lead \ No newline at end of file + return lead From f9e2bfcc2940c90868941cfd84548f8e301c4d49 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 22 Aug 2019 15:03:34 +0530 Subject: [PATCH 009/389] fix: conditionally set lead title as organization or person --- erpnext/crm/doctype/lead/lead.json | 12 ++++++++++-- erpnext/crm/doctype/lead/lead.py | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index d2a98b609a..88a562f720 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -54,7 +54,8 @@ "website", "territory", "unsubscribed", - "blog_subscriber" + "blog_subscriber", + "title" ], "fields": [ { @@ -409,6 +410,13 @@ "fieldtype": "Data", "label": "Postal Code", "options": "Country" + }, + { + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "print_hide": 1 } ], "icon": "fa fa-user", @@ -477,5 +485,5 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "title_field": "lead_name" + "title_field": "title" } \ No newline at end of file diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index cc2badf984..9e5fdc0e12 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -36,6 +36,7 @@ class Lead(SellingController): def validate(self): self.set_lead_name() + self.set_title() self._prev = frappe._dict({ "contact_date": frappe.db.get_value("Lead", self.name, "contact_date") if (not cint(self.is_new())) else None, "ends_on": frappe.db.get_value("Lead", self.name, "ends_on") if (not cint(self.is_new())) else None, @@ -120,6 +121,12 @@ class Lead(SellingController): self.lead_name = self.company_name + def set_title(self): + if self.organization_lead: + self.title = self.company_name + else: + self.title = self.lead_name + def create_address(self): address_fields = ["address_title", "address_line1", "address_line2", "city", "county", "state", "country", "pincode"] From 75da5af900cb49e509a63433e89830cbb58bd561 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 22 Aug 2019 15:38:44 +0530 Subject: [PATCH 010/389] fix: set missing values --- erpnext/crm/doctype/lead/lead.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 9e5fdc0e12..bd0c742aa2 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -145,6 +145,9 @@ class Lead(SellingController): return address def create_contact(self): + if not self.lead_name: + self.set_lead_name() + names = self.lead_name.split(" ") if len(names) > 1: first_name, last_name = names[0], " ".join(names[1:]) From 69e3868a9dfcaf02c61defac58df8d39cbf1cb51 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 6 Sep 2019 13:38:15 +0530 Subject: [PATCH 011/389] patch: set title for old leads --- erpnext/patches.txt | 3 ++- erpnext/patches/v12_0/set_lead_title_field.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/set_lead_title_field.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 07b646b0f8..0495c02752 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -646,4 +646,5 @@ erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger -erpnext.patches.v12_0.update_price_or_product_discount \ No newline at end of file +erpnext.patches.v12_0.update_price_or_product_discount +erpnext.patches.v12_0.set_lead_title_field diff --git a/erpnext/patches/v12_0/set_lead_title_field.py b/erpnext/patches/v12_0/set_lead_title_field.py new file mode 100644 index 0000000000..86e00038f6 --- /dev/null +++ b/erpnext/patches/v12_0/set_lead_title_field.py @@ -0,0 +1,11 @@ +import frappe + + +def execute(): + frappe.reload_doc("crm", "doctype", "lead") + frappe.db.sql(""" + UPDATE + `tabLead` + SET + title = IF(organization_lead = 1, company_name, lead_name) + """) From fd46fef857b578515db6d12245d8f01895dc00b1 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 6 Sep 2019 14:10:20 +0530 Subject: [PATCH 012/389] fix: add designation to Lead --- erpnext/crm/doctype/lead/lead.json | 153 +++++++++++++++-------------- erpnext/crm/doctype/lead/lead.py | 4 +- 2 files changed, 82 insertions(+), 75 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index 88a562f720..c8e9fbc463 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -17,6 +17,7 @@ "lead_owner", "status", "salutation", + "designation", "gender", "source", "customer", @@ -136,6 +137,13 @@ "reqd": 1, "search_index": 1 }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "salutation", + "fieldtype": "Link", + "label": "Salutation", + "options": "Salutation" + }, { "fieldname": "gender", "fieldtype": "Link", @@ -237,6 +245,56 @@ "label": "Address HTML", "read_only": 1 }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "address_title", + "fieldtype": "Data", + "label": "Address Title" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "address_line1", + "fieldtype": "Data", + "label": "Address Line 1" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "address_line2", + "fieldtype": "Data", + "label": "Address Line 2" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "city", + "fieldtype": "Data", + "label": "City/Town" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "county", + "fieldtype": "Data", + "label": "County" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "country", + "fieldtype": "Link", + "label": "Country", + "options": "Country" + }, + { + "depends_on": "eval: doc.__islocal", + "fieldname": "pincode", + "fieldtype": "Data", + "label": "Postal Code", + "options": "Country" + }, { "fieldname": "column_break2", "fieldtype": "Column Break" @@ -255,13 +313,6 @@ "oldfieldname": "contact_no", "oldfieldtype": "Data" }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "salutation", - "fieldtype": "Link", - "label": "Salutation", - "options": "Salutation" - }, { "depends_on": "eval: doc.__islocal", "fieldname": "mobile_no", @@ -278,22 +329,6 @@ "oldfieldname": "fax", "oldfieldtype": "Data" }, - { - "fieldname": "website", - "fieldtype": "Data", - "label": "Website", - "oldfieldname": "website", - "oldfieldtype": "Data" - }, - { - "fieldname": "territory", - "fieldtype": "Link", - "label": "Territory", - "oldfieldname": "territory", - "oldfieldtype": "Link", - "options": "Territory", - "print_hide": 1 - }, { "collapsible": 1, "fieldname": "more_info", @@ -349,6 +384,22 @@ "options": "Company", "remember_last_selected_value": 1 }, + { + "fieldname": "website", + "fieldtype": "Data", + "label": "Website", + "oldfieldname": "website", + "oldfieldtype": "Data" + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "oldfieldname": "territory", + "oldfieldtype": "Link", + "options": "Territory", + "print_hide": 1 + }, { "default": "0", "fieldname": "unsubscribed", @@ -361,62 +412,18 @@ "fieldtype": "Check", "label": "Blog Subscriber" }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "address_title", - "fieldtype": "Data", - "label": "Address Title" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "address_line1", - "fieldtype": "Data", - "label": "Address Line 1" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "address_line2", - "fieldtype": "Data", - "label": "Address Line 2" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "city", - "fieldtype": "Data", - "label": "City/Town" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "county", - "fieldtype": "Data", - "label": "County" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "state", - "fieldtype": "Data", - "label": "State" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "country", - "fieldtype": "Link", - "label": "Country", - "options": "Country" - }, - { - "depends_on": "eval: doc.__islocal", - "fieldname": "pincode", - "fieldtype": "Data", - "label": "Postal Code", - "options": "Country" - }, { "fieldname": "title", "fieldtype": "Data", "hidden": 1, "label": "Title", "print_hide": 1 + }, + { + "fieldname": "designation", + "fieldtype": "Link", + "label": "Designation", + "options": "Designation" } ], "icon": "fa fa-user", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index bd0c742aa2..c0416092b1 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -154,7 +154,7 @@ class Lead(SellingController): else: first_name, last_name = self.lead_name, None - contact_fields = ["email_id", "salutation", "gender", "phone", "mobile_no"] + contact_fields = ["email_id", "salutation", "gender", "designation", "phone", "mobile_no"] contact = frappe.new_doc("Contact") contact.update({contact_field: self.get(contact_field) for contact_field in contact_fields}) @@ -187,7 +187,7 @@ class Lead(SellingController): def flush_address_and_contact_fields(self): fields = ['address_line1', 'address_line2', 'address_title', 'city', 'country', - 'county', 'fax', 'mobile_no', 'phone', 'pincode', 'salutation', 'state'] + 'county', 'fax', 'mobile_no', 'phone', 'pincode', 'state'] for field in fields: self.set(field, None) From c59ac36378fb720b6bd8562c004ae53fbed1beb7 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 18 Sep 2019 13:46:22 +0530 Subject: [PATCH 013/389] fix: use new Contact schema --- erpnext/crm/doctype/lead/lead.json | 9 +++++++++ erpnext/crm/doctype/lead/lead.py | 31 ++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index c8e9fbc463..2219307caf 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -486,6 +486,15 @@ "read": 1, "report": 1, "role": "Sales User" + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Guest", + "share": 1 } ], "search_fields": "lead_name,lead_owner,status", diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index c0416092b1..6645c4d001 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -80,8 +80,8 @@ class Lead(SellingController): def check_email_id_is_unique(self): if self.email_id: # validate email is unique - duplicate_leads = frappe.db.sql_list("""select name from tabLead - where email_id=%s and name!=%s""", (self.email_id, self.name)) + duplicate_leads = frappe.get_all("Lead", filters={"email_id": self.email_id, "name": ["!=", self.name]}) + duplicate_leads = [lead.name for lead in duplicate_leads] if duplicate_leads: frappe.throw(_("Email Address must be unique, already exists for {0}") @@ -154,13 +154,28 @@ class Lead(SellingController): else: first_name, last_name = self.lead_name, None - contact_fields = ["email_id", "salutation", "gender", "designation", "phone", "mobile_no"] - contact = frappe.new_doc("Contact") - contact.update({contact_field: self.get(contact_field) for contact_field in contact_fields}) contact.update({ "first_name": first_name, - "last_name": last_name + "last_name": last_name, + "salutation": self.salutation, + "gender": self.gender, + "designation": self.designation, + "email_ids": [ + { + "email_id": self.email_id, + "is_primary": 1 + } + ], + "phone_nos": [ + { + "phone": self.phone, + "is_primary": 1 + }, + { + "phone": self.mobile_no, + } + ] }) contact.insert() @@ -186,8 +201,8 @@ class Lead(SellingController): self.contact_doc.save() def flush_address_and_contact_fields(self): - fields = ['address_line1', 'address_line2', 'address_title', 'city', 'country', - 'county', 'fax', 'mobile_no', 'phone', 'pincode', 'state'] + fields = ['address_line1', 'address_line2', 'address_title', + 'city', 'county', 'country', 'fax', 'pincode', 'state'] for field in fields: self.set(field, None) From 236140d94ccf4b836933b88023f3c3b70535349b Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 28 Nov 2019 16:36:24 +0530 Subject: [PATCH 014/389] fix: Division by zero error in Stock Entry --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f81fa683ba..2b99f72565 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -657,7 +657,7 @@ class StockEntry(StockController): item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0) item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ - (t.amount * d.basic_amount) / total_basic_amount + (t.amount * d.basic_amount) / total_basic_amount if total_basic_amount else 0 if item_account_wise_additional_cost: for d in self.get("items"): From 2597817cdeb025caa1f4d4fb7fee49831761778a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 28 Nov 2019 18:57:42 +0530 Subject: [PATCH 015/389] fix: add new routes to handle category wise search --- erpnext/public/js/hub/PageContainer.vue | 2 +- erpnext/public/js/hub/pages/Category.vue | 2 +- erpnext/public/js/hub/pages/Home.vue | 2 +- erpnext/public/js/hub/pages/Search.vue | 15 +++++++++++---- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/hub/PageContainer.vue b/erpnext/public/js/hub/PageContainer.vue index f151add8d5..54c359766d 100644 --- a/erpnext/public/js/hub/PageContainer.vue +++ b/erpnext/public/js/hub/PageContainer.vue @@ -24,7 +24,7 @@ import NotFound from './pages/NotFound.vue'; function get_route_map() { const read_only_routes = { 'marketplace/home': Home, - 'marketplace/search/:keyword': Search, + 'marketplace/search/:category/:keyword': Search, 'marketplace/category/:category': Category, 'marketplace/item/:item': Item, 'marketplace/seller/:seller': Seller, diff --git a/erpnext/public/js/hub/pages/Category.vue b/erpnext/public/js/hub/pages/Category.vue index a1d5d729cc..057fe8bc61 100644 --- a/erpnext/public/js/hub/pages/Category.vue +++ b/erpnext/public/js/hub/pages/Category.vue @@ -67,7 +67,7 @@ export default { }, set_search_route() { - frappe.set_route('marketplace', 'search', this.search_value); + frappe.set_route('marketplace', 'search', this.category, this.search_value); }, } } diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue index 353656957d..aaeaa7eb7c 100644 --- a/erpnext/public/js/hub/pages/Home.vue +++ b/erpnext/public/js/hub/pages/Home.vue @@ -98,7 +98,7 @@ export default { }, set_search_route() { - frappe.set_route('marketplace', 'search', this.search_value); + frappe.set_route('marketplace', 'search', 'All', this.search_value); }, } } diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue index 5118a814e0..2a6088925f 100644 --- a/erpnext/public/js/hub/pages/Search.vue +++ b/erpnext/public/js/hub/pages/Search.vue @@ -29,8 +29,10 @@ export default { return { page_name: frappe.get_route()[1], items: [], - search_value: frappe.get_route()[2], + category: frappe.get_route()[2], + search_value: frappe.get_route()[3], item_id_fieldname: 'name', + filters: {}, // Constants search_placeholder: __('Search for anything ...'), @@ -40,7 +42,7 @@ export default { computed: { page_title() { return this.items.length - ? __(`Results for "${this.search_value}"`) + ? __(`Results for ${this.search_value} in category ${this.category}`) : __('No Items found.'); } }, @@ -49,14 +51,19 @@ export default { }, methods: { get_items() { - hub.call('get_items', { keyword: this.search_value }) + if (this.category !== 'All') { + this.filters['hub_category']=this.category; + } + hub.call('get_items', { keyword: this.search_value, + filters: this.filters + }) .then((items) => { this.items = items; }) }, set_route_and_get_items() { - frappe.set_route('marketplace', 'search', this.search_value); + frappe.set_route('marketplace', 'search', this.category, this.search_value); this.get_items(); }, From 8b599f2bc9c9e6b2dad7c1c6e5c67d6404c273dc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 30 Oct 2019 01:52:03 +0530 Subject: [PATCH 016/389] fix: capacity planning back --- .../doctype/job_card/job_card.json | 1288 ++++------------- .../doctype/job_card/job_card.py | 122 +- .../manufacturing_settings.json | 715 ++------- .../doctype/work_order/work_order.js | 1 + .../doctype/work_order/work_order.py | 66 +- .../doctype/workstation/workstation.json | 583 ++------ .../doctype/workstation/workstation.py | 15 +- .../workstation/workstation_dashboard.py | 8 +- erpnext/patches.txt | 3 +- .../set_production_capacity_in_workstation.py | 8 + 10 files changed, 747 insertions(+), 2062 deletions(-) create mode 100644 erpnext/patches/v12_0/set_production_capacity_in_workstation.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 39c5cce313..2c217028d0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -1,1071 +1,299 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "PO-JOB.#####", - "beta": 0, - "creation": "2018-07-09 17:23:29.518745", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "naming_series:", + "creation": "2018-07-09 17:23:29.518745", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "work_order", + "bom_no", + "workstation", + "operation", + "column_break_4", + "posting_date", + "company", + "for_quantity", + "wip_warehouse", + "timing_detail", + "employee", + "time_logs", + "section_break_13", + "total_completed_qty", + "total_time_in_mins", + "column_break_15", + "section_break_8", + "items", + "more_information", + "operation_id", + "transferred_qty", + "requested_qty", + "project", + "remarks", + "column_break_20", + "status", + "job_started", + "started_time", + "amended_from" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "work_order", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Work Order", - "length": 0, - "no_copy": 0, - "options": "Work Order", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "work_order", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Work Order", + "options": "Work Order", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "bom_no", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "BOM No", - "length": 0, - "no_copy": 0, - "options": "BOM", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "bom_no", + "fieldtype": "Link", + "label": "BOM No", + "options": "BOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "workstation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Workstation", - "length": 0, - "no_copy": 0, - "options": "Workstation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "workstation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Workstation", + "options": "Workstation", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "operation", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Operation", - "length": 0, - "no_copy": 0, - "options": "Operation", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "operation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Operation", + "options": "Operation", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fetch_if_empty": 0, - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "for_quantity", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "For Quantity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "for_quantity", + "fieldtype": "Float", + "in_list_view": 1, + "label": "For Quantity", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "wip_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "WIP Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "wip_warehouse", + "fieldtype": "Link", + "label": "WIP Warehouse", + "options": "Warehouse", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "timing_detail", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Timing Detail", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "timing_detail", + "fieldtype": "Section Break", + "label": "Timing Detail" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee", + "fieldtype": "Link", + "label": "Employee", + "options": "Employee" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "time_logs", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time Logs", - "length": 0, - "no_copy": 0, - "options": "Job Card Time Log", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "time_logs", + "fieldtype": "Table", + "label": "Time Logs", + "options": "Job Card Time Log" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "total_completed_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Completed Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "total_completed_qty", + "fieldtype": "Float", + "label": "Total Completed Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_15", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_15", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "total_time_in_mins", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Time in Mins", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "total_time_in_mins", + "fieldtype": "Float", + "label": "Total Time in Mins", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raw Materials", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_8", + "fieldtype": "Section Break", + "label": "Raw Materials" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "items", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Items", - "length": 0, - "no_copy": 0, - "options": "Job Card Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "options": "Job Card Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "more_information", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "More Information", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "more_information", + "fieldtype": "Section Break", + "label": "More Information" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "operation_id", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Operation ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "operation_id", + "fieldtype": "Data", + "hidden": 1, + "label": "Operation ID", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "transferred_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transferred Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "transferred_qty", + "fieldtype": "Float", + "label": "Transferred Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "requested_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Requested Qty", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "requested_qty", + "fieldtype": "Float", + "label": "Requested Qty", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "project", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Project", - "length": 0, - "no_copy": 0, - "options": "Project", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "remarks", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Remarks", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "remarks", + "fieldtype": "Small Text", + "label": "Remarks" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Open", - "fetch_if_empty": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Open\nWork In Progress\nMaterial Transferred\nSubmitted\nCancelled\nCompleted", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "no_copy": 1, + "options": "Open\nWork In Progress\nMaterial Transferred\nSubmitted\nCancelled\nCompleted", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "job_started", - "fieldtype": "Check", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Job Started", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "job_started", + "fieldtype": "Check", + "hidden": 1, + "label": "Job Started", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "started_time", - "fieldtype": "Datetime", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Started Time", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "started_time", + "fieldtype": "Datetime", + "hidden": 1, + "label": "Started Time", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Job Card", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Job Card", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "PO-JOB.#####", + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Naming Series", + "options": "PO-JOB.#####", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-03-10 17:38:37.499871", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Job Card", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "modified": "2019-10-30 01:49:19.606178", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "operation", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "operation", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 9d2e620e58..e8787ed7db 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -4,10 +4,16 @@ from __future__ import unicode_literals import frappe +import datetime from frappe import _ -from frappe.utils import flt, time_diff_in_hours, get_datetime from frappe.model.mapper import get_mapped_doc from frappe.model.document import Document +from frappe.utils import (flt, cint, time_diff_in_hours, get_datetime, getdate, + get_time, add_to_date, time_diff, add_days, get_datetime_str) + +from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations + +class OverlapError(frappe.ValidationError): pass class JobCard(Document): def validate(self): @@ -26,7 +32,7 @@ class JobCard(Document): data = self.get_overlap_for(d) if data: frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}") - .format(d.idx, self.name, data.name)) + .format(d.idx, self.name, data.name), OverlapError) if d.from_time and d.to_time: d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 @@ -35,27 +41,120 @@ class JobCard(Document): if d.completed_qty: self.total_completed_qty += d.completed_qty - def get_overlap_for(self, args): - existing = frappe.db.sql("""select jc.name as name from + def get_overlap_for(self, args, check_next_available_slot=False): + production_capacity = 1 + + if self.workstation: + production_capacity = frappe.get_cached_value("Workstation", + self.workstation, 'production_capacity') or 1 + validate_overlap_for = " and jc.workstation = %(workstation)s " + + if self.employee: + # override capacity for employee + production_capacity = 1 + validate_overlap_for = " and jc.employee = %(employee)s " + + extra_cond = '' + if check_next_available_slot: + extra_cond = " or (%(from_time)s <= jctl.from_time and %(to_time)s <= jctl.to_time)" + + existing = frappe.db.sql("""select jc.name as name, jctl.to_time from `tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and ( (%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or (%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or - (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time)) - and jctl.name!=%(name)s - and jc.name!=%(parent)s - and jc.docstatus < 2 - and jc.employee = %(employee)s """, + (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time) {0} + ) + and jctl.name != %(name)s and jc.name != %(parent)s and jc.docstatus < 2 {1} + order by jctl.to_time desc limit 1""".format(extra_cond, validate_overlap_for), { "from_time": args.from_time, "to_time": args.to_time, "name": args.name or "No Name", "parent": args.parent or "No Name", - "employee": self.employee + "employee": self.employee, + "workstation": self.workstation }, as_dict=True) + if existing and production_capacity > len(existing): + return + return existing[0] if existing else None + def schedule_time_logs(self, row): + row.remaining_time_in_mins = row.time_in_mins + while row.remaining_time_in_mins > 0: + args = frappe._dict({ + "from_time": row.planned_start_time, + "to_time": row.planned_end_time + }) + + self.validate_overlap_for_workstation(args, row) + self.check_workstation_time(row) + + def validate_overlap_for_workstation(self, args, row): + # get the last record based on the to time from the job card + data = self.get_overlap_for(args, check_next_available_slot=True) + if data: + row.planned_start_time = get_datetime(data.to_time + get_mins_between_operations()) + + def check_workstation_time(self, row): + workstation_doc = frappe.get_cached_doc("Workstation", self.workstation) + if (not workstation_doc.working_hours or + cint(frappe.db.get_single_value("Manufacturing Settings", "allow_overtime"))): + row.remaining_time_in_mins -= time_diff_in_minutes(row.planned_end_time, + row.planned_start_time) + + self.update_time_logs(row) + return + + start_date = getdate(row.planned_start_time) + start_time = get_time(row.planned_start_time) + + new_start_date = workstation_doc.validate_workstation_holiday(start_date) + + if new_start_date != start_date: + row.planned_start_time = datetime.datetime.combine(new_start_date, start_time) + start_date = new_start_date + + total_idx = len(workstation_doc.working_hours) + + for i, time_slot in enumerate(workstation_doc.working_hours): + workstation_start_time = datetime.datetime.combine(start_date, get_time(time_slot.start_time)) + workstation_end_time = datetime.datetime.combine(start_date, get_time(time_slot.end_time)) + + if (get_datetime(row.planned_start_time) >= workstation_start_time and + get_datetime(row.planned_start_time) <= workstation_end_time): + time_in_mins = time_diff_in_minutes(workstation_end_time, row.planned_start_time) + + # If remaining time fit in workstation time logs else split hours as per workstation time + if time_in_mins > row.remaining_time_in_mins: + row.planned_end_time = add_to_date(row.planned_start_time, + minutes=row.remaining_time_in_mins) + row.remaining_time_in_mins = 0 + else: + row.planned_end_time = add_to_date(row.planned_start_time, minutes=time_in_mins) + row.remaining_time_in_mins -= time_in_mins + + self.update_time_logs(row) + + if total_idx != (i+1) and row.remaining_time_in_mins > 0: + row.planned_start_time = datetime.datetime.combine(start_date, + get_time(workstation_doc.working_hours[i+1].start_time)) + + if row.remaining_time_in_mins > 0: + start_date = add_days(start_date, 1) + row.planned_start_time = datetime.datetime.combine(start_date, + get_time(workstation_doc.working_hours[0].start_time)) + + def update_time_logs(self, row): + self.append("time_logs", { + "from_time": row.planned_start_time, + "to_time": row.planned_end_time, + "completed_qty": 0, + "time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time), + }) + def get_required_items(self): if not self.get('work_order'): return @@ -251,3 +350,6 @@ def make_stock_entry(source_name, target_doc=None): }, target_doc, set_missing_values) return doclist + +def time_diff_in_minutes(string_ed_date, string_st_date): + return time_diff(string_ed_date, string_st_date).total_seconds() / 60 \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json index 461b9ab3df..86fa7a8901 100644 --- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json +++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json @@ -1,585 +1,178 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-11-27 14:12:07.542534", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, - "engine": "InnoDB", + "creation": "2014-11-27 14:12:07.542534", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "raw_materials_consumption_section", + "material_consumption", + "column_break_3", + "backflush_raw_materials_based_on", + "capacity_planning", + "disable_capacity_planning", + "allow_overtime", + "allow_production_on_holidays", + "column_break_5", + "capacity_planning_for_days", + "mins_between_operations", + "section_break_6", + "default_wip_warehouse", + "default_fg_warehouse", + "column_break_11", + "default_scrap_warehouse", + "over_production_for_sales_and_work_order_section", + "overproduction_percentage_for_sales_order", + "column_break_16", + "overproduction_percentage_for_work_order", + "other_settings_section", + "update_bom_costs_automatically" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "capacity_planning", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Capacity Planning", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "capacity_planning", + "fieldtype": "Section Break", + "label": "Capacity Planning" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Disables creation of time logs against Work Orders. Operations shall not be tracked against Work Order", - "fieldname": "disable_capacity_planning", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable Capacity Planning and Time Tracking", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:!doc.disable_capacity_planning", + "description": "Plan time logs outside Workstation Working Hours.", + "fieldname": "allow_overtime", + "fieldtype": "Check", + "label": "Allow Overtime" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Plan time logs outside Workstation Working Hours.", - "fieldname": "allow_overtime", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Overtime", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "eval:!doc.disable_capacity_planning", + "fieldname": "allow_production_on_holidays", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Allow Production on Holidays" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "allow_production_on_holidays", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Allow Production on Holidays", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "30", + "depends_on": "eval:!doc.disable_capacity_planning", + "description": "Try planning operations for X days in advance.", + "fieldname": "capacity_planning_for_days", + "fieldtype": "Int", + "label": "Capacity Planning For (Days)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "30", - "description": "Try planning operations for X days in advance.", - "fieldname": "capacity_planning_for_days", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Capacity Planning For (Days)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:!doc.disable_capacity_planning", + "description": "Default 10 mins", + "fieldname": "mins_between_operations", + "fieldtype": "Int", + "label": "Time Between Operations (in mins)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Default 10 mins", - "fieldname": "mins_between_operations", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Time Between Operations (in mins)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break", + "label": "Default Warehouses for Production" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "overproduction_percentage_for_sales_order", + "fieldtype": "Percent", + "label": "Overproduction Percentage For Sales Order" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "overproduction_percentage_for_sales_order", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Overproduction Percentage For Sales Order", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "overproduction_percentage_for_work_order", + "fieldtype": "Percent", + "label": "Overproduction Percentage For Work Order" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "overproduction_percentage_for_work_order", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Overproduction Percentage For Work Order", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "BOM", + "fieldname": "backflush_raw_materials_based_on", + "fieldtype": "Select", + "label": "Backflush Raw Materials Based On", + "options": "BOM\nMaterial Transferred for Manufacture" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "BOM", - "fieldname": "backflush_raw_materials_based_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Backflush Raw Materials Based On", - "length": 0, - "no_copy": 0, - "options": "BOM\nMaterial Transferred for Manufacture", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "description": "Allow multiple Material Consumption against a Work Order", + "fieldname": "material_consumption", + "fieldtype": "Check", + "label": "Allow Multiple Material Consumption" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Allow multiple Material Consumption against a Work Order", - "fieldname": "material_consumption", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Multiple Material Consumption", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.", + "fieldname": "update_bom_costs_automatically", + "fieldtype": "Check", + "label": "Update BOM Cost Automatically" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Update BOM cost automatically via Scheduler, based on latest valuation rate / price list rate / last purchase rate of raw materials.", - "fieldname": "update_bom_costs_automatically", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Update BOM Cost Automatically", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_wip_warehouse", + "fieldtype": "Link", + "label": "Default Work In Progress Warehouse", + "options": "Warehouse" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_wip_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Work In Progress Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "default_fg_warehouse", + "fieldtype": "Link", + "label": "Default Finished Goods Warehouse", + "options": "Warehouse" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_fg_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Finished Goods Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fieldname": "disable_capacity_planning", + "fieldtype": "Check", + "label": "Disable Capacity Planning" + }, + { + "fieldname": "default_scrap_warehouse", + "fieldtype": "Link", + "label": "Default Scrap Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "over_production_for_sales_and_work_order_section", + "fieldtype": "Section Break", + "label": "Over Production for Sales and Work Order" + }, + { + "fieldname": "raw_materials_consumption_section", + "fieldtype": "Section Break", + "label": "Raw Materials Consumption" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "fieldname": "other_settings_section", + "fieldtype": "Section Break", + "label": "Other Settings" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-wrench", - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-05-28 00:46:25.310621", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Manufacturing Settings", - "name_case": "", - "owner": "Administrator", + ], + "icon": "icon-wrench", + "issingle": 1, + "modified": "2019-11-26 13:10:45.569341", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Manufacturing Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "read": 1, + "role": "Manufacturing Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 107c79b89b..5c721c723d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -551,6 +551,7 @@ erpnext.work_order = { if (!r.exe) { frm.set_value("wip_warehouse", r.message.wip_warehouse); frm.set_value("fg_warehouse", r.message.fg_warehouse); + frm.set_value("scrap_warehouse", r.message.scrap_warehouse); } } }); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 2c16bbe90c..84f570b07a 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -12,7 +12,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items from dateutil.relativedelta import relativedelta from erpnext.stock.doctype.item.item import validate_end_of_life from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError -from erpnext.projects.doctype.timesheet.timesheet import OverlapError +from erpnext.manufacturing.doctype.job_card.job_card import OverlapError from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty @@ -260,12 +260,50 @@ class WorkOrder(Document): self.update_reserved_qty_for_production() def create_job_card(self): - for row in self.operations: + manufacturing_settings_doc = frappe.get_doc("Manufacturing Settings") + + enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning) + plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30 + + for i, row in enumerate(self.operations): + self.set_operation_start_end_time(i, row) + if not row.workstation: frappe.throw(_("Row {0}: select the workstation against the operation {1}") .format(row.idx, row.operation)) - create_job_card(self, row, auto_create=True) + original_start_time = row.planned_start_time + job_card_doc = create_job_card(self, row, + enable_capacity_planning=enable_capacity_planning, auto_create=True) + + if enable_capacity_planning and job_card_doc: + row.planned_start_time = job_card_doc.time_logs[0].from_time + row.planned_end_time = job_card_doc.time_logs[-1].to_time + + if date_diff(row.planned_start_time, original_start_time) > plan_days: + frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") + .format(plan_days, row.operation)) + + row.db_update() + + planned_end_date = self.operations and self.operations[-1].planned_end_time + if planned_end_date: + self.db_set("planned_end_date", planned_end_date) + + def set_operation_start_end_time(self, idx, row): + """Set start and end time for given operation. If first operation, set start as + `planned_start_date`, else add time diff to end time of earlier operation.""" + if idx==0: + # first operation at planned_start date + row.planned_start_time = self.planned_start_date + else: + row.planned_start_time = get_datetime(self.operations[idx-1].planned_end_time)\ + + get_mins_between_operations() + + row.planned_end_time = get_datetime(row.planned_start_time) + relativedelta(minutes = row.time_in_mins) + + if row.planned_start_time == row.planned_end_time: + frappe.throw(_("Capacity Planning Error, planned start time can not be same as end time")) def validate_cancel(self): if self.status == "Stopped": @@ -327,9 +365,8 @@ class WorkOrder(Document): """Fetch operations from BOM and set in 'Work Order'""" self.set('operations', []) - if not self.bom_no \ - or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")): - return + if not self.bom_no: + return if self.use_multi_level_bom: bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() @@ -681,11 +718,13 @@ def make_stock_entry(work_order_id, purpose, qty=None): @frappe.whitelist() def get_default_warehouse(): - wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", - "default_wip_warehouse") - fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", - "default_fg_warehouse") - return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse} + doc = frappe.get_cached_doc("Manufacturing Settings") + + return { + "wip_warehouse": doc.default_wip_warehouse, + "fg_warehouse": doc.default_fg_warehouse, + "scrap_warehouse": doc.default_scrap_warehouse + } @frappe.whitelist() def stop_unstop(work_order, status): @@ -721,7 +760,7 @@ def make_job_card(work_order, operation, workstation, qty=0): if row: return create_job_card(work_order, row, qty) -def create_job_card(work_order, row, qty=0, auto_create=False): +def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto_create=False): doc = frappe.new_doc("Job Card") doc.update({ 'work_order': work_order.name, @@ -741,6 +780,9 @@ def create_job_card(work_order, row, qty=0, auto_create=False): if auto_create: doc.flags.ignore_mandatory = True + if enable_capacity_planning: + doc.schedule_time_logs(row) + doc.insert() frappe.msgprint(_("Job card {0} created").format(doc.name)) diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json index dca9891277..d130391cec 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.json +++ b/erpnext/manufacturing/doctype/workstation/workstation.json @@ -1,466 +1,159 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:workstation_name", - "beta": 0, - "creation": "2013-01-10 16:34:17", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:workstation_name", + "creation": "2013-01-10 16:34:17", + "doctype": "DocType", + "document_type": "Setup", + "field_order": [ + "workstation_name", + "production_capacity", + "column_break_3", + "over_heads", + "hour_rate_electricity", + "hour_rate_consumable", + "column_break_11", + "hour_rate_rent", + "hour_rate_labour", + "hour_rate", + "working_hours_section", + "holiday_list", + "working_hours", + "workstaion_description", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "description_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "workstation_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Workstation Name", + "oldfieldname": "workstation_name", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "workstation_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Workstation Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "workstation_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "over_heads", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Operating Costs", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "over_heads", + "fieldtype": "Section Break", + "label": "Operating Costs", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "per hour", - "fieldname": "hour_rate_electricity", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Electricity Cost", - "length": 0, - "no_copy": 0, - "oldfieldname": "hour_rate_electricity", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "bold": 1, + "description": "per hour", + "fieldname": "hour_rate_electricity", + "fieldtype": "Currency", + "label": "Electricity Cost", + "oldfieldname": "hour_rate_electricity", + "oldfieldtype": "Currency" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "per hour", - "fieldname": "hour_rate_consumable", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Consumable Cost", - "length": 0, - "no_copy": 0, - "oldfieldname": "hour_rate_consumable", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "bold": 1, + "description": "per hour", + "fieldname": "hour_rate_consumable", + "fieldtype": "Currency", + "label": "Consumable Cost", + "oldfieldname": "hour_rate_consumable", + "oldfieldtype": "Currency" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "per hour", - "fieldname": "hour_rate_rent", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rent Cost", - "length": 0, - "no_copy": 0, - "oldfieldname": "hour_rate_rent", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "bold": 1, + "description": "per hour", + "fieldname": "hour_rate_rent", + "fieldtype": "Currency", + "label": "Rent Cost", + "oldfieldname": "hour_rate_rent", + "oldfieldtype": "Currency" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Wages per hour", - "fieldname": "hour_rate_labour", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Wages", - "length": 0, - "no_copy": 0, - "oldfieldname": "hour_rate_labour", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "bold": 1, + "description": "Wages per hour", + "fieldname": "hour_rate_labour", + "fieldtype": "Currency", + "label": "Wages", + "oldfieldname": "hour_rate_labour", + "oldfieldtype": "Currency" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "per hour", - "fieldname": "hour_rate", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Hour Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "hour_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "description": "per hour", + "fieldname": "hour_rate", + "fieldtype": "Currency", + "label": "Net Hour Rate", + "oldfieldname": "hour_rate", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "working_hours_section", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Working Hours", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "working_hours_section", + "fieldtype": "Section Break", + "label": "Working Hours" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "working_hours", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Working Hours", - "length": 0, - "no_copy": 0, - "options": "Workstation Working Hour", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "working_hours", + "fieldtype": "Table", + "label": "Working Hours", + "options": "Workstation Working Hour" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "holiday_list", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Holiday List", - "length": 0, - "no_copy": 0, - "options": "Holiday List", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "holiday_list", + "fieldtype": "Link", + "label": "Holiday List", + "options": "Holiday List" + }, + { + "default": "1", + "fieldname": "production_capacity", + "fieldtype": "Int", + "label": "Production Capacity", + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "collapsible": 1, + "fieldname": "workstaion_description", + "fieldtype": "Section Break", + "label": "Description" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "icon-wrench", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-07-18 22:28:50.163219", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Workstation", - "owner": "Administrator", + ], + "icon": "icon-wrench", + "idx": 1, + "modified": "2019-11-26 12:39:19.742052", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Workstation", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "show_name_in_global_search": 1, + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index f6ab72ade4..3512e59045 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -4,7 +4,9 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, cint, getdate, formatdate, comma_and, time_diff_in_seconds, to_timedelta +from erpnext.support.doctype.issue.issue import get_holidays +from frappe.utils import (flt, cint, getdate, formatdate, + comma_and, time_diff_in_seconds, to_timedelta, add_days) from frappe.model.document import Document from dateutil.parser import parse @@ -43,6 +45,17 @@ class Workstation(Document): where parent = %s and workstation = %s""", (self.hour_rate, bom_no[0], self.name)) + def validate_workstation_holiday(self, schedule_date, skip_holiday_list_check=False): + if not skip_holiday_list_check and (not self.holiday_list or + cint(frappe.db.get_single_value("Manufacturing Settings", "allow_production_on_holidays"))): + return schedule_date + + if schedule_date in tuple(get_holidays(self.holiday_list)): + schedule_date = add_days(schedule_date, 1) + self.validate_workstation_holiday(schedule_date, skip_holiday_list_check=True) + + return schedule_date + @frappe.whitelist() def get_default_holiday_list(): return frappe.get_cached_value('Company', frappe.defaults.get_user_default("Company"), "default_holiday_list") diff --git a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py index 9e0d1d1739..7f0124b503 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py +++ b/erpnext/manufacturing/doctype/workstation/workstation_dashboard.py @@ -6,8 +6,12 @@ def get_data(): 'fieldname': 'workstation', 'transactions': [ { - 'label': _('Manufacture'), - 'items': ['BOM', 'Routing', 'Work Order', 'Job Card', 'Operation', 'Timesheet'] + 'label': _('Master'), + 'items': ['BOM', 'Routing', 'Operation'] + }, + { + 'label': _('Transaction'), + 'items': ['Work Order', 'Job Card', 'Timesheet'] } ] } diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 07b646b0f8..daedca7a28 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -646,4 +646,5 @@ erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger -erpnext.patches.v12_0.update_price_or_product_discount \ No newline at end of file +erpnext.patches.v12_0.update_price_or_product_discount +erpnext.patches.v12_0.set_production_capacity_in_workstation \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_production_capacity_in_workstation.py b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py new file mode 100644 index 0000000000..bae1e28deb --- /dev/null +++ b/erpnext/patches/v12_0/set_production_capacity_in_workstation.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("manufacturing", "doctype", "workstation") + + frappe.db.sql(""" UPDATE `tabWorkstation` + SET production_capacity = 1 """) \ No newline at end of file From 92b9d7383d246ceca8447a1afe7e9712279b76a7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 29 Nov 2019 14:22:52 +0530 Subject: [PATCH 017/389] fix: show create payment request for so that are not billed --- erpnext/accounts/doctype/payment_request/payment_request.py | 4 ++-- erpnext/selling/doctype/sales_order/sales_order.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index eda59abf04..6133b1ccd4 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -350,13 +350,13 @@ def get_amount(ref_doc): if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid) - if dt in ["Sales Invoice", "Purchase Invoice"]: + elif dt in ["Sales Invoice", "Purchase Invoice"]: if ref_doc.party_account_currency == ref_doc.currency: grand_total = flt(ref_doc.outstanding_amount) else: grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate - if dt == "Fees": + elif dt == "Fees": grand_total = ref_doc.outstanding_amount if grand_total > 0 : diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 85e81436d1..7dc58b582a 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -202,7 +202,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( } } // payment request - if(flt(doc.per_billed)==0) { + if(flt(doc.per_billed)<100) { this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create')); this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create')); } From 8287c3d05a67065c1e494d3679fc2e9c6ab13cb0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 29 Nov 2019 15:52:29 +0530 Subject: [PATCH 018/389] feat(marketplace): unpublish item from hub --- erpnext/hub_node/api.py | 19 ++++++++++++++++++- erpnext/public/js/hub/pages/Item.vue | 16 +++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 0d01c67650..b0b00b2b46 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -70,7 +70,7 @@ def map_fields(items): field_mappings = get_field_mappings() table_fields = [d.fieldname for d in frappe.get_meta('Item').get_table_fields()] - hub_seller_name = frappe.db.get_value('Marketplace Settings' , 'Marketplace Settings', 'hub_seller_name') + hub_seller_name = frappe.db.get_value('Marketplace Settings', 'Marketplace Settings', 'hub_seller_name') for item in items: for fieldname in table_fields: @@ -147,6 +147,23 @@ def publish_selected_items(items_to_publish): except Exception as e: frappe.log_error(message=e, title='Hub Sync Error') +@frappe.whitelist() +def unpublish_item(item): + ''' Remove item listing from the marketplace ''' + item = json.loads(item) + + item_code = item.get('item_code') + frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) + + item = map_fields([item])[0] + + try: + connection = get_hub_connection() + connection.set_value('Hub Item', item.get('name'), 'published', 0) + + except Exception as e: + frappe.log_error(message=e, title='Hub Sync Error') + @frappe.whitelist() def get_unregistered_users(): settings = frappe.get_single('Marketplace Settings') diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue index 841d0046db..285f696926 100644 --- a/erpnext/public/js/hub/pages/Item.vue +++ b/erpnext/public/js/hub/pages/Item.vue @@ -300,7 +300,21 @@ export default { }, unpublish_item() { - frappe.msgprint(__('This feature is under development...')); + let me = this; + frappe.confirm(__(`Unpublish ${this.item.item_name}?`), function () { + frappe.call( + 'erpnext.hub_node.api.unpublish_item', + { + item: me.item + } + ) + .then((r) => { + frappe.set_route(`marketplace/home`); + frappe.show_alert(__('Item listing removed')) + + }) + + }) } } } From 611d2ccd04edf186718704b60f6f0f125c3f9e32 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 Nov 2019 10:11:03 +0530 Subject: [PATCH 019/389] chore: updated path for set_request in v13-develop --- erpnext/portal/doctype/homepage/test_homepage.py | 2 +- .../portal/doctype/homepage_section/test_homepage_section.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py index b262c4640c..bf5c4025a0 100644 --- a/erpnext/portal/doctype/homepage/test_homepage.py +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.tests.test_website import set_request +from frappe.utils import set_request from frappe.website.render import render class TestHomepage(unittest.TestCase): diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py index c52b7a96c6..5b3196def2 100644 --- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py +++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe import unittest from bs4 import BeautifulSoup -from frappe.tests.test_website import set_request +from frappe.utils import set_request from frappe.website.render import render class TestHomepageSection(unittest.TestCase): From 8c6bc34bc4204e19f9712c6df25e71acae0b6ffb Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 29 Nov 2019 18:42:20 +0530 Subject: [PATCH 020/389] added test cases --- .../doctype/work_order/test_work_order.py | 46 ++++++++++++++++--- .../doctype/work_order/work_order.py | 3 +- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index ea2e7a96e1..0a8f41fc49 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -5,10 +5,10 @@ from __future__ import unicode_literals import unittest import frappe -from frappe.utils import flt, time_diff_in_hours, now, add_days, cint +from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory -from erpnext.manufacturing.doctype.work_order.work_order \ - import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError +from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry, + ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError) from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.utils import get_bin from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order @@ -307,14 +307,50 @@ class TestWorkOrder(unittest.TestCase): {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) if data: + frappe.db.set_value("Manufacturing Settings", + None, "disable_capacity_planning", 0) + bom, bom_item = data bom_doc = frappe.get_doc('BOM', bom) work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + self.assertTrue(work_order.planned_end_date) job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name}) self.assertEqual(len(job_cards), len(bom_doc.operations)) + def test_capcity_planning(self): + frappe.db.set_value("Manufacturing Settings", None, { + "disable_capacity_planning": 0, + "capacity_planning_for_days": 1 + }) + + data = frappe.get_cached_value('BOM', {'docstatus': 1, 'item': '_Test FG Item 2', + 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + + if data: + bom, bom_item = data + + planned_start_date = add_months(today(), months=-1) + work_order = make_wo_order_test_record(item=bom_item, + qty=10, bom_no=bom, planned_start_date=planned_start_date) + + work_order1 = make_wo_order_test_record(item=bom_item, + qty=30, bom_no=bom, planned_start_date=planned_start_date, do_not_submit=1) + + self.assertRaises(CapacityError, work_order1.submit) + + frappe.db.set_value("Manufacturing Settings", None, { + "capacity_planning_for_days": 30 + }) + + work_order1.reload() + work_order1.submit() + self.assertTrue(work_order1.docstatus, 1) + + work_order1.cancel() + work_order.cancel() + def test_work_order_with_non_transfer_item(self): items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0} for item, allow_transfer in items.items(): @@ -371,14 +407,12 @@ def make_wo_order_test_record(**args): wo_order.skip_transfer=1 wo_order.get_items_and_operations_from_bom() wo_order.sales_order = args.sales_order or None + wo_order.planned_start_date = args.planned_start_date or now() if args.source_warehouse: for item in wo_order.get("required_items"): item.source_warehouse = args.source_warehouse - if args.planned_start_date: - wo_order.planned_start_date = args.planned_start_date - if not args.do_not_save: wo_order.insert() diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 84f570b07a..ff489542f6 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -22,6 +22,7 @@ from erpnext.utilities.transaction_base import validate_uom_is_integer from frappe.model.mapper import get_mapped_doc class OverProductionError(frappe.ValidationError): pass +class CapacityError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass class OperationTooLongError(frappe.ValidationError): pass class ItemHasVariantError(frappe.ValidationError): pass @@ -282,7 +283,7 @@ class WorkOrder(Document): if date_diff(row.planned_start_time, original_start_time) > plan_days: frappe.throw(_("Unable to find the time slot in the next {0} days for the operation {1}.") - .format(plan_days, row.operation)) + .format(plan_days, row.operation), CapacityError) row.db_update() From 23b1dae6a7218df6a4220e905dbebcf00488aecb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 Nov 2019 20:22:06 +0530 Subject: [PATCH 021/389] chore: dropped dead setup_wizard test fix: fixed imports for teste utils --- .../test_product_configurator.py | 2 +- .../setup/setup_wizard/test_setup_wizard.py | 71 ------------------- 2 files changed, 1 insertion(+), 72 deletions(-) delete mode 100644 erpnext/setup/setup_wizard/test_setup_wizard.py diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py index a534e5f838..97042dba92 100644 --- a/erpnext/portal/product_configurator/test_product_configurator.py +++ b/erpnext/portal/product_configurator/test_product_configurator.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals from bs4 import BeautifulSoup import frappe, unittest -from frappe.tests.test_website import set_request, get_html_for_route +from frappe.utils import set_request, get_html_for_route from frappe.website.render import render from erpnext.portal.product_configurator.utils import get_products_for_website from erpnext.stock.doctype.item.test_item import make_item_variant diff --git a/erpnext/setup/setup_wizard/test_setup_wizard.py b/erpnext/setup/setup_wizard/test_setup_wizard.py deleted file mode 100644 index a489133aba..0000000000 --- a/erpnext/setup/setup_wizard/test_setup_wizard.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals - -import frappe, time -from frappe.utils.selenium_testdriver import TestDriver - -def run_setup_wizard_test(): - driver = TestDriver() - frappe.db.set_default('in_selenium', '1') - frappe.db.commit() - - driver.login('#page-setup-wizard') - print('Running Setup Wizard Test...') - - # Language slide - driver.wait_for_ajax(True) - time.sleep(1) - - driver.set_select("language", "English (United States)") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - - # Region slide - driver.wait_for_ajax(True) - driver.set_select("country", "India") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - - # Profile slide - driver.set_field("full_name", "Great Tester") - driver.set_field("email", "great@example.com") - driver.set_field("password", "test") - driver.wait_for_ajax(True) - time.sleep(1) - driver.click(".next-btn") - time.sleep(1) - - # domain slide - driver.set_multicheck("domains", ["Manufacturing"]) - time.sleep(1) - driver.click(".next-btn") - - # Org slide - driver.set_field("company_name", "For Testing") - time.sleep(1) - driver.print_console() - driver.click(".next-btn") - - driver.set_field("company_tagline", "Just for GST") - driver.set_field("bank_account", "HDFC") - time.sleep(3) - driver.click(".complete-btn") - - # Wait for desktop - driver.wait_for('#page-desktop', timeout=600) - - driver.print_console() - time.sleep(3) - - frappe.db.set_default('in_selenium', None) - frappe.db.set_value("Company", "For Testing", "write_off_account", "Write Off - FT") - frappe.db.set_value("Company", "For Testing", "exchange_gain_loss_account", "Exchange Gain/Loss - FT") - frappe.db.commit() - - driver.close() - - return True From ad66677029bccd534639ef8158dcf7f1af03e2d7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 29 Nov 2019 23:59:02 +0530 Subject: [PATCH 022/389] fix: add conditional message in template string --- erpnext/public/js/hub/pages/Search.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/hub/pages/Search.vue b/erpnext/public/js/hub/pages/Search.vue index 2a6088925f..8ab48d0e8d 100644 --- a/erpnext/public/js/hub/pages/Search.vue +++ b/erpnext/public/js/hub/pages/Search.vue @@ -42,7 +42,7 @@ export default { computed: { page_title() { return this.items.length - ? __(`Results for ${this.search_value} in category ${this.category}`) + ? __(`Results for "${this.search_value}" ${this.category!=='All'? `in category ${this.category}`: ''}`) : __('No Items found.'); } }, From 4405a2a5f86ff66099a676ec050780b9b8aaf613 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 30 Nov 2019 16:58:31 +0530 Subject: [PATCH 023/389] fix: add project in child for items --- erpnext/projects/doctype/project/project.js | 50 ++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 25c97d1fb8..069e3dc6fe 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -16,6 +16,54 @@ frappe.ui.form.on("Project", { time_log.parenttype = 'Timesheet'; new_doc.time_logs = [time_log]; + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); + }); + }, + 'Purchase Order': () => { + let doctype = 'Purchase Order'; + frappe.model.with_doctype(doctype, () => { + let new_doc = frappe.model.get_new_doc(doctype); + + // add a new row and set the project + let purchase_order_item = frappe.model.get_new_doc('Purchase Order Item'); + purchase_order_item.project = frm.doc.name; + purchase_order_item.parent = new_doc.name; + purchase_order_item.parentfield = 'items'; + purchase_order_item.parenttype = 'Purchase Order'; + new_doc.items = [purchase_order_item]; + + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); + }); + }, + 'Purchase Receipt': () => { + let doctype = 'Purchase Receipt'; + frappe.model.with_doctype(doctype, () => { + let new_doc = frappe.model.get_new_doc(doctype); + + // add a new row and set the project + let purchase_receipt_item = frappe.model.get_new_doc('Purchase Receipt Item'); + purchase_receipt_item.project = frm.doc.name; + purchase_receipt_item.parent = new_doc.name; + purchase_receipt_item.parentfield = 'items'; + purchase_receipt_item.parenttype = 'Purchase Receipt'; + new_doc.items = [purchase_receipt_item]; + + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); + }); + }, + 'Purchase Invoice': () => { + let doctype = 'Purchase Invoice'; + frappe.model.with_doctype(doctype, () => { + let new_doc = frappe.model.get_new_doc(doctype); + + // add a new row and set the project + let purchase_invoice_item = frappe.model.get_new_doc('Purchase Invoice Item'); + purchase_invoice_item.project = frm.doc.name; + purchase_invoice_item.parent = new_doc.name; + purchase_invoice_item.parentfield = 'items'; + purchase_invoice_item.parenttype = 'Purchase Invoice'; + new_doc.items = [purchase_invoice_item]; + frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); }, @@ -80,7 +128,7 @@ frappe.ui.form.on("Project", { frm.events.set_status(frm, 'Cancelled'); }, __('Set Status')); } - + if (frappe.model.can_read("Task")) { frm.add_custom_button(__("Gantt Chart"), function () { frappe.route_options = { From 98a54355ae5c88fdfcec488a5c12eb58f52b721a Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sun, 1 Dec 2019 10:06:16 +0530 Subject: [PATCH 024/389] fix: Post GL entry fix for asset (#19751) --- erpnext/assets/doctype/asset/asset.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 40f1e1efc9..d32f834f0f 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -517,15 +517,18 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): - if not is_cwip_accounting_enabled(self.asset_category): - return - assets = frappe.db.sql_list(""" select name from `tabAsset` - where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) + asset_categories = frappe.db.get_all('Asset Category', fields = ['name', 'enable_cwip_accounting']) - for asset in assets: - doc = frappe.get_doc('Asset', asset) - doc.make_gl_entries() + for asset_category in asset_categories: + if cint(asset_category.enable_cwip_accounting): + assets = frappe.db.sql_list(""" select name from `tabAsset` + where asset_category = %s and ifnull(booked_fixed_asset, 0) = 0 + and available_for_use_date = %s""", (asset_category.name, nowdate())) + + for asset in assets: + doc = frappe.get_doc('Asset', asset) + doc.make_gl_entries() def get_asset_naming_series(): meta = frappe.get_meta('Asset') From 5249a6348fbbc861652b9a70c23dedd8502f1c00 Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Sun, 1 Dec 2019 06:17:21 +0100 Subject: [PATCH 025/389] Update de.csv (#19755) Changed all translations from "Aufgabe" to "Vorgang" in singular and plural where the word "Task" is project related. Left all agricultural and others how they are (as Aufgabe). --- erpnext/translations/de.csv | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 2e25a127d8..cdff3ff422 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1329,7 +1329,7 @@ apps/erpnext/erpnext/hr/doctype/job_offer/job_offer.js,Create Employee,Mitarbeit apps/erpnext/erpnext/utilities/transaction_base.py,Invalid Posting Time,Ungültige Buchungszeit DocType: Salary Component,Condition and Formula,Zustand und Formel DocType: Lead,Campaign Name,Kampagnenname -apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss der Aufgabe +apps/erpnext/erpnext/setup/default_energy_point_rules.py,On Task Completion,Bei Abschluss des Vorgangs apps/erpnext/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py,There is no leave period in between {0} and {1},Es gibt keinen Urlaub zwischen {0} und {1} DocType: Fee Validity,Healthcare Practitioner,praktischer Arzt DocType: Hotel Room,Capacity,Kapazität @@ -1353,7 +1353,7 @@ DocType: Payment Entry,Received Amount (Company Currency),Erhaltene Menge (Gesel apps/erpnext/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py,Payment Cancelled. Please check your GoCardless Account for more details,Zahlung abgebrochen. Bitte überprüfen Sie Ihr GoCardless Konto für weitere Details DocType: Work Order,Skip Material Transfer to WIP Warehouse,Überspringen Sie die Materialübertragung in das WIP-Lager DocType: Contract,N/A,nicht verfügbar -DocType: Task Type,Task Type,Aufgabentyp +DocType: Task Type,Task Type,Vorgangstyp DocType: Topic,Topic Content,Themeninhalt DocType: Delivery Settings,Send with Attachment,Senden mit Anhang DocType: Service Level,Priorities,Prioritäten @@ -2449,7 +2449,7 @@ apps/erpnext/erpnext/public/js/utils/serial_no_batch_selector.js,Please select b DocType: Asset,Depreciation Schedules,Abschreibungen Termine apps/erpnext/erpnext/projects/doctype/timesheet/timesheet.js,Create Sales Invoice,Verkaufsrechnung erstellen apps/erpnext/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html,Ineligible ITC,Nicht förderfähiges ITC -DocType: Task,Dependent Tasks,Abhängige Aufgaben +DocType: Task,Dependent Tasks,Abhängige Vorgänge apps/erpnext/erpnext/regional/report/gstr_1/gstr_1.py,Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden: apps/erpnext/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js,Quantity to Produce,Menge zu produzieren apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Application period cannot be outside leave allocation period,Beantragter Zeitraum kann nicht außerhalb der beantragten Urlaubszeit liegen @@ -2846,7 +2846,7 @@ DocType: Loan,Applicant Type,Bewerbertyp DocType: Purchase Invoice,03-Deficiency in services,03-Mangel an Dienstleistungen DocType: Healthcare Settings,Default Medical Code Standard,Default Medical Code Standard DocType: Purchase Invoice Item,HSN/SAC,HSN / SAC -DocType: Project Template Task,Project Template Task,Projektvorlagenaufgabe +DocType: Project Template Task,Project Template Task,Projektvorgangsvorlage DocType: Accounts Settings,Over Billing Allowance (%),Mehr als Abrechnungsbetrag (%) apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Purchase Receipt {0} is not submitted,Kaufbeleg {0} wurde nicht übertragen DocType: Company,Default Payable Account,Standard-Verbindlichkeitenkonto @@ -3323,7 +3323,7 @@ DocType: Soil Texture,Silt,Schlick ,Qty to Order,Zu bestellende Menge DocType: Period Closing Voucher,"The account head under Liability or Equity, in which Profit/Loss will be booked","Der Kontenkopf unter Eigen- oder Fremdkapital, in dem Gewinn / Verlust verbucht wird" apps/erpnext/erpnext/accounts/doctype/budget/budget.py,Another Budget record '{0}' already exists against {1} '{2}' and account '{3}' for fiscal year {4},Ein weiterer Budgeteintrag '{0}' existiert bereits für {1} '{2}' und für '{3}' für das Geschäftsjahr {4} -apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Aufgaben +apps/erpnext/erpnext/config/projects.py,Gantt chart of all tasks.,Gantt-Diagramm aller Vorgänge DocType: Opportunity,Mins to First Response,Minuten zum First Response DocType: Pricing Rule,Margin Type,Margenart apps/erpnext/erpnext/projects/doctype/project/project_dashboard.html,{0} hours,{0} Stunden @@ -3961,7 +3961,7 @@ apps/erpnext/erpnext/selling/doctype/sales_order/sales_order.py,Maintenance Sche apps/erpnext/erpnext/education/doctype/student/student_dashboard.py,Student LMS Activity,Student LMS Aktivität DocType: POS Profile,Applicable for Users,Anwendbar für Benutzer DocType: Supplier Quotation,PUR-SQTN-.YYYY.-,PUR-SQTN-.JJJJ.- -apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Aufgaben auf Status {0} setzen? +apps/erpnext/erpnext/projects/doctype/project/project.js,Set Project and all Tasks to status {0}?,Projekt und alle Vorgänge auf Status {0} setzen? DocType: Purchase Invoice,Set Advances and Allocate (FIFO),Vorschüsse setzen und zuordnen (FIFO) apps/erpnext/erpnext/manufacturing/doctype/production_plan/production_plan.py,No Work Orders created,Keine Arbeitsaufträge erstellt apps/erpnext/erpnext/hr/doctype/salary_slip/salary_slip.py,Salary Slip of employee {0} already created for this period,Gehaltsabrechnung der Mitarbeiter {0} für diesen Zeitraum bereits erstellt @@ -4418,7 +4418,7 @@ DocType: Normal Test Items,Result Value,Ergebnis Wert DocType: Hotel Room,Hotels,Hotels apps/erpnext/erpnext/accounts/doctype/cost_center/cost_center_tree.js,New Cost Center Name,Neuer Kostenstellenname DocType: Leave Control Panel,Leave Control Panel,Urlaubsverwaltung -DocType: Project,Task Completion,Aufgabenerledigung +DocType: Project,Task Completion,Vorgangserfüllung apps/erpnext/erpnext/templates/generators/item/item_add_to_cart.html,Not in Stock,Nicht lagernd DocType: Volunteer,Volunteer Skills,Freiwillige Fähigkeiten DocType: Additional Salary,HR User,Nutzer Personalabteilung @@ -5197,7 +5197,7 @@ DocType: Work Order,Material Transferred for Manufacturing,Material zur Herstell apps/erpnext/erpnext/accounts/report/general_ledger/general_ledger.py,Account {0} does not exists,Konto {0} existiert nicht apps/erpnext/erpnext/accounts/doctype/sales_invoice/sales_invoice.js,Select Loyalty Program,Wählen Sie Treueprogramm DocType: Project,Project Type,Projekttyp -apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diese Aufgabe existiert eine untergeordnete Aufgabe. Sie können diese Aufgabe daher nicht löschen. +apps/erpnext/erpnext/projects/doctype/task/task.py,Child Task exists for this Task. You can not delete this Task.,Für diesen Vorgang existiert ein untergeordneter Vorgang. Sie können diese Aufgabe daher nicht löschen. apps/erpnext/erpnext/setup/doctype/sales_person/sales_person.py,Either target qty or target amount is mandatory.,Entweder Zielstückzahl oder Zielmenge ist zwingend erforderlich. apps/erpnext/erpnext/config/projects.py,Cost of various activities,Aufwendungen für verschiedene Tätigkeiten apps/erpnext/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py,"Setting Events to {0}, since the Employee attached to the below Sales Persons does not have a User ID{1}","Einstellen Events auf {0}, da die Mitarbeiter auf die beigefügten unter Verkaufs Personen keine Benutzer-ID {1}" @@ -5597,7 +5597,7 @@ apps/erpnext/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py,Paid apps/erpnext/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py,{0} is not a valid Batch Number for Item {1},{0} ist keine gültige Chargennummer für Artikel {1} apps/erpnext/erpnext/shopping_cart/cart.py,Please enter valid coupon code !!,Bitte geben Sie einen gültigen Gutscheincode ein !! apps/erpnext/erpnext/hr/doctype/leave_application/leave_application.py,Note: There is not enough leave balance for Leave Type {0},Hinweis: Es gibt nicht genügend Urlaubsguthaben für Abwesenheitstyp {0} -DocType: Task,Task Description,Aufgabenbeschreibung +DocType: Task,Task Description,Vorgangsbeschreibung DocType: Training Event,Seminar,Seminar DocType: Program Enrollment Fee,Program Enrollment Fee,Programm Einschreibegebühr DocType: Item,Supplier Items,Lieferantenartikel @@ -5754,7 +5754,7 @@ apps/erpnext/erpnext/accounts/doctype/sales_invoice/pos.py,All Territories,Alle DocType: Lost Reason Detail,Lost Reason Detail,Verlorene Begründung Detail apps/erpnext/erpnext/hr/utils.py,Please set leave policy for employee {0} in Employee / Grade record,Legen Sie die Abwesenheitsrichtlinie für den Mitarbeiter {0} im Mitarbeiter- / Notensatz fest apps/erpnext/erpnext/public/js/controllers/transaction.js,Invalid Blanket Order for the selected Customer and Item,Ungültiger Blankoauftrag für den ausgewählten Kunden und Artikel -apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Aufgaben hinzufügen +apps/erpnext/erpnext/projects/doctype/task/task_tree.js,Add Multiple Tasks,Mehrere Vorgänge hinzufügen DocType: Purchase Invoice,Items,Artikel apps/erpnext/erpnext/crm/doctype/contract/contract.py,End Date cannot be before Start Date.,Das Enddatum darf nicht vor dem Startdatum liegen. apps/erpnext/erpnext/education/doctype/course_enrollment/course_enrollment.py,Student is already enrolled.,Student ist bereits eingetragen sind. From f89b1722028684f6d1e19bc367de1f547aeea8c3 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 1 Dec 2019 21:53:32 +0530 Subject: [PATCH 026/389] fix: Available stock for packing item report --- .../available_stock_for_packing_items.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py index 32711b2fce..056492a327 100644 --- a/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py +++ b/erpnext/selling/report/available_stock_for_packing_items/available_stock_for_packing_items.py @@ -7,7 +7,7 @@ from frappe.utils import flt def execute(filters=None): if not filters: filters = {} - + columns = get_columns() iwq_map = get_item_warehouse_quantity_map() item_map = get_item_details() @@ -15,22 +15,23 @@ def execute(filters=None): for sbom, warehouse in iwq_map.items(): total = 0 total_qty = 0 - + for wh, item_qty in warehouse.items(): total += 1 - row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description, - item_map.get(sbom).stock_uom, wh] - available_qty = item_qty - total_qty += flt(available_qty) - row += [available_qty] - - if available_qty: - data.append(row) - if (total == len(warehouse)): - row = ["", "", "Total", "", "", total_qty] + if item_map.get(sbom): + row = [sbom, item_map.get(sbom).item_name, item_map.get(sbom).description, + item_map.get(sbom).stock_uom, wh] + available_qty = item_qty + total_qty += flt(available_qty) + row += [available_qty] + + if available_qty: data.append(row) + if (total == len(warehouse)): + row = ["", "", "Total", "", "", total_qty] + data.append(row) return columns, data - + def get_columns(): columns = ["Item Code:Link/Item:100", "Item Name::100", "Description::120", \ "UOM:Link/UOM:80", "Warehouse:Link/Warehouse:100", "Quantity::100"] From c131bd1b257ddd13fef98ef0527da63ae86e1255 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 2 Dec 2019 01:09:59 +0530 Subject: [PATCH 027/389] feat(marketplace): edit item dialog --- .../js/hub/components/edit_details_dialog.js | 45 +++++++++++++++++++ erpnext/public/js/hub/pages/Item.vue | 37 ++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 erpnext/public/js/hub/components/edit_details_dialog.js diff --git a/erpnext/public/js/hub/components/edit_details_dialog.js b/erpnext/public/js/hub/components/edit_details_dialog.js new file mode 100644 index 0000000000..b341901fec --- /dev/null +++ b/erpnext/public/js/hub/components/edit_details_dialog.js @@ -0,0 +1,45 @@ +function EditDetailsDialog(primary_action, defaults) { + let dialog = new frappe.ui.Dialog({ + title: __('Update Details'), + fields: [ + { + "label": "Item Name", + "fieldname": "item_name", + "fieldtype": "Data", + "default": defaults.item_name, + "reqd": 1 + }, + { + "label": "Hub Category", + "fieldname": "hub_category", + "fieldtype": "Autocomplete", + "default": defaults.hub_category, + "options": [], + "reqd": 1 + }, + { + "label": "Description", + "fieldname": "description", + "fieldtype": "Text", + "default": defaults.description, + "options": [], + "reqd": 1 + } + ], + primary_action_label: primary_action.label || __('Update Details'), + primary_action: primary_action.fn, + }); + + hub.call('get_categories') + .then(categories => { + categories = categories.map(d => d.name); + dialog.fields_dict.hub_category.df.options = categories; + dialog.fields_dict.hub_category.set_options(); + }); + + return dialog; +} + +export { + EditDetailsDialog +}; \ No newline at end of file diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue index 841d0046db..b8399e30d6 100644 --- a/erpnext/public/js/hub/pages/Item.vue +++ b/erpnext/public/js/hub/pages/Item.vue @@ -35,6 +35,7 @@ From ba48546678245b28d48880add9062494c8c3b8ed Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 9 Dec 2019 16:31:14 +0530 Subject: [PATCH 067/389] fix: display new item value --- .../js/hub/components/edit_details_dialog.js | 50 +++++++++---------- erpnext/public/js/hub/pages/Item.vue | 4 +- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/erpnext/public/js/hub/components/edit_details_dialog.js b/erpnext/public/js/hub/components/edit_details_dialog.js index b341901fec..e249ca5d7a 100644 --- a/erpnext/public/js/hub/components/edit_details_dialog.js +++ b/erpnext/public/js/hub/components/edit_details_dialog.js @@ -3,43 +3,39 @@ function EditDetailsDialog(primary_action, defaults) { title: __('Update Details'), fields: [ { - "label": "Item Name", - "fieldname": "item_name", - "fieldtype": "Data", - "default": defaults.item_name, - "reqd": 1 + label: 'Item Name', + fieldname: 'item_name', + fieldtype: 'Data', + default: defaults.item_name, + reqd: 1 }, { - "label": "Hub Category", - "fieldname": "hub_category", - "fieldtype": "Autocomplete", - "default": defaults.hub_category, - "options": [], - "reqd": 1 + label: 'Hub Category', + fieldname: 'hub_category', + fieldtype: 'Autocomplete', + default: defaults.hub_category, + options: [], + reqd: 1 }, { - "label": "Description", - "fieldname": "description", - "fieldtype": "Text", - "default": defaults.description, - "options": [], - "reqd": 1 + label: 'Description', + fieldname: 'description', + fieldtype: 'Text', + default: defaults.description, + options: [], + reqd: 1 } ], primary_action_label: primary_action.label || __('Update Details'), - primary_action: primary_action.fn, + primary_action: primary_action.fn }); - hub.call('get_categories') - .then(categories => { - categories = categories.map(d => d.name); - dialog.fields_dict.hub_category.df.options = categories; - dialog.fields_dict.hub_category.set_options(); - }); + hub.call('get_categories').then(categories => { + categories = categories.map(d => d.name); + dialog.fields_dict.hub_category.set_data(categories); + }); return dialog; } -export { - EditDetailsDialog -}; \ No newline at end of file +export { EditDetailsDialog }; diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue index b8399e30d6..31cc8d5c8c 100644 --- a/erpnext/public/js/hub/pages/Item.vue +++ b/erpnext/public/js/hub/pages/Item.vue @@ -311,7 +311,9 @@ export default { } ) .then((r) => { - this.get_item_details(); + return this.get_item_details(); + }) + .then(() => { frappe.show_alert(__(`${this.item.item_name} Updated`)); }) }, From 2944301c87a8726d039a5f9fa38a1e9139d2a369 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Mon, 9 Dec 2019 16:35:54 +0530 Subject: [PATCH 068/389] fix: website showing disabled items in list of products --- erpnext/portal/product_configurator/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 3a373a4ab1..9c0120d416 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -52,7 +52,6 @@ def get_attribute_filter_data(): def get_products_for_website(field_filters=None, attribute_filters=None, search=None): - if attribute_filters: item_codes = get_item_codes_by_attributes(attribute_filters) items_by_attributes = get_items([['name', 'in', item_codes]]) @@ -336,7 +335,9 @@ def get_items(filters=None, search=None): filter_condition = get_conditions(filters, 'and') - where_conditions = ' and '.join( + where_conditions = 'disabled = 0 and ' + + where_conditions += ' and '.join( [condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition] ) From 7e958da6d6062a6d1a363bde8ec42ae4e0d3ec0a Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 9 Dec 2019 17:23:19 +0530 Subject: [PATCH 069/389] fix: sales order reqd only checks for stock items (#19865) --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 6 ++---- .../selling/doctype/selling_settings/selling_settings.json | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index c4d64a72fd..0f4d4451be 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -535,10 +535,8 @@ class SalesInvoice(SellingController): for i in dic: if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes': for d in self.get('items'): - is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item') - if (d.item_code and is_stock_item == 1\ - and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])): - msgprint(_("{0} is mandatory for Stock Item {1}").format(i,d.item_code), raise_exception=1) + if (d.item_code and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])): + msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1) def validate_proj_cust(self): diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 5033d7aa7f..c04bfd281e 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -77,7 +77,6 @@ "fieldtype": "Column Break" }, { - "description": "Only for Stock Items", "fieldname": "so_required", "fieldtype": "Select", "label": "Sales Order Required", @@ -138,7 +137,7 @@ "icon": "fa fa-cog", "idx": 1, "issingle": 1, - "modified": "2019-11-25 18:35:51.472653", + "modified": "2019-12-09 13:38:36.486298", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", From 9e0b3b29fa71f940eec73ffd514c9d459c3cbdd3 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 9 Dec 2019 18:21:23 +0530 Subject: [PATCH 070/389] feat: better message for serial number creation (#19863) --- erpnext/stock/doctype/serial_no/serial_no.py | 30 ++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 19eb398130..23d00da7c1 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -353,17 +353,19 @@ def get_auto_serial_nos(serial_no_series, qty): def auto_make_serial_nos(args): serial_nos = get_serial_nos(args.get('serial_no')) created_numbers = [] + voucher_type = args.get('voucher_type') + item_code = args.get('item_code') for serial_no in serial_nos: if frappe.db.exists("Serial No", serial_no): sr = frappe.get_doc("Serial No", serial_no) sr.via_stock_ledger = True - sr.item_code = args.get('item_code') + sr.item_code = item_code sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None sr.batch_no = args.get('batch_no') sr.location = args.get('location') sr.company = args.get('company') sr.supplier = args.get('supplier') - if sr.sales_order and args.get('voucher_type') == "Stock Entry" \ + if sr.sales_order and voucher_type == "Stock Entry" \ and not args.get('actual_qty', 0) > 0: sr.sales_order = None sr.save(ignore_permissions=True) @@ -371,10 +373,28 @@ def auto_make_serial_nos(args): created_numbers.append(make_serial_no(serial_no, args)) form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers)) + + # Setting up tranlated title field for all cases + singular_title = _("Serial Number Created") + multiple_title = _("Serial Numbers Created") + + if voucher_type: + multiple_title = singular_title = _("{0} Created").format(voucher_type) + if len(form_links) == 1: - frappe.msgprint(_("Serial No {0} created").format(form_links[0])) + frappe.msgprint(_("Serial No {0} Created").format(form_links[0]), singular_title) elif len(form_links) > 0: - frappe.msgprint(_("The following serial numbers were created:
{0}").format(', '.join(form_links))) + message = _("The following serial numbers were created:

{0}").format(get_items_html(form_links, item_code)) + frappe.msgprint(message, multiple_title) + +def get_items_html(serial_nos, item_code): + body = ', '.join(serial_nos) + return '''
+ {0}: {1} Serial Numbers + +
{2}
+ '''.format(item_code, len(serial_nos), body) + def get_item_details(item_code): return frappe.db.sql("""select name, has_batch_no, docstatus, @@ -397,7 +417,7 @@ def make_serial_no(serial_no, args): sr.via_stock_ledger = args.get('via_stock_ledger') or True sr.asset = args.get('asset') sr.location = args.get('location') - + if args.get('purchase_document_type'): sr.purchase_document_type = args.get('purchase_document_type') From da4847e46d55dbc2647c91ca15ee8648726599f9 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 9 Dec 2019 18:27:50 +0530 Subject: [PATCH 071/389] patch: Set against_blanket_order value in existing SO/PO (#19810) * patch: Set against_blanket_order value in existing SO/PO * Update set_against_blanket_order_in_sales_and_purchase_order.py --- erpnext/patches.txt | 3 ++- ..._against_blanket_order_in_sales_and_purchase_order.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index daedca7a28..68b6cc0f37 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -647,4 +647,5 @@ erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger erpnext.patches.v12_0.update_price_or_product_discount -erpnext.patches.v12_0.set_production_capacity_in_workstation \ No newline at end of file +erpnext.patches.v12_0.set_production_capacity_in_workstation +erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order \ No newline at end of file diff --git a/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py new file mode 100644 index 0000000000..555d8ae797 --- /dev/null +++ b/erpnext/patches/v12_0/set_against_blanket_order_in_sales_and_purchase_order.py @@ -0,0 +1,9 @@ +import frappe +def execute(): + for doctype in ['Sales Order Item', 'Purchase Order Item']: + frappe.reload_doctype(doctype) + frappe.db.sql(""" + UPDATE `tab{0}` + SET against_blanket_order = 1 + WHERE ifnull(blanket_order, '') != '' + """.format(doctype)) From ae02b4119dae0482334a512b92301f61f2d85201 Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Mon, 9 Dec 2019 13:33:35 +0000 Subject: [PATCH 072/389] add init.py for appointment modules (#19823) --- erpnext/www/book-appointment/__init__.py | 0 erpnext/www/book-appointment/verify/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 erpnext/www/book-appointment/__init__.py create mode 100644 erpnext/www/book-appointment/verify/__init__.py diff --git a/erpnext/www/book-appointment/__init__.py b/erpnext/www/book-appointment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/www/book-appointment/verify/__init__.py b/erpnext/www/book-appointment/verify/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 5998034b02c0bc474769f2ca719e6645beb34ecb Mon Sep 17 00:00:00 2001 From: Saqib Date: Mon, 9 Dec 2019 19:07:05 +0530 Subject: [PATCH 073/389] fix: error message displays asset category as None (#19873) * fix: error message displays asset category as None * fix: asset gl_entries doesn't considers asset category's cwip account --- erpnext/assets/doctype/asset/asset.py | 10 ++++++++-- .../stock/doctype/purchase_receipt/purchase_receipt.py | 8 +++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index d32f834f0f..3e7f6833a0 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -610,13 +610,19 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non if asset: account = get_asset_category_account(account_name, asset=asset, asset_category = asset_category, company = company) + + if not asset and not account: + account = get_asset_category_account(account_name, asset_category = asset_category, company = company) if not account: account = frappe.get_cached_value('Company', company, account_name) if not account: - frappe.throw(_("Set {0} in asset category {1} or company {2}") - .format(account_name.replace('_', ' ').title(), asset_category, company)) + if not asset_category: + frappe.throw(_("Set {0} in company {2}").format(account_name.replace('_', ' ').title(), company)) + else: + frappe.throw(_("Set {0} in asset category {1} or company {2}") + .format(account_name.replace('_', ' ').title(), asset_category, company)) return account diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d0fae6a227..9b73d0f271 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -95,7 +95,8 @@ class PurchaseReceipt(BuyingController): # check cwip accounts before making auto assets # Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account arbnb_account = self.get_company_default("asset_received_but_not_billed") - 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) break def validate_with_previous_doc(self): @@ -364,8 +365,9 @@ class PurchaseReceipt(BuyingController): def add_asset_gl_entries(self, item, gl_entries): arbnb_account = self.get_company_default("asset_received_but_not_billed") - # This returns company's default cwip account - cwip_account = get_asset_account("capital_work_in_progress_account", company = self.company) + # This returns category's cwip account if not then fallback to company's default cwip account + cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \ + company = self.company) asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) From 0cb2783f8039592b2c334da2ba708a63e61ee1e0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 9 Dec 2019 19:24:29 +0530 Subject: [PATCH 074/389] fix: create unpublish log on the hub --- erpnext/hub_node/api.py | 20 ++++++++------------ erpnext/public/js/hub/pages/Home.vue | 8 ++++---- erpnext/public/js/hub/pages/Item.vue | 6 +++--- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index b0b00b2b46..3a5b06fdc4 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -148,21 +148,17 @@ def publish_selected_items(items_to_publish): frappe.log_error(message=e, title='Hub Sync Error') @frappe.whitelist() -def unpublish_item(item): +def unpublish_item(item_code, hub_item_name): ''' Remove item listing from the marketplace ''' - item = json.loads(item) - item_code = item.get('item_code') - frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) + response = call_hub_method('unpublish_item', { + 'hub_item_name': hub_item_name + }) - item = map_fields([item])[0] - - try: - connection = get_hub_connection() - connection.set_value('Hub Item', item.get('name'), 'published', 0) - - except Exception as e: - frappe.log_error(message=e, title='Hub Sync Error') + if response: + frappe.db.set_value('Item', item_code, 'publish_in_hub', 0) + else: + frappe.throw(_('Unable to update remote activity')) @frappe.whitelist() def get_unregistered_users(): diff --git a/erpnext/public/js/hub/pages/Home.vue b/erpnext/public/js/hub/pages/Home.vue index 84a42367b2..79cfe46e15 100644 --- a/erpnext/public/js/hub/pages/Home.vue +++ b/erpnext/public/js/hub/pages/Home.vue @@ -58,10 +58,10 @@ export default { this.search_value = ''; this.get_items(); }, - watch:{ - $route (to, from){ - this.get_items() - } + mounted() { + frappe.route.on('change', () => { + this.get_items(); + }) }, methods: { get_items() { diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue index e4b0eeaecb..d0c0ebe603 100644 --- a/erpnext/public/js/hub/pages/Item.vue +++ b/erpnext/public/js/hub/pages/Item.vue @@ -309,11 +309,11 @@ export default { }, unpublish_item() { - let me = this; - frappe.confirm(__(`Unpublish ${this.item.item_name}?`), function() { + frappe.confirm(__(`Unpublish ${this.item.item_name}?`), () => { frappe .call('erpnext.hub_node.api.unpublish_item', { - item: me.item + item_code: this.item.item_code, + hub_item_name: this.hub_item_name }) .then(r => { frappe.set_route(`marketplace/home`); From 214acc9a42a202f652d33f4b1c9197ac48eb681d Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 9 Dec 2019 20:26:50 +0530 Subject: [PATCH 075/389] fix: Changed check condition and added test --- .../stock/doctype/stock_entry/stock_entry.js | 2 +- .../stock/doctype/stock_entry/stock_entry.py | 9 ++++++- .../doctype/stock_entry/test_stock_entry.py | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index d9c94fced7..ca480f969d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -536,7 +536,7 @@ frappe.ui.form.on('Stock Entry Detail', { if(r.message) { var d = locals[cdt][cdn]; $.each(r.message, function(k, v) { - d[k] = v; + frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered }); refresh_field("items"); erpnext.stock.select_batch_and_serial_no(frm, d); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 2b99f72565..913656ad02 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -27,6 +27,7 @@ class IncorrectValuationRateError(frappe.ValidationError): pass class DuplicateEntryForWorkOrderError(frappe.ValidationError): pass class OperationsNotCompleteError(frappe.ValidationError): pass class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass +class TotalBasicAmountZeroError(frappe.ValidationError): pass from erpnext.controllers.stock_controller import StockController @@ -649,6 +650,12 @@ class StockEntry(StockController): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + + if self.get("additional_costs") and not total_basic_amount: + #If additional costs table is populated and total basic amount is + #somehow 0, interrupt transaction. + frappe.throw(_("Total Basic Amount in Items Table cannot be 0"), TotalBasicAmountZeroError) + item_account_wise_additional_cost = {} for t in self.get("additional_costs"): @@ -657,7 +664,7 @@ class StockEntry(StockController): item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0) item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ - (t.amount * d.basic_amount) / total_basic_amount if total_basic_amount else 0 + (t.amount * d.basic_amount) / total_basic_amount if item_account_wise_additional_cost: for d in self.get("items"): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index eddab5d79d..c5e67092d3 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError +from erpnext.stock.doctype.stock_entry.stock_entry import TotalBasicAmountZeroError from six import iteritems def get_sle(**args): @@ -790,6 +791,32 @@ class TestStockEntry(unittest.TestCase): filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening") self.assertEqual(is_opening, "Yes") + def test_total_basic_amount_zero(self): + se = frappe.get_doc({"doctype":"Stock Entry", + "purpose":"Material Receipt", + "stock_entry_type":"Material Receipt", + "posting_date": nowdate(), + "company":"_Test Company with perpetual inventory", + "items":[ + {"item_code":"Basil Leaves", + "description":"Basil Leaves", + "qty": 1, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1"} + ], + "additional_costs":[ + {"expense_account":"Miscellaneous Expenses - TCP1", + "amount":100, + "description": "miscellanous"} + ] + }) + + se.insert() + self.assertRaises(TotalBasicAmountZeroError, se.submit) + def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): se = frappe.copy_doc(test_records[0]) se.get("items")[0].item_code = item_code or "_Test Serialized Item With Series" From 6f68f2d02048fc1dca795dd7e610e7478b52f805 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 10 Dec 2019 08:40:10 +0530 Subject: [PATCH 076/389] feat: Cost center for each expenses in expense claim and allowed deferred expense (#19807) * refactor: Expense Claim Cost Center * refactor: Expense Claim Cost Center * refactor: Expense Claim Cost Center * fix: copy cost center from other row * patch: set cost center in children based on parent * fix: test cases for expense claim --- .../hr/doctype/expense_claim/expense_claim.js | 96 +++--- .../doctype/expense_claim/expense_claim.json | 3 +- .../hr/doctype/expense_claim/expense_claim.py | 7 +- .../expense_claim/test_expense_claim.py | 13 +- .../expense_claim_detail.json | 316 ++---------------- .../expense_claim_type/expense_claim_type.js | 2 +- .../expense_claim_type.json | 225 ++++--------- erpnext/patches.txt | 3 +- ..._center_in_child_table_of_expense_claim.py | 8 + 9 files changed, 160 insertions(+), 513 deletions(-) create mode 100644 erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 0d37c10e9c..570f2ef4c7 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -42,12 +42,6 @@ cur_frm.cscript.onload = function(doc) { cur_frm.set_value("posting_date", frappe.datetime.get_today()); cur_frm.cscript.clear_sanctioned(doc); } - - cur_frm.fields_dict.employee.get_query = function() { - return { - query: "erpnext.controllers.queries.employee_query" - }; - }; }; cur_frm.cscript.clear_sanctioned = function(doc) { @@ -119,7 +113,7 @@ cur_frm.cscript.calculate_total_amount = function(doc,cdt,cdn){ }; erpnext.expense_claim = { - set_title :function(frm) { + set_title: function(frm) { if (!frm.doc.task) { frm.set_value("title", frm.doc.employee_name); } @@ -131,20 +125,20 @@ erpnext.expense_claim = { frappe.ui.form.on("Expense Claim", { setup: function(frm) { - frm.trigger("set_query_for_cost_center"); - frm.trigger("set_query_for_payable_account"); frm.add_fetch("company", "cost_center", "cost_center"); frm.add_fetch("company", "default_expense_claim_payable_account", "payable_account"); - frm.set_query("employee_advance", "advances", function(doc) { + + frm.set_query("employee_advance", "advances", function() { return { filters: [ ['docstatus', '=', 1], - ['employee', '=', doc.employee], + ['employee', '=', frm.doc.employee], ['paid_amount', '>', 0], ['paid_amount', '>', 'claimed_amount'] ] }; }); + frm.set_query("expense_approver", function() { return { query: "erpnext.hr.doctype.department_approver.department_approver.get_approvers", @@ -154,14 +148,49 @@ frappe.ui.form.on("Expense Claim", { } }; }); - frm.set_query("account_head", "taxes", function(doc) { + + frm.set_query("account_head", "taxes", function() { return { filters: [ - ['company', '=', doc.company], + ['company', '=', frm.doc.company], ['account_type', 'in', ["Tax", "Chargeable", "Income Account", "Expenses Included In Valuation"]] ] }; }); + + frm.set_query("cost_center", "expenses", function() { + return { + filters: { + "company": frm.doc.company, + "is_group": 0 + } + }; + }); + + frm.set_query("payable_account", function() { + return { + filters: { + "report_type": "Balance Sheet", + "account_type": "Payable", + "company": frm.doc.company, + "is_group": 0 + } + }; + }); + + frm.set_query("task", function() { + return { + filters: { + 'project': frm.doc.project + } + }; + }); + + frm.set_query("employee", function() { + return { + query: "erpnext.controllers.queries.employee_query" + }; + }); }, onload: function(frm) { @@ -244,30 +273,6 @@ frappe.ui.form.on("Expense Claim", { }); }, - set_query_for_cost_center: function(frm) { - frm.fields_dict["cost_center"].get_query = function() { - return { - filters: { - "company": frm.doc.company, - "is_group": 0 - } - }; - }; - }, - - set_query_for_payable_account: function(frm) { - frm.fields_dict["payable_account"].get_query = function() { - return { - filters: { - "report_type": "Balance Sheet", - "account_type": "Payable", - "company": frm.doc.company, - "is_group": 0 - } - }; - }; - }, - is_paid: function(frm) { frm.trigger("toggle_fields"); }, @@ -329,6 +334,10 @@ frappe.ui.form.on("Expense Claim", { }); frappe.ui.form.on("Expense Claim Detail", { + expenses_add: function(frm, cdt, cdn) { + var row = frappe.get_doc(cdt, cdn); + frm.script_manager.copy_from_first_row("expenses", row, ["cost_center"]); + }, amount: function(frm, cdt, cdn) { var child = locals[cdt][cdn]; var doc = frm.doc; @@ -341,6 +350,9 @@ frappe.ui.form.on("Expense Claim Detail", { cur_frm.cscript.calculate_total(doc,cdt,cdn); frm.trigger("get_taxes"); frm.trigger("calculate_grand_total"); + }, + cost_center: function(frm, cdt, cdn) { + erpnext.utils.copy_value_in_all_rows(frm.doc, cdt, cdn, "expenses", "cost_center"); } }); @@ -411,12 +423,4 @@ frappe.ui.form.on("Expense Taxes and Charges", { tax_amount: function(frm, cdt, cdn) { frm.trigger("calculate_total_tax", cdt, cdn); } -}); - -cur_frm.fields_dict['task'].get_query = function(doc) { - return { - filters:{ - 'project': doc.project - } - }; -}; +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index 5c2f490171..b5b6823e1c 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -43,7 +43,6 @@ "accounting_dimensions_section", "project", "dimension_col_break", - "cost_center", "more_details", "status", "amended_from", @@ -366,7 +365,7 @@ "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-11-08 14:13:08.964547", + "modified": "2019-11-09 14:13:08.964547", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 59391505fa..dfb0bb96d8 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -127,7 +127,7 @@ class ExpenseClaim(AccountsController): "debit": data.sanctioned_amount, "debit_in_account_currency": data.sanctioned_amount, "against": self.employee, - "cost_center": self.cost_center + "cost_center": data.cost_center }) ) @@ -190,8 +190,9 @@ class ExpenseClaim(AccountsController): ) def validate_account_details(self): - if not self.cost_center: - frappe.throw(_("Cost center is required to book an expense claim")) + for data in self.expenses: + if not data.cost_center: + frappe.throw(_("Cost center is required to book an expense claim")) if self.is_paid: if not self.mode_of_payment: diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index b559dfd81d..6e97f0513d 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -126,7 +126,7 @@ def generate_taxes(): def make_expense_claim(payable_account, amount, sanctioned_amount, company, account, project=None, task_name=None, do_not_submit=False, taxes=None): employee = frappe.db.get_value("Employee", {"status": "Active"}) - currency = frappe.db.get_value('Company', company, 'default_currency') + currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center']) expense_claim = { "doctype": "Expense Claim", "employee": employee, @@ -134,12 +134,15 @@ def make_expense_claim(payable_account, amount, sanctioned_amount, company, acco "approval_status": "Approved", "company": company, 'currency': currency, - "expenses": - [{"expense_type": "Travel", + "expenses": [{ + "expense_type": "Travel", "default_account": account, - 'currency': currency, + "currency": currency, "amount": amount, - "sanctioned_amount": sanctioned_amount}]} + "sanctioned_amount": sanctioned_amount, + "cost_center": cost_center + }] + } if taxes: expense_claim.update(taxes) diff --git a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json index b23fb6af0f..b60db2c3af 100644 --- a/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json +++ b/erpnext/hr/doctype/expense_claim_detail/expense_claim_detail.json @@ -1,378 +1,118 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, "creation": "2013-02-22 01:27:46", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "expense_date", + "column_break_2", + "expense_type", + "default_account", + "section_break_4", + "description", + "section_break_6", + "amount", + "column_break_8", + "sanctioned_amount", + "cost_center" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "expense_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Expense Date", - "length": 0, - "no_copy": 0, "oldfieldname": "expense_date", "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "expense_type", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Expense Claim Type", - "length": 0, - "no_copy": 0, "oldfieldname": "expense_type", "oldfieldtype": "Link", "options": "Expense Claim Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "expense_type", "fieldname": "default_account", "fieldtype": "Link", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Account", - "length": 0, - "no_copy": 0, "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "read_only": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "", "fieldname": "description", "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Description", - "length": 0, - "no_copy": 0, "oldfieldname": "description", "oldfieldtype": "Small Text", - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "300px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "300px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Section Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Amount", - "length": 0, - "no_copy": 0, "oldfieldname": "claim_amount", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_8", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "sanctioned_amount", "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Sanctioned Amount", - "length": 0, "no_copy": 1, "oldfieldname": "sanctioned_amount", "oldfieldtype": "Currency", "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, "width": "150px" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, "istable": 1, - "max_attachments": 0, - "modified": "2019-06-10 08:41:36.122565", - "modified_by": "Administrator", + "modified": "2019-11-22 11:57:25.110942", + "modified_by": "jangeles@bai.ph", "module": "HR", "name": "Expense Claim Detail", "owner": "harshada@webnotestech.com", "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js index fda6809e6b..c487797677 100644 --- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js +++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js @@ -8,7 +8,7 @@ frappe.ui.form.on("Expense Claim Type", { return{ filters: { "is_group": 0, - "root_type": "Expense", + "root_type": frm.doc.deferred_expense_account ? "Asset" : "Expense", 'company': d.company } } diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json index d0c41221cc..e45f640c07 100644 --- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json +++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.json @@ -1,181 +1,72 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:expense_type", - "beta": 0, - "creation": "2012-03-27 14:35:55", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:expense_type", + "creation": "2012-03-27 14:35:55", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "deferred_expense_account", + "expense_type", + "description", + "accounts" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "expense_type", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Expense Claim Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "expense_type", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "fieldname": "expense_type", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Expense Claim Type", + "oldfieldname": "expense_type", + "oldfieldtype": "Data", + "reqd": 1, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Small Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Small Text", "width": "300px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accounts", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Expense Claim Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Expense Claim Account" + }, + { + "default": "0", + "fieldname": "deferred_expense_account", + "fieldtype": "Check", + "label": "Deferred Expense Account" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-flag", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-09-18 14:13:43.770829", - "modified_by": "Administrator", - "module": "HR", - "name": "Expense Claim Type", - "owner": "harshada@webnotestech.com", + ], + "icon": "fa fa-flag", + "idx": 1, + "modified": "2019-11-22 12:00:18.710408", + "modified_by": "jangeles@bai.ph", + "module": "HR", + "name": "Expense Claim Type", + "owner": "harshada@webnotestech.com", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Employee", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Employee" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 68b6cc0f37..053cae0567 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -648,4 +648,5 @@ erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger erpnext.patches.v12_0.update_price_or_product_discount erpnext.patches.v12_0.set_production_capacity_in_workstation -erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order \ No newline at end of file +erpnext.patches.v12_0.set_against_blanket_order_in_sales_and_purchase_order +erpnext.patches.v12_0.set_cost_center_in_child_table_of_expense_claim diff --git a/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py new file mode 100644 index 0000000000..8ba0d79a83 --- /dev/null +++ b/erpnext/patches/v12_0/set_cost_center_in_child_table_of_expense_claim.py @@ -0,0 +1,8 @@ +import frappe +def execute(): + frappe.reload_doc('hr', 'doctype', 'expense_claim_detail') + frappe.db.sql(""" + UPDATE `tabExpense Claim Detail` child, `tabExpense Claim` par + SET child.cost_center = par.cost_center + WHERE child.parent = par.name + """) \ No newline at end of file From ce42f48bfbe8ec13dc5d0da44b718eb1355091b0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 10 Dec 2019 12:15:34 +0530 Subject: [PATCH 077/389] fix: Append expense account only if expense account exists (#19880) --- erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 7b2061ac16..917acba92c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -248,7 +248,7 @@ class PurchaseInvoice(BuyingController): def set_against_expense_account(self): against_accounts = [] for item in self.get("items"): - if item.expense_account not in against_accounts: + if item.expense_account and (item.expense_account not in against_accounts): against_accounts.append(item.expense_account) self.against_expense_account = ",".join(against_accounts) From 9cc5c18f36ec8736a688315adb520d995ed0d753 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 10 Dec 2019 15:20:47 +0530 Subject: [PATCH 078/389] fix: removed only custom dashboard --- erpnext/hr/doctype/leave_application/leave_application.js | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index e32d57011d..14ffa0ed1f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -60,6 +60,7 @@ frappe.ui.form.on("Leave Application", { } } }); + $("div").remove(".form-dashboard-section.custom"); frm.dashboard.add_section( frappe.render_template('leave_application_dashboard', { data: leave_details From 6e2c13f4c86c73c09f13c9167717f602b701de71 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 10 Dec 2019 15:55:05 +0530 Subject: [PATCH 079/389] feat(regional): Auto state wise taxation for GST India (#19469) * feat(regional): Auto state wise taxation for GST India * fix: Update gst category on addition of GSTIN * fix: Codacy and travis fixes * fix: Travis * fix(test): Update GST category only if GSTIN field available * fix: Test Cases * fix: Do not skip accounts if place of supply is not present * fix: Auto GST taxation for SEZ Party types * fix: Automatic taxation for multi state * fix: Codacy and travis fixes * fix: Auto GST template selection in Sales Order * fix: Move inter state check and source state to tax category * fix: Remove unique check from tax template * fix: Remove unique check from tax template * fix: Address fetching logic in Sales * fix: fecth tax template on company address change * fix: fetch company gstin on address change * fix: company_gstin set value fix * fix: Mutiple fixes and code refactor * fix: Add missing semicolon * fix: Company address fetching in sales invoice * fix: Remove print statement * fix: Import functools * fix: Naming fixes and code cleanup * fix: Iteritems compatibility for python 3 --- .../purchase_invoice/regional/india.js | 3 + .../purchase_taxes_and_charges_template.json | 374 +++++------------ .../doctype/sales_invoice/regional/india.js | 5 + .../doctype/sales_invoice/sales_invoice.js | 4 +- .../sales_taxes_and_charges_template.json | 382 +++++------------- erpnext/accounts/party.py | 107 ++--- .../doctype/purchase_order/regional/india.js | 3 + erpnext/hooks.py | 6 +- erpnext/patches.txt | 1 + .../add_export_type_field_in_party_master.py | 40 ++ erpnext/public/js/queries.js | 2 +- erpnext/public/js/utils/party.js | 44 ++ .../gstr_3b_report/test_gstr_3b_report.py | 17 +- erpnext/regional/india/__init__.py | 5 +- erpnext/regional/india/setup.py | 59 ++- erpnext/regional/india/taxes.js | 41 ++ erpnext/regional/india/utils.py | 116 +++++- .../doctype/sales_order/regional/india.js | 3 + .../doctype/sales_order/sales_order.py | 16 +- erpnext/setup/doctype/company/company.py | 25 ++ .../doctype/delivery_note/delivery_note.py | 7 +- .../doctype/delivery_note/regional/india.js | 4 + .../purchase_receipt/regional/india.js | 3 + 23 files changed, 598 insertions(+), 669 deletions(-) create mode 100644 erpnext/accounts/doctype/purchase_invoice/regional/india.js create mode 100644 erpnext/buying/doctype/purchase_order/regional/india.js create mode 100644 erpnext/patches/v12_0/add_export_type_field_in_party_master.py create mode 100644 erpnext/regional/india/taxes.js create mode 100644 erpnext/selling/doctype/sales_order/regional/india.js create mode 100644 erpnext/stock/doctype/delivery_note/regional/india.js create mode 100644 erpnext/stock/doctype/purchase_receipt/regional/india.js diff --git a/erpnext/accounts/doctype/purchase_invoice/regional/india.js b/erpnext/accounts/doctype/purchase_invoice/regional/india.js new file mode 100644 index 0000000000..81488a2c52 --- /dev/null +++ b/erpnext/accounts/doctype/purchase_invoice/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Purchase Invoice'); diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json index bc42630d47..a18fec61cf 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json @@ -1,300 +1,108 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:title", - "beta": 0, - "creation": "2013-01-10 16:34:08", - "custom": 0, - "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_import": 1, + "allow_rename": 1, + "creation": "2013-01-10 16:34:08", + "description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.", + "doctype": "DocType", + "document_type": "Setup", + "field_order": [ + "title", + "is_default", + "disabled", + "column_break4", + "company", + "tax_category", + "section_break6", + "taxes" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "oldfieldname": "title", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "no_copy": 1, + "oldfieldname": "title", + "oldfieldtype": "Data", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Disabled" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break4", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break6", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "purchase_tax_details", - "oldfieldtype": "Table", - "options": "Purchase Taxes and Charges", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Purchase Taxes and Charges", + "oldfieldname": "purchase_tax_details", + "oldfieldtype": "Table", + "options": "Purchase Taxes and Charges" + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-money", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-11-07 05:18:44.095798", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Purchase Taxes and Charges Template", - "owner": "wasim@webnotestech.com", + ], + "icon": "fa fa-money", + "idx": 1, + "modified": "2019-11-25 13:05:26.220275", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Purchase Taxes and Charges Template", + "owner": "wasim@webnotestech.com", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Manager" + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Master Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Master Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Purchase User" } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "DESC", - "track_seen": 0 + ], + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index c8305e325f..48fa364faf 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,3 +1,7 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Sales Invoice'); + frappe.ui.form.on("Sales Invoice", { setup: function(frm) { frm.set_query('transporter', function() { @@ -35,4 +39,5 @@ frappe.ui.form.on("Sales Invoice", { }, __("Make")); } } + }); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 2ea74f6d85..7f4ae3c1fc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -697,8 +697,8 @@ frappe.ui.form.on('Sales Invoice', { if (frm.doc.company) { frappe.call({ - method:"frappe.contacts.doctype.address.address.get_default_address", - args:{ doctype:'Company',name:frm.doc.company}, + method:"erpnext.setup.doctype.company.company.get_default_company_address", + args:{name:frm.doc.company, existing_address: frm.doc.company_address}, callback: function(r){ if (r.message){ frm.set_value("company_address",r.message) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json index 29e15d165f..19781bdffa 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json @@ -1,299 +1,119 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:title", - "beta": 0, - "creation": "2013-01-10 16:34:09", - "custom": 0, - "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_import": 1, + "allow_rename": 1, + "creation": "2013-01-10 16:34:09", + "description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "title", + "is_default", + "disabled", + "column_break_3", + "company", + "tax_category", + "section_break_5", + "taxes" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "oldfieldname": "title", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "title", + "fieldtype": "Data", + "label": "Title", + "no_copy": 1, + "oldfieldname": "title", + "oldfieldtype": "Data", + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Default", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Default" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "disabled", + "fieldtype": "Check", + "label": "Disabled" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "* Will be calculated in the transaction.", - "fieldname": "taxes", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges", - "oldfieldtype": "Table", - "options": "Sales Taxes and Charges", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "description": "* Will be calculated in the transaction.", + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Sales Taxes and Charges", + "oldfieldname": "other_charges", + "oldfieldtype": "Table", + "options": "Sales Taxes and Charges" + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-money", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-11-07 05:18:41.743257", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Sales Taxes and Charges Template", - "owner": "Administrator", + ], + "icon": "fa fa-money", + "idx": 1, + "modified": "2019-11-25 13:06:03.279099", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Sales Taxes and Charges Template", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 1, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User" + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales Master Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Master Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "sort_order": "ASC", - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 59936d5116..156f2181b8 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -23,7 +23,7 @@ class DuplicatePartyAccountError(frappe.ValidationError): pass @frappe.whitelist() def get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, fetch_payment_terms_template=True, - party_address=None, shipping_address=None, pos_profile=None): + party_address=None, company_address=None, shipping_address=None, pos_profile=None): if not party: return {} @@ -31,14 +31,14 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N frappe.throw(_("{0}: {1} does not exists").format(party_type, party)) return _get_party_details(party, account, party_type, company, posting_date, bill_date, price_list, currency, doctype, ignore_permissions, - fetch_payment_terms_template, party_address, shipping_address, pos_profile) + fetch_payment_terms_template, party_address, company_address, shipping_address, pos_profile) def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, - fetch_payment_terms_template=True, party_address=None, shipping_address=None, pos_profile=None): + fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None): - out = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) - party = out[party_type.lower()] + party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) + party = party_details[party_type.lower()] if not ignore_permissions and not frappe.has_permission(party_type, "read", party): frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError) @@ -46,76 +46,81 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= party = frappe.get_doc(party_type, party) currency = party.default_currency if party.get("default_currency") else get_company_currency(company) - party_address, shipping_address = set_address_details(out, party, party_type, doctype, company, party_address, shipping_address) - set_contact_details(out, party, party_type) - set_other_values(out, party, party_type) - set_price_list(out, party, party_type, price_list, pos_profile) + party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address) + set_contact_details(party_details, party, party_type) + set_other_values(party_details, party, party_type) + set_price_list(party_details, party, party_type, price_list, pos_profile) - out["tax_category"] = get_address_tax_category(party.get("tax_category"), + party_details["tax_category"] = get_address_tax_category(party.get("tax_category"), party_address, shipping_address if party_type != "Supplier" else party_address) - out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, - customer_group=out.customer_group, supplier_group=out.supplier_group, tax_category=out.tax_category, - billing_address=party_address, shipping_address=shipping_address) + + if not party_details.get("taxes_and_charges"): + party_details["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, + customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category, + billing_address=party_address, shipping_address=shipping_address) if fetch_payment_terms_template: - out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) + party_details["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) - if not out.get("currency"): - out["currency"] = currency + if not party_details.get("currency"): + party_details["currency"] = currency # sales team if party_type=="Customer": - out["sales_team"] = [{ + party_details["sales_team"] = [{ "sales_person": d.sales_person, "allocated_percentage": d.allocated_percentage or None } for d in party.get("sales_team")] # supplier tax withholding category if party_type == "Supplier" and party: - out["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category") + party_details["supplier_tds"] = frappe.get_value(party_type, party.name, "tax_withholding_category") - return out + return party_details -def set_address_details(out, party, party_type, doctype=None, company=None, party_address=None, shipping_address=None): +def set_address_details(party_details, party, party_type, doctype=None, company=None, party_address=None, company_address=None, shipping_address=None): billing_address_field = "customer_address" if party_type == "Lead" \ else party_type.lower() + "_address" - out[billing_address_field] = party_address or get_default_address(party_type, party.name) + party_details[billing_address_field] = party_address or get_default_address(party_type, party.name) if doctype: - out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field])) + party_details.update(get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])) # address display - out.address_display = get_address_display(out[billing_address_field]) + party_details.address_display = get_address_display(party_details[billing_address_field]) # shipping address if party_type in ["Customer", "Lead"]: - out.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name) - out.shipping_address = get_address_display(out["shipping_address_name"]) + party_details.shipping_address_name = shipping_address or get_party_shipping_address(party_type, party.name) + party_details.shipping_address = get_address_display(party_details["shipping_address_name"]) if doctype: - out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name)) + party_details.update(get_fetch_values(doctype, 'shipping_address_name', party_details.shipping_address_name)) - if doctype and doctype in ['Delivery Note', 'Sales Invoice']: - out.update(get_company_address(company)) - if out.company_address: - out.update(get_fetch_values(doctype, 'company_address', out.company_address)) - get_regional_address_details(out, doctype, company) + if company_address: + party_details.update({'company_address': company_address}) + else: + party_details.update(get_company_address(company)) - elif doctype and doctype == "Purchase Invoice": - out.update(get_company_address(company)) - if out.company_address: - out["shipping_address"] = shipping_address or out["company_address"] - out.shipping_address_display = get_address_display(out["shipping_address"]) - out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address)) - get_regional_address_details(out, doctype, company) + if doctype and doctype in ['Delivery Note', 'Sales Invoice', 'Sales Order']: + if party_details.company_address: + party_details.update(get_fetch_values(doctype, 'company_address', party_details.company_address)) + get_regional_address_details(party_details, doctype, company) - return out.get(billing_address_field), out.shipping_address_name + elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]: + if party_details.company_address: + party_details["shipping_address"] = shipping_address or party_details["company_address"] + party_details.shipping_address_display = get_address_display(party_details["shipping_address"]) + party_details.update(get_fetch_values(doctype, 'shipping_address', party_details.shipping_address)) + get_regional_address_details(party_details, doctype, company) + + return party_details.get(billing_address_field), party_details.shipping_address_name @erpnext.allow_regional -def get_regional_address_details(out, doctype, company): +def get_regional_address_details(party_details, doctype, company): pass -def set_contact_details(out, party, party_type): - out.contact_person = get_default_contact(party_type, party.name) +def set_contact_details(party_details, party, party_type): + party_details.contact_person = get_default_contact(party_type, party.name) - if not out.contact_person: - out.update({ + if not party_details.contact_person: + party_details.update({ "contact_person": None, "contact_display": None, "contact_email": None, @@ -125,22 +130,22 @@ def set_contact_details(out, party, party_type): "contact_department": None }) else: - out.update(get_contact_details(out.contact_person)) + party_details.update(get_contact_details(party_details.contact_person)) -def set_other_values(out, party, party_type): +def set_other_values(party_details, party, party_type): # copy if party_type=="Customer": to_copy = ["customer_name", "customer_group", "territory", "language"] else: to_copy = ["supplier_name", "supplier_group", "language"] for f in to_copy: - out[f] = party.get(f) + party_details[f] = party.get(f) # fields prepended with default in Customer doctype for f in ['currency'] \ + (['sales_partner', 'commission_rate'] if party_type=="Customer" else []): if party.get("default_" + f): - out[f] = party.get("default_" + f) + party_details[f] = party.get("default_" + f) def get_default_price_list(party): """Return default price list for party (Document object)""" @@ -155,7 +160,7 @@ def get_default_price_list(party): return None -def set_price_list(out, party, party_type, given_price_list, pos=None): +def set_price_list(party_details, party, party_type, given_price_list, pos=None): # price list price_list = get_permitted_documents('Price List') @@ -173,9 +178,9 @@ def set_price_list(out, party, party_type, given_price_list, pos=None): price_list = get_default_price_list(party) or given_price_list if price_list: - out.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True) + party_details.price_list_currency = frappe.db.get_value("Price List", price_list, "currency", cache=True) - out["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list + party_details["selling_price_list" if party.doctype=="Customer" else "buying_price_list"] = price_list def set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype): diff --git a/erpnext/buying/doctype/purchase_order/regional/india.js b/erpnext/buying/doctype/purchase_order/regional/india.js new file mode 100644 index 0000000000..42d3995907 --- /dev/null +++ b/erpnext/buying/doctype/purchase_order/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Purchase Order'); \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index ed5bf9b5d8..2a5e6d8f49 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -246,10 +246,10 @@ doc_events = { "on_trash": "erpnext.regional.check_deletion_permission" }, 'Address': { - 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code'] + 'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category'] }, - ('Sales Invoice', 'Purchase Invoice', 'Delivery Note'): { - 'validate': 'erpnext.regional.india.utils.set_place_of_supply' + ('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): { + 'validate': ['erpnext.regional.india.utils.set_place_of_supply'] }, "Contact": { "on_trash": "erpnext.support.doctype.issue.issue.update_issue", diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 053cae0567..e26b1c88a8 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -645,6 +645,7 @@ erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template +erpnext.patches.v12_0.add_export_type_field_in_party_master erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger erpnext.patches.v12_0.update_price_or_product_discount erpnext.patches.v12_0.set_production_capacity_in_workstation diff --git a/erpnext/patches/v12_0/add_export_type_field_in_party_master.py b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py new file mode 100644 index 0000000000..c565b7ecd8 --- /dev/null +++ b/erpnext/patches/v12_0/add_export_type_field_in_party_master.py @@ -0,0 +1,40 @@ +from __future__ import unicode_literals +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + make_custom_fields() + + frappe.reload_doctype('Tax Category') + frappe.reload_doctype('Sales Taxes and Charges Template') + frappe.reload_doctype('Purchase Taxes and Charges Template') + + # Create tax category with inter state field checked + tax_category = frappe.db.get_value('Tax Category', {'name': 'OUT OF STATE'}, 'name') + + if not tax_category: + inter_state_category = frappe.get_doc({ + 'doctype': 'Tax Category', + 'title': 'OUT OF STATE', + 'is_inter_state': 1 + }).insert() + + tax_category = inter_state_category.name + + for doctype in ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template'): + template = frappe.db.get_value(doctype, {'is_inter_state': 1, 'disabled': 0}, ['name']) + if template: + frappe.db.set_value(doctype, template, 'tax_category', tax_category) + + frappe.db.sql(""" + DELETE FROM `tabCustom Field` + WHERE fieldname = 'is_inter_state' + AND dt IN ('Sales Taxes and Charges Template', 'Purchase Taxes and Charges Template') + """) + + diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index 84d2113c06..560a5617da 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -65,7 +65,7 @@ $.extend(erpnext.queries, { frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, frappe.dynamic_link.fieldname, doc.name))])); } - console.log(frappe.dynamic_link) + return { query: 'frappe.contacts.doctype.address.address.address_query', filters: { diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js index a8d3888ba0..99c1b8ae8f 100644 --- a/erpnext/public/js/utils/party.js +++ b/erpnext/public/js/utils/party.js @@ -7,6 +7,21 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { if(!method) { method = "erpnext.accounts.party.get_party_details"; } + + if (args) { + if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { + if (frm.doc.company_address && (!args.company_address)) { + args.company_address = frm.doc.company_address; + } + } + + if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { + if (frm.doc.shipping_address && (!args.shipping_address)) { + args.shipping_address = frm.doc.shipping_address; + } + } + } + if(!args) { if((frm.doctype != "Purchase Order" && frm.doc.customer) || (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) { @@ -30,6 +45,35 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) { }; } + if (in_list(['Sales Invoice', 'Sales Order', 'Delivery Note'], frm.doc.doctype)) { + if (!args) { + args = { + party: frm.doc.customer || frm.doc.party_name, + party_type: 'Customer' + } + } + if (frm.doc.company_address && (!args.company_address)) { + args.company_address = frm.doc.company_address; + } + + if (frm.doc.shipping_address_name &&(!args.shipping_address_name)) { + args.shipping_address_name = frm.doc.shipping_address_name; + } + } + + if (in_list(['Purchase Invoice', 'Purchase Order', 'Purchase Receipt'], frm.doc.doctype)) { + if (!args) { + args = { + party: frm.doc.supplier, + party_type: 'Supplier' + } + } + + if (frm.doc.shipping_address && (!args.shipping_address)) { + args.shipping_address = frm.doc.shipping_address; + } + } + if (args) { args.posting_date = frm.doc.posting_date || frm.doc.transaction_date; } diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py index fef73d9a0a..fa6fb706e9 100644 --- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py +++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py @@ -64,7 +64,8 @@ class TestGSTR3BReport(unittest.TestCase): self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18), self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100), self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250) - self.assertEqual(output["itc_elg"]["itc_avl"][4]["iamt"], 45) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50) + self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50) def make_sales_invoice(): si = create_sales_invoice(company="_Test Company GST", @@ -158,10 +159,18 @@ def create_purchase_invoices(): pi.append("taxes", { "charge_type": "On Net Total", - "account_head": "IGST - _GST", + "account_head": "CGST - _GST", "cost_center": "Main - _GST", - "description": "IGST @ 18.0", - "rate": 18 + "description": "CGST @ 9.0", + "rate": 9 + }) + + pi.append("taxes", { + "charge_type": "On Net Total", + "account_head": "SGST - _GST", + "cost_center": "Main - _GST", + "description": "SGST @ 9.0", + "rate": 9 }) pi.submit() diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py index 46c874b252..0ed98b74ee 100644 --- a/erpnext/regional/india/__init__.py +++ b/erpnext/regional/india/__init__.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from six import iteritems states = [ '', @@ -79,4 +80,6 @@ state_numbers = { "Uttar Pradesh": "09", "Uttarakhand": "05", "West Bengal": "19", -} \ No newline at end of file +} + +number_state_mapping = {v: k for k, v in iteritems(state_numbers)} \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 756c17dc3b..14fdba013c 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -107,7 +107,12 @@ def make_custom_fields(update=True): dict(fieldname='gst_category', label='GST Category', fieldtype='Select', insert_after='gst_section', print_hide=1, options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders', - fetch_from='supplier.gst_category', fetch_if_empty=1) + fetch_from='supplier.gst_category', fetch_if_empty=1), + dict(fieldname='export_type', label='Export Type', + fieldtype='Select', insert_after='gst_category', print_hide=1, + depends_on='eval:in_list(["SEZ", "Overseas"], doc.gst_category)', + options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='supplier.export_type', + fetch_if_empty=1), ] sales_invoice_gst_category = [ @@ -116,20 +121,21 @@ def make_custom_fields(update=True): dict(fieldname='gst_category', label='GST Category', fieldtype='Select', insert_after='gst_section', print_hide=1, options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', - fetch_from='customer.gst_category', fetch_if_empty=1) + fetch_from='customer.gst_category', fetch_if_empty=1), + dict(fieldname='export_type', label='Export Type', + fieldtype='Select', insert_after='gst_category', print_hide=1, + depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', + options='\nWith Payment of Tax\nWithout Payment of Tax', fetch_from='customer.export_type', + fetch_if_empty=1), ] invoice_gst_fields = [ dict(fieldname='invoice_copy', label='Invoice Copy', - fieldtype='Select', insert_after='gst_category', print_hide=1, allow_on_submit=1, + fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1, options='Original for Recipient\nDuplicate for Transporter\nDuplicate for Supplier\nTriplicate for Supplier'), dict(fieldname='reverse_charge', label='Reverse Charge', fieldtype='Select', insert_after='invoice_copy', print_hide=1, options='Y\nN', default='N'), - dict(fieldname='export_type', label='Export Type', - fieldtype='Select', insert_after='reverse_charge', print_hide=1, - depends_on='eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', - options='\nWith Payment of Tax\nWithout Payment of Tax'), dict(fieldname='ecommerce_gstin', label='E-commerce GSTIN', fieldtype='Data', insert_after='export_type', print_hide=1), dict(fieldname='gst_col_break', fieldtype='Column Break', insert_after='ecommerce_gstin'), @@ -142,13 +148,13 @@ def make_custom_fields(update=True): purchase_invoice_gst_fields = [ dict(fieldname='supplier_gstin', label='Supplier GSTIN', fieldtype='Data', insert_after='supplier_address', - fetch_from='supplier_address.gstin', print_hide=1), + fetch_from='supplier_address.gstin', print_hide=1, read_only=1), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='shipping_address_display', - fetch_from='shipping_address.gstin', print_hide=1), + fetch_from='shipping_address.gstin', print_hide=1, read_only=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='shipping_address', - print_hide=1, read_only=0), + print_hide=1, read_only=1), ] purchase_invoice_itc_fields = [ @@ -167,17 +173,17 @@ def make_custom_fields(update=True): sales_invoice_gst_fields = [ dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', - fieldtype='Data', insert_after='customer_address', + fieldtype='Data', insert_after='customer_address', read_only=1, fetch_from='customer_address.gstin', print_hide=1), dict(fieldname='customer_gstin', label='Customer GSTIN', fieldtype='Data', insert_after='shipping_address_name', fetch_from='shipping_address_name.gstin', print_hide=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='customer_gstin', - print_hide=1, read_only=0), + print_hide=1, read_only=1), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='company_address', - fetch_from='company_address.gstin', print_hide=1), + fetch_from='company_address.gstin', print_hide=1, read_only=1), ] sales_invoice_shipping_fields = [ @@ -194,7 +200,11 @@ def make_custom_fields(update=True): inter_state_gst_field = [ dict(fieldname='is_inter_state', label='Is Inter State', - fieldtype='Check', insert_after='disabled', print_hide=1) + fieldtype='Check', insert_after='disabled', print_hide=1), + dict(fieldname='tax_category_column_break', fieldtype='Column Break', + insert_after='is_inter_state'), + dict(fieldname='gst_state', label='Source State', fieldtype='Select', + options='\n'.join(states), insert_after='company') ] ewaybill_fields = [ @@ -374,8 +384,7 @@ def make_custom_fields(update=True): 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields, 'Sales Order': sales_invoice_gst_fields, - 'Sales Taxes and Charges Template': inter_state_gst_field, - 'Purchase Taxes and Charges Template': inter_state_gst_field, + 'Tax Category': inter_state_gst_field, 'Item': [ dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Link', options='GST HSN Code', insert_after='item_group'), @@ -459,6 +468,15 @@ def make_custom_fields(update=True): 'insert_after': 'gst_transporter_id', 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nUIN Holders', 'default': 'Unregistered' + }, + { + 'fieldname': 'export_type', + 'label': 'Export Type', + 'fieldtype': 'Select', + 'insert_after': 'gst_category', + 'default': 'Without Payment of Tax', + 'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)', + 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } ], 'Customer': [ @@ -469,6 +487,15 @@ def make_custom_fields(update=True): 'insert_after': 'customer_type', 'options': 'Registered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders', 'default': 'Unregistered' + }, + { + 'fieldname': 'export_type', + 'label': 'Export Type', + 'fieldtype': 'Select', + 'insert_after': 'gst_category', + 'default': 'Without Payment of Tax', + 'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)', + 'options': '\nWith Payment of Tax\nWithout Payment of Tax' } ] } diff --git a/erpnext/regional/india/taxes.js b/erpnext/regional/india/taxes.js new file mode 100644 index 0000000000..1e59032db1 --- /dev/null +++ b/erpnext/regional/india/taxes.js @@ -0,0 +1,41 @@ +erpnext.setup_auto_gst_taxation = (doctype) => { + frappe.ui.form.on(doctype, { + company_address: function(frm) { + frm.trigger('get_tax_template'); + }, + shipping_address: function(frm) { + frm.trigger('get_tax_template'); + }, + tax_category: function(frm) { + frm.trigger('get_tax_template'); + }, + get_tax_template: function(frm) { + let party_details = { + 'shipping_address': frm.doc.shipping_address || '', + 'shipping_address_name': frm.doc.shipping_address_name || '', + 'customer_address': frm.doc.customer_address || '', + 'customer': frm.doc.customer, + 'supplier': frm.doc.supplier, + 'supplier_gstin': frm.doc.supplier_gstin, + 'company_gstin': frm.doc.company_gstin, + 'tax_category': frm.doc.tax_category + }; + + frappe.call({ + method: 'erpnext.regional.india.utils.get_regional_address_details', + args: { + party_details: JSON.stringify(party_details), + doctype: frm.doc.doctype, + company: frm.doc.company, + return_taxes: 1 + }, + callback: function(r) { + if(r.message) { + frm.set_value('taxes_and_charges', r.message.taxes_and_charges); + } + } + }); + } + }); +}; + diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index aae07797a1..77bcc80aba 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -7,6 +7,8 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_ from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.hr.utils import get_salary_assignment from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip +from erpnext.regional.india import number_state_mapping +from six import string_types def validate_gstin_for_india(doc, method): if hasattr(doc, 'gst_state') and doc.gst_state: @@ -46,6 +48,14 @@ def validate_gstin_for_india(doc, method): frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.") .format(doc.gst_state_number)) +def update_gst_category(doc, method): + for link in doc.links: + if link.link_doctype in ['Customer', 'Supplier']: + if doc.get('gstin'): + frappe.db.sql(""" + UPDATE `tab{0}` SET gst_category = %s WHERE name = %s AND gst_category = 'Unregistered' + """.format(link.link_doctype), ("Registered Regular", link.link_name)) #nosec + def set_gst_state_and_state_number(doc): if not doc.gst_state: if not doc.state: @@ -122,44 +132,106 @@ def test_method(): '''test function''' return 'overridden' -def get_place_of_supply(out, doctype): +def get_place_of_supply(party_details, doctype): if not frappe.get_meta('Address').has_field('gst_state'): return - if doctype in ("Sales Invoice", "Delivery Note"): - address_name = out.shipping_address_name or out.customer_address - elif doctype == "Purchase Invoice": - address_name = out.shipping_address or out.supplier_address + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): + address_name = party_details.shipping_address_name or party_details.customer_address + elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): + address_name = party_details.shipping_address or party_details.supplier_address if address_name: address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1) if address and address.gst_state and address.gst_state_number: return cstr(address.gst_state_number) + "-" + cstr(address.gst_state) -def get_regional_address_details(out, doctype, company): - out.place_of_supply = get_place_of_supply(out, doctype) +@frappe.whitelist() +def get_regional_address_details(party_details, doctype, company, return_taxes=None): - if not out.place_of_supply: return + if isinstance(party_details, string_types): + party_details = json.loads(party_details) + party_details = frappe._dict(party_details) - if doctype in ("Sales Invoice", "Delivery Note"): + party_details.place_of_supply = get_place_of_supply(party_details, doctype) + if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" - if not out.company_gstin: - return - elif doctype == "Purchase Invoice": - master_doctype = "Purchase Taxes and Charges Template" - if not out.supplier_gstin: + + get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') + get_tax_template_based_on_category(master_doctype, company, party_details) + + if party_details.get('taxes_and_charges') and return_taxes: + return party_details + + if not party_details.company_gstin: return - if ((doctype in ("Sales Invoice", "Delivery Note") and out.company_gstin - and out.company_gstin[:2] != out.place_of_supply[:2]) or (doctype == "Purchase Invoice" - and out.supplier_gstin and out.supplier_gstin[:2] != out.place_of_supply[:2])): - default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0}) + elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): + master_doctype = "Purchase Taxes and Charges Template" + + get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') + get_tax_template_based_on_category(master_doctype, company, party_details) + + if party_details.get('taxes_and_charges') and return_taxes: + return party_details + + if not party_details.supplier_gstin: + return + + if not party_details.place_of_supply: return + + if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin + and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", + "Purchase Order", "Purchase Receipt") and party_details.supplier_gstin and party_details.supplier_gstin[:2] != party_details.place_of_supply[:2])): + default_tax = get_tax_template(master_doctype, company, 1, party_details.company_gstin[:2]) else: - default_tax = frappe.db.get_value(master_doctype, {"company": company, "disabled":0, "is_default": 1}) + default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2]) if not default_tax: return - out["taxes_and_charges"] = default_tax - out.taxes = get_taxes_and_charges(master_doctype, default_tax) + party_details["taxes_and_charges"] = default_tax + party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + + if return_taxes: + return party_details + +def get_tax_template_based_on_category(master_doctype, company, party_details): + if not party_details.get('tax_category'): + return + + default_tax = frappe.db.get_value(master_doctype, {'company': company, 'tax_category': party_details.get('tax_category')}, + 'name') + + if default_tax: + party_details["taxes_and_charges"] = default_tax + party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + +def get_tax_template(master_doctype, company, is_inter_state, state_code): + tax_categories = frappe.get_all('Tax Category', fields = ['name', 'is_inter_state', 'gst_state'], + filters = {'is_inter_state': is_inter_state}) + + default_tax = '' + + for tax_category in tax_categories: + if tax_category.gst_state == number_state_mapping[state_code] or \ + (not default_tax and not tax_category.gst_state): + default_tax = frappe.db.get_value(master_doctype, + {'disabled': 0, 'tax_category': tax_category.name}, 'name') + + return default_tax + +def get_tax_template_for_sez(party_details, master_doctype, company, party_type): + + gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))}, + ['gst_category', 'export_type'], as_dict=1) + + if gst_details: + if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax': + default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0, + "gst_state": number_state_mapping[party_details.company_gstin[:2]]}) + + party_details["taxes_and_charges"] = default_tax + party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) + def calculate_annual_eligible_hra_exemption(doc): basic_component = frappe.get_cached_value('Company', doc.company, "basic_component") @@ -555,7 +627,7 @@ def get_gst_accounts(company, account_wise=False): filters={"parent": "GST Settings", "company": company}, fields=["cgst_account", "sgst_account", "igst_account", "cess_account"]) - if not gst_settings_accounts: + if not gst_settings_accounts and not frappe.flags.in_test: frappe.throw(_("Please set GST Accounts in GST Settings")) for d in gst_settings_accounts: diff --git a/erpnext/selling/doctype/sales_order/regional/india.js b/erpnext/selling/doctype/sales_order/regional/india.js new file mode 100644 index 0000000000..c11cfcc50b --- /dev/null +++ b/erpnext/selling/doctype/sales_order/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Sales Order'); diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index e97a4ee461..2112a4174b 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -578,8 +578,12 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): target.run_method("set_po_nos") target.run_method("calculate_taxes_and_totals") - # set company address - target.update(get_company_address(target.company)) + if source.company_address: + target.update({'company_address': source.company_address}) + else: + # set company address + target.update(get_company_address(target.company)) + if target.company_address: target.update(get_fetch_values("Delivery Note", 'company_address', target.company_address)) @@ -645,8 +649,12 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): target.run_method("set_po_nos") target.run_method("calculate_taxes_and_totals") - # set company address - target.update(get_company_address(target.company)) + if source.company_address: + target.update({'company_address': source.company_address}) + else: + # set company address + target.update(get_company_address(target.company)) + if target.company_address: target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address)) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 04e8a83e7f..2eee919b53 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -14,6 +14,8 @@ from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact from frappe.utils.nestedset import NestedSet +import functools + class Company(NestedSet): nsm_parent_field = 'parent_company' @@ -560,3 +562,26 @@ def get_timeline_data(doctype, name): return json.loads(history) if history and '{' in history else {} return date_to_value_dict + +@frappe.whitelist() +def get_default_company_address(name, sort_key='is_primary_address', existing_address=None): + if sort_key not in ['is_shipping_address', 'is_primary_address']: + return None + + out = frappe.db.sql(""" SELECT + addr.name, addr.%s + FROM + `tabAddress` addr, `tabDynamic Link` dl + WHERE + dl.parent = addr.name and dl.link_doctype = 'Company' and + dl.link_name = %s and ifnull(addr.disabled, 0) = 0 + """ %(sort_key, '%s'), (name)) #nosec + + if existing_address: + if existing_address in [d[0] for d in out]: + return existing_address + + if out: + return sorted(out, key = functools.cmp_to_key(lambda x,y: cmp(y[1], x[1])))[0][0] + else: + return None \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index c98dfe35fb..39aad2e007 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -424,7 +424,12 @@ def make_sales_invoice(source_name, target_doc=None): target.run_method("calculate_taxes_and_totals") # set company address - target.update(get_company_address(target.company)) + if source.company_address: + target.update({'company_address': source.company_address}) + else: + # set company address + target.update(get_company_address(target.company)) + if target.company_address: target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address)) diff --git a/erpnext/stock/doctype/delivery_note/regional/india.js b/erpnext/stock/doctype/delivery_note/regional/india.js new file mode 100644 index 0000000000..22f4716ea5 --- /dev/null +++ b/erpnext/stock/doctype/delivery_note/regional/india.js @@ -0,0 +1,4 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Delivery Note'); + diff --git a/erpnext/stock/doctype/purchase_receipt/regional/india.js b/erpnext/stock/doctype/purchase_receipt/regional/india.js new file mode 100644 index 0000000000..b4f1201f36 --- /dev/null +++ b/erpnext/stock/doctype/purchase_receipt/regional/india.js @@ -0,0 +1,3 @@ +{% include "erpnext/regional/india/taxes.js" %} + +erpnext.setup_auto_gst_taxation('Purchase Receipt'); \ No newline at end of file From 664d0d89b53f4a39df3566a4c87694e432c234c3 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 10 Dec 2019 21:32:17 +0530 Subject: [PATCH 080/389] fix: incorrect account mapping for child companies (#19887) * fix: incorrect account mapping for child companies on adding account to parent company * Update account.py --- erpnext/accounts/doctype/account/account.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index cccced8e0b..cf1748f6a7 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -109,12 +109,13 @@ class Account(NestedSet): if not descendants: return parent_acc_name_map = {} - parent_acc_name = frappe.db.get_value('Account', self.parent_account, "account_name") + parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \ + ["account_name", "account_number"]) for d in frappe.db.get_values('Account', - {"company": ["in", descendants], "account_name": parent_acc_name}, + { "company": ["in", descendants], "account_name": parent_acc_name, + "account_number": parent_acc_number }, ["company", "name"], as_dict=True): parent_acc_name_map[d["company"]] = d["name"] - if not parent_acc_name_map: return self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) From c58495dceedad0031fa946abc862c0de37956c26 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 10 Dec 2019 21:34:03 +0530 Subject: [PATCH 081/389] fix: Item-wise Sales History report not working (#19889) --- .../item_wise_sales_history/item_wise_sales_history.js | 4 ++++ .../item_wise_sales_history/item_wise_sales_history.py | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js index ee806a78fb..daca2e3bd0 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.js @@ -20,11 +20,15 @@ frappe.query_reports["Item-wise Sales History"] = { }, { fieldname:"from_date", + reqd: 1, label: __("From Date"), fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), }, { fieldname:"to_date", + reqd: 1, + default: frappe.datetime.get_today(), label: __("To Date"), fieldtype: "Date", }, diff --git a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py index 226c34f735..1fc3663bed 100644 --- a/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py +++ b/erpnext/selling/report/item_wise_sales_history/item_wise_sales_history.py @@ -196,6 +196,7 @@ def get_customer_details(): def get_sales_order_details(company_list, filters): conditions = get_conditions(filters) + return frappe.db.sql(""" SELECT so_item.item_code, so_item.item_name, so_item.item_group, @@ -208,7 +209,6 @@ def get_sales_order_details(company_list, filters): `tabSales Order` so, `tabSales Order Item` so_item WHERE so.name = so_item.parent - AND so.company in (%s) - AND so.docstatus = 1 - {0} - """.format(conditions), company_list, as_dict=1) #nosec + AND so.company in ({0}) + AND so.docstatus = 1 {1} + """.format(','.join(["%s"] * len(company_list)), conditions), tuple(company_list), as_dict=1) From 6aa763221b658453d59b01706ce788ea0914d7a3 Mon Sep 17 00:00:00 2001 From: Pranav Nachnekar Date: Tue, 10 Dec 2019 16:05:28 +0000 Subject: [PATCH 082/389] fix: rename appointment booking route (#19886) * rename appoinment booking route * fix: replace all references to book-appointment route --- erpnext/crm/doctype/appointment/appointment.py | 2 +- erpnext/support/web_form/issues/issues.json | 2 +- .../{book-appointment => book_appointment}/__init__.py | 0 .../www/{book-appointment => book_appointment}/index.css | 0 .../www/{book-appointment => book_appointment}/index.html | 2 +- .../www/{book-appointment => book_appointment}/index.js | 8 ++++---- .../www/{book-appointment => book_appointment}/index.py | 0 .../verify/__init__.py | 0 .../verify/index.html | 0 .../verify/index.py | 0 10 files changed, 7 insertions(+), 7 deletions(-) rename erpnext/www/{book-appointment => book_appointment}/__init__.py (100%) rename erpnext/www/{book-appointment => book_appointment}/index.css (100%) rename erpnext/www/{book-appointment => book_appointment}/index.html (98%) rename erpnext/www/{book-appointment => book_appointment}/index.js (96%) rename erpnext/www/{book-appointment => book_appointment}/index.py (100%) rename erpnext/www/{book-appointment => book_appointment}/verify/__init__.py (100%) rename erpnext/www/{book-appointment => book_appointment}/verify/index.html (100%) rename erpnext/www/{book-appointment => book_appointment}/verify/index.py (100%) diff --git a/erpnext/crm/doctype/appointment/appointment.py b/erpnext/crm/doctype/appointment/appointment.py index 2affba2ac4..b6c4c4707d 100644 --- a/erpnext/crm/doctype/appointment/appointment.py +++ b/erpnext/crm/doctype/appointment/appointment.py @@ -171,7 +171,7 @@ class Appointment(Document): self.save(ignore_permissions=True) def _get_verify_url(self): - verify_route = '/book-appointment/verify' + verify_route = '/book_appointment/verify' params = { 'email': self.customer_email, 'appointment': self.name diff --git a/erpnext/support/web_form/issues/issues.json b/erpnext/support/web_form/issues/issues.json index 652114f738..9b904ad796 100644 --- a/erpnext/support/web_form/issues/issues.json +++ b/erpnext/support/web_form/issues/issues.json @@ -18,7 +18,7 @@ "is_standard": 1, "login_required": 1, "max_attachment_size": 0, - "modified": "2019-06-27 22:58:49.609672", + "modified": "2019-12-10 13:48:19.894186", "modified_by": "Administrator", "module": "Support", "name": "issues", diff --git a/erpnext/www/book-appointment/__init__.py b/erpnext/www/book_appointment/__init__.py similarity index 100% rename from erpnext/www/book-appointment/__init__.py rename to erpnext/www/book_appointment/__init__.py diff --git a/erpnext/www/book-appointment/index.css b/erpnext/www/book_appointment/index.css similarity index 100% rename from erpnext/www/book-appointment/index.css rename to erpnext/www/book_appointment/index.css diff --git a/erpnext/www/book-appointment/index.html b/erpnext/www/book_appointment/index.html similarity index 98% rename from erpnext/www/book-appointment/index.html rename to erpnext/www/book_appointment/index.html index 96774d5656..f242f43ad5 100644 --- a/erpnext/www/book-appointment/index.html +++ b/erpnext/www/book_appointment/index.html @@ -4,7 +4,7 @@ {% block script %} - + {% endblock %} {% block page_content %} diff --git a/erpnext/www/book-appointment/index.js b/erpnext/www/book_appointment/index.js similarity index 96% rename from erpnext/www/book-appointment/index.js rename to erpnext/www/book_appointment/index.js index 13c87ddbcf..c8dd5013d5 100644 --- a/erpnext/www/book-appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -15,10 +15,10 @@ async function initialise_select_date() { async function get_global_variables() { // Using await through this file instead of then. window.appointment_settings = (await frappe.call({ - method: 'erpnext.www.book-appointment.index.get_appointment_settings' + method: 'erpnext.www.book_appointment.index.get_appointment_settings' })).message; window.timezones = (await frappe.call({ - method:'erpnext.www.book-appointment.index.get_timezones' + method:'erpnext.www.book_appointment.index.get_timezones' })).message; window.holiday_list = window.appointment_settings.holiday_list; } @@ -79,7 +79,7 @@ function on_date_or_timezone_select() { async function get_time_slots(date, timezone) { let slots = (await frappe.call({ - method: 'erpnext.www.book-appointment.index.get_appointment_slots', + method: 'erpnext.www.book_appointment.index.get_appointment_slots', args: { date: date, timezone: timezone @@ -201,7 +201,7 @@ async function submit() { } let contact = get_form_data(); let appointment = frappe.call({ - method: 'erpnext.www.book-appointment.index.create_appointment', + method: 'erpnext.www.book_appointment.index.create_appointment', args: { 'date': window.selected_date, 'time': window.selected_time, diff --git a/erpnext/www/book-appointment/index.py b/erpnext/www/book_appointment/index.py similarity index 100% rename from erpnext/www/book-appointment/index.py rename to erpnext/www/book_appointment/index.py diff --git a/erpnext/www/book-appointment/verify/__init__.py b/erpnext/www/book_appointment/verify/__init__.py similarity index 100% rename from erpnext/www/book-appointment/verify/__init__.py rename to erpnext/www/book_appointment/verify/__init__.py diff --git a/erpnext/www/book-appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html similarity index 100% rename from erpnext/www/book-appointment/verify/index.html rename to erpnext/www/book_appointment/verify/index.html diff --git a/erpnext/www/book-appointment/verify/index.py b/erpnext/www/book_appointment/verify/index.py similarity index 100% rename from erpnext/www/book-appointment/verify/index.py rename to erpnext/www/book_appointment/verify/index.py From 9046c13858be493e53be47312fbb3d5c1670cb2d Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Tue, 10 Dec 2019 21:37:27 +0530 Subject: [PATCH 083/389] fix: added extra condition (#19884) --- erpnext/hr/doctype/leave_application/leave_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 65fcbf7a99..915cea149d 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -351,7 +351,7 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): - if self.status != 'Approved': + if self.status != 'Approved' and submit: return expiry_date = get_allocation_expiry(self.employee, self.leave_type, From 5380a4c3db67cf8e69fce0c7c06661c893a4fee4 Mon Sep 17 00:00:00 2001 From: Khushal Trivedi Date: Tue, 10 Dec 2019 21:38:42 +0530 Subject: [PATCH 084/389] fix-education: date of birth validation on student form (#19875) * fix: date validation on inpatient record, else condition removing on clinical prcd templ which is not req * fix:Pricing Rule error AttributeError: 'str' object has no attribute 'get' #19770 * fix:Pricing Rule error AttributeError: 'str' object has no attribute 'get' #19770 * fix: joining and relieving Date can be on same date as valid use case * fix-education: date of birth validation --- erpnext/education/doctype/student/student.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index 76825cec1b..8e4b4e16f9 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -5,12 +5,14 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.utils import getdate,today from frappe import _ from frappe.desk.form.linked_with import get_linked_doctypes from erpnext.education.utils import check_content_completion, check_quiz_completion class Student(Document): def validate(self): self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) + self.validate_dates() if self.student_applicant: self.check_unique() @@ -19,6 +21,10 @@ class Student(Document): if frappe.get_value("Student", self.name, "title") != self.title: self.update_student_name_in_linked_doctype() + def validate_dates(self): + if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()): + frappe.throw(_("Date of Birth cannot be greater than today.")) + def update_student_name_in_linked_doctype(self): linked_doctypes = get_linked_doctypes("Student") for d in linked_doctypes: From 9e32f587f5ab35142c4945ace04ba48718a32335 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Tue, 10 Dec 2019 17:11:05 +0100 Subject: [PATCH 085/389] fix: remove contact info, use international format (#19828) - for international letters, city and country should be upper case: https://www.deutschepost.de/content/dam/dpag/images/B_b/Briefe_ins_Ausland/downloads/dp-brief-international-handlingbroschuere-072019.pdf#page=15 - it is not customary, to use contact information such as phone number in the address --- erpnext/regional/germany/address_template.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/erpnext/regional/germany/address_template.html b/erpnext/regional/germany/address_template.html index 0df786713c..7fa4c32612 100644 --- a/erpnext/regional/germany/address_template.html +++ b/erpnext/regional/germany/address_template.html @@ -1,8 +1,8 @@ {{ address_line1 }}
{% if address_line2 %}{{ address_line2 }}
{% endif -%} -{{ pincode }} {{ city }}
-{% if country %}{{ country }}
{% endif -%} -
-{% if phone %}Tel: {{ phone }}
{% endif -%} -{% if fax %}Fax: {{ fax }}
{% endif -%} -{% if email_id %}E-Mail: {{ email_id }}
{% endif -%} +{% if country in ["Germany", "Deutschland"] %} + {{ pincode }} {{ city }} +{% else %} + {{ pincode }} {{ city | upper }}
+ {{ country | upper }} +{% endif %} From ca6dbad7cb8c4566cd811dd21162035b2279795f Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 11 Dec 2019 12:10:05 +0530 Subject: [PATCH 086/389] fix: Disable Rounded Total always showing field default value --- erpnext/public/js/controllers/buying.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index 02c30587f6..926227b24c 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -30,7 +30,7 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({ && frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")) { var df = frappe.meta.get_docfield(this.frm.doc.doctype, "disable_rounded_total"); - var disable = df.default || cint(frappe.sys_defaults.disable_rounded_total); + var disable = cint(df.default) || cint(frappe.sys_defaults.disable_rounded_total); this.frm.set_value("disable_rounded_total", disable); } From 45e4b73e08e231862b264687d10849b3b9e4864e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 11 Dec 2019 13:07:41 +0530 Subject: [PATCH 087/389] fix: rename fields --- .../quality_action_resolution/quality_action_resolution.json | 2 +- .../quality_procedure_process/quality_procedure_process.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json index 74370cc3ef..a4e6aed86a 100644 --- a/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json +++ b/erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json @@ -13,7 +13,7 @@ "fieldname": "problem", "fieldtype": "Long Text", "in_list_view": 1, - "label": "Problem" + "label": "Review" }, { "fieldname": "sb_00", diff --git a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json index f5c0fbc252..0a67fa505e 100644 --- a/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json +++ b/erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json @@ -18,7 +18,7 @@ "fieldname": "procedure", "fieldtype": "Link", "in_list_view": 1, - "label": "Procedure", + "label": "Child Procedure", "options": "Quality Procedure" } ], From 37cf096102abf4b9520a7a78b4fa2867700d1488 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 11 Dec 2019 13:33:23 +0530 Subject: [PATCH 088/389] fix: not able to cancel the landed cost voucher --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d0fae6a227..ad2f07d084 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -609,7 +609,7 @@ def make_stock_entry(source_name,target_doc=None): def get_item_account_wise_additional_cost(purchase_document): landed_cost_voucher = frappe.get_value("Landed Cost Purchase Receipt", - {"receipt_document": purchase_document}, "parent") + {"receipt_document": purchase_document, "docstatus": 1}, "parent") if not landed_cost_voucher: return From 2ef26fbb0704b6ddd560708584625acca2e437ac Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 11 Dec 2019 14:21:04 +0530 Subject: [PATCH 089/389] fix: empty fname and fcontent of uploaded file --- .../doctype/bank_transaction/bank_transaction_upload.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py index deedafdfb5..33ae45439e 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction_upload.py @@ -15,8 +15,8 @@ def upload_bank_statement(): with open(frappe.uploaded_file, "rb") as upfile: fcontent = upfile.read() else: - from frappe.utils.file_manager import get_uploaded_content - fname, fcontent = get_uploaded_content() + fcontent = frappe.local.uploaded_file + fname = frappe.local.uploaded_filename if frappe.safe_encode(fname).lower().endswith("csv".encode('utf-8')): from frappe.utils.csvutils import read_csv_content From d3605d235400813ae62ef2b346f8e83bcbf7445f Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 11 Dec 2019 15:02:23 +0530 Subject: [PATCH 090/389] fix: issue in javascript timezones --- erpnext/www/book_appointment/index.js | 2 +- erpnext/www/book_appointment/index.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js index c8dd5013d5..5a814c6381 100644 --- a/erpnext/www/book_appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -114,7 +114,7 @@ function get_timeslot_div_layout(timeslot) { timeslot_div.classList.add('unavailable') } timeslot_div.innerHTML = get_slot_layout(start_time); - timeslot_div.id = timeslot.time.substr(11, 20); + timeslot_div.id = timeslot.time.substring(11, 19); timeslot_div.addEventListener('click', select_time); return timeslot_div } diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py index 5b60dd5e7b..e4af7e8e43 100644 --- a/erpnext/www/book_appointment/index.py +++ b/erpnext/www/book_appointment/index.py @@ -90,7 +90,7 @@ def get_available_slots_between(query_start_time, query_end_time, settings): @frappe.whitelist(allow_guest=True) def create_appointment(date, time, tz, contact): - format_string = '%Y-%m-%d %H:%M:%S%z' + format_string = '%Y-%m-%d %H:%M:%S' scheduled_time = datetime.datetime.strptime(date + " " + time, format_string) # Strip tzinfo from datetime objects since it's handled by the doctype scheduled_time = scheduled_time.replace(tzinfo = None) From 5b622eace1d0c3602ecb820cccf6d0c769560e8f Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Wed, 11 Dec 2019 16:08:31 +0530 Subject: [PATCH 091/389] fix: handle scenario with no condition --- erpnext/portal/product_configurator/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 9c0120d416..2220892118 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -335,11 +335,13 @@ def get_items(filters=None, search=None): filter_condition = get_conditions(filters, 'and') - where_conditions = 'disabled = 0 and ' - - where_conditions += ' and '.join( + where_conditions = ' and '.join( [condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition] ) + if where_conditions: + where_conditions += ' and disabled = 0' + else: + where_conditions += 'disabled = 0' left_joins = [] for f in filters: From c6599fd1e21ec04dc11a80a8bd1d7b97bd3a6a33 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 11 Dec 2019 17:29:04 +0530 Subject: [PATCH 092/389] fix: add publihed in hub track item --- .../hub_tracked_item/hub_tracked_item.json | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json index 2e89887dac..7d07ba4093 100644 --- a/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json +++ b/erpnext/hub_node/doctype/hub_tracked_item/hub_tracked_item.json @@ -77,6 +77,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "published", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Published", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -120,7 +152,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-09-10 11:37:35.951019", + "modified": "2019-12-10 11:37:35.951019", "modified_by": "Administrator", "module": "Hub Node", "name": "Hub Tracked Item", From 894c2100272d7d7c9c3b95cdd17978e8f77afd93 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Wed, 11 Dec 2019 17:43:04 +0530 Subject: [PATCH 093/389] fix: enable address without checkout feature * fix add address form country link field --- .../templates/includes/cart/cart_address.html | 39 ++++++++++++------- erpnext/templates/pages/cart.html | 2 - 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html index fe53f34dba..f7f3548320 100644 --- a/erpnext/templates/includes/cart/cart_address.html +++ b/erpnext/templates/includes/cart/cart_address.html @@ -64,16 +64,6 @@ frappe.ready(() => { fieldtype: 'Data', reqd: 1 }, - { - label: __('Address Type'), - fieldname: 'address_type', - fieldtype: 'Select', - options: [ - 'Billing', - 'Shipping' - ], - reqd: 1 - }, { label: __('Address Line 1'), fieldname: 'address_line1', @@ -96,16 +86,37 @@ frappe.ready(() => { fieldname: 'state', fieldtype: 'Data' }, + { + label: __('Country'), + fieldname: 'country', + fieldtype: 'Link', + options: 'Country', + reqd: 1 + }, + { + fieldname: "column_break0", + fieldtype: "Column Break", + width: "50%" + }, + { + label: __('Address Type'), + fieldname: 'address_type', + fieldtype: 'Select', + options: [ + 'Billing', + 'Shipping' + ], + reqd: 1 + }, { label: __('Pin Code'), fieldname: 'pincode', fieldtype: 'Data' }, { - label: __('Country'), - fieldname: 'country', - fieldtype: 'Link', - reqd: 1 + fieldname: "phone", + fieldtype: "Data", + label: "Phone" }, ], primary_action_label: __('Save'), diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html index b301fc0665..912702e386 100644 --- a/erpnext/templates/pages/cart.html +++ b/erpnext/templates/pages/cart.html @@ -83,12 +83,10 @@ {% endif %} - {% if cart_settings.enable_checkout %}
{% include "templates/includes/cart/cart_address.html" %}
{% endif %} - {% endif %}
From 107989316c7301c9475329f9a9eac81712743f2f Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Wed, 11 Dec 2019 18:15:54 +0530 Subject: [PATCH 094/389] fix: additional notes from Quotations not saved in SO --- .../sales_order_item/sales_order_item.json | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json index 86b09c2814..aad37d321e 100644 --- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json +++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json @@ -77,10 +77,12 @@ "ordered_qty", "planned_qty", "column_break_69", - "delivered_qty", "work_order_qty", + "delivered_qty", "produced_qty", "returned_qty", + "shopping_cart_section", + "additional_notes", "section_break_63", "page_break", "item_tax_rate", @@ -746,15 +748,20 @@ "label": "Image" }, { - "default": "0", - "fieldname": "against_blanket_order", - "fieldtype": "Check", - "label": "Against Blanket Order" + "collapsible": 1, + "fieldname": "shopping_cart_section", + "fieldtype": "Section Break", + "label": "Shopping Cart" + }, + { + "fieldname": "additional_notes", + "fieldtype": "Text", + "label": "Additional Notes" } ], "idx": 1, "istable": 1, - "modified": "2019-11-19 14:19:29.491945", + "modified": "2019-12-11 18:06:26.238169", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order Item", From 9b0d7b93dd8c3e8a4d33a986fee256a8ee449699 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 12 Dec 2019 12:10:25 +0530 Subject: [PATCH 095/389] fix: not able to submit the landed cost voucher --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 8e34a8a479..060175f904 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -245,7 +245,7 @@ class PurchaseReceipt(BuyingController): negative_expense_to_be_booked += flt(d.item_tax_amount) # Amount added through landed-cost-voucher - if landed_cost_entries: + if d.landed_cost_voucher_amount and landed_cost_entries: for account, amount in iteritems(landed_cost_entries[(d.item_code, d.name)]): gl_entries.append(self.get_gl_dict({ "account": account, @@ -622,8 +622,7 @@ def get_item_account_wise_additional_cost(purchase_document): based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on) for item in landed_cost_voucher_doc.items: - if item.receipt_document == purchase_document: - total_item_cost += item.get(based_on_field) + total_item_cost += item.get(based_on_field) for item in landed_cost_voucher_doc.items: if item.receipt_document == purchase_document: From f95267041da8b4c6ebda77ab9ffaa1d70e8dba23 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 12 Dec 2019 13:06:17 +0530 Subject: [PATCH 096/389] fix: Financial statement report - Hidden column should note be considered in the report - Remove hardcoded currency formatting - Remove duplicate letterhead in the report (print_template already adds one) - Remove extra quotes from Total Amount text --- .../accounts/report/financial_statements.html | 34 +++++++++++-------- .../accounts/report/financial_statements.py | 4 +-- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/erpnext/accounts/report/financial_statements.html b/erpnext/accounts/report/financial_statements.html index 4081723bf0..50947ecf5e 100644 --- a/erpnext/accounts/report/financial_statements.html +++ b/erpnext/accounts/report/financial_statements.html @@ -1,5 +1,6 @@ {% var report_columns = report.get_columns_for_print(); + report_columns = report_columns.filter(col => !col.hidden); if (report_columns.length > 8) { frappe.throw(__("Too many columns. Export the report and print it using a spreadsheet application.")); @@ -15,34 +16,35 @@ height: 37px; } -{% var letterhead= filters.letter_head || (frappe.get_doc(":Company", filters.company) && frappe.get_doc(":Company", filters.company).default_letter_head) %} -{% if(letterhead) { %} -
- {%= frappe.boot.letter_heads[letterhead].header %} -
-{% } %} +

{%= __(report.report_name) %}

{%= filters.company %}

+ {% if 'cost_center' in filters %}

{%= filters.cost_center %}

{% endif %} +

{%= filters.fiscal_year %}

-
{%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
+
+ {%= __("Currency") %} : {%= filters.presentation_currency || erpnext.get_currency(filters.company) %} +
{% if (filters.from_date) { %} -

{%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %}

+
+ {%= frappe.datetime.str_to_user(filters.from_date) %} - {%= frappe.datetime.str_to_user(filters.to_date) %} +
{% } %}
- - {% for(var i=2, l=report_columns.length; i + {% for (let i=1, l=report_columns.length; i{%= report_columns[i].label %} {% } %} - {% for(var j=0, k=data.length-1; j {%= row.account_name %} - {% for(var i=2, l=report_columns.length; i - {% var fieldname = report_columns[i].fieldname; %} + {% const fieldname = report_columns[i].fieldname; %} {% if (!is_null(row[fieldname])) { %} - {%= format_currency(row[fieldname], filters.presentation_currency) %} + {%= frappe.format(row[fieldname], report_columns[i], {}, row) %} {% } %} {% } %} @@ -64,4 +66,6 @@ {% } %}
-

Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

+

+ Printed On {%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %} +

diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 3c8de6026a..40d5682726 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -264,8 +264,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False def add_total_row(out, root_type, balance_must_be, period_list, company_currency): total_row = { - "account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", - "account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'", + "account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), + "account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)), "currency": company_currency } From c58dc873d501b38edbc41c6100e60b277fe04d84 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 12 Dec 2019 14:55:57 +0530 Subject: [PATCH 097/389] fix: Get regional address details fix --- erpnext/regional/india/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 77bcc80aba..0f9156a6b4 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -179,6 +179,8 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N if not party_details.place_of_supply: return + if not party_details.company_gstin: return + if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt") and party_details.supplier_gstin and party_details.supplier_gstin[:2] != party_details.place_of_supply[:2])): From 0e33f792d3aae99c781e804b5fc27ceb497ad5eb Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 12 Dec 2019 16:34:41 +0530 Subject: [PATCH 098/389] fix: Distribute charges based on quantity if Total Basic Amount is Zero. --- .../stock/doctype/stock_entry/stock_entry.py | 12 +++--- .../doctype/stock_entry/test_stock_entry.py | 39 +++++++++++++------ 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 913656ad02..00d27ef232 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -27,7 +27,6 @@ class IncorrectValuationRateError(frappe.ValidationError): pass class DuplicateEntryForWorkOrderError(frappe.ValidationError): pass class OperationsNotCompleteError(frappe.ValidationError): pass class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass -class TotalBasicAmountZeroError(frappe.ValidationError): pass from erpnext.controllers.stock_controller import StockController @@ -650,11 +649,11 @@ class StockEntry(StockController): gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account) total_basic_amount = sum([flt(t.basic_amount) for t in self.get("items") if t.t_warehouse]) + divide_based_on = total_basic_amount if self.get("additional_costs") and not total_basic_amount: - #If additional costs table is populated and total basic amount is - #somehow 0, interrupt transaction. - frappe.throw(_("Total Basic Amount in Items Table cannot be 0"), TotalBasicAmountZeroError) + # if total_basic_amount is 0, distribute additional charges based on qty + divide_based_on = sum(item.qty for item in list(self.get("items"))) item_account_wise_additional_cost = {} @@ -663,8 +662,11 @@ class StockEntry(StockController): if d.t_warehouse: item_account_wise_additional_cost.setdefault((d.item_code, d.name), {}) item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, 0.0) + + multiply_based_on = d.basic_amount if total_basic_amount else d.qty + item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account] += \ - (t.amount * d.basic_amount) / total_basic_amount + (t.amount * multiply_based_on) / divide_based_on if item_account_wise_additional_cost: for d in self.get("items"): diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index c5e67092d3..ee5f237098 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -16,7 +16,6 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import OpeningEntryAccountError -from erpnext.stock.doctype.stock_entry.stock_entry import TotalBasicAmountZeroError from six import iteritems def get_sle(**args): @@ -798,14 +797,26 @@ class TestStockEntry(unittest.TestCase): "posting_date": nowdate(), "company":"_Test Company with perpetual inventory", "items":[ - {"item_code":"Basil Leaves", - "description":"Basil Leaves", - "qty": 1, - "basic_rate": 0, - "uom":"Nos", - "t_warehouse": "Stores - TCP1", - "allow_zero_valuation_rate": 1, - "cost_center": "Main - TCP1"} + { + "item_code":"Basil Leaves", + "description":"Basil Leaves", + "qty": 1, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1" + }, + { + "item_code":"Basil Leaves", + "description":"Basil Leaves", + "qty": 2, + "basic_rate": 0, + "uom":"Nos", + "t_warehouse": "Stores - TCP1", + "allow_zero_valuation_rate": 1, + "cost_center": "Main - TCP1" + }, ], "additional_costs":[ {"expense_account":"Miscellaneous Expenses - TCP1", @@ -813,9 +824,15 @@ class TestStockEntry(unittest.TestCase): "description": "miscellanous"} ] }) - se.insert() - self.assertRaises(TotalBasicAmountZeroError, se.submit) + se.submit() + + self.check_gl_entries("Stock Entry", se.name, + sorted([ + ["Stock Adjustment - TCP1", 100.0, 0.0], + ["Miscellaneous Expenses - TCP1", 0.0, 100.0] + ]) + ) def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): se = frappe.copy_doc(test_records[0]) From 81990aee9ff7fc385e3a6eb5a2e124f081d19829 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 13 Dec 2019 13:12:10 +0530 Subject: [PATCH 099/389] fix: Removed 'manufacturers' table from Item Master --- erpnext/stock/doctype/item/item.json | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index a2aab3f69e..af8e13288a 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -135,8 +135,7 @@ "publish_in_hub", "hub_category_to_publish", "hub_warehouse", - "synced_with_hub", - "manufacturers" + "synced_with_hub" ], "fields": [ { @@ -1016,12 +1015,6 @@ "label": "Synced With Hub", "read_only": 1 }, - { - "fieldname": "manufacturers", - "fieldtype": "Table", - "label": "Manufacturers", - "options": "Item Manufacturer" - }, { "depends_on": "eval:!doc.__islocal", "fieldname": "over_delivery_receipt_allowance", @@ -1049,7 +1042,7 @@ "idx": 2, "image_field": "image", "max_attachments": 1, - "modified": "2019-10-09 17:05:59.576119", + "modified": "2019-12-13 12:15:56.197246", "modified_by": "Administrator", "module": "Stock", "name": "Item", From 8c9c6ec919570ce73432286b563e2f930b4f3212 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Fri, 13 Dec 2019 13:47:15 +0530 Subject: [PATCH 100/389] fix: Price rule filtering fix --- erpnext/accounts/doctype/pricing_rule/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 637e503e65..7af6748254 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -284,7 +284,7 @@ def filter_pricing_rules_for_qty_amount(qty, rate, pricing_rules, args=None): status = True # if user has created item price against the transaction UOM - if rule.get("uom") == args.get("uom"): + if args and rule.get("uom") == args.get("uom"): conversion_factor = 1.0 if status and (flt(rate) >= (flt(rule.min_amt) * conversion_factor) From 4bea44b9c93df4aa42d7362bac912b91489a777e Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 13 Dec 2019 15:05:43 +0530 Subject: [PATCH 101/389] fix: update already existing items --- erpnext/hub_node/api.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 3a5b06fdc4..06ad222034 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -119,6 +119,7 @@ def get_valid_items(search_value=''): @frappe.whitelist() def publish_selected_items(items_to_publish): items_to_publish = json.loads(items_to_publish) + items_to_update = [] if not len(items_to_publish): frappe.throw(_('No items to publish')) @@ -126,14 +127,24 @@ def publish_selected_items(items_to_publish): item_code = item.get('item_code') frappe.db.set_value('Item', item_code, 'publish_in_hub', 1) - frappe.get_doc({ + hub_dict = { 'doctype': 'Hub Tracked Item', 'item_code': item_code, + 'published': 1, 'hub_category': item.get('hub_category'), 'image_list': item.get('image_list') - }).insert(ignore_if_duplicate=True) + } + if frappe.db.exists('Hub Tracked Item', item_code): + items_to_update.append(item) + hub_tracked_item = frappe.get_doc('Hub Tracked Item', item_code) + hub_tracked_item.update(hub_dict) + hub_tracked_items.save() + else: + frappe.get_doc(hub_dict).insert(ignore_if_duplicate=True) - items = map_fields(items_to_publish) + items_to_publish = list(filter(lambda x: x not in items_to_update, items_to_publish)) + new_items = map_fields(items_to_publish) + existing_items = map_fields(items_to_update) try: item_sync_preprocess(len(items)) @@ -141,7 +152,8 @@ def publish_selected_items(items_to_publish): # TODO: Publish Progress connection = get_hub_connection() - connection.insert_many(items) + connection.insert_many(new_items) + connection.bulk_update(existing_items) item_sync_postprocess() except Exception as e: From bfc43d3b15d3dba788eeef5ad7d98777b0717a0b Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Fri, 13 Dec 2019 15:09:51 +0530 Subject: [PATCH 102/389] fix: gl entries doesn't filter based on debit precision --- erpnext/accounts/general_ledger.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index feb598a2e5..bb1b7e392d 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -90,8 +90,12 @@ def merge_similar_entries(gl_map): else: merged_gl_map.append(entry) + company = gl_map[0].company if gl_map else erpnext.get_default_company() + company_currency = erpnext.get_company_currency(company) + precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"), company_currency) + # filter zero debit and credit entries - merged_gl_map = filter(lambda x: flt(x.debit, 9)!=0 or flt(x.credit, 9)!=0, merged_gl_map) + merged_gl_map = filter(lambda x: flt(x.debit, precision)!=0 or flt(x.credit, precision)!=0, merged_gl_map) merged_gl_map = list(merged_gl_map) return merged_gl_map From 5da7e729a59db35c3616a56b7fc833d595cb6169 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Fri, 13 Dec 2019 15:44:39 +0530 Subject: [PATCH 103/389] fix: remove mandatory purchase reference for existing asset --- erpnext/assets/doctype/asset/asset.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 6b3f2c777c..f6a7fa20d0 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -144,6 +144,10 @@ frappe.ui.form.on('Asset', { frm.set_df_property('purchase_invoice', 'read_only', 1); frm.set_df_property('purchase_receipt', 'read_only', 1); } + else if (frm.doc.is_existing_asset) { + frm.toggle_reqd('purchase_receipt', 0); + frm.toggle_reqd('purchase_invoice', 0); + } else if (frm.doc.purchase_receipt) { // if purchase receipt link is set then set PI disabled frm.toggle_reqd('purchase_invoice', 0); @@ -256,6 +260,7 @@ frappe.ui.form.on('Asset', { }, is_existing_asset: function(frm) { + frm.trigger("toggle_reference_doc"); // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); }, From 94ff8058acf448f4f7f60f8ea1535c1408676c4d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sat, 14 Dec 2019 21:25:30 +0530 Subject: [PATCH 104/389] fix: Add missing import --- erpnext/patches/v12_0/set_gst_category.py | 2 ++ erpnext/setup/doctype/company/company.py | 1 + 2 files changed, 3 insertions(+) diff --git a/erpnext/patches/v12_0/set_gst_category.py b/erpnext/patches/v12_0/set_gst_category.py index 54bc5b3c74..55bbdee7ed 100644 --- a/erpnext/patches/v12_0/set_gst_category.py +++ b/erpnext/patches/v12_0/set_gst_category.py @@ -7,6 +7,8 @@ def execute(): if not company: return + frappe.reload_doc('accounts', 'doctype', 'Tax Category') + make_custom_fields() for doctype in ['Sales Invoice', 'Purchase Invoice']: diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 2eee919b53..ff3515485c 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -14,6 +14,7 @@ from frappe.model.document import Document from frappe.contacts.address_and_contact import load_address_and_contact from frappe.utils.nestedset import NestedSet +from past.builtins import cmp import functools class Company(NestedSet): From 40b7ac4987cfd76e8226e924a8bf098592262662 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 15 Dec 2019 21:20:55 +0530 Subject: [PATCH 105/389] fix: Minor filter fix in expense claim type --- erpnext/hr/doctype/expense_claim/expense_claim.js | 6 +++--- erpnext/hr/doctype/expense_claim/expense_claim.json | 5 ++++- erpnext/hr/doctype/expense_claim_type/expense_claim_type.js | 6 +++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.js b/erpnext/hr/doctype/expense_claim/expense_claim.js index 570f2ef4c7..e0bfc83a9b 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.js +++ b/erpnext/hr/doctype/expense_claim/expense_claim.js @@ -243,11 +243,11 @@ frappe.ui.form.on("Expense Claim", { update_employee_advance_claimed_amount: function(frm) { let amount_to_be_allocated = frm.doc.grand_total; - $.each(frm.doc.advances || [], function(i, advance){ - if (amount_to_be_allocated >= advance.unclaimed_amount){ + $.each(frm.doc.advances || [], function(i, advance) { + if (amount_to_be_allocated >= advance.unclaimed_amount) { frm.doc.advances[i].allocated_amount = frm.doc.advances[i].unclaimed_amount; amount_to_be_allocated -= advance.allocated_amount; - } else{ + } else { frm.doc.advances[i].allocated_amount = amount_to_be_allocated; amount_to_be_allocated = 0; } diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json index b5b6823e1c..96baaab595 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.json +++ b/erpnext/hr/doctype/expense_claim/expense_claim.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-01-10 16:34:14", @@ -43,6 +44,7 @@ "accounting_dimensions_section", "project", "dimension_col_break", + "cost_center", "more_details", "status", "amended_from", @@ -365,7 +367,8 @@ "icon": "fa fa-money", "idx": 1, "is_submittable": 1, - "modified": "2019-11-09 14:13:08.964547", + "links": [], + "modified": "2019-12-14 23:52:05.388458", "modified_by": "Administrator", "module": "HR", "name": "Expense Claim", diff --git a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js index c487797677..d007e1a6c2 100644 --- a/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js +++ b/erpnext/hr/doctype/expense_claim_type/expense_claim_type.js @@ -2,10 +2,10 @@ // License: GNU General Public License v3. See license.txt frappe.ui.form.on("Expense Claim Type", { - refresh: function(frm){ - frm.fields_dict["accounts"].grid.get_field("default_account").get_query = function(frm, cdt, cdn){ + refresh: function(frm) { + frm.fields_dict["accounts"].grid.get_field("default_account").get_query = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; - return{ + return { filters: { "is_group": 0, "root_type": frm.doc.deferred_expense_account ? "Asset" : "Expense", From b223dd95e2ce03af052dc4b954a2070312de30bf Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 15 Dec 2019 21:21:31 +0530 Subject: [PATCH 106/389] fix: Dashboard for employee advance doctype --- .../employee_advance_dashboard.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py diff --git a/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py new file mode 100644 index 0000000000..c3b4a3a889 --- /dev/null +++ b/erpnext/hr/doctype/employee_advance/employee_advance_dashboard.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'employee_advance', + 'non_standard_fieldnames': { + 'Payment Entry': 'reference_name', + 'Journal Entry': 'reference_name' + }, + 'transactions': [ + { + 'items': ['Expense Claim'] + }, + { + 'items': ['Payment Entry', 'Journal Entry'] + } + ] + } From c05bd1964f066cc965a1d2facb1228bd8f618263 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Sun, 15 Dec 2019 21:22:28 +0530 Subject: [PATCH 107/389] feat: Capture return amount against Employee advance via Journal Entry --- .../doctype/journal_entry/journal_entry.js | 1 - .../employee_advance/employee_advance.js | 2 +- .../employee_advance/employee_advance.json | 890 ++++-------------- .../employee_advance/employee_advance.py | 14 + .../hr/doctype/expense_claim/expense_claim.py | 2 +- 5 files changed, 199 insertions(+), 710 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index d6236cdb04..3604b60b75 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -190,7 +190,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({ if(jvd.reference_type==="Employee Advance") { return { filters: { - 'status': ['=', 'Unpaid'], 'docstatus': 1 } }; diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index 77a2bbcbc0..69915fa6e9 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', { } else if ( frm.doc.docstatus === 1 - && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) + && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount) && frappe.model.can_create("Expense Claim") ) { frm.add_custom_button( diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json index 3597e76f1e..a4e4676547 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.json +++ b/erpnext/hr/doctype/employee_advance/employee_advance.json @@ -1,737 +1,213 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2017-10-09 14:26:29.612365", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2017-10-09 14:26:29.612365", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "column_break_4", + "posting_date", + "department", + "section_break_8", + "purpose", + "column_break_11", + "advance_amount", + "paid_amount", + "due_advance_amount", + "claimed_amount", + "return_amount", + "section_break_7", + "status", + "company", + "amended_from", + "column_break_18", + "advance_account", + "mode_of_payment" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "HR-EAD-.YYYY.-", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "HR-EAD-.YYYY.-" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_name", - "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Read Only", + "label": "Employee Name" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_4", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Posting Date", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Posting Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_8", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_8", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purpose", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Purpose", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "purpose", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Purpose", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "advance_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Advance Amount", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "advance_amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Advance Amount", + "options": "Company:company:default_currency", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "paid_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Paid Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "paid_amount", + "fieldtype": "Currency", + "label": "Paid Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:cur_frm.doc.employee", - "fieldname": "due_advance_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Due Advance Amount", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:cur_frm.doc.employee", + "fieldname": "due_advance_amount", + "fieldtype": "Currency", + "label": "Due Advance Amount", + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "claimed_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Claimed Amount", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "claimed_amount", + "fieldtype": "Currency", + "label": "Claimed Amount", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_7", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "no_copy": 1, + "options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Employee Advance", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Advance", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_18", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "advance_account", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Advance Account", - "length": 0, - "no_copy": 0, - "options": "Account", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "advance_account", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Advance Account", + "options": "Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mode_of_payment", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mode of Payment", - "length": 0, - "no_copy": 0, - "options": "Mode of Payment", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "mode_of_payment", + "fieldtype": "Link", + "label": "Mode of Payment", + "options": "Mode of Payment" + }, + { + "fieldname": "return_amount", + "fieldtype": "Currency", + "label": "Return Amount", + "options": "Company:company:default_currency", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-30 11:28:15.529649", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Advance", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "links": [], + "modified": "2019-12-15 19:04:07.044505", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Advance", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Expense Approver", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Expense Approver", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "employee,employee_name", - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "employee,employee_name", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 7813da78ca..674e46438b 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -53,11 +53,25 @@ class EmployeeAdvance(Document): and party = %s """, (self.name, self.employee), as_dict=1)[0].paid_amount + return_amount = frappe.db.sql(""" + select name, ifnull(sum(credit_in_account_currency), 0) as return_amount + from `tabGL Entry` + where against_voucher_type = 'Employee Advance' + and voucher_type != 'Expense Claim' + and against_voucher = %s + and party_type = 'Employee' + and party = %s + """, (self.name, self.employee), as_dict=1)[0].return_amount + if flt(paid_amount) > self.advance_amount: frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"), EmployeeAdvanceOverPayment) + if flt(return_amount) > self.paid_amount - self.claimed_amount: + frappe.throw(_("Return amount cannot be greater unclaimed amount")) + self.db_set("paid_amount", paid_amount) + self.db_set("return_amount", return_amount) self.set_status() frappe.db.set_value("Employee Advance", self.name , "status", self.status) diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index dfb0bb96d8..9aeb7e8498 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -322,7 +322,7 @@ def get_expense_claim_account(expense_claim_type, company): @frappe.whitelist() def get_advances(employee, advance_id=None): if not advance_id: - condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount'.format(frappe.db.escape(employee)) + condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount'.format(frappe.db.escape(employee)) else: condition = 'name={0}'.format(frappe.db.escape(advance_id)) From 21fe97e72373cfae21572de39fa37a39732679c5 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 13 Dec 2019 16:18:38 +0530 Subject: [PATCH 108/389] fix: pricing rule not working for production discount --- .../doctype/pricing_rule/pricing_rule.json | 9 ++- .../doctype/pricing_rule/pricing_rule.py | 12 ++-- .../accounts/doctype/pricing_rule/utils.py | 57 +++++++++++++------ erpnext/controllers/accounts_controller.py | 4 +- erpnext/controllers/buying_controller.py | 8 +-- erpnext/public/js/controllers/transaction.js | 18 ++++++ 6 files changed, 76 insertions(+), 32 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 971d308368..f73fb10d32 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:title", @@ -439,19 +440,20 @@ }, { "default": "0", - "depends_on": "eval:!doc.mixed_conditions", + "depends_on": "eval:!doc.mixed_conditions && doc.price_or_product_discount == 'Price'", "fieldname": "same_item", "fieldtype": "Check", "label": "Same Item" }, { - "depends_on": "eval:!doc.same_item || doc.mixed_conditions", + "depends_on": "eval:(!doc.same_item || doc.apply_on == 'Transaction') || doc.mixed_conditions", "fieldname": "free_item", "fieldtype": "Link", "label": "Free Item", "options": "Item" }, { + "default": "0", "fieldname": "free_qty", "fieldtype": "Float", "label": "Qty" @@ -554,7 +556,8 @@ ], "icon": "fa fa-gift", "idx": 1, - "modified": "2019-10-15 12:39:40.399792", + "links": [], + "modified": "2019-12-13 15:48:48.331495", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index e871d98af6..af6e4c87af 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -182,7 +182,7 @@ def get_serial_no_for_item(args): def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules, - get_applied_pricing_rules, get_pricing_rule_items) + get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule) if isinstance(doc, string_types): doc = json.loads(doc) @@ -241,9 +241,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if pricing_rule.coupon_code_based==1 and args.coupon_code==None: return item_details - if (not pricing_rule.validate_applied_rule and - pricing_rule.price_or_product_discount == "Price"): - apply_price_discount_pricing_rule(pricing_rule, item_details, args) + if not pricing_rule.validate_applied_rule: + if pricing_rule.price_or_product_discount == "Price": + apply_price_discount_rule(pricing_rule, item_details, args) + else: + get_product_discount_rule(pricing_rule, item_details) item_details.has_pricing_rule = 1 @@ -293,7 +295,7 @@ def get_pricing_rule_details(args, pricing_rule): 'child_docname': args.get('child_docname') }) -def apply_price_discount_pricing_rule(pricing_rule, item_details, args): +def apply_price_discount_rule(pricing_rule, item_details, args): item_details.pricing_rule_for = pricing_rule.rate_or_discount if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 637e503e65..346eba259b 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe, copy, json from frappe import throw, _ from six import string_types -from frappe.utils import flt, cint, get_datetime +from frappe.utils import flt, cint, get_datetime, get_link_to_form from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor @@ -408,7 +408,8 @@ def apply_pricing_rule_on_transaction(doc): conditions = get_other_conditions(conditions, values, doc) pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` - where {conditions} """.format(conditions = conditions), values, as_dict=1) + where {conditions} and `tabPricing Rule`.disable = 0 + """.format(conditions = conditions), values, as_dict=1) if pricing_rules: pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, @@ -420,39 +421,59 @@ def apply_pricing_rule_on_transaction(doc): doc.set('apply_discount_on', d.apply_discount_on) for field in ['additional_discount_percentage', 'discount_amount']: - if not d.get(field): continue - pr_field = ('discount_percentage' if field == 'additional_discount_percentage' else field) + if not d.get(pr_field): continue + if d.validate_applied_rule and doc.get(field) < d.get(pr_field): frappe.msgprint(_("User has not applied rule on the invoice {0}") .format(doc.name)) else: doc.set(field, d.get(pr_field)) + + doc.calculate_taxes_and_totals() elif d.price_or_product_discount == 'Product': - apply_pricing_rule_for_free_items(doc, d) + item_details = frappe._dict() + get_product_discount_rule(d, item_details) + apply_pricing_rule_for_free_items(doc, item_details.free_item_data) + doc.set_missing_values() def get_applied_pricing_rules(item_row): return (item_row.get("pricing_rules").split(',') if item_row.get("pricing_rules") else []) -def apply_pricing_rule_for_free_items(doc, pricing_rule): - if pricing_rule.get('free_item'): +def get_product_discount_rule(pricing_rule, item_details): + free_item = (pricing_rule.free_item + if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code) + + if not free_item: + frappe.throw(_("Free item not set in the pricing rule {0}") + .format(get_link_to_form("Pricing Rule", pricing_rule.name))) + + item_details.free_item_data = { + 'item_code': free_item, + 'qty': pricing_rule.free_qty or 1, + 'rate': pricing_rule.free_item_rate or 0, + 'price_list_rate': pricing_rule.free_item_rate or 0, + 'is_free_item': 1 + } + + item_data = frappe.get_cached_value('Item', free_item, ['item_name', + 'description', 'stock_uom'], as_dict=1) + + item_details.free_item_data.update(item_data) + item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom + item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, + item_details.free_item_data['uom']).get("conversion_factor", 1) + +def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): + if pricing_rule_args.get('item_code'): items = [d.item_code for d in doc.items - if d.item_code == (d.item_code - if pricing_rule.get('same_item') else pricing_rule.get('free_item')) and d.is_free_item] + if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item] if not items: - doc.append('items', { - 'item_code': pricing_rule.get('free_item'), - 'qty': pricing_rule.get('free_qty'), - 'uom': pricing_rule.get('free_item_uom'), - 'rate': pricing_rule.get('free_item_rate') or 0, - 'is_free_item': 1 - }) - - doc.set_missing_values() + doc.append('items', pricing_rule_args) def get_pricing_rule_items(pr_doc): apply_on_data = [] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 75564afe59..6150516ac8 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -319,8 +319,8 @@ class AccountsController(TransactionBase): if item.get('discount_amount'): item.rate = item.price_list_rate - item.discount_amount - elif pricing_rule_args.get('free_item'): - apply_pricing_rule_for_free_items(self, pricing_rule_args) + elif pricing_rule_args.get('free_item_data'): + apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) elif pricing_rule_args.get("validate_applied_rule"): for pricing_rule in get_applied_pricing_rules(item): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 3ec7aff9cb..17fba8ec4f 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -735,10 +735,6 @@ class BuyingController(StockController): if not self.get("items"): return - earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) - if earliest_schedule_date: - self.schedule_date = earliest_schedule_date - if self.schedule_date: for d in self.get('items'): if not d.schedule_date: @@ -750,6 +746,10 @@ class BuyingController(StockController): else: frappe.throw(_("Please enter Reqd by Date")) + earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) + if earliest_schedule_date: + self.schedule_date = earliest_schedule_date + def validate_items(self): # validate items to see if they have is_purchase_item or is_subcontracted_item enabled if self.doctype=="Material Request": return diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 46a58fba7c..1be4f27289 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1305,6 +1305,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name)); } + if (d.free_item_data) { + me.apply_product_discount(d.free_item_data); + } + if (d.apply_rule_on_other_items) { items_rule_dict[d.name] = d; } @@ -1334,6 +1338,20 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ } }, + apply_product_discount: function(free_item_data) { + const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code + && d.is_free_item)) || []; + + if (!items.length) { + let row_to_modify = frappe.model.add_child(this.frm.doc, + this.frm.doc.doctype + ' Item', 'items'); + + for (let key in free_item_data) { + row_to_modify[key] = free_item_data[key]; + } + } + }, + apply_price_list: function(item, reset_plc_conversion) { // We need to reset plc_conversion_rate sometimes because the call to // `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value From 5ab823beab4d3b7e1f7093ec696984cf98d651ca Mon Sep 17 00:00:00 2001 From: marination Date: Mon, 16 Dec 2019 12:08:41 +0530 Subject: [PATCH 109/389] fix: Removed validation from non existent manufacturers table --- erpnext/stock/doctype/item/item.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 189261cb2d..151be110fc 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -125,7 +125,6 @@ class Item(WebsiteGenerator): self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() self.update_show_in_website() - self.validate_manufacturer() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -145,13 +144,6 @@ class Item(WebsiteGenerator): if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')): self.description = clean_html(self.description) - def validate_manufacturer(self): - list_man = [(x.manufacturer, x.manufacturer_part_no) for x in self.get('manufacturers')] - set_man = set(list_man) - - if len(list_man) != len(set_man): - frappe.throw(_("Duplicate entry in Manufacturers table")) - def validate_customer_provided_part(self): if self.is_customer_provided_item: if self.is_purchase_item: From 8bd84b28cae9cd513b3374dadad3c82202c12591 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Dec 2019 12:29:07 +0530 Subject: [PATCH 110/389] fix: schedule date --- .../accounts/doctype/pricing_rule/pricing_rule.py | 2 +- erpnext/accounts/doctype/pricing_rule/utils.py | 14 ++++++++++---- erpnext/controllers/buying_controller.py | 8 ++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index af6e4c87af..b99c07e636 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -245,7 +245,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa if pricing_rule.price_or_product_discount == "Price": apply_price_discount_rule(pricing_rule, item_details, args) else: - get_product_discount_rule(pricing_rule, item_details) + get_product_discount_rule(pricing_rule, item_details, doc) item_details.has_pricing_rule = 1 diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 346eba259b..bd5a14971c 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import frappe, copy, json from frappe import throw, _ from six import string_types -from frappe.utils import flt, cint, get_datetime, get_link_to_form +from frappe.utils import flt, cint, get_datetime, get_link_to_form, today from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor @@ -434,8 +434,8 @@ def apply_pricing_rule_on_transaction(doc): doc.calculate_taxes_and_totals() elif d.price_or_product_discount == 'Product': - item_details = frappe._dict() - get_product_discount_rule(d, item_details) + item_details = frappe._dict({'parenttype': doc.doctype}) + get_product_discount_rule(d, item_details, doc) apply_pricing_rule_for_free_items(doc, item_details.free_item_data) doc.set_missing_values() @@ -443,7 +443,7 @@ def get_applied_pricing_rules(item_row): return (item_row.get("pricing_rules").split(',') if item_row.get("pricing_rules") else []) -def get_product_discount_rule(pricing_rule, item_details): +def get_product_discount_rule(pricing_rule, item_details, doc=None): free_item = (pricing_rule.free_item if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code) @@ -467,6 +467,12 @@ def get_product_discount_rule(pricing_rule, item_details): item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, item_details.free_item_data['uom']).get("conversion_factor", 1) + if item_details.get("parenttype") == 'Purchase Order': + item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today() + + if item_details.get("parenttype") == 'Sales Order': + item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() + def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): if pricing_rule_args.get('item_code'): items = [d.item_code for d in doc.items diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 17fba8ec4f..3ec7aff9cb 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -735,6 +735,10 @@ class BuyingController(StockController): if not self.get("items"): return + earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) + if earliest_schedule_date: + self.schedule_date = earliest_schedule_date + if self.schedule_date: for d in self.get('items'): if not d.schedule_date: @@ -746,10 +750,6 @@ class BuyingController(StockController): else: frappe.throw(_("Please enter Reqd by Date")) - earliest_schedule_date = min([d.schedule_date for d in self.get("items")]) - if earliest_schedule_date: - self.schedule_date = earliest_schedule_date - def validate_items(self): # validate items to see if they have is_purchase_item or is_subcontracted_item enabled if self.doctype=="Material Request": return From e9c9cbd2fe981bd68d9a9fc36ce458b17e156821 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Mon, 16 Dec 2019 14:49:59 +0530 Subject: [PATCH 111/389] fix: display serial no selection on adding items to cart --- erpnext/selling/page/point_of_sale/point_of_sale.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index b213a29ae7..33fbc229b6 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -286,14 +286,14 @@ erpnext.pos.PointOfSale = class PointOfSale { if (in_list(['serial_no', 'batch_no'], field)) { args[field] = value; } - + // add to cur_frm const item = this.frm.add_child('items', args); frappe.flags.hide_serial_batch_dialog = true; frappe.run_serially([ () => { - this.frm.script_manager.trigger('item_code', item.doctype, item.name) + return this.frm.script_manager.trigger('item_code', item.doctype, item.name) .then(() => { this.frm.script_manager.trigger('qty', item.doctype, item.name) .then(() => { From f195ccd85ff7ca7b859c6606bb4f5476429fc884 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Mon, 16 Dec 2019 15:03:27 +0530 Subject: [PATCH 112/389] fix: review changes --- erpnext/portal/product_configurator/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py index 2220892118..0993e69e04 100644 --- a/erpnext/portal/product_configurator/utils.py +++ b/erpnext/portal/product_configurator/utils.py @@ -301,6 +301,8 @@ def get_items(filters=None, search=None): if isinstance(filters, dict): filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()] + enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and') + show_in_website_condition = '' if products_settings.hide_variants: show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and') @@ -336,12 +338,9 @@ def get_items(filters=None, search=None): filter_condition = get_conditions(filters, 'and') where_conditions = ' and '.join( - [condition for condition in [show_in_website_condition, search_condition, filter_condition] if condition] + [condition for condition in [enabled_items_filter, show_in_website_condition, \ + search_condition, filter_condition] if condition] ) - if where_conditions: - where_conditions += ' and disabled = 0' - else: - where_conditions += 'disabled = 0' left_joins = [] for f in filters: From 197a99ee354a1a67f4cb2fbc1d80ba4f40f61318 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Dec 2019 16:18:33 +0530 Subject: [PATCH 113/389] fix: incorrect children boms fetched --- erpnext/manufacturing/doctype/bom/bom.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index e3ece56964..f8146bb01e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -65,6 +65,7 @@ class BOM(WebsiteGenerator): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] def on_update(self): + frappe.cache().hdel('bom_children', self.name) self.check_recursion() self.update_stock_qty() self.update_exploded_items() From b5bf22a821fc84923a7bd3fa33496c880716e4c2 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 16 Dec 2019 16:53:00 +0530 Subject: [PATCH 114/389] fix: now allow to over production against work order --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 ++ erpnext/manufacturing/doctype/work_order/work_order.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 8ca89171b6..176ca2e4f5 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -605,6 +605,8 @@ erpnext.work_order = { description: __('Max: {0}', [max]), default: max }, data => { + max += (max * (frm.doc.__onload.overproduction_percentage || 0.0)) / 100; + if (data.qty > max) { frappe.msgprint(__('Quantity must not be more than {0}', [max])); reject(); diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 227ef787ca..2b936be798 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -38,7 +38,7 @@ class WorkOrder(Document): ms = frappe.get_doc("Manufacturing Settings") self.set_onload("material_consumption", ms.material_consumption) self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on) - + self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order) def validate(self): self.validate_production_item() From 83ecaefd4cd5a8de24ebc1a6dd8fedd82b0a8e58 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 17 Dec 2019 11:03:22 +0530 Subject: [PATCH 115/389] feat: Custom button to create return entry from Employee Advance --- .../employee_advance/employee_advance.js | 27 +++++++++++++++ .../employee_advance/employee_advance.py | 33 +++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index 69915fa6e9..54a75925da 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -45,6 +45,15 @@ frappe.ui.form.on('Employee Advance', { __('Create') ); } + + if (frm.doc.docstatus === 1 + && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount)) + && frappe.model.can_create("Journal Entry")) { + + frm.add_custom_button(__("Return"), function() { + frm.trigger('make_return_entry'); + }, __('Create')); + } }, make_payment_entry: function(frm) { @@ -83,6 +92,24 @@ frappe.ui.form.on('Employee Advance', { }); }, + make_return_entry: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.employee_advance.employee_advance.make_return_entry', + args: { + 'employee_name': frm.doc.employee, + 'company': frm.doc.company, + 'employee_advance_name': frm.doc.name, + 'return_amount': flt(frm.doc.paid_amount - frm.doc.claimed_amount), + 'mode_of_payment': frm.doc.mode_of_payment, + 'advance_account': frm.doc.advance_account + }, + callback: function(r) { + const doclist = frappe.model.sync(r.message); + frappe.set_route('Form', doclist[0].doctype, doclist[0].name); + } + }) + }, + employee: function (frm) { if (frm.doc.employee) { return frappe.call({ diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py index 674e46438b..7fe2ebc79e 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.py +++ b/erpnext/hr/doctype/employee_advance/employee_advance.py @@ -7,6 +7,7 @@ import frappe, erpnext from frappe import _ from frappe.model.document import Document from frappe.utils import flt, nowdate +from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account class EmployeeAdvanceOverPayment(frappe.ValidationError): pass @@ -102,8 +103,6 @@ def get_due_advance_amount(employee, posting_date): @frappe.whitelist() def make_bank_entry(dt, dn): - from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account - doc = frappe.get_doc(dt, dn) payment_account = get_default_bank_cash_account(doc.company, account_type="Cash", mode_of_payment=doc.mode_of_payment) @@ -132,3 +131,33 @@ def make_bank_entry(dt, dn): }) return je.as_dict() + +@frappe.whitelist() +def make_return_entry(employee_name, company, employee_advance_name, return_amount, mode_of_payment, advance_account): + return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment) + je = frappe.new_doc('Journal Entry') + je.posting_date = nowdate() + je.voucher_type = 'Bank Entry' + je.company = company + je.remark = 'Return against Employee Advance: ' + employee_advance_name + + je.append('accounts', { + 'account': advance_account, + 'credit_in_account_currency': return_amount, + 'reference_type': 'Employee Advance', + 'reference_name': employee_advance_name, + 'party_type': 'Employee', + 'party': employee_name, + 'is_advance': 'Yes' + }) + + je.append("accounts", { + "account": return_account.account, + "debit_in_account_currency": return_amount, + "account_currency": return_account.account_currency, + "account_type": return_account.account_type + }) + + return je.as_dict() + + From 0f583b8c5a3c284d8029bcb7589e041e8e2f49a0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 17 Dec 2019 12:44:19 +0530 Subject: [PATCH 116/389] fix: incorrect outstanding amount shwoing in the AP/AR report --- .../accounts/report/accounts_receivable/accounts_receivable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 2c53f6e997..c70a2cd1a7 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -171,7 +171,7 @@ class ReceivablePayableReport(object): row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision) row.invoice_grand_total = row.invoiced - if abs(row.outstanding) > 0.1/10 ** self.currency_precision: + if abs(row.outstanding) > 1.0/10 ** self.currency_precision: # non-zero oustanding, we must consider this row if self.is_invoice(row) and self.filters.based_on_payment_terms: From e65ada28077e980d6a58a168a0bd58f013e10b60 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 17 Dec 2019 17:54:34 +0530 Subject: [PATCH 117/389] feat: Dynamic filters for dimensions in budget variance report --- .../budget_variance_report.js | 21 ++++++--- .../budget_variance_report.py | 43 +++++++++---------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index 24511871fd..3ec4d306c3 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -46,13 +46,24 @@ frappe.query_reports["Budget Variance Report"] = { fieldtype: "Select", options: ["Cost Center", "Project"], default: "Cost Center", - reqd: 1 + reqd: 1, + on_change: function() { + frappe.query_report.set_filter_value("budget_against_filter", []); + frappe.query_report.refresh(); + } }, { - fieldname: "cost_center", - label: __("Cost Center"), - fieldtype: "Link", - options: "Cost Center" + fieldname:"budget_against_filter", + label: __('Dimension Filter'), + fieldtype: "MultiSelectList", + get_data: function(txt) { + if (!frappe.query_report.filters) return; + + let budget_against = frappe.query_report.get_filter_value('budget_against'); + if (!budget_against) return; + + return frappe.db.get_link_options(budget_against, txt); + } }, { fieldname:"show_cumulative", diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 8d65ac8714..39e218bfad 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -12,22 +12,22 @@ from six import iteritems from pprint import pprint def execute(filters=None): if not filters: filters = {} - validate_filters(filters) + columns = get_columns(filters) - if filters.get("cost_center"): - cost_centers = [filters.get("cost_center")] + if filters.get("budget_against_filter"): + dimensions = filters.get("budget_against_filter") else: - cost_centers = get_cost_centers(filters) + dimensions = get_cost_centers(filters) period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) - cam_map = get_cost_center_account_month_map(filters) + cam_map = get_dimension_account_month_map(filters) data = [] - for cost_center in cost_centers: - cost_center_items = cam_map.get(cost_center) - if cost_center_items: - for account, monthwise_data in iteritems(cost_center_items): - row = [cost_center, account] + for dimension in dimensions: + dimension_items = cam_map.get(dimension) + if dimension_items: + for account, monthwise_data in iteritems(dimension_items): + row = [dimension, account] totals = [0, 0, 0] for year in get_fiscal_years(filters): last_total = 0 @@ -55,10 +55,6 @@ def execute(filters=None): return columns, data -def validate_filters(filters): - if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"): - frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) - def get_columns(filters): columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] @@ -98,11 +94,12 @@ def get_cost_centers(filters): else: return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec -#Get cost center & target details -def get_cost_center_target_details(filters): +#Get dimension & target details +def get_dimension_target_details(filters): cond = "" - if filters.get("cost_center"): - cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center")) + if filters.get("budget_against_filter"): + cond += " and b.{budget_against} in (%s)".format(budget_against = \ + frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter'))) return frappe.db.sql(""" select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year @@ -110,8 +107,8 @@ def get_cost_center_target_details(filters): where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), - (filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) - + tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')), + as_dict=True) #Get target distribution details of accounts of cost center @@ -153,14 +150,14 @@ def get_actual_details(name, filters): return cc_actual_details -def get_cost_center_account_month_map(filters): +def get_dimension_account_month_map(filters): import datetime - cost_center_target_details = get_cost_center_target_details(filters) + dimension_target_details = get_dimension_target_details(filters) tdd = get_target_distribution_details(filters) cam_map = {} - for ccd in cost_center_target_details: + for ccd in dimension_target_details: actual_details = get_actual_details(ccd.budget_against, filters) for month_id in range(1, 13): From b695fd31d633ea18d9bf786c5244a56dc1704e80 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 17 Dec 2019 18:14:33 +0530 Subject: [PATCH 118/389] fix: Remove trailing whitespace and add semicolon --- erpnext/hr/doctype/employee_advance/employee_advance.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.js b/erpnext/hr/doctype/employee_advance/employee_advance.js index 54a75925da..ba62853336 100644 --- a/erpnext/hr/doctype/employee_advance/employee_advance.js +++ b/erpnext/hr/doctype/employee_advance/employee_advance.js @@ -46,10 +46,10 @@ frappe.ui.form.on('Employee Advance', { ); } - if (frm.doc.docstatus === 1 + if (frm.doc.docstatus === 1 && (flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount)) && frappe.model.can_create("Journal Entry")) { - + frm.add_custom_button(__("Return"), function() { frm.trigger('make_return_entry'); }, __('Create')); @@ -107,7 +107,7 @@ frappe.ui.form.on('Employee Advance', { const doclist = frappe.model.sync(r.message); frappe.set_route('Form', doclist[0].doctype, doclist[0].name); } - }) + }); }, employee: function (frm) { From 4203eb4ee12638c67293152bc0e3dc4f305a991b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 17 Dec 2019 18:13:54 +0530 Subject: [PATCH 119/389] fix: not able to make work order from BOM --- erpnext/manufacturing/doctype/work_order/work_order.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 2b936be798..c4238accac 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -657,8 +657,9 @@ def make_work_order(item, qty=0, project=None): wo_doc = frappe.new_doc("Work Order") wo_doc.production_item = item wo_doc.update(item_details) - if qty > 0: - wo_doc.qty = qty + + if flt(qty) > 0: + wo_doc.qty = flt(qty) wo_doc.get_items_and_operations_from_bom() return wo_doc From be8c4068082ea59fa8b75f8f268438db200e97a4 Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Tue, 17 Dec 2019 22:27:23 +0530 Subject: [PATCH 120/389] fix: Update sales register report --- .../report/sales_register/sales_register.py | 235 +++++++++++++++--- 1 file changed, 205 insertions(+), 30 deletions(-) diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 0e2821ac16..afdd31df16 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -38,32 +38,46 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", []))) warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", []))) - row = [ - inv.name, inv.posting_date, inv.customer, inv.customer_name - ] + row = { + 'invoice': inv.name, + 'posting_date': inv.posting_date, + 'customer': inv.customer, + 'customer_name': inv.customer_name + } if additional_query_columns: for col in additional_query_columns: - row.append(inv.get(col)) + row.update({ + col: inv.get(col) + }) + + row.update({ + 'customer_group': inv.get("customer_group"), + 'territory': inv.get("territory"), + 'tax_id': inv.get("tax_id"), + 'receivable_account': inv.debit_to, + 'mode_of_payment': ", ".join(mode_of_payments.get(inv.name, [])), + 'project': inv.project, + 'owner': inv.owner, + 'remarks': inv.remarks, + 'sales_order': ", ".join(sales_order), + 'delivery_note': ", ".join(delivery_note), + 'cost_center': ", ".join(cost_center), + 'warehouse': ", ".join(warehouse), + 'currency': company_currency + }) - row +=[ - inv.get("customer_group"), - inv.get("territory"), - inv.get("tax_id"), - inv.debit_to, ", ".join(mode_of_payments.get(inv.name, [])), - inv.project, inv.owner, inv.remarks, - ", ".join(sales_order), ", ".join(delivery_note),", ".join(cost_center), - ", ".join(warehouse), company_currency - ] # map income values base_net_total = 0 for income_acc in income_accounts: income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc)) base_net_total += income_amount - row.append(income_amount) + row.update({ + frappe.scrub(income_acc): income_amount + }) # net total - row.append(base_net_total or inv.base_net_total) + row.update({'net_total': base_net_total or inv.base_net_total}) # tax account total_tax = 0 @@ -72,10 +86,18 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2 tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision) total_tax += tax_amount - row.append(tax_amount) + row.update({ + frappe.scrub(tax_acc): tax_amount + }) # total tax, grand total, outstanding amount & rounded total - row += [total_tax, inv.base_grand_total, inv.base_rounded_total, inv.outstanding_amount] + + row.update({ + 'tax_total': total_tax, + 'grand_total': inv.base_grand_total, + 'rounded_total': inv.base_rounded_total, + 'outstanding_amount': inv.outstanding_amount + }) data.append(row) @@ -84,19 +106,118 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No def get_columns(invoice_list, additional_table_columns): """return columns based on filters""" columns = [ - _("Invoice") + ":Link/Sales Invoice:120", _("Posting Date") + ":Date:80", - _("Customer") + ":Link/Customer:120", _("Customer Name") + "::120" + { + 'label': _("Invoice"), + 'fieldname': 'invoice', + 'fieldtype': 'Link', + 'options': 'Sales Invoice', + 'width': 120 + }, + { + 'label': _("Posting Date"), + 'fieldname': 'posting_date', + 'fieldtype': 'Date', + 'width': 80 + }, + { + 'label': _("Customer"), + 'fieldname': 'customer', + 'fieldtype': 'Link', + 'options': 'Customer', + 'width': 120 + }, + { + 'label': _("Customer Name"), + 'fieldname': 'customer_name', + 'fieldtype': 'Data', + 'width': 120 + }, ] if additional_table_columns: columns += additional_table_columns columns +=[ - _("Customer Group") + ":Link/Customer Group:120", _("Territory") + ":Link/Territory:80", - _("Tax Id") + "::80", _("Receivable Account") + ":Link/Account:120", _("Mode of Payment") + "::120", - _("Project") +":Link/Project:80", _("Owner") + "::150", _("Remarks") + "::150", - _("Sales Order") + ":Link/Sales Order:100", _("Delivery Note") + ":Link/Delivery Note:100", - _("Cost Center") + ":Link/Cost Center:100", _("Warehouse") + ":Link/Warehouse:100", + { + 'label': _("Custmer Group"), + 'fieldname': 'customer_group', + 'fieldtype': 'Link', + 'options': 'Customer Group', + 'width': 120 + }, + { + 'label': _("Territory"), + 'fieldname': 'territory', + 'fieldtype': 'Link', + 'options': 'Territory', + 'width': 80 + }, + { + 'label': _("Tax Id"), + 'fieldname': 'tax_id', + 'fieldtype': 'Data', + 'width': 120 + }, + { + 'label': _("Receivable Account"), + 'fieldname': 'receivable_account', + 'fieldtype': 'Link', + 'options': 'Account', + 'width': 80 + }, + { + 'label': _("Mode Of Payment"), + 'fieldname': 'mode_of_payment', + 'fieldtype': 'Data', + 'width': 120 + }, + { + 'label': _("Project"), + 'fieldname': 'project', + 'fieldtype': 'Link', + 'options': 'project', + 'width': 80 + }, + { + 'label': _("Owner"), + 'fieldname': 'owner', + 'fieldtype': 'Data', + 'width': 150 + }, + { + 'label': _("Remarks"), + 'fieldname': 'remarks', + 'fieldtype': 'Data', + 'width': 150 + }, + { + 'label': _("Sales Order"), + 'fieldname': 'sales_order', + 'fieldtype': 'Link', + 'options': 'Sales Order', + 'width': 100 + }, + { + 'label': _("Delivery Note"), + 'fieldname': 'delivery_note', + 'fieldtype': 'Link', + 'options': 'Delivery Note', + 'width': 100 + }, + { + 'label': _("Cost Center"), + 'fieldname': 'cost_center', + 'fieldtype': 'Link', + 'options': 'Cost Center', + 'width': 100 + }, + { + 'label': _("Warehouse"), + 'fieldname': 'warehouse', + 'fieldtype': 'Link', + 'options': 'Warehouse', + 'width': 100 + }, { "fieldname": "currency", "label": _("Currency"), @@ -105,7 +226,10 @@ def get_columns(invoice_list, additional_table_columns): } ] - income_accounts = tax_accounts = income_columns = tax_columns = [] + income_accounts = [] + tax_accounts = [] + income_columns = [] + tax_columns = [] if invoice_list: income_accounts = frappe.db.sql_list("""select distinct income_account @@ -119,14 +243,65 @@ def get_columns(invoice_list, additional_table_columns): and parent in (%s) order by account_head""" % ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) - income_columns = [(account + ":Currency/currency:120") for account in income_accounts] + for account in income_accounts: + income_columns.append({ + "label": account, + "fieldname": frappe.scrub(account), + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + }) + for account in tax_accounts: if account not in income_accounts: - tax_columns.append(account + ":Currency/currency:120") + tax_columns.append({ + "label": account, + "fieldname": frappe.scrub(account), + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + }) - columns = columns + income_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \ - [_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120", - _("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"] + net_total_column = [{ + "label": _("Net Total"), + "fieldname": "net_total", + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + }] + + total_columns = [ + { + "label": _("Tax Total"), + "fieldname": "tax_total", + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + }, + { + "label": _("Grand Total"), + "fieldname": "grand_total", + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + }, + { + "label": _("Rounded Total"), + "fieldname": "rounded_total", + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + }, + { + "label": _("Outstanding Amount"), + "fieldname": "outstanding_amount", + "fieldtype": "Currency", + "options": 'currency', + "width": 120 + } + ] + + columns = columns + income_columns + net_total_column + tax_columns + total_columns return columns, income_accounts, tax_accounts From f597ba82ea41a93228ed83c3d4fbe3aae4fae28f Mon Sep 17 00:00:00 2001 From: Ben Knowles Date: Tue, 17 Dec 2019 13:07:02 -0600 Subject: [PATCH 121/389] fix: task validation error when adding tasks to projects (#19919) * fix: task validation error when adding tasks to projects When adding a task to a project, if the project didn't have an Expected End Date the validation would fail. This is because passing a None value to getdate() returns today's date, rather than being optional as expected. * update task.py --- erpnext/projects/doctype/task/task.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 7083d694f8..45f26814a6 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -47,11 +47,11 @@ class Task(NestedSet): if not self.project or frappe.flags.in_test: return - expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date")) + expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date") if expected_end_date: - validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected") - validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual") + validate_project_dates(getdate(expected_end_date), self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(getdate(expected_end_date), self, "act_start_date", "act_end_date", "Actual") def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Completed": @@ -278,4 +278,4 @@ def validate_project_dates(project_end_date, task, task_start, task_end, actual_ frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: - frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) \ No newline at end of file + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) From e7aa20ebef1b3442d7f962434e8ae285f2ad8933 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Mon, 16 Dec 2019 16:52:42 +0530 Subject: [PATCH 122/389] fix: replace sql query by orm in delete_communications and added tests --- .../company/delete_company_transactions.py | 11 ++-- erpnext/setup/doctype/company/test_company.py | 51 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 637e65578a..1503adb504 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -106,7 +106,10 @@ def delete_lead_addresses(company_name): frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) def delete_communications(doctype, company_name, company_fieldname): - frappe.db.sql(""" - DELETE FROM `tabCommunication` WHERE reference_doctype = %s AND - EXISTS (SELECT name FROM `tab{0}` WHERE {1} = %s AND `tabCommunication`.reference_name = name) - """.format(doctype, company_fieldname), (doctype, company_name)) + reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) + reference_doc_names = [r.name for r in reference_docs] + + communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) + communication_names = [c.name for c in communications] + + frappe.delete_doc("Communication", communication_names) diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py index 8d9c23a37d..1664b660b8 100644 --- a/erpnext/setup/doctype/company/test_company.py +++ b/erpnext/setup/doctype/company/test_company.py @@ -88,6 +88,57 @@ class TestCompany(unittest.TestCase): self.delete_mode_of_payment(template) frappe.delete_doc("Company", template) + def test_delete_communication(self): + from erpnext.setup.doctype.company.delete_company_transactions import delete_communications + company = create_child_company() + lead = create_test_lead_in_company(company) + communication = create_company_communication("Lead", lead) + delete_communications("Lead", "Test Company", "company") + self.assertFalse(frappe.db.exists("Communcation", communication)) + self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication})) + def delete_mode_of_payment(self, company): frappe.db.sql(""" delete from `tabMode of Payment Account` where company =%s """, (company)) + +def create_company_communication(doctype, docname): + comm = frappe.get_doc({ + "doctype": "Communication", + "communication_type": "Communication", + "content": "Deduplication of Links", + "communication_medium": "Email", + "reference_doctype":doctype, + "reference_name":docname + }) + comm.insert() + +def create_child_company(): + child_company = frappe.db.exists("Company", "Test Company") + if not child_company: + child_company = frappe.get_doc({ + "doctype":"Company", + "company_name":"Test Company", + "abbr":"test_company", + "default_currency":"INR" + }) + child_company.insert() + else: + child_company = frappe.get_doc("Company", child_company) + + return child_company.name + +def create_test_lead_in_company(company): + lead = frappe.db.exists("Lead", "Test Lead in new company") + if not lead: + lead = frappe.get_doc({ + "doctype": "Lead", + "lead_name": "Test Lead in new company", + "scompany": company + }) + lead.insert() + else: + lead = frappe.get_doc("Lead", lead) + lead.company = company + lead.save() + return lead.name + From 1202e64403eb50222e6012347ba76c0f56553c4d Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 18 Dec 2019 11:43:16 +0530 Subject: [PATCH 123/389] fix: Training Event Email Tenplate (#19652) --- .../training_scheduled.html | 51 ++++++++++++++++--- .../training_scheduled.json | 6 ++- .../training_scheduled/training_scheduled.md | 51 ++++++++++++++++--- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.html b/erpnext/hr/notification/training_scheduled/training_scheduled.html index b1aeb2c873..374038ac20 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.html +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.html @@ -1,9 +1,44 @@ -

{{_("Training Event")}}

+ + + + + + + + +
+
+ {{_("Training Event:")}} {{ doc.event_name }} +
+
-

{{ doc.introduction }}

- -

{{_("Details")}}

-{{_("Event Name")}}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }} -
{{_("Event Location")}}: {{ doc.location }} -
{{_("Start Time")}}: {{ doc.start_time }} -
{{_("End Time")}}: {{ doc.end_time }} + + + + + + + + +
+
+ {{ doc.introduction }} +
    +
  • {{_("Event Location")}}: {{ doc.location }}
  • + {% set start = frappe.utils.get_datetime(doc.start_time) %} + {% set end = frappe.utils.get_datetime(doc.end_time) %} + {% if start.date() == end.date() %} +
  • {{_("Date")}}: {{ start.strftime("%A, %d %b %Y") }}
  • +
  • + {{_("Timing")}}: {{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }} +
  • + {% else %} +
  • {{_("Start Time")}}: {{ start.strftime("%A, %d %b %Y at %I:%M %p") }} +
  • +
  • {{_("End Time")}}: {{ end.strftime("%A, %d %b %Y at %I:%M %p") }} +
  • + {% endif %} +
  • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
  • +
+
+
\ No newline at end of file diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.json b/erpnext/hr/notification/training_scheduled/training_scheduled.json index c07e1a6285..966b887572 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.json +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.json @@ -1,5 +1,7 @@ { "attach_print": 0, + "channel": "Email", + "condition": "", "creation": "2017-08-11 03:13:40.519614", "days_in_advance": 0, "docstatus": 0, @@ -9,8 +11,8 @@ "event": "Submit", "idx": 0, "is_standard": 1, - "message": "

{{_(\"Training Event\")}}

\n\n

{{ doc.introduction }}

\n\n

{{_(\"Details\")}}

\n{{_(\"Event Name\")}}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n
{{_(\"Event Location\")}}: {{ doc.location }}\n
{{_(\"Start Time\")}}: {{ doc.start_time }}\n
{{_(\"End Time\")}}: {{ doc.end_time }}\n", - "modified": "2017-08-13 22:49:42.338881", + "message": "\n \n \n \n \n \n \n \n
\n
\n {{_(\"Training Event:\")}} {{ doc.event_name }}\n
\n
\n\n\n \n \n \n \n \n \n \n
\n
\n
    \n
  • {{ doc.introduction }}
  • \n
  • {{_(\"Event Location\")}}: {{ doc.location }}
  • \n {% set start = frappe.utils.get_datetime(doc.start_time) %}\n {% set end = frappe.utils.get_datetime(doc.end_time) %}\n {% if start.date() == end.date() %}\n
  • {{_(\"Date\")}}: {{ start.strftime(\"%A, %d %b %Y\") }}
  • \n
  • \n {{_(\"Timing\")}}: {{ start.strftime(\"%I:%M %p\") + ' to ' + end.strftime(\"%I:%M %p\") }}\n
  • \n {% else %}\n
  • {{_(\"Start Time\")}}: {{ start.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
  • \n
  • {{_(\"End Time\")}}: {{ end.strftime(\"%A, %d %b %Y at %I:%M %p\") }}\n
  • \n {% endif %}\n
\n {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}\n
\n
", + "modified": "2019-11-29 15:38:31.805409", "modified_by": "Administrator", "module": "HR", "name": "Training Scheduled", diff --git a/erpnext/hr/notification/training_scheduled/training_scheduled.md b/erpnext/hr/notification/training_scheduled/training_scheduled.md index bcadf7df59..374038ac20 100644 --- a/erpnext/hr/notification/training_scheduled/training_scheduled.md +++ b/erpnext/hr/notification/training_scheduled/training_scheduled.md @@ -1,9 +1,44 @@ -

{{_("Training Event")}}

-

{{ message }}

+ + + + + + + + +
+
+ {{_("Training Event:")}} {{ doc.event_name }} +
+
-

{{_("Details")}}

-{{_("Event Name")}}: {{ name }} -
{{_("Event Location")}}: {{ location }} -
{{_("Start Time")}}: {{ start_time }} -
{{_("End Time")}}: {{ end_time }} -
{{_("Attendance")}}: {{ attendance }} + + + + + + + + +
+
+ {{ doc.introduction }} +
    +
  • {{_("Event Location")}}: {{ doc.location }}
  • + {% set start = frappe.utils.get_datetime(doc.start_time) %} + {% set end = frappe.utils.get_datetime(doc.end_time) %} + {% if start.date() == end.date() %} +
  • {{_("Date")}}: {{ start.strftime("%A, %d %b %Y") }}
  • +
  • + {{_("Timing")}}: {{ start.strftime("%I:%M %p") + ' to ' + end.strftime("%I:%M %p") }} +
  • + {% else %} +
  • {{_("Start Time")}}: {{ start.strftime("%A, %d %b %Y at %I:%M %p") }} +
  • +
  • {{_("End Time")}}: {{ end.strftime("%A, %d %b %Y at %I:%M %p") }} +
  • + {% endif %} +
  • {{ _('Event Link') }}: {{ frappe.utils.get_link_to_form(doc.doctype, doc.name) }}
  • +
+
+
\ No newline at end of file From 5150c69ee6af5d21e44eb0c0d4eb2c6967221654 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 18 Dec 2019 12:07:24 +0530 Subject: [PATCH 124/389] use open_mapped_doc instead of create_new_doc --- erpnext/selling/doctype/customer/customer.js | 12 ++-- erpnext/selling/doctype/customer/customer.py | 59 ++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js index cca8efeca4..aa1b92f9a4 100644 --- a/erpnext/selling/doctype/customer/customer.js +++ b/erpnext/selling/doctype/customer/customer.js @@ -5,13 +5,13 @@ frappe.ui.form.on("Customer", { setup: function(frm) { frm.make_methods = { - 'Quotation': () => erpnext.utils.create_new_doc('Quotation', { - 'quotation_to': frm.doc.doctype, - 'party_name': frm.doc.name + 'Quotation': () => frappe.model.open_mapped_doc({ + method: "erpnext.selling.doctype.customer.customer.make_quotation", + frm: cur_frm }), - 'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', { - 'opportunity_from': frm.doc.doctype, - 'party_name': frm.doc.name + 'Opportunity': () => frappe.model.open_mapped_doc({ + method: "erpnext.selling.doctype.customer.customer.make_opportunity", + frm: cur_frm }) } diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 57308cea41..73ab7e6bb7 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -12,6 +12,7 @@ from erpnext.utilities.transaction_base import TransactionBase from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address from frappe.model.rename_doc import update_linked_doctypes +from frappe.model.mapper import get_mapped_doc class Customer(TransactionBase): def get_feed(self): @@ -238,6 +239,64 @@ def create_contact(contact, party_type, party, email): contact.append('links', dict(link_doctype=party_type, link_name=party)) contact.insert() +@frappe.whitelist() +def make_quotation(source_name, target_doc=None): + + def set_missing_values(source, target): + _set_missing_values(source,target) + + target_doc = get_mapped_doc("Customer", source_name, + {"Customer": { + "doctype": "Quotation", + "field_map": { + "name":"party_name" + } + }}, target_doc, set_missing_values) + + target_doc.quotation_to = "Customer" + target_doc.run_method("set_missing_values") + target_doc.run_method("set_other_charges") + target_doc.run_method("calculate_taxes_and_totals") + + target_doc.selling_price_list = frappe.get_doc("Customer",source_name).default_price_list + return target_doc + +@frappe.whitelist() +def make_opportunity(source_name, target_doc=None): + def set_missing_values(source, target): + _set_missing_values(source,target) + + target_doc = get_mapped_doc("Customer", source_name, + {"Customer": { + "doctype": "Opportunity", + "field_map": { + "name": "party_name", + "doctype": "opportunity_from", + } + }}, target_doc, set_missing_values + ) + + return target_doc + +def _set_missing_values(source, target): + address = frappe.get_all('Dynamic Link', { + 'link_doctype': source.doctype, + 'link_name': source.name, + 'parenttype': 'Address', + }, ['parent'], limit=1) + + contact = frappe.get_all('Dynamic Link', { + 'link_doctype': source.doctype, + 'link_name': source.name, + 'parenttype': 'Contact', + }, ['parent'], limit=1) + + if address: + target.customer_address = address[0].parent + + if contact: + target.contact_person = contact[0].parent + @frappe.whitelist() def get_loyalty_programs(doc): ''' returns applicable loyalty programs for a customer ''' From 84ae2cc5431972e0eb6483ccddd34f32950f5bc2 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 18 Dec 2019 15:44:04 +0530 Subject: [PATCH 125/389] fix: defualt timezone not getting selected --- erpnext/www/book_appointment/index.js | 13 ++++--------- erpnext/www/book_appointment/index.py | 20 +++++++++++--------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js index 5a814c6381..262e31b3e4 100644 --- a/erpnext/www/book_appointment/index.js +++ b/erpnext/www/book_appointment/index.js @@ -24,20 +24,15 @@ async function get_global_variables() { } function setup_timezone_selector() { - /** - * window.timezones is a dictionary with the following structure - * { IANA name: Pretty name} - * For example : { Asia/Kolkata : "India Time - Asia/Kolkata"} - */ let timezones_element = document.getElementById('appointment-timezone'); - let offset = new Date().getTimezoneOffset(); - Object.keys(window.timezones).forEach((timezone) => { + let local_timezone = moment.tz.guess() + window.timezones.forEach(timezone => { let opt = document.createElement('option'); opt.value = timezone; - if (timezone == moment.tz.guess()) { + if (timezone == local_timezone) { opt.selected = true; } - opt.innerHTML = window.timezones[timezone] + opt.innerHTML = timezone; timezones_element.appendChild(opt) }); } diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py index e4af7e8e43..fe98c7a0e9 100644 --- a/erpnext/www/book_appointment/index.py +++ b/erpnext/www/book_appointment/index.py @@ -25,18 +25,20 @@ def get_appointment_settings(): @frappe.whitelist(allow_guest=True) def get_timezones(): - from babel.dates import get_timezone, get_timezone_name, Locale - from frappe.utils.momentjs import get_all_timezones + import pytz + return pytz.all_timezones + # from babel.dates import get_timezone, get_timezone_name, Locale + # from frappe.utils.momentjs import get_all_timezones - translated_dict = {} - locale = Locale.parse(frappe.local.lang, sep="-") + # translated_dict = {} + # locale = Locale.parse(frappe.local.lang, sep="-") - for tz in get_all_timezones(): - timezone_name = get_timezone_name(get_timezone(tz), locale=locale, width='short') - if timezone_name: - translated_dict[tz] = timezone_name + ' - ' + tz + # for tz in get_all_timezones(): + # timezone_name = get_timezone_name(get_timezone(tz), locale=locale, width='short') + # if timezone_name: + # translated_dict[tz] = timezone_name + ' - ' + tz - return translated_dict + # return translated_dict @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): From 0db86204cf86d0aff2d8c5c16ba3afef2341ac35 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 18 Dec 2019 16:00:58 +0530 Subject: [PATCH 126/389] fix : only set price list if it exists for customer --- erpnext/selling/doctype/customer/customer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 73ab7e6bb7..9fd37adc13 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -258,7 +258,10 @@ def make_quotation(source_name, target_doc=None): target_doc.run_method("set_other_charges") target_doc.run_method("calculate_taxes_and_totals") - target_doc.selling_price_list = frappe.get_doc("Customer",source_name).default_price_list + price_list = frappe.get_value("Customer",source_name, 'default_price_list') + if price_list: + target_doc.selling_price_list = price_list + return target_doc @frappe.whitelist() From 5f5c725ef99153b8489c6755cac84515aed66c16 Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 18 Dec 2019 16:21:50 +0530 Subject: [PATCH 127/389] add book appointment to the sidebar --- erpnext/hooks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 2a5e6d8f49..c99ae7da5e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -180,6 +180,7 @@ standard_portal_menu_items = [ {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"}, {"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"}, {"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"}, + {"title": _("Appointment Booking"), "route": "/book_appointment"}, ] default_roles = [ From e786eea7dbcf78c272ca8d90440d0afc783faf2e Mon Sep 17 00:00:00 2001 From: 0Pranav Date: Wed, 18 Dec 2019 16:25:26 +0530 Subject: [PATCH 128/389] remove comments --- erpnext/www/book_appointment/index.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py index fe98c7a0e9..7bfac89f30 100644 --- a/erpnext/www/book_appointment/index.py +++ b/erpnext/www/book_appointment/index.py @@ -27,18 +27,6 @@ def get_appointment_settings(): def get_timezones(): import pytz return pytz.all_timezones - # from babel.dates import get_timezone, get_timezone_name, Locale - # from frappe.utils.momentjs import get_all_timezones - - # translated_dict = {} - # locale = Locale.parse(frappe.local.lang, sep="-") - - # for tz in get_all_timezones(): - # timezone_name = get_timezone_name(get_timezone(tz), locale=locale, width='short') - # if timezone_name: - # translated_dict[tz] = timezone_name + ' - ' + tz - - # return translated_dict @frappe.whitelist(allow_guest=True) def get_appointment_slots(date, timezone): From 2ae79b8ac2c64df86bd33971b6e8ed39ae5d8b41 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 18 Dec 2019 17:48:39 +0530 Subject: [PATCH 129/389] fix: Pricing Rule Discount for Product --- erpnext/accounts/doctype/pricing_rule/pricing_rule.json | 9 +++------ erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 3 +++ erpnext/public/js/controllers/transaction.js | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index f73fb10d32..29d83783d0 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -1,5 +1,4 @@ { - "actions": [], "allow_import": 1, "allow_rename": 1, "autoname": "field:title", @@ -390,8 +389,7 @@ "fieldname": "rate_or_discount", "fieldtype": "Select", "label": "Rate or Discount", - "options": "\nRate\nDiscount Percentage\nDiscount Amount", - "reqd": 1 + "options": "\nRate\nDiscount Percentage\nDiscount Amount" }, { "default": "Grand Total", @@ -440,7 +438,7 @@ }, { "default": "0", - "depends_on": "eval:!doc.mixed_conditions && doc.price_or_product_discount == 'Price'", + "depends_on": "eval:!doc.mixed_conditions && doc.apply_on != 'Transaction'", "fieldname": "same_item", "fieldtype": "Check", "label": "Same Item" @@ -556,8 +554,7 @@ ], "icon": "fa fa-gift", "idx": 1, - "links": [], - "modified": "2019-12-13 15:48:48.331495", + "modified": "2019-12-18 17:29:22.957077", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index b99c07e636..3c14819e6f 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -47,6 +47,9 @@ class PricingRule(Document): if tocheck and not self.get(tocheck): throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) + if self.price_or_product_discount == 'Price' and not self.rate_or_discount: + throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) + def validate_applicable_for_selling_or_buying(self): if not self.selling and not self.buying: throw(_("Atleast one of the Selling or Buying must be selected")) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1be4f27289..6db849aca7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -500,6 +500,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ () => { var d = locals[cdt][cdn]; me.add_taxes_from_item_tax_template(d.item_tax_rate); + if (d.free_item_data) { + me.apply_product_discount(d.free_item_data); + } }, () => me.frm.script_manager.trigger("price_list_rate", cdt, cdn), () => me.toggle_conversion_factor(item), From aba58ba50ef19ea3d9dd464b9798f9f736a7f5ca Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 19 Dec 2019 11:02:37 +0530 Subject: [PATCH 130/389] fix: Spacing after commas --- erpnext/selling/doctype/customer/customer.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 9fd37adc13..136236c417 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -243,9 +243,9 @@ def create_contact(contact, party_type, party, email): def make_quotation(source_name, target_doc=None): def set_missing_values(source, target): - _set_missing_values(source,target) + _set_missing_values(source, target) - target_doc = get_mapped_doc("Customer", source_name, + target_doc = get_mapped_doc("Customer", source_name, {"Customer": { "doctype": "Quotation", "field_map": { @@ -258,7 +258,7 @@ def make_quotation(source_name, target_doc=None): target_doc.run_method("set_other_charges") target_doc.run_method("calculate_taxes_and_totals") - price_list = frappe.get_value("Customer",source_name, 'default_price_list') + price_list = frappe.get_value("Customer", source_name, 'default_price_list') if price_list: target_doc.selling_price_list = price_list @@ -267,17 +267,16 @@ def make_quotation(source_name, target_doc=None): @frappe.whitelist() def make_opportunity(source_name, target_doc=None): def set_missing_values(source, target): - _set_missing_values(source,target) + _set_missing_values(source, target) - target_doc = get_mapped_doc("Customer", source_name, + target_doc = get_mapped_doc("Customer", source_name, {"Customer": { "doctype": "Opportunity", "field_map": { "name": "party_name", "doctype": "opportunity_from", } - }}, target_doc, set_missing_values - ) + }}, target_doc, set_missing_values) return target_doc From 9d9a78442e849dd248781f9917361f3e77681852 Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Thu, 19 Dec 2019 13:10:57 +0530 Subject: [PATCH 131/389] fix: bad filter query --- .../purchase_invoice/purchase_invoice.js | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d7e64cf36f..643de7d300 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -382,21 +382,11 @@ cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(do cur_frm.fields_dict['credit_to'].get_query = function(doc) { // filter on Account - if (doc.supplier) { - return { - filters: { - 'account_type': 'Payable', - 'is_group': 0, - 'company': doc.company - } - } - } else { - return { - filters: { - 'report_type': 'Balance Sheet', - 'is_group': 0, - 'company': doc.company - } + return { + filters: { + 'account_type': 'Payable', + 'is_group': 0, + 'company': doc.company } } } From e9cb561d8d1ae27b1d505d820a07424bfb76b9fc Mon Sep 17 00:00:00 2001 From: thefalconx33 Date: Thu, 19 Dec 2019 13:18:46 +0530 Subject: [PATCH 132/389] fix: no role has cancelling permission for share transfer doctype --- erpnext/accounts/doctype/share_transfer/share_transfer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.json b/erpnext/accounts/doctype/share_transfer/share_transfer.json index f17bf04caf..9e549e0685 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.json +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.json @@ -188,7 +188,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-07 13:31:17.999744", + "modified": "2019-12-19 13:15:59.001301", "modified_by": "Administrator", "module": "Accounts", "name": "Share Transfer", @@ -196,6 +196,7 @@ "permissions": [ { "amend": 1, + "cancel": 1, "create": 1, "delete": 1, "email": 1, From 457ca0fe6cc0e66c25cdf05c85098f3d0483b07c Mon Sep 17 00:00:00 2001 From: marination Date: Thu, 19 Dec 2019 13:34:29 +0530 Subject: [PATCH 133/389] fix: Company None not found in get_valuation_rate --- erpnext/stock/doctype/stock_entry/stock_entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 00d27ef232..1b9660e6d2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -372,7 +372,7 @@ class StockEntry(StockController): elif d.t_warehouse and not d.basic_rate: d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse, self.doctype, self.name, d.allow_zero_valuation_rate, - currency=erpnext.get_company_currency(self.company)) + currency=erpnext.get_company_currency(self.company), company=self.company) def set_actual_qty(self): allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")) From 1e4ea466d3a58c2433006cdccf919a7acd0763fa Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 11 Dec 2019 09:36:17 +0530 Subject: [PATCH 134/389] style: update/standardize rename_doc usage --- .../v11_0/merge_land_unit_with_location.py | 5 ++-- .../v11_0/rename_asset_adjustment_doctype.py | 4 ++-- .../patches/v11_0/rename_health_insurance.py | 3 +-- .../rename_healthcare_doctype_and_fields.py | 3 +-- .../rename_production_order_to_work_order.py | 9 ++++--- .../rename_supplier_type_to_supplier_group.py | 3 +-- .../rename_pricing_rule_child_doctypes.py | 3 +-- erpnext/stock/doctype/item/test_item.py | 3 +-- .../stock/doctype/warehouse/test_warehouse.py | 24 +++++++++++-------- 9 files changed, 27 insertions(+), 30 deletions(-) diff --git a/erpnext/patches/v11_0/merge_land_unit_with_location.py b/erpnext/patches/v11_0/merge_land_unit_with_location.py index 1ea486dfd5..7845da255a 100644 --- a/erpnext/patches/v11_0/merge_land_unit_with_location.py +++ b/erpnext/patches/v11_0/merge_land_unit_with_location.py @@ -4,19 +4,18 @@ from __future__ import unicode_literals import frappe -from frappe.model.rename_doc import rename_doc from frappe.model.utils.rename_field import rename_field def execute(): # Rename and reload the Land Unit and Linked Land Unit doctypes if frappe.db.table_exists('Land Unit') and not frappe.db.table_exists('Location'): - rename_doc('DocType', 'Land Unit', 'Location', force=True) + frappe.rename_doc('DocType', 'Land Unit', 'Location', force=True) frappe.reload_doc('assets', 'doctype', 'location') if frappe.db.table_exists('Linked Land Unit') and not frappe.db.table_exists('Linked Location'): - rename_doc('DocType', 'Linked Land Unit', 'Linked Location', force=True) + frappe.rename_doc('DocType', 'Linked Land Unit', 'Linked Location', force=True) frappe.reload_doc('assets', 'doctype', 'linked_location') diff --git a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py index c03ab0b711..fad0cf7a45 100644 --- a/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py +++ b/erpnext/patches/v11_0/rename_asset_adjustment_doctype.py @@ -3,9 +3,9 @@ from __future__ import unicode_literals import frappe -from frappe.model.rename_doc import rename_doc + def execute(): if frappe.db.table_exists("Asset Adjustment") and not frappe.db.table_exists("Asset Value Adjustment"): - rename_doc('DocType', 'Asset Adjustment', 'Asset Value Adjustment', force=True) + frappe.rename_doc('DocType', 'Asset Adjustment', 'Asset Value Adjustment', force=True) frappe.reload_doc('assets', 'doctype', 'asset_value_adjustment') \ No newline at end of file diff --git a/erpnext/patches/v11_0/rename_health_insurance.py b/erpnext/patches/v11_0/rename_health_insurance.py index 24d1ddf031..e605071a29 100644 --- a/erpnext/patches/v11_0/rename_health_insurance.py +++ b/erpnext/patches/v11_0/rename_health_insurance.py @@ -2,9 +2,8 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -from frappe.model.rename_doc import rename_doc import frappe def execute(): - rename_doc('DocType', 'Health Insurance', 'Employee Health Insurance', force=True) + frappe.rename_doc('DocType', 'Health Insurance', 'Employee Health Insurance', force=True) frappe.reload_doc('hr', 'doctype', 'employee_health_insurance') \ No newline at end of file diff --git a/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py b/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py index 8fdac07658..9705681b33 100644 --- a/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py +++ b/erpnext/patches/v11_0/rename_healthcare_doctype_and_fields.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import frappe -from frappe.model.rename_doc import rename_doc from frappe.model.utils.rename_field import rename_field from frappe.modules import scrub, get_doctype_module @@ -37,7 +36,7 @@ doc_rename_map = { def execute(): for dt in doc_rename_map: if frappe.db.exists('DocType', dt): - rename_doc('DocType', dt, doc_rename_map[dt], force=True) + frappe.rename_doc('DocType', dt, doc_rename_map[dt], force=True) for dn in field_rename_map: if frappe.db.exists('DocType', dn): diff --git a/erpnext/patches/v11_0/rename_production_order_to_work_order.py b/erpnext/patches/v11_0/rename_production_order_to_work_order.py index 2c27fbbc9d..2f620f413b 100644 --- a/erpnext/patches/v11_0/rename_production_order_to_work_order.py +++ b/erpnext/patches/v11_0/rename_production_order_to_work_order.py @@ -2,18 +2,17 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -from frappe.model.rename_doc import rename_doc -from frappe.model.utils.rename_field import rename_field import frappe +from frappe.model.utils.rename_field import rename_field def execute(): - rename_doc('DocType', 'Production Order', 'Work Order', force=True) + frappe.rename_doc('DocType', 'Production Order', 'Work Order', force=True) frappe.reload_doc('manufacturing', 'doctype', 'work_order') - rename_doc('DocType', 'Production Order Item', 'Work Order Item', force=True) + frappe.rename_doc('DocType', 'Production Order Item', 'Work Order Item', force=True) frappe.reload_doc('manufacturing', 'doctype', 'work_order_item') - rename_doc('DocType', 'Production Order Operation', 'Work Order Operation', force=True) + frappe.rename_doc('DocType', 'Production Order Operation', 'Work Order Operation', force=True) frappe.reload_doc('manufacturing', 'doctype', 'work_order_operation') frappe.reload_doc('projects', 'doctype', 'timesheet') diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py index 52d4621c7b..c4b3838c71 100644 --- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py +++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py @@ -1,6 +1,5 @@ from __future__ import unicode_literals import frappe -from frappe.model.rename_doc import rename_doc from frappe.model.utils.rename_field import rename_field from frappe import _ from frappe.utils.nestedset import rebuild_tree @@ -9,7 +8,7 @@ def execute(): if frappe.db.table_exists("Supplier Group"): frappe.reload_doc('setup', 'doctype', 'supplier_group') elif frappe.db.table_exists("Supplier Type"): - rename_doc("DocType", "Supplier Type", "Supplier Group", force=True) + frappe.rename_doc("DocType", "Supplier Type", "Supplier Group", force=True) frappe.reload_doc('setup', 'doctype', 'supplier_group') frappe.reload_doc("accounts", "doctype", "pricing_rule") frappe.reload_doc("accounts", "doctype", "tax_rule") diff --git a/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py b/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py index 41ac8cff2b..b9ad622b0e 100644 --- a/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py +++ b/erpnext/patches/v12_0/rename_pricing_rule_child_doctypes.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import frappe -from frappe.model.rename_doc import rename_doc doctypes = { 'Price Discount Slab': 'Promotional Scheme Price Discount', @@ -16,6 +15,6 @@ doctypes = { def execute(): for old_doc, new_doc in doctypes.items(): if not frappe.db.table_exists(new_doc) and frappe.db.table_exists(old_doc): - rename_doc('DocType', old_doc, new_doc) + frappe.rename_doc('DocType', old_doc, new_doc) frappe.reload_doc("accounts", "doctype", frappe.scrub(new_doc)) frappe.delete_doc("DocType", old_doc) diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index da53d8d6b1..cbd5e33b14 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -11,7 +11,6 @@ from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsE InvalidItemAttributeValueError, get_variant) from erpnext.stock.doctype.item.item import StockExistsForTemplate, InvalidBarcode from erpnext.stock.doctype.item.item import get_uom_conv_factor -from frappe.model.rename_doc import rename_doc from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.get_item_details import get_item_details @@ -348,7 +347,7 @@ class TestItem(unittest.TestCase): make_stock_entry(item_code="Test Item for Merging 2", target="_Test Warehouse 1 - _TC", qty=1, rate=100) - rename_doc("Item", "Test Item for Merging 1", "Test Item for Merging 2", merge=True) + frappe.rename_doc("Item", "Test Item for Merging 1", "Test Item for Merging 2", merge=True) self.assertFalse(frappe.db.exists("Item", "Test Item for Merging 1")) diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py index dc39e101ce..e0483d3ab6 100644 --- a/erpnext/stock/doctype/warehouse/test_warehouse.py +++ b/erpnext/stock/doctype/warehouse/test_warehouse.py @@ -1,18 +1,22 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals -from frappe.model.rename_doc import rename_doc -from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + +import unittest + +import frappe from frappe.utils import cint -from erpnext import set_perpetual_inventory from frappe.test_runner import make_test_records -from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account import erpnext -import frappe -import unittest +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from erpnext import set_perpetual_inventory +from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account + + test_records = frappe.get_test_records('Warehouse') + class TestWarehouse(unittest.TestCase): def setUp(self): if not frappe.get_value('Item', '_Test Item'): @@ -41,7 +45,7 @@ class TestWarehouse(unittest.TestCase): # Rename with abbr if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 2 - _TC"): frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC") - rename_doc("Warehouse", "Test Warehouse for Renaming 1 - _TC", "Test Warehouse for Renaming 2 - _TC") + frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 1 - _TC", "Test Warehouse for Renaming 2 - _TC") self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": "Test Warehouse for Renaming 1 - _TC"})) @@ -50,7 +54,7 @@ class TestWarehouse(unittest.TestCase): if frappe.db.exists("Warehouse", "Test Warehouse for Renaming 3 - _TC"): frappe.delete_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC") - rename_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC", "Test Warehouse for Renaming 3") + frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 2 - _TC", "Test Warehouse for Renaming 3") self.assertTrue(frappe.db.get_value("Warehouse", filters={"account": "Test Warehouse for Renaming 1 - _TC"})) @@ -58,7 +62,7 @@ class TestWarehouse(unittest.TestCase): # Another rename with multiple dashes if frappe.db.exists("Warehouse", "Test - Warehouse - Company - _TC"): frappe.delete_doc("Warehouse", "Test - Warehouse - Company - _TC") - rename_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC", "Test - Warehouse - Company") + frappe.rename_doc("Warehouse", "Test Warehouse for Renaming 3 - _TC", "Test - Warehouse - Company") def test_warehouse_merging(self): set_perpetual_inventory(1) @@ -78,7 +82,7 @@ class TestWarehouse(unittest.TestCase): {"item_code": "_Test Item", "warehouse": "Test Warehouse for Merging 2 - _TC"}, "actual_qty")) ) - rename_doc("Warehouse", "Test Warehouse for Merging 1 - _TC", + frappe.rename_doc("Warehouse", "Test Warehouse for Merging 1 - _TC", "Test Warehouse for Merging 2 - _TC", merge=True) self.assertFalse(frappe.db.exists("Warehouse", "Test Warehouse for Merging 1 - _TC")) From c1a1e7d503cf9df43287abe81d3f2d899c6c5ad3 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 19 Dec 2019 20:11:37 +0530 Subject: [PATCH 135/389] style: formatting changes --- erpnext/hub_node/api.py | 4 +- .../js/hub/components/edit_details_dialog.js | 14 +- erpnext/public/js/hub/pages/Item.vue | 181 +++++++++--------- 3 files changed, 101 insertions(+), 98 deletions(-) diff --git a/erpnext/hub_node/api.py b/erpnext/hub_node/api.py index 602d65e81a..f362539ee7 100644 --- a/erpnext/hub_node/api.py +++ b/erpnext/hub_node/api.py @@ -116,10 +116,10 @@ def get_valid_items(search_value=''): return valid_items @frappe.whitelist() -def update_item(ref_doctype, ref_doc, data): +def update_item(ref_doc, data): data = json.loads(data) - data.update(dict(doctype=ref_doctype, name=ref_doc)) + data.update(dict(doctype='Hub Item', name=ref_doc)) try: connection = get_hub_connection() connection.update(data) diff --git a/erpnext/public/js/hub/components/edit_details_dialog.js b/erpnext/public/js/hub/components/edit_details_dialog.js index e249ca5d7a..97c5f83a13 100644 --- a/erpnext/public/js/hub/components/edit_details_dialog.js +++ b/erpnext/public/js/hub/components/edit_details_dialog.js @@ -1,4 +1,4 @@ -function EditDetailsDialog(primary_action, defaults) { +function edit_details_dialog(params) { let dialog = new frappe.ui.Dialog({ title: __('Update Details'), fields: [ @@ -6,14 +6,14 @@ function EditDetailsDialog(primary_action, defaults) { label: 'Item Name', fieldname: 'item_name', fieldtype: 'Data', - default: defaults.item_name, + default: params.defaults.item_name, reqd: 1 }, { label: 'Hub Category', fieldname: 'hub_category', fieldtype: 'Autocomplete', - default: defaults.hub_category, + default: params.defaults.hub_category, options: [], reqd: 1 }, @@ -21,13 +21,13 @@ function EditDetailsDialog(primary_action, defaults) { label: 'Description', fieldname: 'description', fieldtype: 'Text', - default: defaults.description, + default: params.defaults.description, options: [], reqd: 1 } ], - primary_action_label: primary_action.label || __('Update Details'), - primary_action: primary_action.fn + primary_action_label: params.primary_action.label || __('Update Details'), + primary_action: params.primary_action.fn }); hub.call('get_categories').then(categories => { @@ -38,4 +38,4 @@ function EditDetailsDialog(primary_action, defaults) { return dialog; } -export { EditDetailsDialog }; +export { edit_details_dialog }; diff --git a/erpnext/public/js/hub/pages/Item.vue b/erpnext/public/js/hub/pages/Item.vue index 31cc8d5c8c..11744781b7 100644 --- a/erpnext/public/js/hub/pages/Item.vue +++ b/erpnext/public/js/hub/pages/Item.vue @@ -1,10 +1,5 @@