From fcb54762a185d134838b37f4a668162d3a52f426 Mon Sep 17 00:00:00 2001 From: Ronel Cabrera Date: Mon, 18 Nov 2019 17:00:07 +0800 Subject: [PATCH 01/35] feat(Contacts): select billing contact for sales invoice --- erpnext/accounts/party.py | 32 +++++++++- .../erpnext_integrations/custom/contact.json | 60 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 erpnext/erpnext_integrations/custom/contact.json diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 422ace64f57..e9c652ecbae 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -46,7 +46,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= currency = party.default_currency if party.get("default_currency") else get_company_currency(company) 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_contact_details(party_details, party, party_type, doctype) set_other_values(party_details, party, party_type) set_price_list(party_details, party, party_type, price_list, pos_profile) @@ -115,8 +115,11 @@ def set_address_details(party_details, party, party_type, doctype=None, company= def get_regional_address_details(party_details, doctype, company): pass -def set_contact_details(party_details, party, party_type): - party_details.contact_person = get_default_contact(party_type, party.name) +def set_contact_details(party_details, party, party_type, doctype=None): + if doctype == 'Sales Invoice': + party_details.contact_person = get_default_billing_contact(doctype, party.name) + else: + party_details.contact_person = get_default_contact(party_type, party.name) if not party_details.contact_person: party_details.update({ @@ -615,3 +618,26 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None): if data: return frappe._dict(data) + +def get_default_billing_contact(doctype, name): + """ + Returns default contact for the given doctype and name. + Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact. + """ + out = frappe.db.sql(""" + SELECT dl.parent, c.is_primary_contact, c.is_billing_contact + FROM `tabDynamic Link` dl + INNER JOIN tabContact c ON c.name = dl.parent + WHERE + dl.link_doctype=%s AND + dl.link_name=%s AND + dl.parenttype = "Contact" + ORDER BY is_billing_contact DESC, is_primary_contact DESC + """, (doctype, name)) + if out: + try: + return out[0][0] + except: + return None + else: + return None \ No newline at end of file diff --git a/erpnext/erpnext_integrations/custom/contact.json b/erpnext/erpnext_integrations/custom/contact.json new file mode 100644 index 00000000000..98a4bbc795b --- /dev/null +++ b/erpnext/erpnext_integrations/custom/contact.json @@ -0,0 +1,60 @@ +{ + "custom_fields": [ + { + "_assign": null, + "_comments": null, + "_liked_by": null, + "_user_tags": null, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "columns": 0, + "creation": "2019-12-02 11:00:03.432994", + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "dt": "Contact", + "fetch_from": null, + "fetch_if_empty": 0, + "fieldname": "is_billing_contact", + "fieldtype": "Check", + "hidden": 0, + "idx": 27, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "insert_after": "is_primary_contact", + "label": "Is Billing Contact", + "length": 0, + "modified": "2019-12-02 11:00:03.432994", + "modified_by": "Administrator", + "name": "Contact-is_billing_contact", + "no_copy": 0, + "options": null, + "owner": "Administrator", + "parent": null, + "parentfield": null, + "parenttype": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0, + "width": null + } + ], + "custom_perms": [], + "doctype": "Contact", + "property_setters": [], + "sync_on_migrate": 1 +} \ No newline at end of file From ce90848161c842d07e72d7b9efbe61264accefb2 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 19 Mar 2020 13:10:26 +0530 Subject: [PATCH 02/35] fix: allow target warehouses to be changed for work order stock entries --- erpnext/stock/doctype/stock_entry/stock_entry.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 8b072c66ee1..5880c408a1e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -294,13 +294,8 @@ class StockEntry(StockController): if validate_for_manufacture: if d.bom_no: d.s_warehouse = None - if not d.t_warehouse: frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) - - elif self.pro_doc and (cstr(d.t_warehouse) != self.pro_doc.fg_warehouse and cstr(d.t_warehouse) != self.pro_doc.scrap_warehouse): - frappe.throw(_("Target warehouse in row {0} must be same as Work Order").format(d.idx)) - else: d.t_warehouse = None if not d.s_warehouse: From 73c8d23a79ec8b2def31d61d19855e8e3d0d7228 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 20 Mar 2020 17:49:59 +0530 Subject: [PATCH 03/35] fix: UOM fixes in Sales Order,Material Request & Production Plan --- .../doctype/production_plan/production_plan.py | 2 +- .../selling/doctype/sales_order/sales_order.py | 16 +++++----------- .../doctype/material_request/material_request.py | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index a79ea0e14b0..358a5429d91 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -144,7 +144,7 @@ class ProductionPlan(Document): item_condition = " and mr_item.item_code ={0}".format(frappe.db.escape(self.item_code)) items = frappe.db.sql("""select distinct parent, name, item_code, warehouse, description, - (qty - ordered_qty) as pending_qty + (qty - ordered_qty) * conversion_factor as pending_qty from `tabMaterial Request Item` mr_item where parent in (%s) and docstatus = 1 and qty > ordered_qty and exists (select name from `tabBOM` bom where bom.item=mr_item.item_code diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 13d2b1519fb..ef2d19ac546 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -496,7 +496,7 @@ def close_or_unclose_sales_orders(names, status): def get_requested_item_qty(sales_order): return frappe._dict(frappe.db.sql(""" - select sales_order_item, sum(stock_qty) + select sales_order_item, sum(qty) from `tabMaterial Request Item` where docstatus = 1 and sales_order = %s @@ -507,16 +507,12 @@ def get_requested_item_qty(sales_order): def make_material_request(source_name, target_doc=None): requested_item_qty = get_requested_item_qty(source_name) - def postprocess(source, doc): - doc.material_request_type = "Purchase" - def update_item(source, target, source_parent): # qty is for packed items, because packed items don't have stock_qty field - qty = source.get("stock_qty") or source.get("qty") + qty = source.get("qty") target.project = source_parent.project target.qty = qty - requested_item_qty.get(source.name, 0) - target.conversion_factor = 1 - target.stock_qty = qty - requested_item_qty.get(source.name, 0) + target.stock_qty = flt(target.qty) * flt(target.conversion_factor) doc = get_mapped_doc("Sales Order", source_name, { "Sales Order": { @@ -537,14 +533,12 @@ def make_material_request(source_name, target_doc=None): "doctype": "Material Request Item", "field_map": { "name": "sales_order_item", - "parent": "sales_order", - "stock_uom": "uom", - "stock_qty": "qty" + "parent": "sales_order" }, "condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0), "postprocess": update_item } - }, target_doc, postprocess) + }, target_doc) return doc diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 45428470167..285643d712c 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -501,7 +501,7 @@ def raise_work_orders(material_request): wo_order = frappe.new_doc("Work Order") wo_order.update({ "production_item": d.item_code, - "qty": d.qty - d.ordered_qty, + "qty": d.stock_qty - d.ordered_qty, "fg_warehouse": d.warehouse, "wip_warehouse": default_wip_warehouse, "description": d.description, From 4464a591ef39d03d4441ee60fcbf36217f90e32f Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 19 Mar 2020 18:02:35 +0530 Subject: [PATCH 04/35] fix: allow BOM to use same item as raw material --- erpnext/manufacturing/doctype/bom/bom.js | 3 +-- erpnext/manufacturing/doctype/bom/bom.py | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 3acaee4ffbd..70c4c608e7f 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -43,8 +43,7 @@ frappe.ui.form.on("BOM", { frm.set_query("item_code", "items", function() { return { - query: "erpnext.controllers.queries.item_query", - filters: [["Item", "name", "!=", cur_frm.doc.item]] + query: "erpnext.controllers.queries.item_query" }; }); diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index f6cdb2e57cf..b3e602bdfab 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -114,10 +114,6 @@ class BOM(WebsiteGenerator): child = self.append('operations', d) child.hour_rate = flt(d.hour_rate / self.conversion_rate, 2) - def validate_rm_item(self, item): - if (item[0]['name'] in [it.item_code for it in self.items]) and item[0]['name'] == self.item: - frappe.throw(_("BOM #{0}: Raw material cannot be same as main Item").format(self.name)) - def set_bom_material_details(self): for item in self.get("items"): self.validate_bom_currecny(item) @@ -147,7 +143,6 @@ class BOM(WebsiteGenerator): args = json.loads(args) item = self.get_item_det(args['item_code']) - self.validate_rm_item(item) args['bom_no'] = args['bom_no'] or item and cstr(item[0]['default_bom']) or '' args['transfer_for_manufacture'] = (cstr(args.get('include_item_in_manufacturing', '')) or From b7d84725e873877f13acd23ed45f19d134a3b1b1 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 19 Mar 2020 18:44:27 +0530 Subject: [PATCH 05/35] fix: add warehouse check for FG item --- 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 8b072c66ee1..d66a9fe9ced 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -234,7 +234,7 @@ class StockEntry(StockController): if self.purpose == "Manufacture" and self.work_order: production_item = frappe.get_value('Work Order', self.work_order, 'production_item') for item in self.items: - if item.item_code == production_item and item.qty != self.fg_completed_qty: + if item.item_code == production_item and item.t_warehouse and item.qty != self.fg_completed_qty: frappe.throw(_("Finished product quantity {0} and For Quantity {1} cannot be different") .format(item.qty, self.fg_completed_qty)) From 9725e43eedd219e4ea1deb2f0ef7b417c0a07fcb Mon Sep 17 00:00:00 2001 From: Anupam K Date: Thu, 26 Mar 2020 00:17:09 +0530 Subject: [PATCH 06/35] fix: Adding proper error message --- erpnext/accounts/party.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 422ace64f57..86d85ec2d40 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -303,7 +303,13 @@ def validate_party_accounts(doc): company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") if existing_gle_currency and party_account_currency != existing_gle_currency: - frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company)) + if doc.doctype == 'Customer': + error_msg = _("Customer {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.customer_name), frappe.bold(existing_gle_currency), frappe.bold(account.company)) + elif doc.doctype == 'Supplier': + error_msg = _("Supplier {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.supplier_name), frappe.bold(existing_gle_currency), frappe.bold(account.company)) + else: + error_msg = _("{0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.name), frappe.bold(existing_gle_currency), frappe.bold(account.company)) + frappe.throw(error_msg) if doc.get("default_currency") and party_account_currency and company_default_currency: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency: From ea182051d3bd63f104cc47de5b19eb43bf3f8b7e Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 26 Mar 2020 15:26:47 +0530 Subject: [PATCH 07/35] fix: consumed qty values in work order --- .../doctype/work_order/work_order.py | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 98149aedefa..e50dc69b4b2 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -552,24 +552,33 @@ class WorkOrder(Document): d.db_set('transferred_qty', flt(transferred_qty), update_modified = False) def update_consumed_qty_for_required_items(self): - '''update consumed qty from submitted stock entries for that item against - the work order''' + ''' + Update consumed qty from submitted stock entries + against a work order for each stock item + ''' - for d in self.required_items: - consumed_qty = frappe.db.sql('''select sum(qty) - from `tabStock Entry` entry, `tabStock Entry Detail` detail - where + for item in self.required_items: + consumed_qty = frappe.db.sql(''' + SELECT + SUM(qty) + FROM + `tabStock Entry` entry, + `tabStock Entry Detail` detail + WHERE entry.work_order = %(name)s - and (entry.purpose = "Material Consumption for Manufacture" - or entry.purpose = "Manufacture") - and entry.docstatus = 1 - and detail.parent = entry.name - and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', { - 'name': self.name, - 'item': d.item_code - })[0][0] + AND (entry.purpose = "Material Consumption for Manufacture" + OR entry.purpose = "Manufacture") + AND entry.docstatus = 1 + AND detail.parent = entry.name + AND detail.s_warehouse IS NOT null + AND (detail.item_code = %(item)s + OR detail.original_item = %(item)s) + ''', { + 'name': self.name, + 'item': item.item_code + })[0][0] - d.db_set('consumed_qty', flt(consumed_qty), update_modified = False) + item.db_set('consumed_qty', flt(consumed_qty), update_modified=False) def make_bom(self): data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse From 7ca28fdab5058f06a31a20dfcac204ab9f4a777a Mon Sep 17 00:00:00 2001 From: Anupam K Date: Thu, 26 Mar 2020 15:54:15 +0530 Subject: [PATCH 08/35] fix: Adding proper error message --- erpnext/accounts/party.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 86d85ec2d40..87bc4687902 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -281,8 +281,9 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren existing_gle_currency = get_party_gle_currency(party_type, party, company) if existing_gle_currency and party_account_currency != existing_gle_currency: - frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}") - .format(party_type, party, existing_gle_currency), InvalidAccountCurrency) + frappe.throw(_("{0} : {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.") + .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company))) + def validate_party_accounts(doc): companies = [] @@ -302,14 +303,12 @@ def validate_party_accounts(doc): else: company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") - if existing_gle_currency and party_account_currency != existing_gle_currency: - if doc.doctype == 'Customer': - error_msg = _("Customer {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.customer_name), frappe.bold(existing_gle_currency), frappe.bold(account.company)) - elif doc.doctype == 'Supplier': - error_msg = _("Supplier {0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.supplier_name), frappe.bold(existing_gle_currency), frappe.bold(account.company)) - else: - error_msg = _("{0} has Accounting entries in currency {1} for company {2}. Please select a receivable or payable account with currency {1}.").format(frappe.bold(doc.name), frappe.bold(existing_gle_currency), frappe.bold(account.company)) - frappe.throw(error_msg) + if doc.doctype == 'Customer': + validate_party_gle_currency(doc.doctype, doc.customer_name, account.company,party_account_currency) + elif doc.doctype == 'Supplier': + validate_party_gle_currency(doc.doctype, doc.supplier_name, account.company,party_account_currency) + else: + validate_party_gle_currency(doc.doctype, doc.name, account.company,party_account_currency) if doc.get("default_currency") and party_account_currency and company_default_currency: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency: From 71fb8e8b57bef071b403cac420a854205e01e0fa Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 20:43:17 +0530 Subject: [PATCH 09/35] fix: use setup from Supplier Quotation Controller --- .../supplier_quotation/supplier_quotation.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js index 39042b8b068..16061c61ba0 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js @@ -4,15 +4,17 @@ // attach required files {% include 'erpnext/public/js/controllers/buying.js' %}; -frappe.ui.form.on('Suppier Quotation', { - setup: function(frm) { - frm.custom_make_buttons = { - 'Purchase Order': 'Purchase Order' - } - } -}); - erpnext.buying.SupplierQuotationController = erpnext.buying.BuyingController.extend({ + setup: function() { + this.frm.custom_make_buttons = { + 'Purchase Order': 'Purchase Order', + 'Quotation': 'Quotation', + 'Subscription': 'Subscription' + } + + this._super(); + }, + refresh: function() { var me = this; this._super(); From 8488ef8eb16c5ec2a0853dee9e0c2f3bbe90e29c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Sat, 28 Mar 2020 14:07:09 +0530 Subject: [PATCH 10/35] fix: customer group price list not fetched in pos --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index ba1ceffd140..bd18d5799bc 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -437,13 +437,16 @@ class SalesInvoice(SellingController): if (not for_validate) or (for_validate and not self.get(fieldname)): self.set(fieldname, pos.get(fieldname)) - customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list') - if pos.get("company_address"): self.company_address = pos.get("company_address") - if not customer_price_list: - self.set('selling_price_list', pos.get('selling_price_list')) + customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group']) + + customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list') + + selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list') + + self.set('selling_price_list', selling_price_list) if not for_validate: self.update_stock = cint(pos.get("update_stock")) From e7c45654839c86575f1140afb6d53c8b03ea61cc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 28 Mar 2020 14:56:45 +0530 Subject: [PATCH 11/35] fix: item code showing as mandatory even if the 'Item Naming By' is set as Naming Series in stock settings --- erpnext/public/js/utils/item_quick_entry.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/public/js/utils/item_quick_entry.js b/erpnext/public/js/utils/item_quick_entry.js index 2947d5b98eb..27ef107acef 100644 --- a/erpnext/public/js/utils/item_quick_entry.js +++ b/erpnext/public/js/utils/item_quick_entry.js @@ -8,12 +8,19 @@ frappe.ui.form.ItemQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ render_dialog: function() { this.mandatory = this.get_variant_fields().concat(this.mandatory); this.mandatory = this.mandatory.concat(this.get_attributes_fields()); + this.check_naming_series_based_on(); this._super(); this.init_post_render_dialog_operations(); this.preset_fields_for_template(); this.dialog.$wrapper.find('.edit-full').text(__('Edit in full page for more options like assets, serial nos, batches etc.')) }, + check_naming_series_based_on: function() { + if (frappe.defaults.get_default("item_naming_by") === "Naming Series") { + this.mandatory = this.mandatory.filter(d => d.fieldname !== "item_code"); + } + }, + init_post_render_dialog_operations: function() { this.dialog.fields_dict.attribute_html.$wrapper.append(frappe.render_template("item_quick_entry")); this.init_for_create_variant_trigger(); From c6b850eacb8f0dc42682781467a3112fe436d5f0 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 28 Mar 2020 21:17:58 +0530 Subject: [PATCH 12/35] fix: Improve message --- erpnext/accounts/party.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 87bc4687902..457b07b41fd 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -281,7 +281,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren existing_gle_currency = get_party_gle_currency(party_type, party, company) if existing_gle_currency and party_account_currency != existing_gle_currency: - frappe.throw(_("{0} : {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.") + frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.") .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company))) From 1a2cf5cabe8c4faff6d8b204d0916bbcaf8c75c1 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 28 Mar 2020 21:26:16 +0530 Subject: [PATCH 13/35] fix: get_party_gle_currency failing if customer_name or supplier_name is passed --- erpnext/accounts/party.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 457b07b41fd..aed6e230528 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -296,19 +296,13 @@ def validate_party_accounts(doc): companies.append(account.company) party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True) - existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company) if frappe.db.get_default("Company"): company_default_currency = frappe.get_cached_value('Company', frappe.db.get_default("Company"), "default_currency") else: company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") - if doc.doctype == 'Customer': - validate_party_gle_currency(doc.doctype, doc.customer_name, account.company,party_account_currency) - elif doc.doctype == 'Supplier': - validate_party_gle_currency(doc.doctype, doc.supplier_name, account.company,party_account_currency) - else: - validate_party_gle_currency(doc.doctype, doc.name, account.company,party_account_currency) + validate_party_gle_currency(doc.doctype, doc.name, account.company, party_account_currency) if doc.get("default_currency") and party_account_currency and company_default_currency: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency: From 56bc36f9e250c70ad94c0ed25a310a0ab04e7ec7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Mon, 30 Mar 2020 15:05:20 +0530 Subject: [PATCH 14/35] fix: Test cases --- erpnext/accounts/party.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index aed6e230528..8567740ee70 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -282,8 +282,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren if existing_gle_currency and party_account_currency != existing_gle_currency: frappe.throw(_("{0} {1} has accounting entries in currency {2} for company {3}. Please select a receivable or payable account with currency {2}.") - .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company))) - + .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency) def validate_party_accounts(doc): companies = [] From 06b3f6f781d04f48b68246bfa02043483734adda Mon Sep 17 00:00:00 2001 From: Ahmad M Abdelrahman Date: Tue, 31 Mar 2020 08:08:49 +0300 Subject: [PATCH 15/35] Remove Campagin restrection (#21024) Lead Source Now is an independent doctype. forcing the user to add Campaign is not valid Solution Show Campaign keeping it optional and keep the user to be free to add or remove data --- erpnext/crm/doctype/lead/lead.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json index bc007b146f1..5299368f426 100644 --- a/erpnext/crm/doctype/lead/lead.json +++ b/erpnext/crm/doctype/lead/lead.json @@ -171,7 +171,6 @@ "options": "Customer" }, { - "depends_on": "eval: doc.source==\"Campaign\"", "fieldname": "campaign_name", "fieldtype": "Link", "label": "Campaign Name", @@ -512,4 +511,4 @@ "sort_field": "modified", "sort_order": "DESC", "title_field": "title" -} \ No newline at end of file +} From b0ab3981ea51bdc5541b3bf2933b2e4c4814dfea Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Tue, 31 Mar 2020 07:10:03 +0200 Subject: [PATCH 16/35] fix(regional): header row in DATEV report (#21113) * fix(regional): encoding of DATEV report * feat(regional): filter datev report by voucher type * fix: creation time, coa used, is frozen * add voucher types: Payroll Entry, Bank Reconciliation, Asset, Stock Entry * fix indentation * fix indentation * fix indentation --- erpnext/regional/report/datev/datev.js | 6 +++ erpnext/regional/report/datev/datev.py | 40 ++++++++++++------- .../regional/report/datev/datev_constants.py | 20 ++++++++-- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js index 1e000b673e6..d8638ab05de 100644 --- a/erpnext/regional/report/datev/datev.js +++ b/erpnext/regional/report/datev/datev.js @@ -21,6 +21,12 @@ frappe.query_reports["DATEV"] = { "default": frappe.datetime.now_date(), "fieldtype": "Date", "reqd": 1 + }, + { + "fieldname": "voucher_type", + "label": __("Voucher Type"), + "fieldtype": "Select", + "options": "\nSales Invoice\nPurchase Invoice\nPayment Entry\nExpense Claim\nPayroll Entry\nBank Reconciliation\nAsset\nStock Entry" } ], onload: function(query_report) { diff --git a/erpnext/regional/report/datev/datev.py b/erpnext/regional/report/datev/datev.py index e9b42356a22..a6579121cbf 100644 --- a/erpnext/regional/report/datev/datev.py +++ b/erpnext/regional/report/datev/datev.py @@ -62,6 +62,7 @@ def get_transactions(filters, as_dict=1): filters -- dict of filters to be passed to the sql query as_dict -- return as list of dicts [0,1] """ + filter_by_voucher = 'AND gl.voucher_type = %(voucher_type)s' if filters.get('voucher_type') else '' gl_entries = frappe.db.sql(""" SELECT @@ -80,8 +81,10 @@ def get_transactions(filters, as_dict=1): gl.posting_date as 'Belegdatum', gl.voucher_no as 'Belegfeld 1', gl.remarks as 'Buchungstext', - gl.against_voucher_type as 'Beleginfo - Art 1', - gl.against_voucher as 'Beleginfo - Inhalt 1' + gl.voucher_type as 'Beleginfo - Art 1', + gl.voucher_no as 'Beleginfo - Inhalt 1', + gl.against_voucher_type as 'Beleginfo - Art 2', + gl.against_voucher as 'Beleginfo - Inhalt 2' FROM `tabGL Entry` gl @@ -109,7 +112,8 @@ def get_transactions(filters, as_dict=1): WHERE gl.company = %(company)s AND DATE(gl.posting_date) >= %(from_date)s AND DATE(gl.posting_date) <= %(to_date)s - ORDER BY 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) + {} + ORDER BY 'Belegdatum', gl.voucher_no""".format(filter_by_voucher), filters, as_dict=as_dict) return gl_entries @@ -281,24 +285,24 @@ def get_datev_csv(data, filters, csv_class): def get_header(filters, csv_class): coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts") - coa_used = "SKR04" if "SKR04" in coa else ("SKR03" if "SKR03" in coa else "") + coa_used = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "") header = [ # DATEV format - # "DTVF" = created by DATEV software, - # "EXTF" = created by other software + # "DTVF" = created by DATEV software, + # "EXTF" = created by other software '"EXTF"', # version of the DATEV format - # 141 = 1.41, - # 510 = 5.10, - # 720 = 7.20 + # 141 = 1.41, + # 510 = 5.10, + # 720 = 7.20 '700', csv_class.DATA_CATEGORY, '"%s"' % csv_class.FORMAT_NAME, # Format version (regarding format name) csv_class.FORMAT_VERSION, # Generated on - datetime.datetime.now().strftime("%Y%m%d%H%M%S"), + datetime.datetime.now().strftime("%Y%m%d%H%M%S") + '000', # Imported on -- stays empty '', # Origin. Any two symbols, will be replaced by "SV" on import. @@ -328,13 +332,21 @@ def get_header(filters, csv_class): # R = Diktatkürzel '', # S = Buchungstyp - # 1 = Transaction batch (Finanzbuchführung), - # 2 = Annual financial statement (Jahresabschluss) + # 1 = Transaction batch (Finanzbuchführung), + # 2 = Annual financial statement (Jahresabschluss) '1' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '', # T = Rechnungslegungszweck - '', + # 0 oder leer = vom Rechnungslegungszweck unabhängig + # 50 = Handelsrecht + # 30 = Steuerrecht + # 64 = IFRS + # 40 = Kalkulatorik + # 11 = Reserviert + # 12 = Reserviert + '0', # U = Festschreibung - '', + # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" + '0', # V = Default currency, for example, "EUR" '"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"), # reserviert diff --git a/erpnext/regional/report/datev/datev_constants.py b/erpnext/regional/report/datev/datev_constants.py index a4cd5fc10ec..a059ed365a9 100644 --- a/erpnext/regional/report/datev/datev_constants.py +++ b/erpnext/regional/report/datev/datev_constants.py @@ -498,13 +498,27 @@ QUERY_REPORT_COLUMNS = [ }, { "label": "Beleginfo - Art 1", - "fieldname": "Beleginfo - Art 2", - "fieldtype": "Data", + "fieldname": "Beleginfo - Art 1", + "fieldtype": "Link", + "options": "DocType" }, { "label": "Beleginfo - Inhalt 1", + "fieldname": "Beleginfo - Inhalt 1", + "fieldtype": "Dynamic Link", + "options": "Beleginfo - Art 1" + }, + { + "label": "Beleginfo - Art 2", + "fieldname": "Beleginfo - Art 2", + "fieldtype": "Link", + "options": "DocType" + }, + { + "label": "Beleginfo - Inhalt 2", "fieldname": "Beleginfo - Inhalt 2", - "fieldtype": "Data", + "fieldtype": "Dynamic Link", + "options": "Beleginfo - Art 2" } ] From 3fa03df1e37810aa48ee3f85e974a9b60ac5407f Mon Sep 17 00:00:00 2001 From: prafful1234 <43948551+prafful1234@users.noreply.github.com> Date: Tue, 31 Mar 2020 10:40:42 +0530 Subject: [PATCH 17/35] fix(transaction): Add comment-by from frappe session (#20867) Co-authored-by: prafful1234 --- erpnext/buying/doctype/purchase_order/purchase_order.js | 3 ++- erpnext/selling/doctype/sales_order/sales_order.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index a3264a4c0f8..3111a3a7d53 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -499,7 +499,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend( reference_doctype: me.frm.doctype, reference_name: me.frm.docname, content: __('Reason for hold: ')+data.reason_for_hold, - comment_email: frappe.session.user + comment_email: frappe.session.user, + comment_by: frappe.session.user_fullname }, callback: function(r) { if(!r.exc) { diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index fa765dfaad9..61aa608dd36 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -664,7 +664,8 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( reference_doctype: me.frm.doctype, reference_name: me.frm.docname, content: __('Reason for hold: ')+data.reason_for_hold, - comment_email: frappe.session.user + comment_email: frappe.session.user, + comment_by: frappe.session.user_fullname }, callback: function(r) { if(!r.exc) { From 5c54adec28c7fda2e290a1bd2bfd1ab7f3c751d0 Mon Sep 17 00:00:00 2001 From: Andy Zhu Date: Tue, 31 Mar 2020 18:13:00 +1300 Subject: [PATCH 18/35] fix: docfield of sales_order is not fetching route options for new doc (#21123) Using the wrong method to get `so` as docfield. frappe.meta changes will not effect the `sales_order` in `frm`. --- erpnext/projects/doctype/project/project.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index 3570a0f2be4..58629634968 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -18,7 +18,7 @@ frappe.ui.form.on("Project", { }; }, onload: function (frm) { - var so = frappe.meta.get_docfield("Project", "sales_order"); + var so = frm.get_docfield("Project", "sales_order"); so.get_route_options_for_new_doc = function (field) { if (frm.is_new()) return; return { @@ -135,4 +135,4 @@ function open_form(frm, doctype, child_doctype, parentfield) { frappe.ui.form.make_quick_entry(doctype, null, null, new_doc); }); -} \ No newline at end of file +} From fbf6e56d860c041dff6ceb901e1ed129c1db37a2 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 31 Mar 2020 10:45:32 +0530 Subject: [PATCH 19/35] fix: Expense account currency validation in Landed Cost voucher (#21073) * fix: Expense account currency validation in Landed Cost voucher * fix: Remove unused imports --- erpnext/controllers/queries.py | 14 +++++++++----- .../landed_cost_voucher/landed_cost_voucher.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index d18f8e54d8f..163ef72ee10 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +import erpnext from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.utils import nowdate, getdate from collections import defaultdict @@ -129,23 +130,26 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters): }) def tax_account_query(doctype, txt, searchfield, start, page_len, filters): + company_currency = erpnext.get_company_currency(filters.get('company')) + tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount where tabAccount.docstatus!=2 and account_type in (%s) and is_group = 0 and company = %s + and account_currency = %s and `%s` LIKE %s order by idx desc, name limit %s, %s""" % - (", ".join(['%s']*len(filters.get("account_type"))), "%s", searchfield, "%s", "%s", "%s"), - tuple(filters.get("account_type") + [filters.get("company"), "%%%s%%" % txt, + (", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"), + tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len])) if not tax_accounts: tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount where tabAccount.docstatus!=2 and is_group = 0 - and company = %s and `%s` LIKE %s limit %s, %s""" - % ("%s", searchfield, "%s", "%s", "%s"), - (filters.get("company"), "%%%s%%" % txt, start, page_len)) + and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec + % ("%s", "%s", searchfield, "%s", "%s", "%s"), + (filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len)) return tax_accounts diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index d97b699a0f3..5ad0e13db9a 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -8,6 +8,7 @@ from frappe.utils import flt from frappe.model.meta import get_field_precision from frappe.model.document import Document from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos +from erpnext.accounts.doctype.account.account import get_account_currency class LandedCostVoucher(Document): def get_items_from_purchase_receipts(self): @@ -43,6 +44,7 @@ class LandedCostVoucher(Document): else: self.validate_applicable_charges_for_item() self.validate_purchase_receipts() + self.validate_expense_accounts() self.set_total_taxes_and_charges() def check_mandatory(self): @@ -71,6 +73,14 @@ class LandedCostVoucher(Document): frappe.throw(_("Row {0}: Cost center is required for an item {1}") .format(item.idx, item.item_code)) + def validate_expense_accounts(self): + company_currency = erpnext.get_company_currency(self.company) + for account in self.taxes: + if get_account_currency(account.expense_account) != company_currency: + frappe.throw(msg=_(""" Row {0}: Expense account currency should be same as company's default currency. + Please select expense account with account currency as {1}""") + .format(account.idx, frappe.bold(company_currency)), title=_("Invalid Account Currency")) + def set_total_taxes_and_charges(self): self.total_taxes_and_charges = sum([flt(d.amount) for d in self.get("taxes")]) From bba78f038452fb2f58f63a58389f0a0e2d19dd14 Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 31 Mar 2020 10:49:44 +0530 Subject: [PATCH 20/35] fix: auto created asset message (#21108) * fix: auto created asset message * Update erpnext/controllers/buying_controller.py Co-authored-by: Nabin Hait --- erpnext/controllers/buying_controller.py | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 88c8dba4c6c..fcc9098ba10 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -672,19 +672,32 @@ class BuyingController(StockController): # If asset has to be auto created # Check for asset naming series if item_data.get('asset_naming_series'): + created_assets = [] + for qty in range(cint(d.qty)): - self.make_asset(d) - is_plural = 's' if cint(d.qty) != 1 else '' - messages.append(_('{0} Asset{2} Created for {1}').format(cint(d.qty), d.item_code, is_plural)) + asset = self.make_asset(d) + created_assets.append(asset) + + if len(created_assets) > 5: + # dont show asset form links if more than 5 assets are created + messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code))) + else: + assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets)) + assets_link = frappe.bold(','.join(assets_link)) + + is_plural = 's' if len(created_assets) != 1 else '' + messages.append( + _('Asset{} {assets_link} created for {}').format(is_plural, frappe.bold(d.item_code), assets_link=assets_link) + ) else: - frappe.throw(_("Row {1}: Asset Naming Series is mandatory for the auto creation for item {0}") - .format(d.item_code, d.idx)) + frappe.throw(_("Row {}: Asset Naming Series is mandatory for the auto creation for item {}") + .format(d.idx, frappe.bold(d.item_code))) else: - messages.append(_("Assets not created for {0}. You will have to create asset manually.") - .format(d.item_code)) + messages.append(_("Assets not created for {0}. You will have to create asset manually.") + .format(frappe.bold(d.item_code))) for message in messages: - frappe.msgprint(message, title="Success") + frappe.msgprint(message, title="Success", indicator="green") def make_asset(self, row): if not row.asset_location: @@ -716,6 +729,8 @@ class BuyingController(StockController): asset.set_missing_values() asset.insert() + return asset.name + def update_fixed_asset(self, field, delete_asset = False): for d in self.get("items"): if d.is_fixed_asset: @@ -1026,4 +1041,4 @@ def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty available_batches.append({'batch': batch, 'qty': available_qty}) required_qty -= available_qty - return available_batches \ No newline at end of file + return available_batches From ee0dec8776cbd821318cb7c32d5cdd3c86106a33 Mon Sep 17 00:00:00 2001 From: Anupam Kumar Date: Tue, 31 Mar 2020 10:51:09 +0530 Subject: [PATCH 21/35] fix: email_to, party_type and party are not set in payment request when order made from portal (#21084) Co-authored-by: Anupam K --- erpnext/accounts/doctype/payment_request/payment_request.py | 6 +++--- 1 file 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 0fade8c456c..7e9211af9f4 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -317,13 +317,13 @@ def make_payment_request(**args): "payment_request_type": args.get("payment_request_type"), "currency": ref_doc.currency, "grand_total": grand_total, - "email_to": args.recipient_id or "", + "email_to": args.recipient_id or ref_doc.owner, "subject": _("Payment Request for {0}").format(args.dn), "message": gateway_account.get("message") or get_dummy_message(ref_doc), "reference_doctype": args.dt, "reference_name": args.dn, - "party_type": args.get("party_type"), - "party": args.get("party"), + "party_type": args.get("party_type") or "Customer", + "party": args.get("party") or ref_doc.customer, "bank_account": bank_account }) From 86aff0de1f834353aee25fc14676806f58b801eb Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 31 Mar 2020 15:26:45 +0530 Subject: [PATCH 22/35] fix: serial no scan not adding the serial nos in stock entry (#21083) --- erpnext/public/js/controllers/transaction.js | 7 ++++++- .../stock/doctype/stock_entry/stock_entry.js | 17 +++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index fc4541a2567..4397fe49c3c 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -362,12 +362,17 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ ['serial_no', 'batch_no', 'barcode'].forEach(field => { if (data[field] && frappe.meta.has_field(row_to_modify.doctype, field)) { + + let value = (row_to_modify[field] && field === "serial_no") + ? row_to_modify[field] + '\n' + data[field] : data[field]; + frappe.model.set_value(row_to_modify.doctype, - row_to_modify.name, field, data[field]); + row_to_modify.name, field, value); } }); scan_barcode_field.set_value(''); + refresh_field("items"); }); } return false; diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 3af35244230..3bb941573b6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -310,12 +310,12 @@ frappe.ui.form.on('Stock Entry', { method: "erpnext.stock.get_item_details.get_serial_no", args: {"args": args}, callback: function(r) { - if (!r.exe){ + if (!r.exe && r.message){ frappe.model.set_value(cdt, cdn, "serial_no", r.message); - } - if (callback) { - callback(); + if (callback) { + callback(); + } } } }); @@ -623,10 +623,15 @@ frappe.ui.form.on('Stock Entry Detail', { if(r.message) { var d = locals[cdt][cdn]; $.each(r.message, function(k, v) { - frappe.model.set_value(cdt, cdn, k, v); // qty and it's subsequent fields weren't triggered + if (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); + + if (!d.serial_no) { + erpnext.stock.select_batch_and_serial_no(frm, d); + } } } }); From d274923ae13d2bf76b1c281c87e23fa0df665e1d Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Tue, 31 Mar 2020 15:28:31 +0530 Subject: [PATCH 23/35] fix: "Qty To Manufacture" field as non mandatory in job card (#21081) --- erpnext/manufacturing/doctype/bom/bom.js | 1 + .../doctype/job_card/job_card.js | 14 ++++--- .../doctype/job_card/job_card.json | 6 +-- .../doctype/job_card/job_card.py | 42 ++++++++++--------- .../doctype/work_order/work_order.py | 3 +- 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 3acaee4ffbd..4f08bbc3fc7 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -135,6 +135,7 @@ frappe.ui.form.on("BOM", { frappe.call({ method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order", args: { + bom_no: frm.doc.name, item: frm.doc.item, qty: data.qty || 0.0, project: frm.doc.project diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index bc8c22998ae..8c7876d48de 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -20,7 +20,7 @@ frappe.ui.form.on('Job Card', { } } - if (frm.doc.docstatus == 0 && frm.doc.for_quantity > frm.doc.total_completed_qty + if (frm.doc.docstatus == 0 && (frm.doc.for_quantity > frm.doc.total_completed_qty || !frm.doc.for_quantity) && (!frm.doc.items.length || frm.doc.for_quantity == frm.doc.transferred_qty)) { frm.trigger("prepare_timer_buttons"); } @@ -59,10 +59,14 @@ frappe.ui.form.on('Job Card', { let completed_time = frappe.datetime.now_datetime(); frm.trigger("hide_timer"); - frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), - fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { - frm.events.complete_job(frm, completed_time, data.qty); - }, __("Enter Value"), __("Complete")); + if (frm.doc.for_quantity) { + frappe.prompt({fieldtype: 'Float', label: __('Completed Quantity'), + fieldname: 'qty', reqd: 1, default: frm.doc.for_quantity}, data => { + frm.events.complete_job(frm, completed_time, data.qty); + }, __("Enter Value"), __("Complete")); + } else { + frm.events.complete_job(frm, completed_time, 0); + } }).addClass("btn-primary"); } }, diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index 156accee74e..7661fffa864 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -99,8 +99,7 @@ "fieldname": "for_quantity", "fieldtype": "Float", "in_list_view": 1, - "label": "Qty To Manufacture", - "reqd": 1 + "label": "Qty To Manufacture" }, { "fieldname": "wip_warehouse", @@ -122,6 +121,7 @@ "options": "Employee" }, { + "allow_bulk_edit": 1, "fieldname": "time_logs", "fieldtype": "Table", "label": "Time Logs", @@ -290,7 +290,7 @@ } ], "is_submittable": 1, - "modified": "2019-12-03 13:08:57.926201", + "modified": "2020-03-27 13:36:35.417502", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 029db1cb4e8..f8c60f2a114 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -191,12 +191,9 @@ class JobCard(Document): if not self.time_logs: frappe.throw(_("Time logs are required for job card {0}").format(self.name)) - if self.total_completed_qty <= 0.0: - frappe.throw(_("Total completed qty must be greater than zero")) - - if self.total_completed_qty != self.for_quantity: - frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})") - .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity))) + if self.for_quantity and self.total_completed_qty != self.for_quantity: + frappe.throw(_("The total completed qty({0}) must be equal to qty to manufacture({1})" + .format(frappe.bold(self.total_completed_qty),frappe.bold(self.for_quantity)))) def update_work_order(self): if not self.work_order: @@ -205,27 +202,34 @@ class JobCard(Document): for_quantity, time_in_mins = 0, 0 from_time_list, to_time_list = [], [] - for d in frappe.get_all('Job Card', - filters = {'docstatus': 1, 'operation_id': self.operation_id}): - doc = frappe.get_doc('Job Card', d.name) - for_quantity += doc.total_completed_qty - time_in_mins += doc.total_time_in_mins - for time_log in doc.time_logs: - if time_log.from_time: - from_time_list.append(time_log.from_time) - if time_log.to_time: - to_time_list.append(time_log.to_time) + data = frappe.get_all('Job Card', + fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"], + filters = {"docstatus": 1, "work_order": self.work_order, + "workstation": self.workstation, "operation": self.operation}) + + if data and len(data) > 0: + for_quantity = data[0].completed_qty + time_in_mins = data[0].time_in_mins if for_quantity: + time_data = frappe.db.sql(""" + SELECT + min(from_time) as start_time, max(to_time) as end_time + FROM `tabJob Card` jc, `tabJob Card Time Log` jctl + WHERE + jctl.parent = jc.name and jc.work_order = %s + and jc.workstation = %s and jc.operation = %s and jc.docstatus = 1 + """, (self.work_order, self.workstation, self.operation), as_dict=1) + wo = frappe.get_doc('Work Order', self.work_order) for data in wo.operations: - if data.name == self.operation_id: + if data.workstation == self.workstation and data.operation == self.operation: data.completed_qty = for_quantity data.actual_operation_time = time_in_mins - data.actual_start_time = min(from_time_list) if from_time_list else None - data.actual_end_time = max(to_time_list) if to_time_list else None + data.actual_start_time = time_data[0].start_time if time_data else None + data.actual_end_time = time_data[0].end_time if time_data else None wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 71a62e48d2e..a124b1fcc57 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -648,7 +648,7 @@ def get_item_details(item, project = None): return res @frappe.whitelist() -def make_work_order(item, qty=0, project=None): +def make_work_order(bom_no, item, qty=0, project=None): if not frappe.has_permission("Work Order", "write"): frappe.throw(_("Not permitted"), frappe.PermissionError) @@ -657,6 +657,7 @@ 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) + wo_doc.bom_no = bom_no if flt(qty) > 0: wo_doc.qty = flt(qty) From c19330df634ef0bb2cc4d9027a7b25eb5fbdce39 Mon Sep 17 00:00:00 2001 From: Raffael Meyer Date: Tue, 31 Mar 2020 11:58:45 +0200 Subject: [PATCH 24/35] fix: account groups (#21070) --- ..._kontenplan_SKR04_with_account_number.json | 387 ++++++++---------- 1 file changed, 177 insertions(+), 210 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json index ff95c5ae194..3fc109bfd67 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json @@ -2433,29 +2433,26 @@ "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": { "account_number": "4849" }, - "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": { - "is_group": 1, - "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { - "account_number": "4850" - }, - "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": { - "account_number": "4851" - }, - "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": { - "account_number": "4852" - }, - "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": { - "account_number": "4855" - }, - "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": { - "account_number": "4856" - }, - "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": { - "account_number": "4857" - }, - "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": { - "account_number": "4858" - } + "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { + "account_number": "4850" + }, + "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": { + "account_number": "4851" + }, + "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": { + "account_number": "4852" + }, + "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": { + "account_number": "4855" + }, + "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": { + "account_number": "4856" + }, + "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": { + "account_number": "4857" + }, + "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": { + "account_number": "4858" }, "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": { "account_number": "4910", @@ -2578,20 +2575,17 @@ "Entnahme von Gegenst\u00e4nden ohne USt": { "account_number": "4605" }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": { - "is_group": 1, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { - "account_number": "4630" - }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": { - "account_number": "4637" - }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": { - "account_number": "4638" - }, - "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": { - "account_number": "4639" - } + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { + "account_number": "4630" + }, + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": { + "account_number": "4637" + }, + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": { + "account_number": "4638" + }, + "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": { + "account_number": "4639" }, "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": { "is_group": 1, @@ -2629,14 +2623,11 @@ "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": { "account_number": "4689" }, - "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": { - "is_group": 1, - "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { - "account_number": "4690" - }, - "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": { - "account_number": "4695" - } + "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { + "account_number": "4690" + }, + "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": { + "account_number": "4695" }, "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": { "is_group": 1, @@ -2646,41 +2637,35 @@ "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": { "account_number": "7401" }, - "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": { - "is_group": 1, - "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": { - "account_number": "7450" - }, - "Ertr\u00e4ge durch Verschmelzung und Umwandlung": { - "account_number": "7451" - }, - "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": { - "account_number": "7452" - }, - "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": { - "account_number": "7453" - }, - "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": { - "account_number": "7454" - } + "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": { + "account_number": "7450" }, - "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": { - "is_group": 1, - "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { - "account_number": "7460" - }, - "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": { - "account_number": "7461" - }, - "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": { - "account_number": "7462" - }, - "Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": { - "account_number": "7463" - }, - "Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": { - "account_number": "7464" - } + "Ertr\u00e4ge durch Verschmelzung und Umwandlung": { + "account_number": "7451" + }, + "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": { + "account_number": "7452" + }, + "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": { + "account_number": "7453" + }, + "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": { + "account_number": "7454" + }, + "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { + "account_number": "7460" + }, + "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": { + "account_number": "7461" + }, + "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": { + "account_number": "7462" + }, + "Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": { + "account_number": "7463" + }, + "Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": { + "account_number": "7464" } } }, @@ -2718,40 +2703,43 @@ }, "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { "account_number": "4729" + } + }, + "Gew\u00e4hrte Skonti (Gruppe)": { + "is_group": 1, + "Gew. Skonti": { + "account_number": "4730" }, - "Gew\u00e4hrte Skonti (Gruppe)": { - "is_group": 1, - "Gew. Skonti": { - "account_number": "4730" - }, - "Gew. Skonti 7 % USt": { - "account_number": "4731" - }, - "Gew. Skonti 19 % USt": { - "account_number": "4736" - }, - "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": { - "account_number": "4738" - }, - "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": { - "account_number": "4741" - }, - "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": { - "account_number": "4742" - }, - "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": { - "account_number": "4743" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": { - "account_number": "4745" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": { - "account_number": "4746" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": { - "account_number": "4748" - } + "Gew. Skonti 7 % USt": { + "account_number": "4731" }, + "Gew. Skonti 19 % USt": { + "account_number": "4736" + }, + "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": { + "account_number": "4738" + }, + "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": { + "account_number": "4741" + }, + "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": { + "account_number": "4742" + }, + "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": { + "account_number": "4743" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": { + "account_number": "4745" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": { + "account_number": "4746" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": { + "account_number": "4748" + } + }, + "Gew\u00e4hrte Boni (Gruppe)": { + "is_group": 1, "Gew\u00e4hrte Boni 7 % USt": { "account_number": "4750" }, @@ -2864,103 +2852,79 @@ "account_number": "6398" } }, - "Versicherungen (Gruppe)": { - "is_group": 1, - "Versicherungen": { - "account_number": "6400" - }, - "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": { - "account_number": "6405" - }, - "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": { - "account_number": "6410" - }, - "Beitr\u00e4ge": { - "account_number": "6420" - }, - "Sonstige Abgaben": { - "account_number": "6430" - }, - "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { - "account_number": "6436" - }, - "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { - "account_number": "6437" - }, - "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": { - "account_number": "6440" - }, - "Reparaturen und Instandhaltung von Bauten": { - "account_number": "6450" - }, - "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": { - "account_number": "6460" - }, - "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": { - "account_number": "6470" - }, - "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": { - "account_number": "6475" - }, - "Reparaturen und Instandhaltung von anderen Anlagen": { - "account_number": "6485" - }, - "Sonstige Reparaturen und Instandhaltungen": { - "account_number": "6490" - }, - "Wartungskosten f. Hard- und Software": { - "account_number": "6495" - }, - "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": { - "account_number": "6498" - } + "Versicherungen": { + "account_number": "6400" + }, + "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": { + "account_number": "6405" + }, + "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": { + "account_number": "6410" + }, + "Beitr\u00e4ge": { + "account_number": "6420" + }, + "Sonstige Abgaben": { + "account_number": "6430" + }, + "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { + "account_number": "6436" + }, + "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { + "account_number": "6437" + }, + "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": { + "account_number": "6440" + }, + "Reparaturen und Instandhaltung von Bauten": { + "account_number": "6450" + }, + "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": { + "account_number": "6460" + }, + "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": { + "account_number": "6470" + }, + "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": { + "account_number": "6475" + }, + "Reparaturen und Instandhaltung von anderen Anlagen": { + "account_number": "6485" + }, + "Sonstige Reparaturen und Instandhaltungen": { + "account_number": "6490" + }, + "Wartungskosten f. Hard- und Software": { + "account_number": "6495" + }, + "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": { + "account_number": "6498" }, "Fahrzeugkosten (Gruppe)": { "is_group": 1, "Fahrzeugkosten": { "account_number": "6500" }, - "Kfz-Versicherungen (Gruppe)": { - "is_group": 1, - "Kfz-Versicherungen": { - "account_number": "6520" - } + "Kfz-Versicherungen": { + "account_number": "6520" }, - "Laufende Kfz-Betriebskosten (Gruppe)": { - "is_group": 1, - "Laufende Kfz-Betriebskosten": { - "account_number": "6530" - } + "Laufende Kfz-Betriebskosten": { + "account_number": "6530" }, - "Kfz-Reparaturen (Gruppe)": { - "is_group": 1, - "Kfz-Reparaturen": { - "account_number": "6540" - } + "Kfz-Reparaturen": { + "account_number": "6540" }, - "Garagenmiete (Gruppe)": { - "is_group": 1, - "Garagenmiete": { - "account_number": "6550" - } + "Garagenmiete": { + "account_number": "6550" }, - "Mietleasing Kfz (Gruppe)": { - "is_group": 1, - "Mietleasing Kfz": { - "account_number": "6560" - } + "Mietleasing Kfz": { + "account_number": "6560" }, - "Sonstige Kfz-Kosten (Gruppe)": { - "is_group": 1, - "Sonstige Kfz-Kosten": { - "account_number": "6570" - } + "Sonstige Kfz-Kosten": { + "account_number": "6570" }, - "Mautgeb\u00fchren (Gruppe)": { - "is_group": 1, - "Mautgeb\u00fchren": { - "account_number": "6580" - } + "Mautgeb\u00fchren": { + "account_number": "6580" }, "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": { "account_number": "6590" @@ -3022,20 +2986,23 @@ "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": { "account_number": "6645" }, - "Reisekosten Arbeitnehmer": { - "account_number": "6650" - }, - "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": { - "account_number": "6660" - }, - "Reisekosten Arbeitnehmer Fahrtkosten": { - "account_number": "6663" - }, - "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": { - "account_number": "6664" - }, - "Kilometergelderstattung Arbeitnehmer": { - "account_number": "6668" + "Reisekosten Arbeitnehmer (Gruppe)": { + "is_group": 1, + "Reisekosten Arbeitnehmer": { + "account_number": "6650" + }, + "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": { + "account_number": "6660" + }, + "Reisekosten Arbeitnehmer Fahrtkosten": { + "account_number": "6663" + }, + "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": { + "account_number": "6664" + }, + "Kilometergelderstattung Arbeitnehmer": { + "account_number": "6668" + } }, "Reisekosten Unternehmer (Gruppe)": { "is_group": 1, From 25846cccbdaa0302e94a436db86f66ea1328e10d Mon Sep 17 00:00:00 2001 From: Saqib Date: Tue, 31 Mar 2020 15:31:15 +0530 Subject: [PATCH 25/35] fix: warehouse_account_map not getting reset for diff company transac (#20995) * fix: warehouse_account_map not getting reset for diff company transaction * fix: potential key errors while fetching warehouse_account_map * fix: travis Co-authored-by: Nabin Hait --- erpnext/stock/__init__.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py index a4d4cbd8ef8..8d64efe41dd 100644 --- a/erpnext/stock/__init__.py +++ b/erpnext/stock/__init__.py @@ -13,12 +13,16 @@ install_docs = [ ] def get_warehouse_account_map(company=None): - if not frappe.flags.warehouse_account_map or frappe.flags.in_test: + company_warehouse_account_map = company and frappe.flags.setdefault('warehouse_account_map', {}).get(company) + warehouse_account_map = frappe.flags.warehouse_account_map + + if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test: warehouse_account = frappe._dict() filters = {} if company: filters['company'] = company + frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {}) for d in frappe.get_all('Warehouse', fields = ["name", "account", "parent_warehouse", "company", "is_group"], @@ -30,10 +34,12 @@ def get_warehouse_account_map(company=None): if d.account: d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True) warehouse_account.setdefault(d.name, d) - - frappe.flags.warehouse_account_map = warehouse_account - - return frappe.flags.warehouse_account_map + if company: + frappe.flags.warehouse_account_map[company] = warehouse_account + else: + frappe.flags.warehouse_account_map = warehouse_account + + return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map def get_warehouse_account(warehouse, warehouse_account=None): account = warehouse.account From e567563aa17a92b35f993e0fae3efac2692c1afe Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 4 Mar 2020 13:11:41 +0530 Subject: [PATCH 26/35] fix: if mandatory fields are missing do not create address against lead --- erpnext/crm/doctype/lead/lead.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 73ef79b894a..cefe32c88bf 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -135,10 +135,14 @@ class Lead(SellingController): # 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"]): + address = frappe.new_doc("Address") + + mandatory_fields = get_mandatory_fields(address) + + if not all([self.get(field) for field in mandatory_fields]): + frappe.msgprint(_('Missing mandatory fields in address.'), alert=True, indicator='yellow') 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() @@ -370,3 +374,10 @@ def get_lead_with_phone_number(number): lead = leads[0].name if leads else None return lead + +def get_mandatory_fields(doc): + return [ + df.fieldname + for df in doc.meta.fields + if df.reqd + ] \ No newline at end of file From 6c8d63f3b92a11a36de2ec1a99e06a8909472baa Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 9 Mar 2020 11:57:47 +0530 Subject: [PATCH 27/35] fix: provision to setup new address from lead if address creation break due to mandatory exception --- erpnext/crm/doctype/lead/lead.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index cefe32c88bf..04f21ffb749 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -137,10 +137,13 @@ class Lead(SellingController): # skipping country since the system auto-sets it from system defaults address = frappe.new_doc("Address") - mandatory_fields = get_mandatory_fields(address) + mandatory_fields = [ df.fieldname for df in doc.meta.fields if df.reqd ] if not all([self.get(field) for field in mandatory_fields]): - frappe.msgprint(_('Missing mandatory fields in address.'), alert=True, indicator='yellow') + frappe.msgprint(_('Missing mandatory fields in address. \ + {0} to create address' ).format(" Click here "), + alert=True, indicator='yellow') return address.update({addr_field: self.get(addr_field) for addr_field in address_fields}) @@ -374,10 +377,3 @@ def get_lead_with_phone_number(number): lead = leads[0].name if leads else None return lead - -def get_mandatory_fields(doc): - return [ - df.fieldname - for df in doc.meta.fields - if df.reqd - ] \ No newline at end of file From 84b93414d0a61b4a8f535c3befcefa7b0061d4fa Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 31 Mar 2020 15:57:39 +0530 Subject: [PATCH 28/35] fix: typo --- erpnext/crm/doctype/lead/lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 04f21ffb749..eb9f86076c6 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -137,7 +137,7 @@ class Lead(SellingController): # skipping country since the system auto-sets it from system defaults address = frappe.new_doc("Address") - mandatory_fields = [ df.fieldname for df in doc.meta.fields if df.reqd ] + mandatory_fields = [ df.fieldname for df in address.meta.fields if df.reqd ] if not all([self.get(field) for field in mandatory_fields]): frappe.msgprint(_('Missing mandatory fields in address. \ From cf9347d2f5e702cfb7f097836bb29afff1d22702 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 31 Mar 2020 17:28:43 +0530 Subject: [PATCH 29/35] fix: Undo unneccessary changes --- erpnext/accounts/party.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 98b6d796265..4cfeb251d60 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -11,7 +11,7 @@ from frappe.utils import (add_days, getdate, formatdate, date_diff, add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day) from frappe.contacts.doctype.address.address import (get_address_display, get_default_address, get_company_address) -from frappe.contacts.doctype.contact.contact import get_contact_details, get_default_contact +from frappe.contacts.doctype.contact.contact import get_contact_details from erpnext.exceptions import PartyFrozen, PartyDisabled, InvalidAccountCurrency from erpnext.accounts.utils import get_fiscal_year from erpnext import get_company_currency @@ -46,7 +46,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= currency = party.default_currency if party.get("default_currency") else get_company_currency(company) 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, doctype) + 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) @@ -115,11 +115,8 @@ def set_address_details(party_details, party, party_type, doctype=None, company= def get_regional_address_details(party_details, doctype, company): pass -def set_contact_details(party_details, party, party_type, doctype=None): - if doctype == 'Sales Invoice': - party_details.contact_person = get_default_billing_contact(doctype, party.name) - else: - party_details.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 party_details.contact_person: party_details.update({ @@ -617,8 +614,8 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None): if data: return frappe._dict(data) -def get_default_billing_contact(doctype, name): - """ +def get_default_contact(doctype, name): + """ Returns default contact for the given doctype and name. Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact. """ @@ -626,11 +623,11 @@ def get_default_billing_contact(doctype, name): SELECT dl.parent, c.is_primary_contact, c.is_billing_contact FROM `tabDynamic Link` dl INNER JOIN tabContact c ON c.name = dl.parent - WHERE + WHERE dl.link_doctype=%s AND dl.link_name=%s AND dl.parenttype = "Contact" - ORDER BY is_billing_contact DESC, is_primary_contact DESC + ORDER BY is_primary_contact DESC, is_billing_contact DESC """, (doctype, name)) if out: try: From 48aa2dd94bac306d634327d210558af3c455940f Mon Sep 17 00:00:00 2001 From: Poranut Chollavorn Date: Thu, 26 Mar 2020 13:46:36 +0000 Subject: [PATCH 30/35] fix(pos): fix pos display item instock --- .../page/point_of_sale/point_of_sale.py | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 3425f8f2a56..17136e04723 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -64,30 +64,40 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p for d in item_prices_data: item_prices[d.item_code] = d - + # prepare filter for bin query + bin_filters = {'item_code': ['in', items]} + if warehouse: + bin_filters['warehouse'] = warehouse if display_items_in_stock: - filters = {'actual_qty': [">", 0], 'item_code': ['in', items]} + bin_filters['actual_qty'] = [">", 0] - if warehouse: - filters['warehouse'] = warehouse + # query item bin + bin_data = frappe.get_all( + 'Bin', fields=['item_code', 'sum(actual_qty) as actual_qty'], + filters=bin_filters, group_by='item_code' + ) - bin_data = frappe._dict( - frappe.get_all("Bin", fields = ["item_code", "sum(actual_qty) as actual_qty"], - filters = filters, group_by = "item_code") - ) + # convert list of dict into dict as {item_code: actual_qty} + bin_dict = {} + for b in bin_data: + bin_dict[b.get('item_code')] = b.get('actual_qty') for item in items_data: - row = {} + item_code = item.item_code + item_price = item_prices.get(item_code) or {} + item_stock_qty = bin_dict.get(item_code) - row.update(item) - item_price = item_prices.get(item.item_code) or {} - row.update({ - 'price_list_rate': item_price.get('price_list_rate'), - 'currency': item_price.get('currency'), - 'actual_qty': bin_data.get('actual_qty') - }) - - result.append(row) + if display_items_in_stock and not item_stock_qty: + pass + else: + row = {} + row.update(item) + row.update({ + 'price_list_rate': item_price.get('price_list_rate'), + 'currency': item_price.get('currency'), + 'actual_qty': item_stock_qty, + }) + result.append(row) res = { 'items': result From 9a7851096c607d3a90c18e3c633c930856ed049c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Tue, 31 Mar 2020 18:48:35 +0530 Subject: [PATCH 31/35] fix: check if selling price exists then set it --- erpnext/accounts/doctype/sales_invoice/sales_invoice.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index bd18d5799bc..a42166241eb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -446,7 +446,8 @@ class SalesInvoice(SellingController): selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list') - self.set('selling_price_list', selling_price_list) + if selling_price_list: + self.set('selling_price_list', selling_price_list) if not for_validate: self.update_stock = cint(pos.get("update_stock")) From 6f0dc8257ccb4953e0aa84bb5c00cf478e854420 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 1 Apr 2020 11:04:14 +0530 Subject: [PATCH 32/35] fix: Typo in stock level validation in Stock Ledger --- erpnext/stock/stock_ledger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index b100f453273..7567a1ae758 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -428,7 +428,7 @@ class update_entries_after(object): frappe.get_desk_link(self.exceptions[0]["voucher_type"], self.exceptions[0]["voucher_no"])) if self.verbose: - frappe.throw(msg, NegativeStockError, title='Insufficent Stock') + frappe.throw(msg, NegativeStockError, title='Insufficient Stock') else: raise NegativeStockError(msg) From e48bcb30e7ebd891a477f21362eb4cc73330ba91 Mon Sep 17 00:00:00 2001 From: Anupam K Date: Thu, 2 Apr 2020 01:04:01 +0530 Subject: [PATCH 33/35] Sum of years not needed. --- .../customer_acquisition_and_loyalty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index 4509904249f..aabc503a1ba 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -48,7 +48,7 @@ def execute(filters=None): new = new_customers_in.get(key, [0,0.0]) repeat = repeat_customers_in.get(key, [0,0.0]) - out.append([year, calendar.month_name[month], + out.append([str(year), calendar.month_name[month], new[0], repeat[0], new[0] + repeat[0], new[1], repeat[1], new[1] + repeat[1]]) From b20dbff72627294ab6134bfe20178aef351de83e Mon Sep 17 00:00:00 2001 From: Anupam K Date: Thu, 2 Apr 2020 11:37:44 +0530 Subject: [PATCH 34/35] Sum of years not needed. --- .../customer_acquisition_and_loyalty.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py index aabc503a1ba..28dd0564075 100644 --- a/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py +++ b/erpnext/selling/report/customer_acquisition_and_loyalty/customer_acquisition_and_loyalty.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import getdate, cint +from frappe.utils import getdate, cint, cstr import calendar def execute(filters=None): @@ -48,7 +48,7 @@ def execute(filters=None): new = new_customers_in.get(key, [0,0.0]) repeat = repeat_customers_in.get(key, [0,0.0]) - out.append([str(year), calendar.month_name[month], + out.append([cstr(year), calendar.month_name[month], new[0], repeat[0], new[0] + repeat[0], new[1], repeat[1], new[1] + repeat[1]]) From 872fcb6caed285701c88512b56d578f339378b5a Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 2 Apr 2020 18:11:46 +0530 Subject: [PATCH 35/35] fix: Travis --- .../doctype/purchase_invoice_item/purchase_invoice_item.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index ef90b942b59..9c974260a25 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -761,7 +761,7 @@ "depends_on": "is_fixed_asset", "fetch_from": "item_code.asset_category", "fieldname": "asset_category", - "fieldtype": "Data", + "fieldtype": "Link", "label": "Asset Category", "options": "Asset Category", "read_only": 1 @@ -777,7 +777,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-11 14:20:17.297284", + "modified": "2020-04-01 14:20:17.297284", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item",