From 773e7debb900e148b78849eb1f54c57f0fd29ba1 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Wed, 26 Feb 2020 14:50:57 +0530 Subject: [PATCH 01/20] fix: validated leave allocation (#20636) * fix: validated leave allocation * fix: changes requested * Update erpnext/hr/doctype/leave_encashment/leave_encashment.py Co-Authored-By: Nabin Hait Co-authored-by: Nabin Hait --- erpnext/hr/doctype/leave_encashment/leave_encashment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 42f0179baf..ad2cc02fd7 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -64,6 +64,9 @@ class LeaveEncashment(Document): allocation = self.get_leave_allocation() + if not allocation: + frappe.throw(_("No Leaves Allocated to Employee: {0} for Leave Type: {1}").format(self.employee, self.leave_type)) + self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) @@ -116,4 +119,4 @@ def create_leave_encashment(leave_allocation): leave_type=allocation.leave_type, encashment_date=allocation.to_date )) - leave_encashment.insert(ignore_permissions=True) \ No newline at end of file + leave_encashment.insert(ignore_permissions=True) From c88a7f8317d65798affb0843a7892277e4401737 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 26 Feb 2020 16:49:58 +0530 Subject: [PATCH 02/20] fix: User permission for Loan repayment doctype --- .../doctype/loan_repayment/loan_repayment.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json index 92e98177ea..4b930c50ae 100644 --- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json +++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json @@ -229,13 +229,14 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-02-24 07:35:47.168123", + "modified": "2020-02-26 06:18:54.934538", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Repayment", "owner": "Administrator", "permissions": [ { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -245,9 +246,11 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 }, { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -257,6 +260,7 @@ "report": 1, "role": "Loan Manager", "share": 1, + "submit": 1, "write": 1 } ], From fe18b92b6104310866738f2a7d808b5f7538e8c6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Wed, 26 Feb 2020 17:17:02 +0530 Subject: [PATCH 03/20] fix: Mandatory bank account error fix (#20733) * fix: Mandatory bank account error fix * fix: SQL condition --- .../bank_reconciliation/bank_reconciliation.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index 52bbe3327a..2436b15dd4 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py @@ -13,9 +13,11 @@ form_grid_templates = { class BankReconciliation(Document): def get_payment_entries(self): - if not (self.bank_account and self.from_date and self.to_date): - msgprint(_("Bank Account, From Date and To Date are Mandatory")) - return + if not (self.from_date and self.to_date): + frappe.throw(_("From Date and To Date are Mandatory")) + + if not self.account: + frappe.throw(_("Account is mandatory to get payment entries")) condition = "" if not self.include_reconciled_entries: @@ -37,6 +39,11 @@ class BankReconciliation(Document): order by t1.posting_date ASC, t1.name DESC """, {"condition":condition, "account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) + condition = '' + + if self.bank_account: + condition += 'and bank_account = %(bank_account)s' + payment_entries = frappe.db.sql(""" select "Payment Entry" as payment_document, name as payment_entry, @@ -49,10 +56,10 @@ class BankReconciliation(Document): where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 and posting_date >= %(from)s and posting_date <= %(to)s - and bank_account = %(bank_account)s + {condition} order by posting_date ASC, name DESC - """, {"account": self.account, "from":self.from_date, + """.format(condition=condition), {"account": self.account, "from":self.from_date, "to": self.to_date, "bank_account": self.bank_account}, as_dict=1) pos_entries = [] From 62ba320f5bf403d28749297a6fb0ea480eed9ad6 Mon Sep 17 00:00:00 2001 From: Marica Date: Wed, 26 Feb 2020 18:51:13 +0530 Subject: [PATCH 04/20] fix: Lock stock ledger entries that are being reposted. (#20737) - If stock ledger entries are being reposted, don't let any other transaction apply itself on the same. --- erpnext/controllers/stock_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 57b4ddd0fb..b10d8befe4 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -429,7 +429,7 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no from `tabStock Ledger Entry` sle where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} - order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc""".format(condition=condition), + order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition), tuple([posting_date, posting_time] + values), as_dict=True): future_stock_vouchers.append([d.voucher_type, d.voucher_no]) From d40a5d92c275db4ef524ac98aafb055f57108d05 Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 26 Feb 2020 18:54:14 +0530 Subject: [PATCH 05/20] fix: sort Issues chronologically (#20741) --- erpnext/support/doctype/issue/issue.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 53af80cb5e..6641f56d18 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -366,7 +366,7 @@ "icon": "fa fa-ticket", "idx": 7, "links": [], - "modified": "2020-02-18 21:26:35.636013", + "modified": "2020-02-26 02:19:49.477928", "modified_by": "Administrator", "module": "Support", "name": "Issue", @@ -387,9 +387,9 @@ "quick_entry": 1, "search_fields": "status,customer,subject,raised_by", "sort_field": "modified", - "sort_order": "ASC", + "sort_order": "DESC", "timeline_field": "customer", "title_field": "subject", "track_changes": 1, "track_seen": 1 -} \ No newline at end of file +} From dd374ff77bfad1bb3013b46b7e10aa5185dd3d4f Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 27 Feb 2020 12:51:31 +0530 Subject: [PATCH 06/20] fix: only update items if rate or qty changed (#20742) --- erpnext/controllers/accounts_controller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index ca09f76531..d661bcbf34 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1200,6 +1200,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) else: child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) + if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")): + continue if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): frappe.throw(_("Cannot set quantity less than delivered quantity")) From 2866617c82dcec39779d6e286cb462540c2728a5 Mon Sep 17 00:00:00 2001 From: Lokesh Waingankar <59611773+lokesh-indictrans@users.noreply.github.com> Date: Thu, 27 Feb 2020 13:21:14 +0530 Subject: [PATCH 07/20] fix: 'Last Purchase Rate' taking wrong on BOM (#20689) * fix: 'Last Purchase Rate' taking wrong on BOM. #20228 * fix: Added condition for None purchase order and purchase receipt (#20689) * fix: fetch last purchase rate Co-authored-by: Nabin Hait --- erpnext/buying/utils.py | 3 +-- erpnext/stock/doctype/item/item.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/buying/utils.py b/erpnext/buying/utils.py index b5598f8d0b..47b48665b6 100644 --- a/erpnext/buying/utils.py +++ b/erpnext/buying/utils.py @@ -12,7 +12,6 @@ from erpnext.stock.doctype.item.item import validate_end_of_life def update_last_purchase_rate(doc, is_submit): """updates last_purchase_rate in item table for each item""" - import frappe.utils this_purchase_date = frappe.utils.getdate(doc.get('posting_date') or doc.get('transaction_date')) @@ -23,7 +22,7 @@ def update_last_purchase_rate(doc, is_submit): # compare last purchase date and this transaction's date last_purchase_rate = None if last_purchase_details and \ - (last_purchase_details.purchase_date > this_purchase_date): + (doc.get('docstatus') == 2 or last_purchase_details.purchase_date > this_purchase_date): last_purchase_rate = last_purchase_details['base_net_rate'] elif is_submit == 1: # even if this transaction is the latest one, it should be submitted diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a2a913a73f..74ae627d39 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -981,6 +981,7 @@ def _msgprint(msg, verbose): def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): """returns last purchase details in stock uom""" # get last purchase order item details + last_purchase_order = frappe.db.sql("""\ select po.name, po.transaction_date, po.conversion_rate, po_item.conversion_factor, po_item.base_price_list_rate, @@ -991,6 +992,7 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): order by po.transaction_date desc, po.name desc limit 1""", (item_code, cstr(doc_name)), as_dict=1) + # get last purchase receipt item details last_purchase_receipt = frappe.db.sql("""\ select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, @@ -1002,19 +1004,20 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): order by pr.posting_date desc, pr.posting_time desc, pr.name desc limit 1""", (item_code, cstr(doc_name)), as_dict=1) + + purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date or "1900-01-01") purchase_receipt_date = getdate(last_purchase_receipt and last_purchase_receipt[0].posting_date or "1900-01-01") - if (purchase_order_date > purchase_receipt_date) or \ - (last_purchase_order and not last_purchase_receipt): + if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt): # use purchase order + last_purchase = last_purchase_order[0] purchase_date = purchase_order_date - elif (purchase_receipt_date > purchase_order_date) or \ - (last_purchase_receipt and not last_purchase_order): + elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order): # use purchase receipt last_purchase = last_purchase_receipt[0] purchase_date = purchase_receipt_date @@ -1026,10 +1029,11 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): out = frappe._dict({ "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, "base_rate": flt(last_purchase.base_rate) / conversion_factor, - "base_net_rate": flt(last_purchase.net_rate) / conversion_factor, + "base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor, "discount_percentage": flt(last_purchase.discount_percentage), "purchase_date": purchase_date }) + conversion_rate = flt(conversion_rate) or 1.0 out.update({ From 50b3472ebaab53e7cfa79b6a899b52ffbb49a4c9 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 Feb 2020 16:01:47 +0530 Subject: [PATCH 08/20] fix: Journal Entry not being fetched in Bank Reconciliation --- .../doctype/bank_reconciliation/bank_reconciliation.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index 2436b15dd4..883c4207ea 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py @@ -21,7 +21,7 @@ class BankReconciliation(Document): condition = "" if not self.include_reconciled_entries: - condition = " and (clearance_date is null or clearance_date='0000-00-00')" + condition = "and clearance_date IS NULL or clearance_date='0000-00-00'" journal_entries = frappe.db.sql(""" select @@ -34,11 +34,10 @@ class BankReconciliation(Document): where t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1 and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s - and ifnull(t1.is_opening, 'No') = 'No' %(condition)s + and ifnull(t1.is_opening, 'No') = 'No' {condition} group by t2.account, t1.name order by t1.posting_date ASC, t1.name DESC - """, {"condition":condition, "account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) - + """.format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1, debug=1) condition = '' if self.bank_account: From f444f451ac0cf284d1094c13e509ac32805dc8bf Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Thu, 27 Feb 2020 16:04:01 +0530 Subject: [PATCH 09/20] fix: Remove debug statement --- .../doctype/bank_reconciliation/bank_reconciliation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py index 883c4207ea..8900767324 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.py @@ -21,7 +21,7 @@ class BankReconciliation(Document): condition = "" if not self.include_reconciled_entries: - condition = "and clearance_date IS NULL or clearance_date='0000-00-00'" + condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')" journal_entries = frappe.db.sql(""" select @@ -37,7 +37,7 @@ class BankReconciliation(Document): and ifnull(t1.is_opening, 'No') = 'No' {condition} group by t2.account, t1.name order by t1.posting_date ASC, t1.name DESC - """.format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1, debug=1) + """.format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1) condition = '' if self.bank_account: From b5a670cdb9b6a4a9029b6eef9a121c3ecf76bbe3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 27 Feb 2020 18:18:10 +0530 Subject: [PATCH 10/20] fix: serial no material transfer performance issue (#20747) --- erpnext/stock/doctype/serial_no/serial_no.py | 84 ++++++++++++------- .../stock_ledger_entry.json | 3 +- erpnext/stock/utils.py | 2 +- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index d34f420091..64d4c6c082 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -205,6 +205,7 @@ def process_serial_no(sle): def validate_serial_no(sle, item_det): serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else [] + validate_material_transfer_entry(sle) if item_det.has_serial_no==0: if serial_nos: @@ -224,7 +225,9 @@ def validate_serial_no(sle, item_det): for serial_no in serial_nos: if frappe.db.exists("Serial No", serial_no): - sr = frappe.get_doc("Serial No", serial_no) + sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order", + "delivery_document_no", "delivery_document_type", "warehouse", + "purchase_document_no", "company"], as_dict=1) if sr.item_code!=sle.item_code: if not allow_serial_nos_with_different_item(serial_no, sle): @@ -305,6 +308,19 @@ def validate_serial_no(sle, item_det): frappe.throw(_("Cannot cancel {0} {1} because Serial No {2} does not belong to the warehouse {3}") .format(sle.voucher_type, sle.voucher_no, serial_no, sle.warehouse)) +def validate_material_transfer_entry(sle_doc): + sle_doc.update({ + "skip_update_serial_no": False, + "skip_serial_no_validaiton": False + }) + + if (sle_doc.voucher_type == "Stock Entry" and sle_doc.is_cancelled == "No" and + frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"): + if sle_doc.actual_qty < 0: + sle_doc.skip_update_serial_no = True + else: + sle_doc.skip_serial_no_validaiton = True + def validate_so_serial_no(sr, sales_order,): if not sr.sales_order or sr.sales_order!= sales_order: frappe.throw(_("""Sales Order {0} has reservation for item {1}, you can @@ -312,7 +328,8 @@ def validate_so_serial_no(sr, sales_order,): be delivered""").format(sales_order, sr.item_code, sr.name)) def has_duplicate_serial_no(sn, sle): - if sn.warehouse and sle.voucher_type != 'Stock Reconciliation': + if (sn.warehouse and not sle.skip_serial_no_validaiton + and sle.voucher_type != 'Stock Reconciliation'): return True if sn.company != sle.company: @@ -337,7 +354,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): """ allow_serial_nos = False if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0: - stock_entry = frappe.get_doc("Stock Entry", sle.voucher_no) + stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no) if stock_entry.purpose in ("Repack", "Manufacture"): for d in stock_entry.get("items"): if d.serial_no and (d.s_warehouse if sle.is_cancelled=="No" else d.t_warehouse): @@ -348,6 +365,7 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): return allow_serial_nos def update_serial_nos(sle, item_det): + if sle.skip_update_serial_no: return if sle.is_cancelled == "No" and not sle.serial_no and cint(sle.actual_qty) > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) @@ -369,22 +387,16 @@ def auto_make_serial_nos(args): voucher_type = args.get('voucher_type') item_code = args.get('item_code') for serial_no in serial_nos: + is_new = False if frappe.db.exists("Serial No", serial_no): - sr = frappe.get_doc("Serial No", serial_no) - sr.via_stock_ledger = True - sr.item_code = item_code - sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None - sr.batch_no = args.get('batch_no') - sr.location = args.get('location') - sr.company = args.get('company') - sr.supplier = args.get('supplier') - if sr.sales_order and voucher_type == "Stock Entry" \ - and not args.get('actual_qty', 0) > 0: - sr.sales_order = None - sr.update_serial_no_reference() - sr.save(ignore_permissions=True) + sr = frappe.get_cached_doc("Serial No", serial_no) elif args.get('actual_qty', 0) > 0: - created_numbers.append(make_serial_no(serial_no, args)) + sr = frappe.new_doc("Serial No") + is_new = True + + sr = update_args_for_serial_no(sr, serial_no, args, is_new=is_new) + if is_new: + created_numbers.append(sr.name) form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers)) @@ -419,20 +431,34 @@ def get_serial_nos(serial_no): return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') if s.strip()] -def make_serial_no(serial_no, args): - sr = frappe.new_doc("Serial No") - sr.serial_no = serial_no - sr.item_code = args.get('item_code') - sr.company = args.get('company') - sr.batch_no = args.get('batch_no') - sr.via_stock_ledger = args.get('via_stock_ledger') or True - sr.warehouse = args.get('warehouse') +def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False): + serial_no_doc.update({ + "item_code": args.get("item_code"), + "company": args.get("company"), + "batch_no": args.get("batch_no"), + "via_stock_ledger": args.get("via_stock_ledger") or True, + "supplier": args.get("supplier"), + "location": args.get("location"), + "warehouse": (args.get("warehouse") + if args.get("actual_qty", 0) > 0 else None) + }) - sr.validate_item() - sr.update_serial_no_reference(serial_no) - sr.db_insert() + if is_new: + serial_no_doc.serial_no = serial_no - return sr.name + if (serial_no_doc.sales_order and args.get("voucher_type") == "Stock Entry" + and not args.get("actual_qty", 0) > 0): + serial_no_doc.sales_order = None + + serial_no_doc.validate_item() + serial_no_doc.update_serial_no_reference(serial_no) + + if is_new: + serial_no_doc.db_insert() + else: + serial_no_doc.db_update() + + return serial_no_doc def update_serial_nos_after_submit(controller, parentfield): stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json index c9eba71b0d..c03eb79eec 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json @@ -240,6 +240,7 @@ "options": "Company", "print_width": "150px", "read_only": 1, + "search_index": 1, "width": "150px" }, { @@ -274,7 +275,7 @@ "icon": "fa fa-list", "idx": 1, "in_create": 1, - "modified": "2019-11-27 12:17:31.522675", + "modified": "2020-02-25 22:53:33.504681", "modified_by": "Administrator", "module": "Stock", "name": "Stock Ledger Entry", diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 2c6c95393b..f3381c7609 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -136,7 +136,7 @@ def get_bin(item_code, warehouse): bin_obj.flags.ignore_permissions = 1 bin_obj.insert() else: - bin_obj = frappe.get_doc('Bin', bin) + bin_obj = frappe.get_cached_doc('Bin', bin) bin_obj.flags.ignore_permissions = True return bin_obj From d42a4a62343a63dd40a99be700c2507fc6db98d8 Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Thu, 27 Feb 2020 18:31:18 +0530 Subject: [PATCH 11/20] fix: deductions calculation based on gross pay (#20727) * fix: deductions calculation based on gross pay * test: salary structure deduction based on gross pay Co-authored-by: Nabin Hait --- .../additional_salary/additional_salary.py | 6 ++- erpnext/hr/doctype/salary_slip/salary_slip.py | 48 ++++++++++--------- .../salary_structure/test_salary_structure.py | 24 +++++++++- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py index 8498b3d277..bc7dcee55e 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.py +++ b/erpnext/hr/doctype/additional_salary/additional_salary.py @@ -39,19 +39,21 @@ class AdditionalSalary(Document): return amount_per_day * no_of_days @frappe.whitelist() -def get_additional_salary_component(employee, start_date, end_date): +def get_additional_salary_component(employee, start_date, end_date, component_type): additional_components = frappe.db.sql(""" select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date from `tabAdditional Salary` where employee=%(employee)s and docstatus = 1 and payroll_date between %(from_date)s and %(to_date)s + and type = %(component_type)s group by salary_component, overwrite_salary_structure_amount order by salary_component, overwrite_salary_structure_amount """, { 'employee': employee, 'from_date': start_date, - 'to_date': end_date + 'to_date': end_date, + 'component_type': "Earning" if component_type == "earnings" else "Deduction" }, as_dict=1) additional_components_list = [] diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index eee7974710..d03a3dd9a3 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -299,9 +299,11 @@ class SalarySlip(TransactionBase): def calculate_net_pay(self): if self.salary_structure: - self.calculate_component_amounts() - + self.calculate_component_amounts("earnings") self.gross_pay = self.get_component_totals("earnings") + + if self.salary_structure: + self.calculate_component_amounts("deductions") self.total_deduction = self.get_component_totals("deductions") self.set_loan_repayment() @@ -309,25 +311,27 @@ class SalarySlip(TransactionBase): self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) self.rounded_total = rounded(self.net_pay) - def calculate_component_amounts(self): + def calculate_component_amounts(self, component_type): if not getattr(self, '_salary_structure_doc', None): self._salary_structure_doc = frappe.get_doc('Salary Structure', self.salary_structure) payroll_period = get_payroll_period(self.start_date, self.end_date, self.company) - self.add_structure_components() - self.add_employee_benefits(payroll_period) - self.add_additional_salary_components() - self.add_tax_components(payroll_period) - self.set_component_amounts_based_on_payment_days() + self.add_structure_components(component_type) + self.add_additional_salary_components(component_type) + if component_type == "earnings": + self.add_employee_benefits(payroll_period) + else: + self.add_tax_components(payroll_period) - def add_structure_components(self): + self.set_component_amounts_based_on_payment_days(component_type) + + def add_structure_components(self, component_type): data = self.get_data_for_eval() - for key in ('earnings', 'deductions'): - for struct_row in self._salary_structure_doc.get(key): - amount = self.eval_condition_and_formula(struct_row, data) - if amount and struct_row.statistical_component == 0: - self.update_component_row(struct_row, amount, key) + for struct_row in self._salary_structure_doc.get(component_type): + amount = self.eval_condition_and_formula(struct_row, data) + if amount and struct_row.statistical_component == 0: + self.update_component_row(struct_row, amount, component_type) def get_data_for_eval(self): '''Returns data for evaluating formula''' @@ -400,14 +404,15 @@ class SalarySlip(TransactionBase): amount = last_benefit.amount self.update_component_row(frappe._dict(last_benefit.struct_row), amount, "earnings") - def add_additional_salary_components(self): - additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date) + def add_additional_salary_components(self, component_type): + additional_components = get_additional_salary_component(self.employee, + self.start_date, self.end_date, component_type) if additional_components: for additional_component in additional_components: amount = additional_component.amount overwrite = additional_component.overwrite - key = "earnings" if additional_component.type == "Earning" else "deductions" - self.update_component_row(frappe._dict(additional_component.struct_row), amount, key, overwrite=overwrite) + self.update_component_row(frappe._dict(additional_component.struct_row), amount, + component_type, overwrite=overwrite) def add_tax_components(self, payroll_period): # Calculate variable_based_on_taxable_salary after all components updated in salary slip @@ -736,7 +741,7 @@ class SalarySlip(TransactionBase): total += d.amount return total - def set_component_amounts_based_on_payment_days(self): + def set_component_amounts_based_on_payment_days(self, component_type): joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) @@ -746,9 +751,8 @@ class SalarySlip(TransactionBase): if not joining_date: frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) - for component_type in ("earnings", "deductions"): - for d in self.get(component_type): - d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] + for d in self.get(component_type): + d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] def set_loan_repayment(self): self.set('loans', []) diff --git a/erpnext/hr/doctype/salary_structure/test_salary_structure.py b/erpnext/hr/doctype/salary_structure/test_salary_structure.py index 78150946c8..6ca6dfd2c0 100644 --- a/erpnext/hr/doctype/salary_structure/test_salary_structure.py +++ b/erpnext/hr/doctype/salary_structure/test_salary_structure.py @@ -25,7 +25,6 @@ class TestSalaryStructure(unittest.TestCase): make_employee("test_employee@salary.com") make_employee("test_employee_2@salary.com") - def make_holiday_list(self): if not frappe.db.get_value("Holiday List", "Salary Structure Test Holiday List"): holiday_list = frappe.get_doc({ @@ -38,6 +37,29 @@ class TestSalaryStructure(unittest.TestCase): holiday_list.get_weekly_off_dates() holiday_list.save() + def test_salary_structure_deduction_based_on_gross_pay(self): + + emp = make_employee("test_employee_3@salary.com") + + sal_struct = make_salary_structure("Salary Structure 2", "Monthly", dont_submit = True) + + sal_struct.earnings = [sal_struct.earnings[0]] + sal_struct.earnings[0].amount_based_on_formula = 1 + sal_struct.earnings[0].formula = "base" + + sal_struct.deductions = [sal_struct.deductions[0]] + + sal_struct.deductions[0].amount_based_on_formula = 1 + sal_struct.deductions[0].condition = "gross_pay > 100" + sal_struct.deductions[0].formula = "gross_pay * 0.2" + + sal_struct.submit() + + assignment = create_salary_structure_assignment(emp, "Salary Structure 2") + ss = make_salary_slip(sal_struct.name, employee = emp) + + self.assertEqual(assignment.base * 0.2, ss.deductions[0].amount) + def test_amount_totals(self): frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0) sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"}) From fb35a54beea41ca93b5d7781e4aa48f5e6f6e584 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 27 Feb 2020 18:32:19 +0530 Subject: [PATCH 12/20] perf: improve gl entry submission (#20676) * perf: improve gl entry submission * perf: add indexes * fix: replace **kwargs with *args * fix: syntax error * fix: remove cypress * fix: travis * chore: remove purchase invoice from status updater * fix: set_staus args Co-Authored-By: Nabin Hait * fix: only update status for invoices & fees Co-authored-by: Nabin Hait --- .../discounted_invoice.json | 5 +- .../discounted_invoice/discounted_invoice.py | 2 +- erpnext/accounts/doctype/gl_entry/gl_entry.py | 30 ++++- .../purchase_invoice/purchase_invoice.py | 49 ++++++++ .../doctype/sales_invoice/sales_invoice.py | 117 +++++++++++------- erpnext/accounts/general_ledger.py | 5 +- erpnext/controllers/status_updater.py | 11 -- 7 files changed, 155 insertions(+), 64 deletions(-) diff --git a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json index 5c3519a159..02b0c4d937 100644 --- a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json +++ b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json @@ -18,7 +18,8 @@ "in_list_view": 1, "label": "Invoice", "options": "Sales Invoice", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fetch_from": "sales_invoice.customer", @@ -60,7 +61,7 @@ } ], "istable": 1, - "modified": "2019-09-26 11:05:36.016772", + "modified": "2020-02-20 16:16:20.724620", "modified_by": "Administrator", "module": "Accounts", "name": "Discounted Invoice", diff --git a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py index 93dfcc14bd..109737f727 100644 --- a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py +++ b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.py @@ -7,4 +7,4 @@ from __future__ import unicode_literals from frappe.model.document import Document class DiscountedInvoice(Document): - pass + pass \ No newline at end of file diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 041e419752..f9e4fd7714 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -232,11 +232,36 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga if bal < 0 and not on_cancel: frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))) - # Update outstanding amt on against voucher if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: + update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal) + +def update_outstanding_amt_in_ref(against_voucher, against_voucher_type, bal): + data = [] + # Update outstanding amt on against voucher + if against_voucher_type == "Fees": ref_doc = frappe.get_doc(against_voucher_type, against_voucher) ref_doc.db_set('outstanding_amount', bal) ref_doc.set_status(update=True) + return + elif against_voucher_type == "Purchase Invoice": + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_status + data = frappe.db.get_value(against_voucher_type, against_voucher, + ["name as purchase_invoice", "outstanding_amount", + "is_return", "due_date", "docstatus"]) + elif against_voucher_type == "Sales Invoice": + from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_status + data = frappe.db.get_value(against_voucher_type, against_voucher, + ["name as sales_invoice", "outstanding_amount", "is_discounted", + "is_return", "due_date", "docstatus"]) + + precision = frappe.get_precision(against_voucher_type, "outstanding_amount") + data = list(data) + data.append(precision) + status = get_status(data) + frappe.db.set_value(against_voucher_type, against_voucher, { + 'outstanding_amount': bal, + 'status': status + }) def validate_frozen_account(account, adv_adj=None): frozen_account = frappe.db.get_value("Account", account, "freeze_account") @@ -274,6 +299,9 @@ def update_against_account(voucher_type, voucher_no): if d.against != new_against: frappe.db.set_value("GL Entry", d.name, "against", new_against) +def on_doctype_update(): + frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"]) + frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"]) def rename_gle_sle_docs(): for doctype in ["GL Entry", "Stock Ledger Entry"]: diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index a68c36846d..847fbed58c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -125,6 +125,27 @@ class PurchaseInvoice(BuyingController): else: self.remarks = _("No Remarks") + def set_status(self, update=False, status=None, update_modified=True): + if self.is_new(): + if self.get('amended_from'): + self.status = 'Draft' + return + + if not status: + precision = self.precision("outstanding_amount") + args = [ + self.name, + self.outstanding_amount, + self.is_return, + self.due_date, + self.docstatus, + precision + ] + status = get_status(args) + + if update: + self.db_set('status', status, update_modified = update_modified) + def set_missing_values(self, for_validate=False): if not self.credit_to: self.credit_to = get_party_account("Supplier", self.supplier, self.company) @@ -1007,6 +1028,34 @@ class PurchaseInvoice(BuyingController): # calculate totals again after applying TDS self.calculate_taxes_and_totals() +def get_status(*args): + purchase_invoice, outstanding_amount, is_return, due_date, docstatus, precision = args[0] + + outstanding_amount = flt(outstanding_amount, precision) + due_date = getdate(due_date) + now_date = getdate() + + if docstatus == 2: + status = "Cancelled" + elif docstatus == 1: + if outstanding_amount > 0 and due_date < now_date: + status = "Overdue" + elif outstanding_amount > 0 and due_date >= now_date: + status = "Unpaid" + #Check if outstanding amount is 0 due to debit note issued against invoice + elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': purchase_invoice, 'docstatus': 1}): + status = "Debit Note Issued" + elif is_return == 1: + status = "Return" + elif outstanding_amount <=0: + status = "Paid" + else: + status = "Submitted" + else: + status = "Draft" + + return status + def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context list_context = get_list_context(context) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 658e703b4e..bcaa394b06 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1217,62 +1217,83 @@ class SalesInvoice(SellingController): self.set_missing_values(for_validate = True) - def get_discounting_status(self): - status = None - if self.is_discounted: - invoice_discounting_list = frappe.db.sql(""" - select status - from `tabInvoice Discounting` id, `tabDiscounted Invoice` d - where - id.name = d.parent - and d.sales_invoice=%s - and id.docstatus=1 - and status in ('Disbursed', 'Settled') - """, self.name) - for d in invoice_discounting_list: - status = d[0] - if status == "Disbursed": - break - return status - def set_status(self, update=False, status=None, update_modified=True): if self.is_new(): if self.get('amended_from'): self.status = 'Draft' return - precision = self.precision("outstanding_amount") - outstanding_amount = flt(self.outstanding_amount, precision) - due_date = getdate(self.due_date) - nowdate = getdate() - discountng_status = self.get_discounting_status() - if not status: - if self.docstatus == 2: - status = "Cancelled" - elif self.docstatus == 1: - if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': - self.status = "Overdue and Discounted" - elif outstanding_amount > 0 and due_date < nowdate: - self.status = "Overdue" - elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed': - self.status = "Unpaid and Discounted" - elif outstanding_amount > 0 and due_date >= nowdate: - self.status = "Unpaid" - #Check if outstanding amount is 0 due to credit note issued against invoice - elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): - self.status = "Credit Note Issued" - elif self.is_return == 1: - self.status = "Return" - elif outstanding_amount<=0: - self.status = "Paid" - else: - self.status = "Submitted" - else: - self.status = "Draft" + precision = self.precision("outstanding_amount") + args = [ + self.name, + self.outstanding_amount, + self.is_discounted, + self.is_return, + self.due_date, + self.docstatus, + precision, + ] + status = get_status(args) if update: - self.db_set('status', self.status, update_modified = update_modified) + self.db_set('status', status, update_modified = update_modified) + +def get_discounting_status(sales_invoice): + status = None + + invoice_discounting_list = frappe.db.sql(""" + select status + from `tabInvoice Discounting` id, `tabDiscounted Invoice` d + where + id.name = d.parent + and d.sales_invoice=%s + and id.docstatus=1 + and status in ('Disbursed', 'Settled') + """, sales_invoice) + + for d in invoice_discounting_list: + status = d[0] + if status == "Disbursed": + break + + return status + +def get_status(*args): + sales_invoice, outstanding_amount, is_discounted, is_return, due_date, docstatus, precision = args[0] + + discounting_status = None + if is_discounted: + discounting_status = get_discounting_status(sales_invoice) + + outstanding_amount = flt(outstanding_amount, precision) + due_date = getdate(due_date) + now_date = getdate() + + if docstatus == 2: + status = "Cancelled" + elif docstatus == 1: + if outstanding_amount > 0 and due_date < now_date and is_discounted and discounting_status=='Disbursed': + status = "Overdue and Discounted" + elif outstanding_amount > 0 and due_date < now_date: + status = "Overdue" + elif outstanding_amount > 0 and due_date >= now_date and is_discounted and discounting_status=='Disbursed': + status = "Unpaid and Discounted" + elif outstanding_amount > 0 and due_date >= now_date: + status = "Unpaid" + #Check if outstanding amount is 0 due to credit note issued against invoice + elif outstanding_amount <= 0 and is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': sales_invoice, 'docstatus': 1}): + status = "Credit Note Issued" + elif is_return == 1: + status = "Return" + elif outstanding_amount <=0: + status = "Paid" + else: + status = "Submitted" + else: + status = "Draft" + + return status def validate_inter_company_party(doctype, party, company, inter_company_reference): if not party: @@ -1444,7 +1465,7 @@ def get_inter_company_details(doc, doctype): "party": party, "company": company } - + def get_internal_party(parties, link_doctype, doc): if len(parties) == 1: party = parties[0].name diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index bb1b7e392d..6d53530321 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -140,8 +140,11 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False): gle = frappe.get_doc(args) gle.flags.ignore_permissions = 1 gle.flags.from_repost = from_repost - gle.insert() + gle.validate() + gle.flags.ignore_permissions = True + gle.db_insert() gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost) + gle.flags.ignore_validate = True gle.submit() def validate_account_for_perpetual_inventory(gl_map): diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 8b275a64fb..b465a106f0 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -44,17 +44,6 @@ status_map = { ["Closed", "eval:self.status=='Closed'"], ["On Hold", "eval:self.status=='On Hold'"], ], - "Purchase Invoice": [ - ["Draft", None], - ["Submitted", "eval:self.docstatus==1"], - ["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"], - ["Return", "eval:self.is_return==1 and self.docstatus==1"], - ["Debit Note Issued", - "eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"], - ["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"], - ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"], - ["Cancelled", "eval:self.docstatus==2"], - ], "Purchase Order": [ ["Draft", None], ["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"], From d20e36f3fdcddd29070a4380451b0760532070e5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 27 Feb 2020 19:07:58 +0530 Subject: [PATCH 13/20] feat: ignore permission when deleting linked emails (#20751) * feat: ignore permission when deleting linked emails * fix: uncommented important snippet --- erpnext/setup/doctype/company/delete_company_transactions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 1503adb504..8ecc13b2fb 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -108,8 +108,8 @@ def delete_lead_addresses(company_name): def delete_communications(doctype, company_name, company_fieldname): reference_docs = frappe.get_all(doctype, filters={company_fieldname:company_name}) reference_doc_names = [r.name for r in reference_docs] - + communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in", reference_doc_names]}) communication_names = [c.name for c in communications] - frappe.delete_doc("Communication", communication_names) + frappe.delete_doc("Communication", communication_names, ignore_permissions=True) From 8cf841ce602948c87819669b220f40b83ee3d306 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 27 Feb 2020 19:09:34 +0530 Subject: [PATCH 14/20] fix: wrong calculation of depreciation eliminated for a period (#20502) --- .../asset_depreciations_and_balances.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index 78546609ad..d7efbad240 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -29,7 +29,7 @@ def get_data(filters): row.update(next(asset for asset in assets if asset["asset_category"] == asset_category.get("asset_category", ""))) row.accumulated_depreciation_as_on_to_date = (flt(row.accumulated_depreciation_as_on_from_date) + - flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated)) + flt(row.depreciation_amount_during_the_period) - flt(row.depreciation_eliminated_during_the_period)) row.net_asset_value_as_on_from_date = (flt(row.cost_as_on_from_date) - flt(row.accumulated_depreciation_as_on_from_date)) @@ -86,7 +86,6 @@ def get_asset_categories(filters): group by asset_category """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) - def get_assets(filters): return frappe.db.sql(""" SELECT results.asset_category, @@ -94,9 +93,7 @@ def get_assets(filters): sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period, sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period from (SELECT a.asset_category, - ifnull(sum(a.opening_accumulated_depreciation + - case when ds.schedule_date < %(from_date)s and - (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then + ifnull(sum(case when ds.schedule_date < %(from_date)s then ds.depreciation_amount else 0 @@ -107,7 +104,6 @@ def get_assets(filters): else 0 end), 0) as depreciation_eliminated_during_the_period, - ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then ds.depreciation_amount @@ -120,7 +116,8 @@ def get_assets(filters): union SELECT a.asset_category, ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 - and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then + and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) + then 0 else a.opening_accumulated_depreciation @@ -133,7 +130,6 @@ def get_assets(filters): 0 as depreciation_amount_during_the_period from `tabAsset` a where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s - and not exists(select * from `tabDepreciation Schedule` ds where a.name = ds.parent) group by a.asset_category) as results group by results.asset_category """, {"to_date": filters.to_date, "from_date": filters.from_date, "company": filters.company}, as_dict=1) From 0db423ed5f6801631d5f07a7e4c1ca323bdeb9b2 Mon Sep 17 00:00:00 2001 From: Karthikeyan S Date: Fri, 28 Feb 2020 12:27:21 +0530 Subject: [PATCH 15/20] fix(auto attendance): bug in marking absent (#20759) This bug was introduces in the commit bd6e8b9cec5414f81c67468030ec174030845720 i.e. The mark_absent function was renamed to mark_attendance, but there is a miss match in the order of the parameters. --- erpnext/hr/doctype/shift_type/shift_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index 49884103e2..d56080eecd 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -75,7 +75,7 @@ class ShiftType(Document): for date in dates: shift_details = get_employee_shift(employee, date, True) if shift_details and shift_details.shift_type.name == self.name: - mark_attendance(employee, date, self.name, 'Absent') + mark_attendance(employee, date, 'Absent', self.name) def get_assigned_employee(self, from_date=None, consider_default_shift=False): filters = {'date':('>=', from_date), 'shift_type': self.name, 'docstatus': '1'} From 5707c9a7314eb5b96d19f351fd47a0fa86873869 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Fri, 28 Feb 2020 12:28:54 +0530 Subject: [PATCH 16/20] fix: Item Wise report query fix (#20760) --- .../item_wise_purchase_register.py | 8 ++------ .../item_wise_sales_register.py | 10 +++------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py index 8b6359c134..4523f66deb 100644 --- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py +++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py @@ -306,10 +306,6 @@ def get_conditions(filters): def get_items(filters, additional_query_columns): conditions = get_conditions(filters) - match_conditions = frappe.build_match_conditions("Purchase Invoice") - - if match_conditions: - match_conditions = " and {0} ".format(match_conditions) if additional_query_columns: additional_query_columns = ', ' + ', '.join(additional_query_columns) @@ -327,8 +323,8 @@ def get_items(filters, additional_query_columns): `tabPurchase Invoice`.supplier_name, `tabPurchase Invoice`.mode_of_payment {0} from `tabPurchase Invoice`, `tabPurchase Invoice Item` where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and - `tabPurchase Invoice`.docstatus = 1 %s %s - """.format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) + `tabPurchase Invoice`.docstatus = 1 %s + """.format(additional_query_columns) % (conditions), filters, as_dict=1) def get_aii_accounts(): return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany")) diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index 2cc2db6ca5..786e04dd5a 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -119,7 +119,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns) data.append(total_row_map.get('total_row')) skip_total_row = 1 - + return columns, data, None, None, None, skip_total_row def get_columns(additional_table_columns, filters): @@ -370,10 +370,6 @@ def get_group_by_conditions(filters, doctype): def get_items(filters, additional_query_columns): conditions = get_conditions(filters) - match_conditions = frappe.build_match_conditions("Sales Invoice") - - if match_conditions: - match_conditions = " and {0} ".format(match_conditions) if additional_query_columns: additional_query_columns = ', ' + ', '.join(additional_query_columns) @@ -394,8 +390,8 @@ def get_items(filters, additional_query_columns): `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} from `tabSales Invoice`, `tabSales Invoice Item` where `tabSales Invoice`.name = `tabSales Invoice Item`.parent - and `tabSales Invoice`.docstatus = 1 {1} {2} - """.format(additional_query_columns or '', conditions, match_conditions), filters, as_dict=1) #nosec + and `tabSales Invoice`.docstatus = 1 {1} + """.format(additional_query_columns or '', conditions), filters, as_dict=1) #nosec def get_delivery_notes_against_sales_order(item_list): so_dn_map = frappe._dict() From 24f468399922c774a8f4881921c9df9689e05130 Mon Sep 17 00:00:00 2001 From: marination Date: Fri, 28 Feb 2020 12:58:41 +0530 Subject: [PATCH 17/20] chore: Rearranged Buying Module Dashboard --- erpnext/config/buying.py | 54 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py index 6f5ab32b63..1d4054786e 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -7,6 +7,13 @@ def get_data(): "label": _("Purchasing"), "icon": "fa fa-star", "items": [ + { + "type": "doctype", + "name": "Material Request", + "onboard": 1, + "dependencies": ["Item"], + "description": _("Request for purchase."), + }, { "type": "doctype", "name": "Purchase Order", @@ -20,13 +27,6 @@ def get_data(): "onboard": 1, "dependencies": ["Item", "Supplier"] }, - { - "type": "doctype", - "name": "Material Request", - "onboard": 1, - "dependencies": ["Item"], - "description": _("Request for purchase."), - }, { "type": "doctype", "name": "Request for Quotation", @@ -63,6 +63,11 @@ def get_data(): "name": "Price List", "description": _("Price List master.") }, + { + "type": "doctype", + "name": "Pricing Rule", + "description": _("Rules for applying pricing and discount.") + }, { "type": "doctype", "name": "Product Bundle", @@ -80,11 +85,6 @@ def get_data(): "type": "doctype", "name": "Promotional Scheme", "description": _("Rules for applying different promotional schemes.") - }, - { - "type": "doctype", - "name": "Pricing Rule", - "description": _("Rules for applying pricing and discount.") } ] }, @@ -149,13 +149,6 @@ def get_data(): "reference_doctype": "Purchase Order", "onboard": 1 }, - { - "type": "report", - "is_query_report": True, - "name": "Supplier-Wise Sales Analytics", - "reference_doctype": "Stock Ledger Entry", - "onboard": 1 - }, { "type": "report", "is_query_report": True, @@ -177,6 +170,16 @@ def get_data(): "reference_doctype": "Material Request", "onboard": 1, }, + { + "type": "report", + "is_query_report": True, + "name": "Address And Contacts", + "label": _("Supplier Addresses And Contacts"), + "reference_doctype": "Address", + "route_options": { + "party_type": "Supplier" + } + } ] }, { @@ -226,18 +229,15 @@ def get_data(): { "type": "report", "is_query_report": True, - "name": "Material Requests for which Supplier Quotations are not created", - "reference_doctype": "Material Request" + "name": "Supplier-Wise Sales Analytics", + "reference_doctype": "Stock Ledger Entry", + "onboard": 1 }, { "type": "report", "is_query_report": True, - "name": "Address And Contacts", - "label": _("Supplier Addresses And Contacts"), - "reference_doctype": "Address", - "route_options": { - "party_type": "Supplier" - } + "name": "Material Requests for which Supplier Quotations are not created", + "reference_doctype": "Material Request" } ] }, From eee7f77d477a4e7c146a7e7270ee2e76c63ee1de Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 29 Feb 2020 08:58:15 +0530 Subject: [PATCH 18/20] fix: Add loan to value ratio in loan security type --- .../loan_security_shortfall.py | 8 ++++++-- .../loan_security_type/loan_security_type.json | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py index 599f6dafaa..b7be84f836 100644 --- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py +++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py @@ -55,7 +55,10 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None): "valid_upto": (">=", update_time) }, as_list=1)) - loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty + ltv_ratio_map = frappe._dict(frappe.get_all("Loan Security Type", + fields=["name", "loan_to_value_ratio"], as_list=1)) + + loans = frappe.db.sql(""" SELECT l.name, l.loan_amount, l.total_principal_paid, lp.loan_security, lp.haircut, lp.qty, lp.loan_security_type FROM `tabLoan` l, `tabPledge` lp , `tabLoan Security Pledge`p WHERE lp.parent = p.name and p.loan = l.name and l.docstatus = 1 and l.is_secured_loan and l.status = 'Disbursed' and p.status in ('Pledged', 'Partially Unpledged')""", as_dict=1) @@ -68,11 +71,12 @@ def check_for_ltv_shortfall(process_loan_security_shortfall=None): }) current_loan_security_amount = loan_security_price_map.get(loan.loan_security, 0) * loan.qty + ltv_ratio = ltv_ratio_map.get(loan.loan_security_type) loan_security_map[loan.name]['security_value'] += current_loan_security_amount - (current_loan_security_amount * loan.haircut/100) for loan, value in iteritems(loan_security_map): - if value["security_value"] < value["loan_amount"]: + if (value["security_value"]/value["loan_amount"]) < ltv_ratio: create_loan_security_shortfall(loan, value, process_loan_security_shortfall) def create_loan_security_shortfall(loan, value, process_loan_security_shortfall): diff --git a/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json b/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json index a5ab057cdc..5f296093a4 100644 --- a/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json +++ b/erpnext/loan_management/doctype/loan_security_type/loan_security_type.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "field:loan_security_type", "creation": "2019-08-29 18:46:07.322056", "doctype": "DocType", @@ -8,7 +9,9 @@ "loan_security_type", "unit_of_measure", "haircut", - "disabled" + "disabled", + "column_break_5", + "loan_to_value_ratio" ], "fields": [ { @@ -33,9 +36,19 @@ "fieldtype": "Link", "label": "Unit Of Measure", "options": "UOM" + }, + { + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, + { + "fieldname": "loan_to_value_ratio", + "fieldtype": "Percent", + "label": "Loan To Value Ratio" } ], - "modified": "2019-10-10 03:05:37.912866", + "links": [], + "modified": "2020-02-28 12:43:20.364447", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Type", From 86ecaa59cad6c4519a7eeedc9ce51738559fd069 Mon Sep 17 00:00:00 2001 From: radhikag Date: Sat, 29 Feb 2020 12:47:03 +0530 Subject: [PATCH 19/20] fix : Student Admission accepting end date less than start date. #20625 --- .../doctype/student_admission/student_admission.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/education/doctype/student_admission/student_admission.js b/erpnext/education/doctype/student_admission/student_admission.js index d7f74545eb..2b6296726c 100644 --- a/erpnext/education/doctype/student_admission/student_admission.js +++ b/erpnext/education/doctype/student_admission/student_admission.js @@ -11,5 +11,12 @@ frappe.ui.form.on('Student Admission', { academic_year: function(frm) { frm.trigger("program"); + }, + + admission_end_date: function(frm) { + if(frm.doc.admission_end_date && frm.doc.admission_end_date <= frm.doc.admission_start_date){ + frm.set_value("admission_end_date", ""); + frappe.throw(__("Admission End Date should be greater than Admission Start Date.")); + } } }); From 09188e4a2133864a1d78c04f61d4edec1b332fe7 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sun, 1 Mar 2020 22:16:27 +0530 Subject: [PATCH 20/20] fix: Remove fetch from loan_application.json --- .../doctype/loan_application/loan_application.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.json b/erpnext/loan_management/doctype/loan_application/loan_application.json index 4c433029d7..a353a7740d 100644 --- a/erpnext/loan_management/doctype/loan_application/loan_application.json +++ b/erpnext/loan_management/doctype/loan_application/loan_application.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "ACC-LOAP-.YYYY.-.#####", "creation": "2019-08-29 17:46:49.201740", "doctype": "DocType", @@ -122,7 +123,6 @@ }, { "depends_on": "eval: doc.is_term_loan == 1", - "fetch_from": "loan_type.repayment_method", "fetch_if_empty": 1, "fieldname": "repayment_method", "fieldtype": "Select", @@ -213,7 +213,8 @@ } ], "is_submittable": 1, - "modified": "2019-10-24 10:32:03.740558", + "links": [], + "modified": "2020-03-01 10:21:44.413353", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Application",