From a656151ee970fae142c03bcce04dd5b3dde7578e Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 12 Sep 2019 16:00:25 +0530 Subject: [PATCH 01/22] fix: operating cost calculation in JS --- erpnext/manufacturing/doctype/work_order/work_order.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index ce7b4f9425..d82158af33 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -385,6 +385,11 @@ frappe.ui.form.on("Work Order", { } }); } + }, + + additional_operating_cost: function(frm) { + erpnext.work_order.calculate_cost(frm.doc); + erpnext.work_order.calculate_total_cost(frm); } }); @@ -524,9 +529,8 @@ erpnext.work_order = { }, calculate_total_cost: function(frm) { - var variable_cost = frm.doc.actual_operating_cost ? - flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost); - frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)); + let variable_cost = flt(frm.doc.actual_operating_cost) || flt(frm.doc.planned_operating_cost); + frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) }, set_default_warehouse: function(frm) { From 5ea4328359a526721206d91c150fae38329b797d Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Wed, 13 Nov 2019 12:46:19 +0530 Subject: [PATCH 02/22] fix: Accumulated Values filter disappearing --- erpnext/accounts/report/balance_sheet/balance_sheet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 4bc29da2c7..8c11514aa6 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Balance Sheet"] = erpnext.financial_statements; + frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements); frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", From 3a72cb46bce2168703d976a782a7bdaa9eb1d0de Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 13 Nov 2019 17:58:10 +0530 Subject: [PATCH 03/22] fix: Set due date in accounts receivable based on payment terms (#19563) --- .../report/accounts_receivable/accounts_receivable.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index bcbd427186..14906f2c2e 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -188,7 +188,11 @@ class ReceivablePayableReport(object): self.data.append(row) def set_invoice_details(self, row): - row.update(self.invoice_details.get(row.voucher_no, {})) + invoice_details = self.invoice_details.get(row.voucher_no, {}) + if row.due_date: + invoice_details.pop("due_date", None) + row.update(invoice_details) + if row.voucher_type == 'Sales Invoice': if self.filters.show_delivery_notes: self.set_delivery_notes(row) From ba8fc21594eafcd7accf60a29aca6d14b48485e8 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 18:11:58 +0530 Subject: [PATCH 04/22] fix: merge similar entries for serialized items in stock reconciliation (#19408) --- .../stock_reconciliation.py | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 98a8c59483..daf320e40a 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -52,9 +52,10 @@ class StockReconciliation(StockController): def _changed(item): item_dict = get_stock_balance_for(item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no) - if (((item.qty is None or item.qty==item_dict.get("qty")) and - (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and not item.serial_no) - or (item.serial_no and item.serial_no == item_dict.get("serial_nos"))): + + if ((item.qty is None or item.qty==item_dict.get("qty")) and + (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and + (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")) )): return False else: # set default as current rates @@ -182,9 +183,11 @@ class StockReconciliation(StockController): from erpnext.stock.stock_ledger import get_previous_sle sl_entries = [] + has_serial_no = False for row in self.items: item = frappe.get_doc("Item", row.item_code) if item.has_serial_no or item.has_batch_no: + has_serial_no = True self.get_sle_for_serialized_items(row, sl_entries) else: previous_sle = get_previous_sle({ @@ -212,8 +215,14 @@ class StockReconciliation(StockController): sl_entries.append(self.get_sle_for_items(row)) if sl_entries: + if has_serial_no: + sl_entries = self.merge_similar_item_serial_nos(sl_entries) + self.make_sl_entries(sl_entries) + if has_serial_no and sl_entries: + self.update_valuation_rate_for_serial_no() + def get_sle_for_serialized_items(self, row, sl_entries): from erpnext.stock.stock_ledger import get_previous_sle @@ -275,8 +284,18 @@ class StockReconciliation(StockController): # update valuation rate self.update_valuation_rate_for_serial_nos(row, serial_nos) + def update_valuation_rate_for_serial_no(self): + for d in self.items: + if not d.serial_no: continue + + serial_nos = get_serial_nos(d.serial_no) + self.update_valuation_rate_for_serial_nos(d, serial_nos) + def update_valuation_rate_for_serial_nos(self, row, serial_nos): valuation_rate = row.valuation_rate if self.docstatus == 1 else row.current_valuation_rate + if valuation_rate is None: + return + for d in serial_nos: frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate) @@ -321,11 +340,17 @@ class StockReconciliation(StockController): where voucher_type=%s and voucher_no=%s""", (self.doctype, self.name)) sl_entries = [] + + has_serial_no = False for row in self.items: if row.serial_no or row.batch_no or row.current_serial_no: + has_serial_no = True self.get_sle_for_serialized_items(row, sl_entries) if sl_entries: + if has_serial_no: + sl_entries = self.merge_similar_item_serial_nos(sl_entries) + sl_entries.reverse() allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) @@ -339,6 +364,35 @@ class StockReconciliation(StockController): "posting_time": self.posting_time }) + def merge_similar_item_serial_nos(self, sl_entries): + # If user has put the same item in multiple row with different serial no + new_sl_entries = [] + merge_similar_entries = {} + + for d in sl_entries: + if not d.serial_no or d.actual_qty < 0: + new_sl_entries.append(d) + continue + + key = (d.item_code, d.warehouse) + if key not in merge_similar_entries: + merge_similar_entries[key] = d + elif d.serial_no: + data = merge_similar_entries[key] + data.actual_qty += d.actual_qty + data.qty_after_transaction += d.qty_after_transaction + + data.valuation_rate = (data.valuation_rate + d.valuation_rate) / data.actual_qty + data.serial_no += '\n' + d.serial_no + + if data.incoming_rate: + data.incoming_rate = (data.incoming_rate + d.incoming_rate) / data.actual_qty + + for key, value in merge_similar_entries.items(): + new_sl_entries.append(value) + + return new_sl_entries + def get_gl_entries(self, warehouse_account=None): if not self.cost_center: msgprint(_("Please enter Cost Center"), raise_exception=1) From d064505ebe68b6ea6a89bfcf0bbaf1c0ec20d074 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 18:17:48 +0530 Subject: [PATCH 05/22] fix: incorrect produced qty in the production plan (#19569) --- .../doctype/production_plan/production_plan.js | 5 +++++ erpnext/manufacturing/doctype/work_order/work_order.py | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 51989378d8..3b24d0fa0f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -3,6 +3,11 @@ frappe.ui.form.on('Production Plan', { setup: function(frm) { + frm.custom_make_buttons = { + 'Work Order': 'Work Order', + 'Material Request': 'Material Request', + }; + frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) { return { filters: { diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index ae4d9be282..6ea3dc83ed 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -223,7 +223,15 @@ class WorkOrder(Document): def update_production_plan_status(self): production_plan = frappe.get_doc('Production Plan', self.production_plan) - production_plan.run_method("update_produced_qty", self.produced_qty, self.production_plan_item) + produced_qty = 0 + if self.production_plan_item: + total_qty = frappe.get_all("Work Order", fields = "sum(produced_qty) as produced_qty", + filters = {'docstatus': 1, 'production_plan': self.production_plan, + 'production_plan_item': self.production_plan_item}, as_list=1) + + produced_qty = total_qty[0][0] if total_qty else 0 + + production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item) def on_submit(self): if not self.wip_warehouse: From 732d6afad55c553a7f04ef7227125ee98017887f Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 13 Nov 2019 18:49:23 +0530 Subject: [PATCH 06/22] fix: Show AR summary based on outstanding (#19573) --- .../accounts_receivable_summary/accounts_receivable_summary.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index b90a7a9501..8955830e09 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.filters.report_date) or {} for party, party_dict in iteritems(self.party_total): + if party_dict.outstanding <= 0: + continue + row = frappe._dict() row.party = party From 94565d69d100cfab12bdada14013c48f63586329 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 13 Nov 2019 18:58:22 +0530 Subject: [PATCH 07/22] fix: travis failing (#19568) --- erpnext/setup/doctype/currency_exchange/currency_exchange.py | 4 ++++ .../setup/doctype/currency_exchange/test_currency_exchange.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/currency_exchange/currency_exchange.py b/erpnext/setup/doctype/currency_exchange/currency_exchange.py index 60d367a4bb..6480f60f59 100644 --- a/erpnext/setup/doctype/currency_exchange/currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/currency_exchange.py @@ -14,10 +14,14 @@ class CurrencyExchange(Document): purpose = "" if not self.date: self.date = nowdate() + + # If both selling and buying enabled + purpose = "Selling-Buying" if cint(self.for_buying)==0 and cint(self.for_selling)==1: purpose = "Selling" if cint(self.for_buying)==1 and cint(self.for_selling)==0: purpose = "Buying" + self.name = '{0}-{1}-{2}{3}'.format(formatdate(get_datetime_str(self.date), "yyyy-MM-dd"), self.from_currency, self.to_currency, ("-" + purpose) if purpose else "") diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py index 857f666b2a..c5c01c5775 100644 --- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py +++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py @@ -11,7 +11,9 @@ test_records = frappe.get_test_records('Currency Exchange') def save_new_records(test_records): for record in test_records: - purpose = str("") + # If both selling and buying enabled + purpose = "Selling-Buying" + if cint(record.get("for_buying"))==0 and cint(record.get("for_selling"))==1: purpose = "Selling" if cint(record.get("for_buying"))==1 and cint(record.get("for_selling"))==0: From 3e515e704ddfde9c733d96edbc8502d0f03c677c Mon Sep 17 00:00:00 2001 From: Saqib Date: Wed, 13 Nov 2019 19:00:24 +0530 Subject: [PATCH 08/22] Monthly distribution of depreciation amount (#19493) * feat: allow monthly distribution of depreciation amount * chore: added comments * fix: monthly depr was not starting from use date's month --- erpnext/assets/doctype/asset/asset.json | 13 +++-- erpnext/assets/doctype/asset/asset.py | 72 +++++++++++++++++++++---- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 8fda330bb7..6882f6a992 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -33,6 +33,7 @@ "available_for_use_date", "column_break_18", "calculate_depreciation", + "allow_monthly_depreciation", "is_existing_asset", "opening_accumulated_depreciation", "number_of_depreciations_booked", @@ -216,8 +217,7 @@ { "fieldname": "available_for_use_date", "fieldtype": "Date", - "label": "Available-for-use Date", - "reqd": 1 + "label": "Available-for-use Date" }, { "fieldname": "column_break_18", @@ -450,12 +450,19 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "default": "0", + "depends_on": "calculate_depreciation", + "fieldname": "allow_monthly_depreciation", + "fieldtype": "Check", + "label": "Allow Monthly Depreciation" } ], "idx": 72, "image_field": "image", "is_submittable": 1, - "modified": "2019-10-07 15:34:30.976208", + "modified": "2019-10-22 15:47:36.050828", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 94e6f6168e..d1f8c1a8d3 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext, math, json from frappe import _ from six import string_types -from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days +from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, month_diff, add_days from frappe.model.document import Document from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ @@ -149,19 +149,31 @@ class Asset(AccountsController): schedule_date = add_months(d.depreciation_start_date, n * cint(d.frequency_of_depreciation)) + # schedule date will be a year later from start date + # so monthly schedule date is calculated by removing 11 months from it + monthly_schedule_date = add_months(schedule_date, - d.frequency_of_depreciation + 1) + # For first row if has_pro_rata and n==0: - depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount, + depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, self.available_for_use_date, d.depreciation_start_date) + + # For first depr schedule date will be the start date + # so monthly schedule date is calculated by removing month difference between use date and start date + monthly_schedule_date = add_months(d.depreciation_start_date, - months + 1) + # For last row elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: to_date = add_months(self.available_for_use_date, n * cint(d.frequency_of_depreciation)) - depreciation_amount, days = get_pro_rata_amt(d, + depreciation_amount, days, months = get_pro_rata_amt(d, depreciation_amount, schedule_date, to_date) + monthly_schedule_date = add_months(schedule_date, 1) + schedule_date = add_days(schedule_date, days) + last_schedule_date = schedule_date if not depreciation_amount: continue value_after_depreciation -= flt(depreciation_amount, @@ -175,13 +187,50 @@ class Asset(AccountsController): skip_row = True if depreciation_amount > 0: - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + # With monthly depreciation, each depreciation is divided by months remaining until next date + if self.allow_monthly_depreciation: + # month range is 1 to 12 + # In pro rata case, for first and last depreciation, month range would be different + month_range = months \ + if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \ + else d.frequency_of_depreciation + + for r in range(month_range): + if (has_pro_rata and n == 0): + # For first entry of monthly depr + if r == 0: + days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date) + per_day_amt = depreciation_amount / days + depreciation_amount_for_current_month = per_day_amt * days_until_first_depr + depreciation_amount -= depreciation_amount_for_current_month + date = monthly_schedule_date + amount = depreciation_amount_for_current_month + else: + date = add_months(monthly_schedule_date, r) + amount = depreciation_amount / (month_range - 1) + elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1: + # For last entry of monthly depr + date = last_schedule_date + amount = depreciation_amount / month_range + else: + date = add_months(monthly_schedule_date, r) + amount = depreciation_amount / month_range + + self.append("schedules", { + "schedule_date": date, + "depreciation_amount": amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + else: + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) def check_is_pro_rata(self, row): has_pro_rata = False @@ -588,9 +637,10 @@ def is_cwip_accounting_enabled(company, asset_category=None): def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): days = date_diff(to_date, from_date) + months = month_diff(to_date, from_date) total_days = get_total_days(to_date, row.frequency_of_depreciation) - return (depreciation_amount * flt(days)) / flt(total_days), days + return (depreciation_amount * flt(days)) / flt(total_days), days, months def get_total_days(date, frequency): period_start_date = add_months(date, From af7fe1937ec77028a770b490c942cbd014c36026 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 13 Nov 2019 19:00:56 +0530 Subject: [PATCH 09/22] fix: fetch leave approver defined in employee in leave application (#19559) * fix: fetch leave approver defined in employee in leave application * Update department_approver.py --- .../department_approver/department_approver.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 9f2f2013a7..7bf9905d07 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -19,14 +19,20 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): approvers = [] department_details = {} department_list = [] - employee_department = filters.get("department") or frappe.get_value("Employee", filters.get("employee"), "department") + employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) + if employee.leave_approver: + approver = frappe.db.get_value("User", leave_approver, ['name', 'first_name', 'last_name']) + approvers.append(approver) + return approvers + + employee_department = filters.get("department") or employee.department if employee_department: department_details = frappe.db.get_value("Department", {"name": employee_department}, ["lft", "rgt"], as_dict=True) if department_details: department_list = frappe.db.sql("""select name from `tabDepartment` where lft <= %s and rgt >= %s and disabled=0 - order by lft desc""", (department_details.lft, department_details.rgt), as_list = True) + order by lft desc""", (department_details.lft, department_details.rgt), as_list=True) if filters.get("doctype") == "Leave Application": parentfield = "leave_approvers" @@ -41,4 +47,4 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): and approver.parentfield = %s and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True) - return approvers \ No newline at end of file + return approvers From 9ffa9d4a64eb60ca02da187646af34df1a723fc4 Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 13 Nov 2019 19:10:20 +0530 Subject: [PATCH 10/22] fix(patch): Enable CWIP Accounting --- .../patches/v12_0/set_cwip_and_delete_asset_settings.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py index 3d07fe57a5..5842e9edbf 100644 --- a/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py +++ b/erpnext/patches/v12_0/set_cwip_and_delete_asset_settings.py @@ -9,13 +9,12 @@ def execute(): if frappe.db.exists("DocType","Asset Settings"): frappe.reload_doctype("Company") - cwip_value = frappe.db.sql(""" SELECT value FROM `tabSingles` WHERE doctype='Asset Settings' - and field='disable_cwip_accounting' """, as_dict=1) + cwip_value = frappe.db.get_single_value("Asset Settings","disable_cwip_accounting") companies = [x['name'] for x in frappe.get_all("Company", "name")] for company in companies: - enable_cwip_accounting = cint(not cint(cwip_value[0]['value'])) - frappe.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting) + enable_cwip_accounting = cint(not cint(cwip_value)) + frappe.db.set_value("Company", company, "enable_cwip_accounting", enable_cwip_accounting) frappe.db.sql( """ DELETE FROM `tabSingles` where doctype = 'Asset Settings' """) From 1ad2d4a962cb0bd6b43d255b3ade228e8135d932 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Wed, 13 Nov 2019 19:21:53 +0530 Subject: [PATCH 11/22] fix: get tags for rfq (#19564) * fix: get tags for rfq * chore: remove console log --- .../request_for_quotation/request_for_quotation.js | 2 +- .../request_for_quotation/request_for_quotation.py | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 9ad06f9b7e..2f0cfa64fc 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -134,7 +134,7 @@ frappe.ui.form.on("Request for Quotation",{ if (args.search_type === "Tag" && args.tag) { return frappe.call({ type: "GET", - method: "frappe.desk.tags.get_tagged_docs", + method: "frappe.desk.doctype.tag.tag.get_tagged_docs", args: { "doctype": "Supplier", "tag": args.tag diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index a10ce46e33..95db33b0f8 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -344,13 +344,9 @@ def get_item_from_material_requests_based_on_supplier(source_name, target_doc = @frappe.whitelist() def get_supplier_tag(): - data = frappe.db.sql("select _user_tags from `tabSupplier`") - - tags = [] - for tag in data: - tags += filter(bool, tag[0].split(",")) - - tags = list(set(tags)) - - return tags + if not frappe.cache().hget("Supplier", "Tags"): + filters = {"document_type": "Supplier"} + tags = list(set([tag.tag for tag in frappe.get_all("Tag Link", filters=filters, fields=["tag"]) if tag])) + frappe.cache().hset("Supplier", "Tags", tags) + return frappe.cache().hget("Supplier", "Tags") From 73616d6b3366bd357c4df162349ac3ddff429232 Mon Sep 17 00:00:00 2001 From: Saqib Date: Thu, 14 Nov 2019 10:09:44 +0530 Subject: [PATCH 12/22] fix: duplication while bulk creation of item tax template (#19570) --- .../move_item_tax_to_item_tax_template.py | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 412f32030a..f25b9eaf52 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -1,20 +1,30 @@ import frappe import json from six import iteritems +from frappe.model.naming import make_autoname def execute(): if "tax_type" not in frappe.db.get_table_columns("Item Tax"): return old_item_taxes = {} item_tax_templates = {} - rename_template_to_untitled = [] + + frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1) + frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1) + existing_templates = frappe.db.sql("""select template.name, details.tax_type, details.tax_rate + from `tabItem Tax Template` template, `tabItem Tax Template Detail` details + where details.parent=template.name + """, as_dict=1) + + if len(existing_templates): + for d in existing_templates: + item_tax_templates.setdefault(d.name, {}) + item_tax_templates[d.name][d.tax_type] = d.tax_rate for d in frappe.db.sql("""select parent as item_code, tax_type, tax_rate from `tabItem Tax`""", as_dict=1): old_item_taxes.setdefault(d.item_code, []) old_item_taxes[d.item_code].append(d) - frappe.reload_doc("accounts", "doctype", "item_tax_template_detail", force=1) - frappe.reload_doc("accounts", "doctype", "item_tax_template", force=1) frappe.reload_doc("stock", "doctype", "item", force=1) frappe.reload_doc("stock", "doctype", "item_tax", force=1) frappe.reload_doc("selling", "doctype", "quotation_item", force=1) @@ -27,6 +37,8 @@ def execute(): frappe.reload_doc("accounts", "doctype", "purchase_invoice_item", force=1) frappe.reload_doc("accounts", "doctype", "accounts_settings", force=1) + frappe.db.auto_commit_on_many_writes = True + # for each item that have item tax rates for item_code in old_item_taxes.keys(): # make current item's tax map @@ -34,8 +46,7 @@ def execute(): for d in old_item_taxes[item_code]: item_tax_map[d.tax_type] = d.tax_rate - item_tax_template_name = get_item_tax_template(item_tax_templates, rename_template_to_untitled, - item_tax_map, item_code) + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) # update the item tax table item = frappe.get_doc("Item", item_code) @@ -49,35 +60,33 @@ def execute(): 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice' ] + for dt in doctypes: for d in frappe.db.sql("""select name, parent, item_code, item_tax_rate from `tab{0} Item` - where ifnull(item_tax_rate, '') not in ('', '{{}}')""".format(dt), as_dict=1): + where ifnull(item_tax_rate, '') not in ('', '{{}}') + and item_tax_template is NULL""".format(dt), as_dict=1): item_tax_map = json.loads(d.item_tax_rate) - item_tax_template = get_item_tax_template(item_tax_templates, rename_template_to_untitled, + item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, d.item_code, d.parent) - frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template) + frappe.db.set_value(dt + " Item", d.name, "item_tax_template", item_tax_template_name) - idx = 1 - for oldname in rename_template_to_untitled: - frappe.rename_doc("Item Tax Template", oldname, "Untitled {}".format(idx)) - idx += 1 + frappe.db.auto_commit_on_many_writes = False settings = frappe.get_single("Accounts Settings") settings.add_taxes_from_item_tax_template = 0 settings.determine_address_tax_category_from = "Billing Address" settings.save() -def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_tax_map, item_code, parent=None): +def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parent=None): # search for previously created item tax template by comparing tax maps for template, item_tax_template_map in iteritems(item_tax_templates): if item_tax_map == item_tax_template_map: - if not parent: - rename_template_to_untitled.append(template) return template # if no item tax template found, create one item_tax_template = frappe.new_doc("Item Tax Template") - item_tax_template.title = "{}--{}".format(parent, item_code) if parent else "Item-{}".format(item_code) + item_tax_template.title = make_autoname("Item Tax Template-.####") + for tax_type, tax_rate in iteritems(item_tax_map): if not frappe.db.exists("Account", tax_type): parts = tax_type.strip().split(" - ") From 5503b6cff56d876b4a88046cb7810cf83155dea1 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 14 Nov 2019 10:11:25 +0530 Subject: [PATCH 13/22] fix(Task): Do not create/schedule task after project end (#19184) * fix: do not create/schedule task after project end * fix: check difference between dates * fix: check project date * fix: task creation * fix: tests --- .../doctype/crop_cycle/crop_cycle.py | 14 +++++------ erpnext/projects/doctype/task/task.py | 25 +++++++++++++++++-- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py index bb9045ca81..3e51933df7 100644 --- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py +++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py @@ -51,27 +51,25 @@ class CropCycle(Document): self.create_task(disease_doc.treatment_task, self.name, start_date) def create_project(self, period, crop_tasks): - project = frappe.new_doc("Project") - project.update({ + project = frappe.get_doc({ + "doctype": "Project", "project_name": self.title, "expected_start_date": self.start_date, "expected_end_date": add_days(self.start_date, period - 1) - }) - project.insert() + }).insert() return project.name def create_task(self, crop_tasks, project_name, start_date): for crop_task in crop_tasks: - task = frappe.new_doc("Task") - task.update({ + frappe.get_doc({ + "doctype": "Task", "subject": crop_task.get("task_name"), "priority": crop_task.get("priority"), "project": project_name, "exp_start_date": add_days(start_date, crop_task.get("start_day") - 1), "exp_end_date": add_days(start_date, crop_task.get("end_day") - 1) - }) - task.insert() + }).insert() def reload_linked_analysis(self): linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis'] diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 90e9f05f22..54fce8d6db 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -10,6 +10,7 @@ from frappe import _, throw from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate from frappe.utils.nestedset import NestedSet from frappe.desk.form.assign_to import close_all_assignments, clear +from frappe.utils import date_diff class CircularReferenceError(frappe.ValidationError): pass class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass @@ -28,16 +29,29 @@ class Task(NestedSet): def validate(self): self.validate_dates() + self.validate_parent_project_dates() self.validate_progress() self.validate_status() self.update_depends_on() def validate_dates(self): if self.exp_start_date and self.exp_end_date and getdate(self.exp_start_date) > getdate(self.exp_end_date): - frappe.throw(_("'Expected Start Date' can not be greater than 'Expected End Date'")) + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Expected Start Date"), \ + frappe.bold("Expected End Date"))) if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date): - frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'")) + frappe.throw(_("{0} can not be greater than {1}").format(frappe.bold("Actual Start Date"), \ + frappe.bold("Actual End Date"))) + + def validate_parent_project_dates(self): + if not self.project or frappe.flags.in_test: + return + + expected_end_date = getdate(frappe.db.get_value("Project", self.project, "expected_end_date")) + + if expected_end_date: + validate_project_dates(expected_end_date, self, "exp_start_date", "exp_end_date", "Expected") + validate_project_dates(expected_end_date, self, "act_start_date", "act_end_date", "Actual") def validate_status(self): if self.status!=self.get_db_value("status") and self.status == "Completed": @@ -255,3 +269,10 @@ def add_multiple_tasks(data, parent): def on_doctype_update(): frappe.db.add_index("Task", ["lft", "rgt"]) + +def validate_project_dates(project_end_date, task, task_start, task_end, actual_or_expected_date): + if task.get(task_start) and date_diff(project_end_date, getdate(task.get(task_start))) < 0: + frappe.throw(_("Task's {0} Start Date cannot be after Project's End Date.").format(actual_or_expected_date)) + + if task.get(task_end) and date_diff(project_end_date, getdate(task.get(task_end))) < 0: + frappe.throw(_("Task's {0} End Date cannot be after Project's End Date.").format(actual_or_expected_date)) \ No newline at end of file From fc3b924d4dd727431c82925bce6b2f9afd90d698 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 14 Nov 2019 12:02:10 +0530 Subject: [PATCH 14/22] fix: skip leave ledger entry creation for denied leaves (#19556) * fix: skip leave ledger entry creation for denied leave application * patch: remove incorrect leave ledger entries * fix: create reverse ledger entry before setting status to cancel --- .../leave_application/leave_application.py | 5 +++- erpnext/patches.txt | 4 +-- .../remove_denied_leaves_from_leave_ledger.py | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index e1e5e8001d..0e6630541c 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -55,11 +55,11 @@ class LeaveApplication(Document): self.reload() def on_cancel(self): + self.create_leave_ledger_entry(submit=False) self.status = "Cancelled" # notify leave applier about cancellation self.notify_employee() self.cancel_attendance() - self.create_leave_ledger_entry(submit=False) def validate_applicable_after(self): if self.leave_type: @@ -351,6 +351,9 @@ class LeaveApplication(Document): pass def create_leave_ledger_entry(self, submit=True): + if self.status != 'Approved': + return + expiry_date = get_allocation_expiry(self.employee, self.leave_type, self.to_date, self.from_date) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 0155b27820..9e4dc12e65 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -638,11 +638,11 @@ erpnext.patches.v12_0.add_variant_of_in_item_attribute_table erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account erpnext.patches.v12_0.create_default_energy_point_rules erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order -erpnext.patches.v12_0.generate_leave_ledger_entries erpnext.patches.v12_0.set_default_shopify_app_type erpnext.patches.v12_0.set_cwip_and_delete_asset_settings erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields -erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template \ No newline at end of file +erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template +erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger \ No newline at end of file diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py new file mode 100644 index 0000000000..24f0e7cb21 --- /dev/null +++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py @@ -0,0 +1,26 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import getdate, today + +def execute(): + ''' Delete leave ledger entry created + via leave applications with status != Approved ''' + if not frappe.db.a_row_exists("Leave Ledger Entry"): + return + + leave_application_list = get_denied_leave_application_list() + if leave_application_list: + delete_denied_leaves_from_leave_ledger_entry(leave_application_list) + +def get_denied_leave_application_list(): + return frappe.db.sql_list(''' Select name from `tabLeave Application` where status <> 'Approved' ''') + +def delete_denied_leaves_from_leave_ledger_entry(leave_application_list): + frappe.db.sql(''' Delete + FROM `tabLeave Ledger Entry` + WHERE + transaction_type = 'Leave Application' + AND transaction_name in {0} '''.format(tuple(leave_application_list))) #nosec \ No newline at end of file From d69d0e30467d94f81f230c0facaada552ac5507c Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Thu, 14 Nov 2019 13:05:13 +0530 Subject: [PATCH 15/22] fix(patch): skip leave ledger entry creation for denied leaves (#19579) --- .../v12_0/remove_denied_leaves_from_leave_ledger.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py index 24f0e7cb21..7859606e5c 100644 --- a/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py +++ b/erpnext/patches/v12_0/remove_denied_leaves_from_leave_ledger.py @@ -19,8 +19,10 @@ def get_denied_leave_application_list(): return frappe.db.sql_list(''' Select name from `tabLeave Application` where status <> 'Approved' ''') def delete_denied_leaves_from_leave_ledger_entry(leave_application_list): - frappe.db.sql(''' Delete - FROM `tabLeave Ledger Entry` - WHERE - transaction_type = 'Leave Application' - AND transaction_name in {0} '''.format(tuple(leave_application_list))) #nosec \ No newline at end of file + if leave_application_list: + frappe.db.sql(''' Delete + FROM `tabLeave Ledger Entry` + WHERE + transaction_type = 'Leave Application' + AND transaction_name in (%s) ''' % (', '.join(['%s'] * len(leave_application_list))), #nosec + tuple(leave_application_list)) \ No newline at end of file From ec082754b43906d95a5ccc68b5cded806efeab79 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 14 Nov 2019 13:28:24 +0530 Subject: [PATCH 16/22] fix: One serial no can be tagged in multiple invoices if used against different items (#19580) --- .../accounts/doctype/sales_invoice/sales_invoice.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0ebca8bf51..fefd36a313 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -991,10 +991,8 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - if serial_no and frappe.db.exists('Serial No', serial_no): - sno = frappe.get_doc('Serial No', serial_no) - sno.sales_invoice = invoice - sno.db_update() + if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code: + frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice) def validate_serial_numbers(self): """ @@ -1040,8 +1038,9 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice") - if sales_invoice and self.name != sales_invoice: + sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no, + ["sales_invoice", "item_code"]) + if sales_invoice and item_code == item.item_code and self.name != sales_invoice: sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" From e942f998976fc21443187f9e071f4003a5f86605 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 14 Nov 2019 15:26:18 +0530 Subject: [PATCH 17/22] Update work_order.js --- erpnext/manufacturing/doctype/work_order/work_order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 15a33ca329..107c79b89b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -540,7 +540,7 @@ erpnext.work_order = { calculate_total_cost: function(frm) { let variable_cost = flt(frm.doc.actual_operating_cost) || flt(frm.doc.planned_operating_cost); - frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) + frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)); }, set_default_warehouse: function(frm) { From c9e8a1bf96e379aaa239cd67c6aefb4233e124aa Mon Sep 17 00:00:00 2001 From: Marica Date: Thu, 14 Nov 2019 16:13:43 +0530 Subject: [PATCH 18/22] fix: Account Balance and Stock Value out of sync error message (#19526) * fix: Account Balance and Stock Value out of sync error message Added 'Make Adjustment Entry' button and enhanced message * fix: Split message and changed routing for translation --- erpnext/accounts/general_ledger.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index d4dac72601..38f283c8d4 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -163,9 +163,16 @@ def validate_account_for_perpetual_inventory(gl_map): .format(account), StockAccountInvalidTransaction) elif account_bal != stock_bal: - frappe.throw(_("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and linked warehouse ({3}). Please create adjustment Journal Entry for amount {4}.") - .format(account_bal, stock_bal, account, comma_and(warehouse_list), stock_bal - account_bal), - StockValueAndAccountBalanceOutOfSync) + error_reason = _("Account Balance ({0}) and Stock Value ({1}) is out of sync for account {2} and it's linked warehouses.").format( + account_bal, stock_bal, frappe.bold(account)) + error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(stock_bal - account_bal)) + button_text = _("Make Adjustment Entry") + + frappe.throw("""{0}

{1}

+
+ +
""".format(error_reason, error_resolution, button_text), + StockValueAndAccountBalanceOutOfSync, title=_('Account Balance Out Of Sync')) def validate_cwip_accounts(gl_map): cwip_enabled = cint(frappe.get_cached_value("Company", From cf55c9c6da39c5e0fad8aa7bd7dbaa8337505179 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 14 Nov 2019 18:22:20 +0530 Subject: [PATCH 19/22] fix: stock reconciliation shwoing incorrect current serial no and qty --- .../doctype/stock_reconciliation/stock_reconciliation.py | 2 +- erpnext/stock/utils.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 98a8c59483..3683e60fd6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -456,7 +456,7 @@ def get_qty_rate_for_serial_nos(item_code, warehouse, posting_date, posting_time } serial_nos_list = [serial_no.get("name") - for serial_no in get_available_serial_nos(item_code, warehouse)] + for serial_no in get_available_serial_nos(args)] qty = len(serial_nos_list) serial_nos = '\n'.join(serial_nos_list) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index d7629176a5..2c6c95393b 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -293,9 +293,11 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto row, key, value = data row[key] = value -def get_available_serial_nos(item_code, warehouse): - return frappe.get_all("Serial No", filters = {'item_code': item_code, - 'warehouse': warehouse, 'delivery_document_no': ''}) or [] +def get_available_serial_nos(args): + return frappe.db.sql(""" SELECT name from `tabSerial No` + WHERE item_code = %(item_code)s and warehouse = %(warehouse)s + and timestamp(purchase_date, purchase_time) <= timestamp(%(posting_date)s, %(posting_time)s) + """, args, as_dict=1) def add_additional_uom_columns(columns, result, include_uom, conversion_factors): if not include_uom or not conversion_factors: From d545f6fb6ba01456711d408a3459ddabd7529067 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 14 Nov 2019 19:26:49 +0530 Subject: [PATCH 20/22] fix: fetch approver from employee --- erpnext/hr/doctype/department_approver/department_approver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/department_approver/department_approver.py b/erpnext/hr/doctype/department_approver/department_approver.py index 7bf9905d07..d6b66da081 100644 --- a/erpnext/hr/doctype/department_approver/department_approver.py +++ b/erpnext/hr/doctype/department_approver/department_approver.py @@ -21,7 +21,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters): department_list = [] employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True) if employee.leave_approver: - approver = frappe.db.get_value("User", leave_approver, ['name', 'first_name', 'last_name']) + approver = frappe.db.get_value("User", employee.leave_approver, ['name', 'first_name', 'last_name']) approvers.append(approver) return approvers From 74bbcb539f8303375edf6815e007017d95f8e98f Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 14 Nov 2019 22:44:15 +0530 Subject: [PATCH 21/22] fix: Ignore period closing voucher entries in accounts dashboard --- .../account_balance_timeline/account_balance_timeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 716bef381b..43acded3a9 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -93,7 +93,8 @@ def get_gl_entries(account, to_date): fields = ['posting_date', 'debit', 'credit'], filters = [ dict(posting_date = ('<', to_date)), - dict(account = ('in', child_accounts)) + dict(account = ('in', child_accounts)), + dict(voucher_type = ('!=', 'Period Closing Voucher')) ], order_by = 'posting_date asc') From 642441688687286ba0fce0df1b2319529c723bdd Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 15 Nov 2019 14:18:45 +0530 Subject: [PATCH 22/22] fix: sales order item shwoing incorrect produced qty (#19584) --- .../doctype/work_order/work_order.py | 4 +++- ...ed_qty_field_in_sales_order_for_work_order.py | 14 +++++++++----- .../selling/doctype/sales_order/sales_order.py | 16 ++++++++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 6ea3dc83ed..089cb8014d 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -216,7 +216,9 @@ class WorkOrder(Document): self.db_set(fieldname, qty) from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item - update_produced_qty_in_so_item(self.sales_order_item) + + if self.sales_order and self.sales_order_item: + update_produced_qty_in_so_item(self.sales_order, self.sales_order_item) if self.production_plan: self.update_production_plan_status() diff --git a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py index 44d8fa767a..07026732fd 100644 --- a/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py +++ b/erpnext/patches/v12_0/set_produced_qty_field_in_sales_order_for_work_order.py @@ -3,8 +3,12 @@ from frappe.utils import flt from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item def execute(): - frappe.reload_doctype('Sales Order Item') - frappe.reload_doctype('Sales Order') - sales_order_items = frappe.db.get_all('Sales Order Item', ['name']) - for so_item in sales_order_items: - update_produced_qty_in_so_item(so_item.get('name')) \ No newline at end of file + frappe.reload_doctype('Sales Order Item') + frappe.reload_doctype('Sales Order') + + for d in frappe.get_all('Work Order', + fields = ['sales_order', 'sales_order_item'], + filters={'sales_order': ('!=', ''), 'sales_order_item': ('!=', '')}): + + # update produced qty in sales order + update_produced_qty_in_so_item(d.sales_order, d.sales_order_item) \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index c4c3c0f81e..e12b359bdf 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1038,14 +1038,18 @@ def create_pick_list(source_name, target_doc=None): return doc -def update_produced_qty_in_so_item(sales_order_item): +def update_produced_qty_in_so_item(sales_order, sales_order_item): #for multiple work orders against same sales order item linked_wo_with_so_item = frappe.db.get_all('Work Order', ['produced_qty'], { 'sales_order_item': sales_order_item, + 'sales_order': sales_order, 'docstatus': 1 }) - if len(linked_wo_with_so_item) > 0: - total_produced_qty = 0 - for wo in linked_wo_with_so_item: - total_produced_qty += flt(wo.get('produced_qty')) - frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty) \ No newline at end of file + + total_produced_qty = 0 + for wo in linked_wo_with_so_item: + total_produced_qty += flt(wo.get('produced_qty')) + + if not total_produced_qty and frappe.flags.in_patch: return + + frappe.db.set_value('Sales Order Item', sales_order_item, 'produced_qty', total_produced_qty) \ No newline at end of file