From d0f6f1808beca8926a07d96968d390eba95cdeb7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 8 Apr 2020 17:03:49 +0530 Subject: [PATCH 01/14] fix: minor issues --- .../chart_of_accounts_importer.py | 3 +-- erpnext/stock/doctype/pick_list/test_pick_list.py | 1 + .../doctype/stock_reconciliation/stock_reconciliation.json | 7 ++++--- .../stock_reconciliation/test_stock_reconciliation.py | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index b6f5396ccb1..99221c11001 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -152,10 +152,9 @@ def build_forest(data): return [parent_account] elif account_name == child: parent_account_list = return_parent(data, parent_account) - if not parent_account_list: + if not parent_account_list and parent_account: frappe.throw(_("The parent account {0} does not exists in the uploaded template").format( frappe.bold(parent_account))) - return [child] + parent_account_list charts_map, paths = {}, [] diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 6b4f73b140a..1b9ff41cc33 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -111,6 +111,7 @@ class TestPickList(unittest.TestCase): stock_reconciliation = frappe.get_doc({ 'doctype': 'Stock Reconciliation', + 'purpose': 'Stock Reconciliation', 'company': '_Test Company', 'items': [{ 'item_code': '_Test Serialized Item', diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json index 7f4efba33f8..b7d1497319f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json @@ -4,6 +4,7 @@ "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", "doctype": "DocType", "document_type": "Document", + "engine": "InnoDB", "field_order": [ "naming_series", "company", @@ -44,11 +45,11 @@ "reqd": 1 }, { - "default": "Stock Reconciliation", "fieldname": "purpose", "fieldtype": "Select", "label": "Purpose", - "options": "Opening Stock\nStock Reconciliation" + "options": "\nOpening Stock\nStock Reconciliation", + "reqd": 1 }, { "fieldname": "col1", @@ -153,7 +154,7 @@ "idx": 1, "is_submittable": 1, "max_attachments": 1, - "modified": "2019-05-26 09:03:09.542141", + "modified": "2020-04-08 17:02:47.196206", "modified_by": "Administrator", "module": "Stock", "name": "Stock Reconciliation", diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index e6d7e3fea7d..51d027f22ef 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -240,6 +240,7 @@ def create_batch_or_serial_no_items(): def create_stock_reconciliation(**args): args = frappe._dict(args) sr = frappe.new_doc("Stock Reconciliation") + sr.purpose = args.purpose or "Stock Reconciliation" sr.posting_date = args.posting_date or nowdate() sr.posting_time = args.posting_time or nowtime() sr.set_posting_time = 1 From 2ad7e81560f92cbe542c8635a27926c50f17ac13 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 14 Apr 2020 04:07:14 +0530 Subject: [PATCH 02/14] fix: handle errors in enqueued methods and update status --- .../tally_migration/tally_migration.js | 51 +++-- .../tally_migration/tally_migration.py | 198 +++++++++++------- 2 files changed, 159 insertions(+), 90 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 104ac570c69..682f78efb72 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -2,15 +2,40 @@ // For license information, please see license.txt frappe.ui.form.on('Tally Migration', { - onload: function(frm) { + onload: function (frm) { + let reload_status = true; frappe.realtime.on("tally_migration_progress_update", function (data) { + if (reload_status) { + frappe.model.with_doc(frm.doc.doctype, frm.doc.name, () => { + frm.refresh_header(); + }); + reload_status = false; + } frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message); - if (data.count == data.total) { - window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title); + let error_occurred = data.count === -1; + if (data.count == data.total || error_occurred) { + window.setTimeout((title) => { + frm.dashboard.hide_progress(title) + frm.reload_doc(); + if (error_occurred) { + frappe.msgprint({ + message: __("An error has occurred during {0}. Check {1} for more details", + [ + repl("%(tally_document)s", { + tally_document: frm.docname + }), + "Error Log" + ] + ), + title: __("Tally Migration Error"), + indicator: "red" + }); + } + }, 2000, data.title); } }); }, - refresh: function(frm) { + refresh: function (frm) { if (frm.doc.master_data && !frm.doc.is_master_data_imported) { if (frm.doc.is_master_data_processed) { if (frm.doc.status != "Importing Master Data") { @@ -34,17 +59,17 @@ frappe.ui.form.on('Tally Migration', { } } }, - add_button: function(frm, label, method) { + add_button: function (frm, label, method) { frm.add_custom_button( label, - () => frm.call({ - doc: frm.doc, - method: method, - freeze: true, - callback: () => { - frm.remove_custom_button(label); - } - }) + () => { + frm.call({ + doc: frm.doc, + method: method, + freeze: true + }); + frm.reload_doc(); + } ); } }); diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 01eee5b61f2..ac42d385162 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -4,20 +4,25 @@ from __future__ import unicode_literals -from decimal import Decimal import json +import sys import re import traceback import zipfile +from decimal import Decimal + +from bs4 import BeautifulSoup as bs + import frappe +from erpnext import encode_company_abbr +from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_field +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification from frappe.model.document import Document from frappe.model.naming import getseries, revert_series_if_last from frappe.utils.data import format_datetime -from bs4 import BeautifulSoup as bs -from erpnext import encode_company_abbr -from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts + PRIMARY_ACCOUNT = "Primary" VOUCHER_CHUNK_SIZE = 500 @@ -39,13 +44,15 @@ class TallyMigration(Document): return string master_file = frappe.get_doc("File", {"file_url": data_file}) + master_file_path = master_file.get_full_path() - with zipfile.ZipFile(master_file.get_full_path()) as zf: - encoded_content = zf.read(zf.namelist()[0]) - try: - content = encoded_content.decode("utf-8-sig") - except UnicodeDecodeError: - content = encoded_content.decode("utf-16") + if zipfile.is_zipfile(master_file_path): + with zipfile.ZipFile(master_file_path) as zf: + encoded_content = zf.read(zf.namelist()[0]) + try: + content = encoded_content.decode("utf-8-sig") + except UnicodeDecodeError: + content = encoded_content.decode("utf-16") master = bs(sanitize(emptify(content)), "xml") collection = master.BODY.IMPORTDATA.REQUESTDATA @@ -58,7 +65,8 @@ class TallyMigration(Document): "file_name": key + ".json", "attached_to_doctype": self.doctype, "attached_to_name": self.name, - "content": json.dumps(value) + "content": json.dumps(value), + "is_private": True }).insert() setattr(self, key, f.file_url) @@ -168,8 +176,8 @@ class TallyMigration(Document): address = "\n".join([a.string for a in account.find_all("ADDRESS")]) addresses.append({ "doctype": "Address", - "address_line1": address[:140].strip(), - "address_line2": address[140:].strip(), + "address_line1": address[:140], + "address_line2": address[140:], "country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, "state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, "gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, @@ -188,37 +196,46 @@ class TallyMigration(Document): items = [] for item in collection.find_all("STOCKITEM"): + stock_uom = item.BASEUNITS.string if item.BASEUNITS else "Unit" #self.default_uom items.append({ "doctype": "Item", "item_code" : item.NAME.string, - "stock_uom": item.BASEUNITS.string, + "stock_uom": stock_uom, "is_stock_item": 0, "item_group": "All Item Groups", "item_defaults": [{"company": self.erpnext_company}] }) + return items, uoms + try: + self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5) + collection = self.get_collection(self.master_data) + company = get_company_name(collection) + self.tally_company = company + self.erpnext_company = company - self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5) - collection = self.get_collection(self.master_data) + self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5) + chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection) - company = get_company_name(collection) - self.tally_company = company - self.erpnext_company = company + self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5) + parties, addresses = get_parties_addresses(collection, customers, suppliers) - self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5) - chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection) - self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5) - parties, addresses = get_parties_addresses(collection, customers, suppliers) - self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5) - items, uoms = get_stock_items_uoms(collection) - data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms} - self.publish("Process Master Data", _("Done"), 5, 5) + self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5) + items, uoms = get_stock_items_uoms(collection) + data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms} - self.dump_processed_data(data) - self.is_master_data_processed = 1 - self.status = "" - self.save() + self.publish("Process Master Data", _("Done"), 5, 5) + self.dump_processed_data(data) + + self.is_master_data_processed = 1 + + except: + self.publish("Process Master Data", _("Process Failed"), -1, 5) + self.log() + + finally: + self.set_status() def publish(self, title, message, count, total): frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total}) @@ -256,7 +273,6 @@ class TallyMigration(Document): except: self.log(address) - def create_items_uoms(items_file_url, uoms_file_url): uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url}) for uom in json.loads(uoms_file.get_content()): @@ -273,16 +289,26 @@ class TallyMigration(Document): except: self.log(item) - self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4) - create_company_and_coa(self.chart_of_accounts) - self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4) - create_parties_and_addresses(self.parties, self.addresses) - self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4) - create_items_uoms(self.items, self.uoms) - self.publish("Import Master Data", _("Done"), 4, 4) - self.status = "" - self.is_master_data_imported = 1 - self.save() + try: + self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4) + create_company_and_coa(self.chart_of_accounts) + + self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4) + create_parties_and_addresses(self.parties, self.addresses) + + self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4) + create_items_uoms(self.items, self.uoms) + + self.publish("Import Master Data", _("Done"), 4, 4) + + self.is_master_data_imported = 1 + + except: + self.publish("Import Master Data", _("Process Failed"), -1, 5) + self.log() + + finally: + self.set_status() def _process_day_book_data(self): def get_vouchers(collection): @@ -373,12 +399,12 @@ class TallyMigration(Document): account_field = "expense_account" items = [] for entry in inventory_entries: - qty, uom = entry.ACTUALQTY.string.strip().split() + qty, uom = entry.ACTUALQTY.string.split() items.append({ "item_code": entry.STOCKITEMNAME.string, "description": entry.STOCKITEMNAME.string, - "qty": qty.strip(), - "uom": uom.strip(), + "qty": qty, + "uom": uom, "conversion_factor": 1, "price_list_rate": entry.RATE.string.split("/")[0], "cost_center": self.default_cost_center, @@ -408,15 +434,24 @@ class TallyMigration(Document): elif frappe.db.exists({"doctype": "Customer", "customer_name": party}): return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company) - self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3) - collection = self.get_collection(self.day_book_data) - self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3) - vouchers = get_vouchers(collection) - self.publish("Process Day Book Data", _("Done"), 3, 3) - self.dump_processed_data({"vouchers": vouchers}) - self.status = "" - self.is_day_book_data_processed = 1 - self.save() + try: + self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3) + collection = self.get_collection(self.day_book_data) + + self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3) + vouchers = get_vouchers(collection) + + self.publish("Process Day Book Data", _("Done"), 3, 3) + self.dump_processed_data({"vouchers": vouchers}) + + self.is_day_book_data_processed = 1 + + except: + self.publish("Process Day Book Data", _("Process Failed"), -1, 5) + self.log() + + finally: + self.set_status() def _import_day_book_data(self): def create_fiscal_years(vouchers): @@ -454,23 +489,31 @@ class TallyMigration(Document): "currency": "INR" }).insert() - frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable") - frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable") - frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account) + try: + frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable") + frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable") + frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account) - vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers}) - vouchers = json.loads(vouchers_file.get_content()) + vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers}) + vouchers = json.loads(vouchers_file.get_content()) - create_fiscal_years(vouchers) - create_price_list() - create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) + create_fiscal_years(vouchers) + create_price_list() + create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) - total = len(vouchers) - is_last = False - for index in range(0, total, VOUCHER_CHUNK_SIZE): - if index + VOUCHER_CHUNK_SIZE >= total: - is_last = True - frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last) + total = len(vouchers) + is_last = False + + for index in range(0, total, VOUCHER_CHUNK_SIZE): + if index + VOUCHER_CHUNK_SIZE >= total: + is_last = True + frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last) + + except: + self.log() + + finally: + self.set_status() def _import_vouchers(self, start, total, is_last=False): frappe.flags.in_migrate = True @@ -494,25 +537,26 @@ class TallyMigration(Document): frappe.flags.in_migrate = False def process_master_data(self): - self.status = "Processing Master Data" - self.save() + self.set_status("Processing Master Data") frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600) def import_master_data(self): - self.status = "Importing Master Data" - self.save() + self.set_status("Importing Master Data") frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600) def process_day_book_data(self): - self.status = "Processing Day Book Data" - self.save() + self.set_status("Processing Day Book Data") frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600) def import_day_book_data(self): - self.status = "Importing Day Book Data" - self.save() + self.set_status("Importing Day Book Data") frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600) def log(self, data=None): - message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()]) + data = data or self.status + message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()]) return frappe.log_error(title="Tally Migration Error", message=message) + + def set_status(self, status=""): + self.status = status + self.save() From 72a94941bf5ae1413efa0e91c983cb74fb1a1737 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Apr 2020 16:12:40 +0530 Subject: [PATCH 03/14] fix(tally-migration): DocType improvement --- .../tally_migration/tally_migration.json | 26 +++++++++++++++++-- .../tally_migration/tally_migration.py | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json index 26415caf8d3..dc6f093ac9d 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json @@ -1,4 +1,5 @@ { + "actions": [], "beta": 1, "creation": "2019-02-01 14:27:09.485238", "doctype": "DocType", @@ -14,6 +15,7 @@ "tally_debtors_account", "company_section", "tally_company", + "default_uom", "column_break_8", "erpnext_company", "processed_files_section", @@ -43,6 +45,7 @@ "label": "Status" }, { + "description": "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs", "fieldname": "master_data", "fieldtype": "Attach", "in_list_view": 1, @@ -50,6 +53,7 @@ }, { "default": "Sundry Creditors", + "description": "Creditors Account set in Tally", "fieldname": "tally_creditors_account", "fieldtype": "Data", "label": "Tally Creditors Account", @@ -61,6 +65,7 @@ }, { "default": "Sundry Debtors", + "description": "Debtors Account set in Tally", "fieldname": "tally_debtors_account", "fieldtype": "Data", "label": "Tally Debtors Account", @@ -72,6 +77,7 @@ "fieldtype": "Section Break" }, { + "description": "Company Name as per Imported Tally Data", "fieldname": "tally_company", "fieldtype": "Data", "label": "Tally Company", @@ -82,9 +88,11 @@ "fieldtype": "Column Break" }, { + "description": "Your Company set in ERPNext", "fieldname": "erpnext_company", "fieldtype": "Data", - "label": "ERPNext Company" + "label": "ERPNext Company", + "read_only_depends_on": "eval:doc.is_master_data_processed == 1" }, { "fieldname": "processed_files_section", @@ -155,24 +163,28 @@ "options": "Cost Center" }, { + "default": "0", "fieldname": "is_master_data_processed", "fieldtype": "Check", "label": "Is Master Data Processed", "read_only": 1 }, { + "default": "0", "fieldname": "is_day_book_data_processed", "fieldtype": "Check", "label": "Is Day Book Data Processed", "read_only": 1 }, { + "default": "0", "fieldname": "is_day_book_data_imported", "fieldtype": "Check", "label": "Is Day Book Data Imported", "read_only": 1 }, { + "default": "0", "fieldname": "is_master_data_imported", "fieldtype": "Check", "label": "Is Master Data Imported", @@ -188,13 +200,23 @@ "fieldtype": "Column Break" }, { + "description": "Day Book Data exported from Tally that consists of all historic transactions", "fieldname": "day_book_data", "fieldtype": "Attach", "in_list_view": 1, "label": "Day Book Data" + }, + { + "default": "Unit", + "description": "UOM in case unspecified in imported data", + "fieldname": "default_uom", + "fieldtype": "Link", + "label": "Default UOM", + "options": "UOM" } ], - "modified": "2019-04-29 05:46:54.394967", + "links": [], + "modified": "2020-04-16 13:03:28.894919", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Tally Migration", diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index ac42d385162..9c8cb7ace71 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -196,7 +196,7 @@ class TallyMigration(Document): items = [] for item in collection.find_all("STOCKITEM"): - stock_uom = item.BASEUNITS.string if item.BASEUNITS else "Unit" #self.default_uom + stock_uom = item.BASEUNITS.string if item.BASEUNITS else self.default_uom items.append({ "doctype": "Item", "item_code" : item.NAME.string, From 6abdfc586b3049e67c8291326a8c30b59c1cd976 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Apr 2020 15:51:12 +0530 Subject: [PATCH 04/14] fix: strip data fields of whitespaces --- .../tally_migration/tally_migration.py | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 9c8cb7ace71..98a30273e63 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -72,7 +72,7 @@ class TallyMigration(Document): def _process_master_data(self): def get_company_name(collection): - return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string + return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip() def get_coa_customers_suppliers(collection): root_type_map = { @@ -105,17 +105,17 @@ class TallyMigration(Document): # If Ledger doesn't have PARENT field then don't create Account # For example "Profit & Loss A/c" if account.PARENT: - yield account.PARENT.string, account["NAME"], 0 + yield account.PARENT.string.strip(), account["NAME"], 0 def get_parent(account): if account.PARENT: - return account.PARENT.string + return account.PARENT.string.strip() return { ("Yes", "No"): "Application of Funds (Assets)", ("Yes", "Yes"): "Expenses", ("No", "Yes"): "Income", ("No", "No"): "Source of Funds (Liabilities)", - }[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)] + }[(account.ISDEEMEDPOSITIVE.string.strip(), account.ISREVENUE.string.strip())] def get_children_and_parent_dict(accounts): children, parents = {}, {} @@ -153,38 +153,38 @@ class TallyMigration(Document): parties, addresses = [], [] for account in collection.find_all("LEDGER"): party_type = None - if account.NAME.string in customers: + if account.NAME.string.strip() in customers: party_type = "Customer" parties.append({ "doctype": party_type, - "customer_name": account.NAME.string, - "tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, + "customer_name": account.NAME.string.strip(), + "tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None, "customer_group": "All Customer Groups", "territory": "All Territories", "customer_type": "Individual", }) - elif account.NAME.string in suppliers: + elif account.NAME.string.strip() in suppliers: party_type = "Supplier" parties.append({ "doctype": party_type, - "supplier_name": account.NAME.string, - "pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None, + "supplier_name": account.NAME.string.strip(), + "pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None, "supplier_group": "All Supplier Groups", "supplier_type": "Individual", }) if party_type: - address = "\n".join([a.string for a in account.find_all("ADDRESS")]) + address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")]) addresses.append({ "doctype": "Address", - "address_line1": address[:140], - "address_line2": address[140:], - "country": account.COUNTRYNAME.string if account.COUNTRYNAME else None, - "state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, - "gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None, - "pin_code": account.PINCODE.string if account.PINCODE else None, - "mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None, - "phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None, - "gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None, + "address_line1": address[:140].strip(), + "address_line2": address[140:].strip(), + "country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None, + "state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None, + "gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None, + "pin_code": account.PINCODE.string.strip() if account.PINCODE else None, + "mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None, + "phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None, + "gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None, "links": [{"link_doctype": party_type, "link_name": account["NAME"]}], }) return parties, addresses @@ -192,15 +192,15 @@ class TallyMigration(Document): def get_stock_items_uoms(collection): uoms = [] for uom in collection.find_all("UNIT"): - uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string}) + uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string.strip()}) items = [] for item in collection.find_all("STOCKITEM"): - stock_uom = item.BASEUNITS.string if item.BASEUNITS else self.default_uom + stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom items.append({ "doctype": "Item", - "item_code" : item.NAME.string, - "stock_uom": stock_uom, + "item_code" : item.NAME.string.strip(), + "stock_uom": stock_uom.strip(), "is_stock_item": 0, "item_group": "All Item Groups", "item_defaults": [{"company": self.erpnext_company}] @@ -314,10 +314,10 @@ class TallyMigration(Document): def get_vouchers(collection): vouchers = [] for voucher in collection.find_all("VOUCHER"): - if voucher.ISCANCELLED.string == "Yes": + if voucher.ISCANCELLED.string.strip() == "Yes": continue inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST") - if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries: + if voucher.VOUCHERTYPENAME.string.strip() not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries: function = voucher_to_invoice else: function = voucher_to_journal_entry @@ -333,15 +333,15 @@ class TallyMigration(Document): accounts = [] ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") for entry in ledger_entries: - account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center} - if entry.ISPARTYLEDGER.string == "Yes": - party_details = get_party(entry.LEDGERNAME.string) + account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center} + if entry.ISPARTYLEDGER.string.strip() == "Yes": + party_details = get_party(entry.LEDGERNAME.string.strip()) if party_details: party_type, party_account = party_details account["party_type"] = party_type account["account"] = party_account - account["party"] = entry.LEDGERNAME.string - amount = Decimal(entry.AMOUNT.string) + account["party"] = entry.LEDGERNAME.string.strip() + amount = Decimal(entry.AMOUNT.string.strip()) if amount > 0: account["credit_in_account_currency"] = str(abs(amount)) else: @@ -350,21 +350,21 @@ class TallyMigration(Document): journal_entry = { "doctype": "Journal Entry", - "tally_guid": voucher.GUID.string, - "posting_date": voucher.DATE.string, + "tally_guid": voucher.GUID.string.strip(), + "posting_date": voucher.DATE.string.strip(), "company": self.erpnext_company, "accounts": accounts, } return journal_entry def voucher_to_invoice(voucher): - if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]: + if voucher.VOUCHERTYPENAME.string.strip() in ["Sales", "Credit Note"]: doctype = "Sales Invoice" party_field = "customer" account_field = "debit_to" account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company) price_list_field = "selling_price_list" - elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]: + elif voucher.VOUCHERTYPENAME.string.strip() in ["Purchase", "Debit Note"]: doctype = "Purchase Invoice" party_field = "supplier" account_field = "credit_to" @@ -377,10 +377,10 @@ class TallyMigration(Document): invoice = { "doctype": doctype, - party_field: voucher.PARTYNAME.string, - "tally_guid": voucher.GUID.string, - "posting_date": voucher.DATE.string, - "due_date": voucher.DATE.string, + party_field: voucher.PARTYNAME.string.strip(), + "tally_guid": voucher.GUID.string.strip(), + "posting_date": voucher.DATE.string.strip(), + "due_date": voucher.DATE.string.strip(), "items": get_voucher_items(voucher, doctype), "taxes": get_voucher_taxes(voucher), account_field: account_name, @@ -399,17 +399,17 @@ class TallyMigration(Document): account_field = "expense_account" items = [] for entry in inventory_entries: - qty, uom = entry.ACTUALQTY.string.split() + qty, uom = entry.ACTUALQTY.string.strip().split() items.append({ - "item_code": entry.STOCKITEMNAME.string, - "description": entry.STOCKITEMNAME.string, - "qty": qty, - "uom": uom, + "item_code": entry.STOCKITEMNAME.string.strip(), + "description": entry.STOCKITEMNAME.string.strip(), + "qty": qty.strip(), + "uom": uom.strip(), "conversion_factor": 1, - "price_list_rate": entry.RATE.string.split("/")[0], + "price_list_rate": entry.RATE.string.strip().split("/")[0], "cost_center": self.default_cost_center, "warehouse": self.default_warehouse, - account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company), + account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string.strip(), self.erpnext_company), }) return items @@ -417,13 +417,13 @@ class TallyMigration(Document): ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST") taxes = [] for entry in ledger_entries: - if entry.ISPARTYLEDGER.string == "No": - tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company) + if entry.ISPARTYLEDGER.string.strip() == "No": + tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company) taxes.append({ "charge_type": "Actual", "account_head": tax_account, "description": tax_account, - "tax_amount": entry.AMOUNT.string, + "tax_amount": entry.AMOUNT.string.strip(), "cost_center": self.default_cost_center, }) return taxes From 496c6a3baa38567dfe6f2d9230c11e9236555afd Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 16 Apr 2020 16:51:59 +0530 Subject: [PATCH 05/14] style: removed unused imports and updated formatting --- .../doctype/tally_migration/tally_migration.js | 2 +- .../doctype/tally_migration/tally_migration.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js index 682f78efb72..d84c8234efa 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Tally Migration', { let error_occurred = data.count === -1; if (data.count == data.total || error_occurred) { window.setTimeout((title) => { - frm.dashboard.hide_progress(title) + frm.dashboard.hide_progress(title); frm.reload_doc(); if (error_occurred) { frappe.msgprint({ diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py index 98a30273e63..13474e19ee1 100644 --- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py +++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import json -import sys import re import traceback import zipfile @@ -18,7 +17,6 @@ from erpnext import encode_company_abbr from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts from frappe import _ from frappe.custom.doctype.custom_field.custom_field import create_custom_field -from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification from frappe.model.document import Document from frappe.model.naming import getseries, revert_series_if_last from frappe.utils.data import format_datetime From 03af0299a65639593fa32f5a52fe48b6954d55a5 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 16 Apr 2020 20:11:32 +0530 Subject: [PATCH 06/14] fix: requested qty calculation and some other small fixes (#21296) * fix: Made received qty readonly and no-copy * fix: Made release date mandatory * fix: requested qty calculation fix for UOM --- .../purchase_invoice/purchase_invoice.js | 3 +- .../material_request/material_request.py | 7 ++-- .../material_request/test_material_request.py | 32 +++++++++++++++++ .../material_request_item.json | 8 +++-- erpnext/stock/stock_balance.py | 36 +++++++++++-------- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index c5c54837a7b..9292b633fc3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -174,7 +174,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ read_only: 0, fieldtype:'Date', label: __('Release Date'), - default: me.frm.doc.release_date + default: me.frm.doc.release_date, + reqd: 1 }, { fieldname: 'hold_comment', diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 5b242a51bc8..2d9855713c3 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -175,12 +175,11 @@ class MaterialRequest(BuyingController): frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty) - target_ref_field = 'qty' if self.material_request_type == "Manufacture" else 'stock_qty' self._update_percent_field({ "target_dt": "Material Request Item", "target_parent_dt": self.doctype, "target_parent_field": "per_ordered", - "target_ref_field": target_ref_field, + "target_ref_field": "stock_qty", "target_field": "ordered_qty", "name": self.name, }, update_modified) @@ -499,7 +498,7 @@ def raise_work_orders(material_request): default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") for d in mr.items: - if (d.qty - d.ordered_qty) >0: + if (d.stock_qty - d.ordered_qty) > 0: if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}): wo_order = frappe.new_doc("Work Order") wo_order.update({ @@ -531,7 +530,7 @@ def raise_work_orders(material_request): msgprint(_("The following Work Orders were created:") + '\n' + new_line_sep(message)) if errors: - frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors)) + frappe.throw(_("Work Order cannot be created for following reason:") + '\n' + new_line_sep(errors)) return work_orders diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index b925aedd1ac..30c47c3671c 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -563,6 +563,36 @@ class TestMaterialRequest(unittest.TestCase): item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0] self.assertEqual(requested_qty, new_requested_qty) + def test_requested_qty_multi_uom(self): + existing_requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + + mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture', + uom="_Test UOM 1", conversion_factor=12) + + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + + self.assertEqual(requested_qty, existing_requested_qty + 120) + + work_order = raise_work_orders(mr.name) + wo = frappe.get_doc("Work Order", work_order[0]) + wo.qty = 50 + wo.wip_warehouse = "_Test Warehouse 1 - _TC" + wo.submit() + + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + self.assertEqual(requested_qty, existing_requested_qty + 70) + + wo.cancel() + + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + self.assertEqual(requested_qty, existing_requested_qty + 120) + + mr.reload() + mr.cancel() + requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') + self.assertEqual(requested_qty, existing_requested_qty) + + def test_multi_uom_for_purchase(self): from erpnext.stock.doctype.material_request.material_request import make_purchase_order @@ -633,6 +663,8 @@ def make_material_request(**args): mr.append("items", { "item_code": args.item_code or "_Test Item", "qty": args.qty or 10, + "uom": args.uom or "_Test UOM", + "conversion_factor": args.conversion_factor or 1, "schedule_date": args.schedule_date or today(), "warehouse": args.warehouse or "_Test Warehouse - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC" diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index 30206b62d0c..56049131bb4 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -1,5 +1,4 @@ { - "actions": [], "autoname": "hash", "creation": "2013-02-22 01:28:02", "doctype": "DocType", @@ -374,7 +373,10 @@ { "fieldname": "received_qty", "fieldtype": "Float", - "label": "Received Quantity" + "label": "Received Quantity", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 }, { "collapsible": 1, @@ -410,7 +412,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-07 18:37:54.495112", + "modified": "2020-04-16 09:00:00.992835", "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index 2bdb04ed2c9..d9434e3fe78 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -113,24 +113,32 @@ def get_reserved_qty(item_code, warehouse): return flt(reserved_qty[0][0]) if reserved_qty else 0 def get_indented_qty(item_code, warehouse): - inward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor) - from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr - where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Purchase', 'Manufacture') - and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name - and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) - - outward_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor) + # Ordered Qty is maintained in purchase UOM + requested_qty_for_purchase_and_manufacture = frappe.db.sql(""" + select sum(mr_item.stock_qty - mr_item.ordered_qty) from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Material Issue', 'Material Transfer') - and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name - and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) + and mr.material_request_type in ('Purchase', 'Manufacture') + and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1 + """, (item_code, warehouse)) + requested_qty_for_purchase_and_manufacture = flt(requested_qty_for_purchase_and_manufacture[0][0]) \ + if requested_qty_for_purchase_and_manufacture else 0 - inward_qty, outward_qty = flt(inward_qty[0][0]) if inward_qty else 0, flt(outward_qty[0][0]) if outward_qty else 0 - indented_qty = inward_qty - outward_qty + requested_qty_for_issue_and_transfer = frappe.db.sql(""" + select sum(mr_item.stock_qty - mr_item.ordered_qty) + from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr + where mr_item.item_code=%s and mr_item.warehouse=%s + and mr.material_request_type in ('Material Issue', 'Material Transfer') + and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name + and mr.status!='Stopped' and mr.docstatus=1 + """, (item_code, warehouse)) + requested_qty_for_issue_and_transfer = flt(requested_qty_for_issue_and_transfer[0][0]) \ + if requested_qty_for_issue_and_transfer else 0 - return indented_qty + requested_qty = requested_qty_for_purchase_and_manufacture - requested_qty_for_issue_and_transfer + + return requested_qty def get_ordered_qty(item_code, warehouse): ordered_qty = frappe.db.sql(""" From bf78ed6947ca32346384527d19a5fa6913b05cb4 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 16 Apr 2020 22:53:47 +0530 Subject: [PATCH 07/14] fix: Loan Repayment code clean up and other fixes --- erpnext/hr/doctype/salary_slip/salary_slip.py | 18 +-- .../loan_management/doctype/loan/test_loan.py | 80 +++------- .../loan_interest_accrual.json | 24 +-- .../loan_repayment/loan_repayment.json | 27 ++-- .../doctype/loan_repayment/loan_repayment.py | 147 +++++++++--------- .../doctype/loan_repayment_detail/__init__.py | 0 .../loan_repayment_detail.json | 43 +++++ .../loan_repayment_detail.py | 10 ++ .../salary_slip_loan/salary_slip_loan.json | 13 +- .../page/point_of_sale/point_of_sale.js | 2 +- 10 files changed, 191 insertions(+), 173 deletions(-) create mode 100644 erpnext/loan_management/doctype/loan_repayment_detail/__init__.py create mode 100644 erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json create mode 100644 erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index b3c803b5644..223c4e3e3bf 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -776,22 +776,16 @@ class SalarySlip(TransactionBase): for payment in self.get('loans'): amounts = calculate_amounts(payment.loan, self.posting_date, "Regular Payment") + total_amount = amounts['interest_amount'] + amounts['payable_principal_amount'] + if payment.total_payment > total_amount: + frappe.throw(_("""Row {0}: Paid amount {1} is greater than pending accrued amount {2} + against loan {3}""").format(payment.idx, frappe.bold(payment.total_payment), + frappe.bold(total_amount), frappe.bold(payment.loan))) - if payment.interest_amount > amounts['interest_amount']: - frappe.throw(_("""Row {0}: Paid Interest amount {1} is greater than pending interest amount {2} - against loan {3}""").format(payment.idx, frappe.bold(payment.interest_amount), - frappe.bold(amounts['interest_amount']), frappe.bold(payment.loan))) - - if payment.principal_amount > amounts['payable_principal_amount']: - frappe.throw(_("""Row {0}: Paid Principal amount {1} is greater than pending principal amount {2} - against loan {3}""").format(payment.idx, frappe.bold(payment.principal_amount), - frappe.bold(amounts['payable_principal_amount']), frappe.bold(payment.loan))) - - payment.total_payment = payment.interest_amount + payment.principal_amount self.total_interest_amount += payment.interest_amount self.total_principal_amount += payment.principal_amount - self.total_loan_repayment = self.total_interest_amount + self.total_principal_amount + self.total_loan_repayment += payment.total_payment def get_loan_details(self): diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py index 108672b25ae..2d1ad33ed01 100644 --- a/erpnext/loan_management/doctype/loan/test_loan.py +++ b/erpnext/loan_management/doctype/loan/test_loan.py @@ -149,13 +149,19 @@ class TestLoan(unittest.TestCase): repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 10), "Regular Payment", 111118.68) repayment_entry.save() + repayment_entry.submit() penalty_amount = (accrued_interest_amount * 5 * 25) / (100 * days_in_year(get_datetime(first_date).year)) - - self.assertEquals(flt(repayment_entry.interest_payable, 2), flt(accrued_interest_amount, 2)) self.assertEquals(flt(repayment_entry.penalty_amount, 2), flt(penalty_amount, 2)) - repayment_entry.submit() + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) + + loan.load_from_db() + + self.assertEquals(amounts[0], repayment_entry.interest_payable) + self.assertEquals(flt(loan.total_principal_paid, 2), flt(repayment_entry.amount_paid - + penalty_amount - amounts[0], 2)) def test_loan_closure_repayment(self): pledges = [] @@ -189,15 +195,19 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_demand_loans(posting_date = last_date) repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5), - "Loan Closure", 13315.0681) - repayment_entry.save() + "Loan Closure", flt(loan.loan_amount + accrued_interest_amount)) + repayment_entry.submit() - repayment_entry.amount_paid = repayment_entry.payable_amount + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) - self.assertEquals(flt(repayment_entry.interest_payable, 3), flt(accrued_interest_amount, 3)) + unaccrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * 6) \ + / (days_in_year(get_datetime(first_date).year) * 100) + + self.assertEquals(flt(amounts[0] + unaccrued_interest_amount, 3), + flt(accrued_interest_amount, 3)) self.assertEquals(flt(repayment_entry.penalty_amount, 5), 0) - repayment_entry.submit() loan.load_from_db() self.assertEquals(loan.status, "Loan Closure Requested") @@ -227,57 +237,15 @@ class TestLoan(unittest.TestCase): process_loan_interest_accrual_for_term_loans(posting_date=nowdate()) repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(get_last_day(nowdate()), 5), - "Regular Payment", 89768.7534247) + "Regular Payment", 89768.75) - repayment_entry.save() repayment_entry.submit() - repayment_entry.load_from_db() + amounts = frappe.db.get_value('Loan Interest Accrual', {'loan': loan.name}, ['paid_interest_amount', + 'paid_principal_amount']) - self.assertEquals(repayment_entry.interest_payable, 11250.00) - self.assertEquals(repayment_entry.payable_principal_amount, 78303.00) - - def test_partial_loan_repayment(self): - pledges = [] - pledges.append({ - "loan_security": "Test Security 1", - "qty": 4000.00, - "haircut": 50 - }) - - loan_security_pledge = create_loan_security_pledge(self.applicant2, pledges) - - loan = create_demand_loan(self.applicant2, "Demand Loan", loan_security_pledge.name, - posting_date=get_first_day(nowdate())) - - loan.submit() - - self.assertEquals(loan.loan_amount, 1000000) - - first_date = '2019-10-01' - last_date = '2019-10-30' - - no_of_days = date_diff(last_date, first_date) + 1 - - accrued_interest_amount = (loan.loan_amount * loan.rate_of_interest * no_of_days) \ - / (days_in_year(get_datetime().year) * 100) - - make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date) - - process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 15)) - process_loan_interest_accrual_for_demand_loans(posting_date = add_days(first_date, 30)) - - repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 1), "Regular Payment", 6500) - repayment_entry.save() - repayment_entry.submit() - - penalty_amount = (accrued_interest_amount * 4 * 25) / (100 * days_in_year(get_datetime(first_date).year)) - - lia1 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 1}, 'name') - lia2 = frappe.get_value("Loan Interest Accrual", {"loan": loan.name, "is_paid": 0}, 'name') - - self.assertTrue(lia1) - self.assertTrue(lia2) + self.assertEquals(amounts[0], 11250.00) + self.assertEquals(amounts[1], 78303.00) def test_security_shortfall(self): pledges = [] @@ -294,7 +262,7 @@ class TestLoan(unittest.TestCase): make_loan_disbursement_entry(loan.name, loan.loan_amount) - frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 100 + frappe.db.sql("""UPDATE `tabLoan Security Price` SET loan_security_price = 100 where loan_security='Test Security 2'""") create_process_loan_security_shortfall() diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json index a26112011ca..5fc3e8f4b60 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.json @@ -15,12 +15,13 @@ "company", "posting_date", "is_term_loan", - "is_paid", "section_break_7", "pending_principal_amount", "payable_principal_amount", + "paid_principal_amount", "column_break_14", "interest_amount", + "paid_interest_amount", "section_break_15", "process_loan_interest_accrual", "repayment_schedule_name", @@ -101,13 +102,6 @@ "label": "Company", "options": "Company" }, - { - "default": "0", - "fieldname": "is_paid", - "fieldtype": "Check", - "label": "Is Paid", - "read_only": 1 - }, { "default": "0", "fetch_from": "loan.is_term_loan", @@ -143,12 +137,24 @@ "hidden": 1, "label": "Repayment Schedule Name", "read_only": 1 + }, + { + "fieldname": "paid_principal_amount", + "fieldtype": "Currency", + "label": "Paid Principal Amount", + "options": "Company:company:default_currency" + }, + { + "fieldname": "paid_interest_amount", + "fieldtype": "Currency", + "label": "Paid Interest Amount", + "options": "Company:company:default_currency" } ], "in_create": 1, "is_submittable": 1, "links": [], - "modified": "2020-04-10 18:31:02.369857", + "modified": "2020-04-16 11:24:23.258404", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Interest Accrual", diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 4b930c50aef..789c1299463 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -30,9 +30,8 @@ "reference_number", "column_break_21", "reference_date", - "paid_accrual_entries", - "partial_paid_entry", "principal_amount_paid", + "repayment_details", "amended_from" ], "fields": [ @@ -155,13 +154,6 @@ "options": "Company:company:default_currency", "read_only": 1 }, - { - "fieldname": "paid_accrual_entries", - "fieldtype": "Text", - "hidden": 1, - "label": "Paid Accrual Entries", - "read_only": 1 - }, { "default": "0", "fetch_from": "against_loan.is_term_loan", @@ -197,13 +189,6 @@ "fieldname": "column_break_21", "fieldtype": "Column Break" }, - { - "fieldname": "partial_paid_entry", - "fieldtype": "Text", - "hidden": 1, - "label": "Partial Paid Entry", - "read_only": 1 - }, { "default": "0.0", "fieldname": "principal_amount_paid", @@ -225,11 +210,18 @@ "fieldtype": "Date", "label": "Due Date", "read_only": 1 + }, + { + "fieldname": "repayment_details", + "fieldtype": "Table", + "hidden": 1, + "label": "Repayment Details", + "options": "Loan Repayment Detail" } ], "is_submittable": 1, "links": [], - "modified": "2020-02-26 06:18:54.934538", + "modified": "2020-04-16 18:14:45.166754", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", @@ -264,7 +256,6 @@ "write": 1 } ], - "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py index 2d2ca4c2f4e..87e8a15ab48 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py @@ -19,11 +19,11 @@ class LoanRepayment(AccountsController): def validate(self): amounts = calculate_amounts(self.against_loan, self.posting_date, self.payment_type) self.set_missing_values(amounts) - - def before_submit(self): - self.mark_as_paid() + self.validate_amount() + self.allocate_amounts(amounts['pending_accrual_entries']) def on_submit(self): + self.update_paid_amount() self.make_gl_entries() def on_cancel(self): @@ -38,32 +38,25 @@ class LoanRepayment(AccountsController): self.cost_center = erpnext.get_default_cost_center(self.company) if not self.interest_payable: - self.interest_payable = amounts['interest_amount'] + self.interest_payable = flt(amounts['interest_amount'], 2) if not self.penalty_amount: - self.penalty_amount = amounts['penalty_amount'] + self.penalty_amount = flt(amounts['penalty_amount'], 2) if not self.pending_principal_amount: - self.pending_principal_amount = amounts['pending_principal_amount'] + self.pending_principal_amount = flt(amounts['pending_principal_amount'], 2) if not self.payable_principal_amount and self.is_term_loan: - self.payable_principal_amount = amounts['payable_principal_amount'] + self.payable_principal_amount = flt(amounts['payable_principal_amount'], 2) if not self.payable_amount: - self.payable_amount = amounts['payable_amount'] - - if amounts.get('paid_accrual_entries'): - self.paid_accrual_entries = frappe.as_json(amounts.get('paid_accrual_entries')) + self.payable_amount = flt(amounts['payable_amount'], 2) if amounts.get('due_date'): self.due_date = amounts.get('due_date') - def mark_as_paid(self): - paid_entries = [] - paid_amount = self.amount_paid - interest_paid = paid_amount - - if not paid_amount: + def validate_amount(self): + if not self.amount_paid: frappe.throw(_("Amount paid cannot be zero")) if self.amount_paid < self.penalty_amount: @@ -74,37 +67,15 @@ class LoanRepayment(AccountsController): msg = _("Amount of {0} is required for Loan closure").format(self.payable_amount) frappe.throw(msg) + def update_paid_amount(self): loan = frappe.get_doc("Loan", self.against_loan) - if self.paid_accrual_entries: - paid_accrual_entries = json.loads(self.paid_accrual_entries) - - if paid_amount - self.penalty_amount > 0 and self.paid_accrual_entries: - - interest_paid = paid_amount - self.penalty_amount - - for lia, interest_amount in iteritems(paid_accrual_entries): - if interest_amount <= interest_paid: - paid_entries.append(lia) - interest_paid -= interest_amount - elif interest_paid: - self.partial_paid_entry = frappe.as_json({"name": lia, "interest_amount": interest_amount}) - frappe.db.set_value("Loan Interest Accrual", lia, "interest_amount", - interest_amount - interest_paid) - interest_paid = 0 - - if paid_entries: - self.paid_accrual_entries = frappe.as_json(paid_entries) - else: - self.paid_accrual_entries = "" - - if interest_paid: - self.principal_amount_paid = interest_paid - - if paid_entries: - frappe.db.sql("""UPDATE `tabLoan Interest Accrual` - SET is_paid = 1 where name in (%s)""" #nosec - % ", ".join(['%s']*len(paid_entries)), tuple(paid_entries)) + for payment in self.repayment_details: + frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` + SET paid_principal_amount = `paid_principal_amount` + %s, + paid_interest_amount = `paid_interest_amount` + %s + WHERE name = %s""", + (flt(payment.paid_principal_amount), flt(payment.paid_interest_amount), payment.loan_interest_accrual)) if flt(loan.total_principal_paid + self.principal_amount_paid, 2) >= flt(loan.total_payment, 2): frappe.db.set_value("Loan", self.against_loan, "status", "Loan Closure Requested") @@ -116,21 +87,14 @@ class LoanRepayment(AccountsController): update_shortfall_status(self.against_loan, self.principal_amount_paid) def mark_as_unpaid(self): - loan = frappe.get_doc("Loan", self.against_loan) - if self.paid_accrual_entries: - paid_accrual_entries = json.loads(self.paid_accrual_entries) - - if self.paid_accrual_entries: - frappe.db.sql("""UPDATE `tabLoan Interest Accrual` - SET is_paid = 0 where name in (%s)""" #nosec - % ", ".join(['%s']*len(paid_accrual_entries)), tuple(paid_accrual_entries)) - - if self.partial_paid_entry: - partial_paid_entry = json.loads(self.partial_paid_entry) - frappe.db.set_value("Loan Interest Accrual", partial_paid_entry["name"], "interest_amount", - partial_paid_entry["interest_amount"]) + for payment in self.repayment_details: + frappe.db.sql(""" UPDATE `tabLoan Interest Accrual` + SET paid_principal_amount = `paid_principal_amount` - %s, + paid_interest_amount = `paid_interest_amount` - %s + WHERE name = %s""", + (payment.paid_principal_amount, payment.paid_interest_amount, payment.loan_interest_accrual)) frappe.db.sql(""" UPDATE `tabLoan` SET total_amount_paid = %s, total_principal_paid = %s WHERE name = %s """, (loan.total_amount_paid - self.amount_paid, @@ -139,6 +103,38 @@ class LoanRepayment(AccountsController): if loan.status == "Loan Closure Requested": frappe.db.set_value("Loan", self.against_loan, "status", "Disbursed") + def allocate_amounts(self, paid_entries): + self.set('repayment_details', []) + self.principal_amount_paid = 0 + + if self.amount_paid - self.penalty_amount > 0 and paid_entries: + interest_paid = self.amount_paid - self.penalty_amount + for lia, amounts in iteritems(paid_entries): + if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid: + interest_amount = amounts['interest_amount'] + paid_principal = amounts['payable_principal_amount'] + self.principal_amount_paid += paid_principal + interest_paid -= (interest_amount + paid_principal) + elif interest_paid: + if interest_paid >= amounts['interest_amount']: + interest_amount = amounts['interest_amount'] + paid_principal = interest_paid - interest_amount + self.principal_amount_paid += paid_principal + interest_paid = 0 + else: + interest_amount = interest_paid + interest_paid = 0 + paid_principal=0 + + self.append('repayment_details', { + 'loan_interest_accrual': lia, + 'paid_interest_amount': interest_amount, + 'paid_principal_amount': paid_principal + }) + + if interest_paid: + self.principal_amount_paid += interest_paid + def make_gl_entries(self, cancel=0, adv_adj=0): gle_map = [] loan_details = frappe.get_doc("Loan", self.against_loan) @@ -223,7 +219,7 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, "posting_date": posting_date, "applicant": applicant, "penalty_amount": penalty_amount, - "interst_payable": interest_payable, + "interest_payable": interest_payable, "payable_principal_amount": payable_principal_amount, "amount_paid": amount_paid, "loan_type": loan_type @@ -232,15 +228,22 @@ def create_repayment_entry(loan, applicant, company, posting_date, loan_type, return lr def get_accrued_interest_entries(against_loan): - accrued_interest_entries = frappe.get_all("Loan Interest Accrual", - fields=["name", "interest_amount", "posting_date", "payable_principal_amount"], - filters = { - "loan": against_loan, - "is_paid": 0, - "docstatus": 1 - }, order_by="posting_date") - return accrued_interest_entries + unpaid_accrued_entries = frappe.db.sql( + """ + SELECT name, posting_date, interest_amount - paid_interest_amount as interest_amount, + payable_principal_amount - paid_principal_amount as payable_principal_amount + FROM + `tabLoan Interest Accrual` + WHERE + loan = %s + AND (interest_amount - paid_interest_amount > 0 OR + payable_principal_amount - paid_principal_amount > 0) + AND + docstatus = 1 + """, (against_loan), as_dict=1) + + return unpaid_accrued_entries # This function returns the amounts that are payable at the time of loan repayment based on posting date # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable @@ -273,8 +276,10 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): total_pending_interest += entry.interest_amount payable_principal_amount += entry.payable_principal_amount - pending_accrual_entries.setdefault(entry.name, - flt(entry.interest_amount) + flt(entry.payable_principal_amount)) + pending_accrual_entries.setdefault(entry.name, { + 'interest_amount': flt(entry.interest_amount), + 'payable_principal_amount': flt(entry.payable_principal_amount) + }) final_due_date = due_date @@ -291,7 +296,7 @@ def get_amounts(amounts, against_loan, posting_date, payment_type): amounts["interest_amount"] = total_pending_interest amounts["penalty_amount"] = penalty_amount amounts["payable_amount"] = payable_principal_amount + total_pending_interest + penalty_amount - amounts["paid_accrual_entries"] = pending_accrual_entries + amounts["pending_accrual_entries"] = pending_accrual_entries if final_due_date: amounts["due_date"] = final_due_date diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py b/erpnext/loan_management/doctype/loan_repayment_detail/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json new file mode 100644 index 00000000000..cff1dbb1d29 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.json @@ -0,0 +1,43 @@ +{ + "actions": [], + "creation": "2020-04-15 18:31:54.026923", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "loan_interest_accrual", + "paid_principal_amount", + "paid_interest_amount" + ], + "fields": [ + { + "fieldname": "loan_interest_accrual", + "fieldtype": "Link", + "label": "Loan Interest Accrual", + "options": "Loan Interest Accrual" + }, + { + "fieldname": "paid_principal_amount", + "fieldtype": "Currency", + "label": "Paid Principal Amount", + "options": "Company:company:default_currency" + }, + { + "fieldname": "paid_interest_amount", + "fieldtype": "Currency", + "label": "Paid Interest Amount", + "options": "Company:company:default_currency" + } + ], + "istable": 1, + "links": [], + "modified": "2020-04-15 21:50:03.837019", + "modified_by": "Administrator", + "module": "Loan Management", + "name": "Loan Repayment Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py new file mode 100644 index 00000000000..a83b9b59415 --- /dev/null +++ b/erpnext/loan_management/doctype/loan_repayment_detail/loan_repayment_detail.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class LoanRepaymentDetail(Document): + pass diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json index f7e211656e7..2f4fe249456 100644 --- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json +++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json @@ -28,7 +28,6 @@ { "fieldname": "loan_account", "fieldtype": "Link", - "in_list_view": 1, "label": "Loan Account", "options": "Account", "read_only": 1 @@ -50,21 +49,23 @@ "fieldtype": "Currency", "in_list_view": 1, "label": "Principal Amount", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 }, { "fieldname": "interest_amount", "fieldtype": "Currency", "in_list_view": 1, "label": "Interest Amount", - "options": "Company:company:default_currency" + "options": "Company:company:default_currency", + "read_only": 1 }, { "fieldname": "total_payment", "fieldtype": "Currency", + "in_list_view": 1, "label": "Total Payment", - "options": "Company:company:default_currency", - "read_only": 1 + "options": "Company:company:default_currency" }, { "fieldname": "loan_repayment_entry", @@ -84,7 +85,7 @@ ], "istable": 1, "links": [], - "modified": "2020-04-09 20:01:53.546364", + "modified": "2020-04-16 13:17:04.798335", "modified_by": "Administrator", "module": "Loan Management", "name": "Salary Slip Loan", 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 f175687f26c..7011cf9804b 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -287,7 +287,7 @@ 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; From 250daa3f9444f1548ac30345bd95ecfdc96c5c84 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 16 Apr 2020 23:02:17 +0530 Subject: [PATCH 08/14] fix: job card time issue --- .../doctype/job_card/job_card.js | 31 +++++++++++++++++-- .../doctype/job_card/job_card.py | 12 ++++--- .../doctype/work_order/work_order.js | 2 ++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 8c7876d48de..bab0dfb6b43 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -82,7 +82,9 @@ frappe.ui.form.on('Job Card', { frm.set_value('current_time' , 0); } - frm.save(); + frm.save("Save", () => {}, "", () => { + frm.doc.time_logs.pop(-1); + }); }, complete_job: function(frm, completed_time, completed_qty) { @@ -105,6 +107,24 @@ frappe.ui.form.on('Job Card', { }); }, + validate: function(frm) { + if ((!frm.doc.time_logs || !frm.doc.time_logs.length) && frm.doc.started_time) { + frm.trigger("reset_timer"); + } + }, + + employee: function(frm) { + if (frm.doc.job_started && !frm.doc.current_time) { + frm.trigger("reset_timer"); + } + }, + + reset_timer: function(frm) { + frm.set_value('started_time' , ''); + frm.set_value('job_started', 0); + frm.set_value('current_time' , 0); + }, + make_dashboard: function(frm) { if(frm.doc.__islocal) return; @@ -137,12 +157,12 @@ frappe.ui.form.on('Job Card', { updateStopwatch(current); }, 1000); } - + function updateStopwatch(increment) { var hours = Math.floor(increment / 3600); var minutes = Math.floor((increment - (hours * 3600)) / 60); var seconds = increment - (hours * 3600) - (minutes * 60); - + $(section).find(".hours").text(hours < 10 ? ("0" + hours.toString()) : hours.toString()); $(section).find(".minutes").text(minutes < 10 ? ("0" + minutes.toString()) : minutes.toString()); $(section).find(".seconds").text(seconds < 10 ? ("0" + seconds.toString()) : seconds.toString()); @@ -205,5 +225,10 @@ frappe.ui.form.on('Job Card', { frappe.ui.form.on('Job Card Time Log', { completed_qty: function(frm) { frm.events.set_total_completed_qty(frm); + }, + + to_time: function(frm) { + frm.set_value('job_started', 0); + frm.set_value('started_time', ''); } }) \ 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 f8c60f2a114..e9627a55145 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -9,7 +9,7 @@ from frappe import _ 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) + get_time, add_to_date, time_diff, add_days, get_datetime_str, get_link_to_form) from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations @@ -189,11 +189,15 @@ class JobCard(Document): def validate_job_card(self): if not self.time_logs: - frappe.throw(_("Time logs are required for job card {0}").format(self.name)) + frappe.throw(_("Time logs are required for {0} {1}") + .format(frappe.bold("Job Card"), get_link_to_form("Job Card", self.name))) 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)))) + total_completed_qty = frappe.bold(_("Total Completed Qty")) + qty_to_manufacture = frappe.bold(_("Qty to Manufacture")) + + frappe.throw(_("The {0} ({1}) must be equal to {2} ({3})" + .format(total_completed_qty, frappe.bold(self.total_completed_qty), qty_to_manufacture,frappe.bold(self.for_quantity)))) def update_work_order(self): if not self.work_order: diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 9c8aa453a68..d541866f8b7 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -240,6 +240,8 @@ frappe.ui.form.on("Work Order", { }); }, __("Job Card"), __("Create")); + dialog.fields_dict["operations"].grid.wrapper.find('.grid-add-row').hide(); + var pending_qty = 0; frm.doc.operations.forEach(data => { if(data.completed_qty != frm.doc.qty) { From 7eaa7f2e41693724e431e81fc4e11a3814109434 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 17 Apr 2020 10:52:28 +0530 Subject: [PATCH 09/14] fix: requested qty for customer provided item and rate for sales (#21299) * fix: requested qty for customer provided item and rate for sales * fix: requested qty for material transfer * fix: customer provided item can be sales item * fix: requested qty test cases --- .../sales_invoice/test_sales_invoice.py | 10 -- erpnext/controllers/stock_controller.py | 3 - .../delivery_note/test_delivery_note.py | 9 -- .../material_request/test_material_request.py | 124 +++++++----------- erpnext/stock/stock_balance.py | 18 ++- 5 files changed, 53 insertions(+), 111 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 0e54b62caa4..a2819af5086 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1926,16 +1926,6 @@ class TestSalesInvoice(unittest.TestCase): item.taxes = [] item.save() - def test_customer_provided_parts_si(self): - create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) - si = create_sales_invoice(item_code='CUST-0987', rate=0) - self.assertEqual(si.get("items")[0].allow_zero_valuation_rate, 1) - self.assertEqual(si.get("items")[0].amount, 0) - - # test if Sales Invoice with rate is allowed - si2 = create_sales_invoice(item_code='CUST-0987', do_not_save=True) - self.assertRaises(frappe.ValidationError, si2.save) - def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") args = frappe._dict(args) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 4037f2f60f2..55a2c435a12 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -383,9 +383,6 @@ class StockController(AccountsController): # Customer Provided parts will have zero valuation rate if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 - if d.parenttype in ["Delivery Note", "Sales Invoice"] and d.rate: - frappe.throw(_("Row #{0}: {1} cannot have {2} as it is a Customer Provided Item") - .format(d.idx, frappe.bold(d.item_code), frappe.bold("Rate"))) def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None, company=None): diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index 47a72b21a69..d7a93fb6917 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -434,15 +434,6 @@ class TestDeliveryNote(unittest.TestCase): update_delivery_note_status(dn.name, "Closed") self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed") - def test_customer_provided_parts_dn(self): - create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) - dn = create_delivery_note(item_code='CUST-0987', rate=0) - self.assertEqual(dn.get("items")[0].allow_zero_valuation_rate, 1) - - # test if Delivery Note with rate is allowed against Customer Provided Item - dn2 = create_delivery_note(item_code='CUST-0987', do_not_save=True) - self.assertRaises(frappe.ValidationError, dn2.save) - def test_dn_billing_status_case1(self): # SO -> DN -> SI so = make_sales_order() diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py index 30c47c3671c..19924b16363 100644 --- a/erpnext/stock/doctype/material_request/test_material_request.py +++ b/erpnext/stock/doctype/material_request/test_material_request.py @@ -7,7 +7,8 @@ from __future__ import unicode_literals import frappe, unittest, erpnext from frappe.utils import flt, today -from erpnext.stock.doctype.material_request.material_request import raise_work_orders +from erpnext.stock.doctype.material_request.material_request \ + import raise_work_orders, make_stock_entry, make_purchase_order, make_supplier_quotation from erpnext.stock.doctype.item.test_item import create_item class TestMaterialRequest(unittest.TestCase): @@ -15,8 +16,6 @@ class TestMaterialRequest(unittest.TestCase): erpnext.set_perpetual_inventory(0) def test_make_purchase_order(self): - from erpnext.stock.doctype.material_request.material_request import make_purchase_order - mr = frappe.copy_doc(test_records[0]).insert() self.assertRaises(frappe.ValidationError, make_purchase_order, @@ -30,8 +29,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEqual(len(po.get("items")), len(mr.get("items"))) def test_make_supplier_quotation(self): - from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation - mr = frappe.copy_doc(test_records[0]).insert() self.assertRaises(frappe.ValidationError, make_supplier_quotation, mr.name) @@ -45,12 +42,9 @@ class TestMaterialRequest(unittest.TestCase): def test_make_stock_entry(self): - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - mr = frappe.copy_doc(test_records[0]).insert() - self.assertRaises(frappe.ValidationError, make_stock_entry, - mr.name) + self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name) mr = frappe.get_doc("Material Request", mr.name) mr.material_request_type = "Material Transfer" @@ -62,40 +56,40 @@ class TestMaterialRequest(unittest.TestCase): def _insert_stock_entry(self, qty1, qty2, warehouse = None ): se = frappe.get_doc({ - "company": "_Test Company", - "doctype": "Stock Entry", - "posting_date": "2013-03-01", - "posting_time": "00:00:00", - "purpose": "Material Receipt", - "items": [ - { - "conversion_factor": 1.0, - "doctype": "Stock Entry Detail", - "item_code": "_Test Item Home Desktop 100", - "parentfield": "items", - "basic_rate": 100, - "qty": qty1, - "stock_uom": "_Test UOM 1", - "transfer_qty": qty1, - "uom": "_Test UOM 1", - "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", - "cost_center": "_Test Cost Center - _TC" - }, - { - "conversion_factor": 1.0, - "doctype": "Stock Entry Detail", - "item_code": "_Test Item Home Desktop 200", - "parentfield": "items", - "basic_rate": 100, - "qty": qty2, - "stock_uom": "_Test UOM 1", - "transfer_qty": qty2, - "uom": "_Test UOM 1", - "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", - "cost_center": "_Test Cost Center - _TC" - } - ] - }) + "company": "_Test Company", + "doctype": "Stock Entry", + "posting_date": "2013-03-01", + "posting_time": "00:00:00", + "purpose": "Material Receipt", + "items": [ + { + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item Home Desktop 100", + "parentfield": "items", + "basic_rate": 100, + "qty": qty1, + "stock_uom": "_Test UOM 1", + "transfer_qty": qty1, + "uom": "_Test UOM 1", + "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", + "cost_center": "_Test Cost Center - _TC" + }, + { + "conversion_factor": 1.0, + "doctype": "Stock Entry Detail", + "item_code": "_Test Item Home Desktop 200", + "parentfield": "items", + "basic_rate": 100, + "qty": qty2, + "stock_uom": "_Test UOM 1", + "transfer_qty": qty2, + "uom": "_Test UOM 1", + "t_warehouse": warehouse or "_Test Warehouse 1 - _TC", + "cost_center": "_Test Cost Center - _TC" + } + ] + }) se.set_stock_entry_type() se.insert() @@ -198,14 +192,7 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # check if per complete is None - mr.load_from_db() - self.assertEqual(mr.per_ordered, 0) - self.assertEqual(mr.get("items")[0].ordered_qty, 0) - self.assertEqual(mr.get("items")[1].ordered_qty, 0) - # map a purchase order - from erpnext.stock.doctype.material_request.material_request import make_purchase_order po_doc = make_purchase_order(mr.name) po_doc.supplier = "_Test Supplier" po_doc.transaction_date = "2013-07-07" @@ -276,10 +263,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0) - - from erpnext.stock.doctype.material_request.material_request import make_stock_entry + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) # map a stock entry se_doc = make_stock_entry(mr.name) @@ -331,8 +316,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 27.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 1.5) + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 27.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 1.5) # check if per complete is as expected for Stock Entry cancelled se.cancel() @@ -344,8 +329,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0) + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_completed_qty_for_over_transfer(self): existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") @@ -357,14 +342,7 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # check if per complete is None - mr.load_from_db() - self.assertEqual(mr.per_ordered, 0) - self.assertEqual(mr.get("items")[0].ordered_qty, 0) - self.assertEqual(mr.get("items")[1].ordered_qty, 0) - # map a stock entry - from erpnext.stock.doctype.material_request.material_request import make_stock_entry se_doc = make_stock_entry(mr.name) se_doc.update({ @@ -425,8 +403,8 @@ class TestMaterialRequest(unittest.TestCase): current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC") current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC") - self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 - 54.0) - self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 - 3.0) + self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0) + self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0) def test_incorrect_mapping_of_stock_entry(self): # submit material request of type Transfer @@ -435,9 +413,6 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # map a stock entry - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - se_doc = make_stock_entry(mr.name) se_doc.update({ "posting_date": "2013-03-01", @@ -468,8 +443,6 @@ class TestMaterialRequest(unittest.TestCase): mr.insert() mr.submit() - # map a stock entry - from erpnext.stock.doctype.material_request.material_request import make_stock_entry se_doc = make_stock_entry(mr.name) self.assertEqual(se_doc.get("items")[0].s_warehouse, "_Test Warehouse - _TC") @@ -483,8 +456,6 @@ class TestMaterialRequest(unittest.TestCase): return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")) def test_make_stock_entry_for_material_issue(self): - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - mr = frappe.copy_doc(test_records[0]).insert() self.assertRaises(frappe.ValidationError, make_stock_entry, @@ -503,8 +474,6 @@ class TestMaterialRequest(unittest.TestCase): return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100", "warehouse": "_Test Warehouse - _TC"}, "indented_qty")) - from erpnext.stock.doctype.material_request.material_request import make_stock_entry - existing_requested_qty = _get_requested_qty() mr = frappe.copy_doc(test_records[0]) @@ -594,8 +563,6 @@ class TestMaterialRequest(unittest.TestCase): def test_multi_uom_for_purchase(self): - from erpnext.stock.doctype.material_request.material_request import make_purchase_order - mr = frappe.copy_doc(test_records[0]) mr.material_request_type = 'Purchase' item = mr.items[0] @@ -637,7 +604,6 @@ class TestMaterialRequest(unittest.TestCase): self.assertEqual(mr.per_ordered, 100) def test_customer_provided_parts_mr(self): - from erpnext.stock.doctype.material_request.material_request import make_stock_entry create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0) existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC") diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index d9434e3fe78..56973153609 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -113,30 +113,28 @@ def get_reserved_qty(item_code, warehouse): return flt(reserved_qty[0][0]) if reserved_qty else 0 def get_indented_qty(item_code, warehouse): - # Ordered Qty is maintained in purchase UOM - requested_qty_for_purchase_and_manufacture = frappe.db.sql(""" + # Ordered Qty is always maintained in stock UOM + inward_qty = frappe.db.sql(""" select sum(mr_item.stock_qty - mr_item.ordered_qty) from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Purchase', 'Manufacture') + and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer') and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name and mr.status!='Stopped' and mr.docstatus=1 """, (item_code, warehouse)) - requested_qty_for_purchase_and_manufacture = flt(requested_qty_for_purchase_and_manufacture[0][0]) \ - if requested_qty_for_purchase_and_manufacture else 0 + inward_qty = flt(inward_qty[0][0]) if inward_qty else 0 - requested_qty_for_issue_and_transfer = frappe.db.sql(""" + outward_qty = frappe.db.sql(""" select sum(mr_item.stock_qty - mr_item.ordered_qty) from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr where mr_item.item_code=%s and mr_item.warehouse=%s - and mr.material_request_type in ('Material Issue', 'Material Transfer') + and mr.material_request_type = 'Material Issue' and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name and mr.status!='Stopped' and mr.docstatus=1 """, (item_code, warehouse)) - requested_qty_for_issue_and_transfer = flt(requested_qty_for_issue_and_transfer[0][0]) \ - if requested_qty_for_issue_and_transfer else 0 + outward_qty = flt(outward_qty[0][0]) if outward_qty else 0 - requested_qty = requested_qty_for_purchase_and_manufacture - requested_qty_for_issue_and_transfer + requested_qty = inward_qty - outward_qty return requested_qty From bd14a64bfb6ba95b82860adb5c0b381c70b7363f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 17 Apr 2020 19:52:47 +0530 Subject: [PATCH 10/14] fix: add label to gl entry --- erpnext/accounts/report/general_ledger/general_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 649b363c32f..f776d933014 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -365,6 +365,7 @@ def get_columns(filters): columns = [ { + "label": _("GL Entry"), "fieldname": "gl_entry", "fieldtype": "Link", "options": "GL Entry", From 9793500154bb8b4152ac025b26a503b065286eed Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 18 Apr 2020 19:02:07 +0530 Subject: [PATCH 11/14] fix: account name not showing in the gl print --- erpnext/accounts/report/general_ledger/general_ledger.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html index 9a2205a5791..378fa3791c1 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.html +++ b/erpnext/accounts/report/general_ledger/general_ledger.html @@ -2,7 +2,7 @@

{% if (filters.party_name) { %} {%= filters.party_name %} - {% } else if (filters.party) { %} + {% } else if (filters.party && filters.party.length) { %} {%= filters.party %} {% } else if (filters.account) { %} {%= filters.account %} From 3cec5cbb0f4be9950e4561bc11d59c822d8d82cc Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sat, 18 Apr 2020 22:13:48 +0530 Subject: [PATCH 12/14] fix: Total amount field ordering in transactions (#21313) --- .../doctype/purchase_invoice/purchase_invoice.json | 4 ++-- erpnext/accounts/doctype/sales_invoice/sales_invoice.json | 6 ++++-- erpnext/buying/doctype/purchase_order/purchase_order.json | 8 +++++--- erpnext/selling/doctype/sales_order/sales_order.json | 4 ++-- erpnext/stock/doctype/delivery_note/delivery_note.json | 4 ++-- .../stock/doctype/purchase_receipt/purchase_receipt.json | 4 ++-- 6 files changed, 17 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 3cd988ccd23..3af236cbf9e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -73,9 +73,9 @@ "base_total", "base_net_total", "column_break_28", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_49", @@ -1298,7 +1298,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:13:49.610538", + "modified": "2020-04-17 13:05:25.199832", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index e239f9143dc..918fa140b2d 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-24 19:29:05", @@ -74,9 +75,9 @@ "base_total", "base_net_total", "column_break_32", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "taxes_and_charges", "column_break_38", @@ -1577,7 +1578,8 @@ "icon": "fa fa-file-text", "idx": 181, "is_submittable": 1, - "modified": "2020-02-10 04:57:11.221180", + "links": [], + "modified": "2020-04-17 12:38:41.435728", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 4d836903911..578858ca520 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "autoname": "naming_series:", "creation": "2013-05-21 16:16:39", @@ -63,9 +64,9 @@ "base_total", "base_net_total", "column_break_26", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_50", @@ -170,8 +171,8 @@ "search_index": 1 }, { - "description": "Fetch items based on Default Supplier.", "depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))", + "description": "Fetch items based on Default Supplier.", "fieldname": "get_items_from_open_material_requests", "fieldtype": "Button", "label": "Get Items from Open Material Requests" @@ -1054,7 +1055,8 @@ "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, - "modified": "2020-01-14 18:54:39.694448", + "links": [], + "modified": "2020-04-17 13:04:28.185197", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 54e87f7a3b5..6462d3bc8c3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -60,9 +60,9 @@ "base_total", "base_net_total", "column_break_33", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_38", @@ -1196,7 +1196,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2019-12-30 19:15:28.605085", + "modified": "2020-04-17 12:50:39.640534", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 6f9d83d6749..9f5dee901ce 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -66,9 +66,9 @@ "base_total", "base_net_total", "column_break_33", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_section", "tax_category", "column_break_39", @@ -1256,7 +1256,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2019-12-31 19:17:13.122644", + "modified": "2020-04-17 12:51:41.288600", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index e38bb38b195..eed5749b063 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -59,9 +59,9 @@ "base_total", "base_net_total", "column_break_27", + "total_net_weight", "total", "net_total", - "total_net_weight", "taxes_charges_section", "tax_category", "shipping_col", @@ -1082,7 +1082,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2020-04-06 16:31:37.444891", + "modified": "2020-04-17 13:06:26.970288", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", From bd53f3b08021cf435f1d6d3d195dfeb22d94f2e7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Sun, 19 Apr 2020 12:47:48 +0530 Subject: [PATCH 13/14] fix: filters as dictionary --- erpnext/support/doctype/issue/issue.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index fd72807418f..117267f1a42 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -335,8 +335,13 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord ignore_permissions = False if is_website_user(): - if not filters: filters = [] - filters.append(("Issue", "customer", "=", customer)) if customer else filters.append(("Issue", "raised_by", "=", user)) + if not filters: filters = {} + + if customer: + filters["customer"] = customer + else: + filters["raised_by"] = user + ignore_permissions = True return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) From 6d492381593f1ed42ce3f7b6cd028d10a5b60e3e Mon Sep 17 00:00:00 2001 From: Wolfram Schmidt Date: Sun, 19 Apr 2020 16:49:10 +0200 Subject: [PATCH 14/14] Update project.json (#21274) unnecessary requirement for project creation as this is a field of the Type select which will be pre-filled. When creating a project of list view it will also bring up this field as it has "reqd": 1. A new projekt will have the status set to open in most cases. Co-authored-by: Nabin Hait Co-authored-by: Himanshu --- erpnext/projects/doctype/project/project.json | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 49abab1c131..f3cecd9059b 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -83,7 +83,6 @@ "oldfieldname": "status", "oldfieldtype": "Select", "options": "Open\nCompleted\nCancelled", - "reqd": 1, "search_index": 1 }, {