From 7554ac9569d1b8a2bc3d36ae266010aa656c3790 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Sep 2022 17:32:12 +0530 Subject: [PATCH 01/27] fix: get amount in words for debit note (cherry picked from commit 70f6484d9d9afc3b89e6df6a18993214584722f8) --- erpnext/controllers/buying_controller.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 5659ad0aa9b..48fe7cb083d 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -194,16 +194,16 @@ class BuyingController(SubcontractingController): if self.meta.get_field("base_in_words"): if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled(): - amount = self.base_rounded_total + amount = abs(self.base_rounded_total) else: - amount = self.base_grand_total + amount = abs(self.base_grand_total) self.base_in_words = money_in_words(amount, self.company_currency) if self.meta.get_field("in_words"): if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled(): - amount = self.rounded_total + amount = abs(self.rounded_total) else: - amount = self.grand_total + amount = abs(self.grand_total) self.in_words = money_in_words(amount, self.currency) From d54bf6d91cc7b8afacdbe2fcbf0d4e7292d70445 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 20 Sep 2022 17:56:47 +0530 Subject: [PATCH 02/27] refactor: rewrite `BOM Variance Report` queries in `QB` --- .../bom_variance_report.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py index da283435b9f..70a1850fd0f 100644 --- a/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py +++ b/erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py @@ -64,22 +64,21 @@ def get_columns(filters): def get_data(filters): - cond = "1=1" + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name.as_("work_order"), wo.qty, wo.produced_qty, wo.production_item, wo.bom_no) + .where((wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + ) if filters.get("bom_no") and not filters.get("work_order"): - cond += " and bom_no = '%s'" % filters.get("bom_no") + query = query.where(wo.bom_no == filters.get("bom_no")) if filters.get("work_order"): - cond += " and name = '%s'" % filters.get("work_order") + query = query.where(wo.name == filters.get("work_order")) results = [] - for d in frappe.db.sql( - """ select name as work_order, qty, produced_qty, production_item, bom_no - from `tabWork Order` where produced_qty > qty and docstatus = 1 and {0}""".format( - cond - ), - as_dict=1, - ): + for d in query.run(as_dict=True): results.append(d) for data in frappe.get_all( @@ -95,16 +94,17 @@ def get_data(filters): @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs def get_work_orders(doctype, txt, searchfield, start, page_len, filters): - cond = "1=1" - if filters.get("bom_no"): - cond += " and bom_no = '%s'" % filters.get("bom_no") - - return frappe.db.sql( - """select name from `tabWork Order` - where name like %(name)s and {0} and produced_qty > qty and docstatus = 1 - order by name limit {2} offset {1}""".format( - cond, start, page_len - ), - {"name": "%%%s%%" % txt}, - as_list=1, + wo = frappe.qb.DocType("Work Order") + query = ( + frappe.qb.from_(wo) + .select(wo.name) + .where((wo.name.like(f"{txt}%")) & (wo.produced_qty > wo.qty) & (wo.docstatus == 1)) + .orderby(wo.name) + .limit(page_len) + .offset(start) ) + + if filters.get("bom_no"): + query = query.where(wo.bom_no == filters.get("bom_no")) + + return query.run(as_list=True) From f5c517d08d3089fc7a3fde3cebf2430403fc6c8d Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Wed, 21 Sep 2022 09:54:38 +0530 Subject: [PATCH 03/27] refactor: rewrite `Exponential Smoothing Forecasting` queries in `QB` --- .../exponential_smoothing_forecasting.py | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py index 7500744c228..d3bce831551 100644 --- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py +++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py @@ -96,38 +96,39 @@ class ForecastingReport(ExponentialSmoothingForecast): value["avg"] = flt(sum(list_of_period_value)) / flt(sum(total_qty)) def get_data_for_forecast(self): - cond = "" - if self.filters.item_code: - cond = " AND soi.item_code = %s" % (frappe.db.escape(self.filters.item_code)) - - warehouses = [] - if self.filters.warehouse: - warehouses = get_child_warehouses(self.filters.warehouse) - cond += " AND soi.warehouse in ({})".format(",".join(["%s"] * len(warehouses))) - - input_data = [self.filters.from_date, self.filters.company] - if warehouses: - input_data.extend(warehouses) + parent = frappe.qb.DocType(self.doctype) + child = frappe.qb.DocType(self.child_doctype) date_field = "posting_date" if self.doctype == "Delivery Note" else "transaction_date" - return frappe.db.sql( - """ - SELECT - so.{date_field} as posting_date, soi.item_code, soi.warehouse, - soi.item_name, soi.stock_qty as qty, soi.base_amount as amount - FROM - `tab{doc}` so, `tab{child_doc}` soi - WHERE - so.docstatus = 1 AND so.name = soi.parent AND - so.{date_field} < %s AND so.company = %s {cond} - """.format( - doc=self.doctype, child_doc=self.child_doctype, date_field=date_field, cond=cond - ), - tuple(input_data), - as_dict=1, + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( + parent[date_field].as_("posting_date"), + child.item_code, + child.warehouse, + child.item_name, + child.stock_qty.as_("qty"), + child.base_amount.as_("amount"), + ) + .where( + (parent.docstatus == 1) + & (parent.name == child.parent) + & (parent[date_field] < self.filters.from_date) + & (parent.company == self.filters.company) + ) ) + if self.filters.item_code: + query = query.where(child.item_code == self.filters.item_code) + + if self.filters.warehouse: + warehouses = get_child_warehouses(self.filters.warehouse) or [] + query = query.where(child.warehouse.isin(warehouses)) + + return query.run(as_dict=True) + def prepare_final_data(self): self.data = [] From 4e31448e3ddd51ad7c5bab053d852a1d2ed7bfc4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 21 Sep 2022 13:11:00 +0530 Subject: [PATCH 04/27] fix: item_code key error in production plan --- erpnext/manufacturing/doctype/production_plan/production_plan.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index f1d40c219cb..4bb4dcc648e 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -894,6 +894,7 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p .select( (IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"), item.item_name, + item.name.as_("item_code"), bei.description, bei.stock_uom, item.min_order_qty, From bcfa9b859332e81d2aefb799aed11a8a61578720 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Wed, 7 Sep 2022 16:34:10 +0530 Subject: [PATCH 05/27] fix: Reduce font size for Process Statement of accounts print/pdf (cherry picked from commit 6bfd193b0d20464f2d3370a850cec6b360e604e1) --- .../process_statement_of_accounts.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 82705a9cea4..0da44a464e7 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -25,7 +25,7 @@
- +
From 21a4556f9919ce52107f7f9798c3e3d206fbadfd Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 22 Sep 2022 11:17:11 +0530 Subject: [PATCH 06/27] refactor: rewrite `Production Planning Report` queries in `QB` (cherry picked from commit 8417b9b99c0730c04e5d4b6194522eab500e4760) --- .../production_planning_report.py | 164 +++++++++--------- 1 file changed, 85 insertions(+), 79 deletions(-) diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py index 140488820a5..16c25ce7e6d 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py @@ -4,42 +4,10 @@ import frappe from frappe import _ +from pypika import Order from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses -# and bom_no is not null and bom_no !='' - -mapper = { - "Sales Order": { - "fields": """ item_code as production_item, item_name as production_item_name, stock_uom, - stock_qty as qty_to_manufacture, `tabSales Order Item`.parent as name, bom_no, warehouse, - `tabSales Order Item`.delivery_date, `tabSales Order`.base_grand_total """, - "filters": """`tabSales Order Item`.docstatus = 1 and stock_qty > produced_qty - and `tabSales Order`.per_delivered < 100.0""", - }, - "Material Request": { - "fields": """ item_code as production_item, item_name as production_item_name, stock_uom, - stock_qty as qty_to_manufacture, `tabMaterial Request Item`.parent as name, bom_no, warehouse, - `tabMaterial Request Item`.schedule_date """, - "filters": """`tabMaterial Request`.docstatus = 1 and `tabMaterial Request`.per_ordered < 100 - and `tabMaterial Request`.material_request_type = 'Manufacture' """, - }, - "Work Order": { - "fields": """ production_item, item_name as production_item_name, planned_start_date, - stock_uom, qty as qty_to_manufacture, name, bom_no, fg_warehouse as warehouse """, - "filters": "docstatus = 1 and status not in ('Completed', 'Stopped')", - }, -} - -order_mapper = { - "Sales Order": { - "Delivery Date": "`tabSales Order Item`.delivery_date asc", - "Total Amount": "`tabSales Order`.base_grand_total desc", - }, - "Material Request": {"Required Date": "`tabMaterial Request Item`.schedule_date asc"}, - "Work Order": {"Planned Start Date": "planned_start_date asc"}, -} - def execute(filters=None): return ProductionPlanReport(filters).execute_report() @@ -63,40 +31,78 @@ class ProductionPlanReport(object): return self.columns, self.data def get_open_orders(self): - doctype = ( - "`tabWork Order`" - if self.filters.based_on == "Work Order" - else "`tab{doc}`, `tab{doc} Item`".format(doc=self.filters.based_on) - ) + doctype, order_by = self.filters.based_on, self.filters.order_by - filters = mapper.get(self.filters.based_on)["filters"] - filters = self.prepare_other_conditions(filters, self.filters.based_on) - order_by = " ORDER BY %s" % (order_mapper[self.filters.based_on][self.filters.order_by]) + parent = frappe.qb.DocType(doctype) + query = None - self.orders = frappe.db.sql( - """ SELECT {fields} from {doctype} - WHERE {filters} {order_by}""".format( - doctype=doctype, - filters=filters, - order_by=order_by, - fields=mapper.get(self.filters.based_on)["fields"], - ), - tuple(self.filters.docnames), - as_dict=1, - ) + if doctype == "Work Order": + query = ( + frappe.qb.from_(parent) + .select( + parent.production_item, + parent.item_name.as_("production_item_name"), + parent.planned_start_date, + parent.stock_uom, + parent.qty.as_("qty_to_manufacture"), + parent.name, + parent.bom_no, + parent.fg_warehouse.as_("warehouse"), + ) + .where(parent.status.notin(["Completed", "Stopped"])) + ) - def prepare_other_conditions(self, filters, doctype): - if self.filters.docnames: - field = "name" if doctype == "Work Order" else "`tab{} Item`.parent".format(doctype) - filters += " and %s in (%s)" % (field, ",".join(["%s"] * len(self.filters.docnames))) + if order_by == "Planned Start Date": + query = query.orderby(parent.planned_start_date, order=Order.asc) - if doctype != "Work Order": - filters += " and `tab{doc}`.name = `tab{doc} Item`.parent".format(doc=doctype) + if self.filters.docnames: + query = query.where(parent.name.isin(self.filters.docnames)) + + else: + child = frappe.qb.DocType(f"{doctype} Item") + query = ( + frappe.qb.from_(parent) + .from_(child) + .select( + child.bom_no, + child.stock_uom, + child.warehouse, + child.parent.as_("name"), + child.item_code.as_("production_item"), + child.stock_qty.as_("qty_to_manufacture"), + child.item_name.as_("production_item_name"), + ) + .where(parent.name == child.parent) + ) + + if self.filters.docnames: + query = query.where(child.parent.isin(self.filters.docnames)) + + if doctype == "Sales Order": + query = query.select( + child.delivery_date, + parent.base_grand_total, + ).where((child.stock_qty > child.produced_qty) & (parent.per_delivered < 100.0)) + + if order_by == "Delivery Date": + query = query.orderby(child.delivery_date, order=Order.asc) + elif order_by == "Total Amount": + query = query.orderby(parent.base_grand_total, order=Order.desc) + + elif doctype == "Material Request": + query = query.select(child.schedule_date,).where( + (parent.per_ordered < 100) & (parent.material_request_type == "Manufacture") + ) + + if order_by == "Required Date": + query = query.orderby(child.schedule_date, order=Order.asc) + + query = query.where(parent.docstatus == 1) if self.filters.company: - filters += " and `tab%s`.company = %s" % (doctype, frappe.db.escape(self.filters.company)) + query = query.where(parent.company == self.filters.company) - return filters + self.orders = query.run(as_dict=True) def get_raw_materials(self): if not self.orders: @@ -134,29 +140,29 @@ class ProductionPlanReport(object): bom_nos.append(bom_no) - bom_doctype = ( + bom_item_doctype = ( "BOM Explosion Item" if self.filters.include_subassembly_raw_materials else "BOM Item" ) - qty_field = ( - "qty_consumed_per_unit" - if self.filters.include_subassembly_raw_materials - else "(bom_item.qty / bom.quantity)" - ) + bom = frappe.qb.DocType("BOM") + bom_item = frappe.qb.DocType(bom_item_doctype) - raw_materials = frappe.db.sql( - """ SELECT bom_item.parent, bom_item.item_code, - bom_item.item_name as raw_material_name, {0} as required_qty_per_unit - FROM - `tabBOM` as bom, `tab{1}` as bom_item - WHERE - bom_item.parent in ({2}) and bom_item.parent = bom.name and bom.docstatus = 1 - """.format( - qty_field, bom_doctype, ",".join(["%s"] * len(bom_nos)) - ), - tuple(bom_nos), - as_dict=1, - ) + if self.filters.include_subassembly_raw_materials: + qty_field = bom_item.qty_consumed_per_unit + else: + qty_field = bom_item.qty / bom.quantity + + raw_materials = ( + frappe.qb.from_(bom) + .from_(bom_item) + .select( + bom_item.parent, + bom_item.item_code, + bom_item.item_name.as_("raw_material_name"), + qty_field.as_("required_qty_per_unit"), + ) + .where((bom_item.parent.isin(bom_nos)) & (bom_item.parent == bom.name) & (bom.docstatus == 1)) + ).run(as_dict=True) if not raw_materials: return From b3897406b08e444c0597e1a27c52fee49e8f2cdc Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Sun, 18 Sep 2022 16:16:38 +0530 Subject: [PATCH 07/27] fix: difference amount calculation on payment reconciliation (cherry picked from commit 122d5f2729b28105272fd9c714d0a0ca3a8125e5) --- .../payment_reconciliation.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 601fc87a227..52efd33fefa 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -14,6 +14,7 @@ from erpnext.accounts.utils import ( QueryPaymentLedger, get_outstanding_invoices, reconcile_against_document, + update_reference_in_payment_entry, ) from erpnext.controllers.accounts_controller import get_advance_payment_entries @@ -212,6 +213,23 @@ class PaymentReconciliation(Document): inv.currency = entry.get("currency") inv.outstanding_amount = flt(entry.get("outstanding_amount")) + def get_difference_amount(self, allocated_entry): + if allocated_entry.get("reference_type") != "Payment Entry": + return + + dr_or_cr = ( + "credit_in_account_currency" + if erpnext.get_party_account_type(self.party_type) == "Receivable" + else "debit_in_account_currency" + ) + + row = self.get_payment_details(allocated_entry, dr_or_cr) + + doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name) + update_reference_in_payment_entry(row, doc, do_not_save=True) + + return doc.difference_amount + @frappe.whitelist() def allocate_entries(self, args): self.validate_entries() @@ -227,12 +245,16 @@ class PaymentReconciliation(Document): res = self.get_allocated_entry(pay, inv, pay["amount"]) inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount")) pay["amount"] = 0 + + res.difference_amount = self.get_difference_amount(res) + if pay.get("amount") == 0: entries.append(res) break elif inv.get("outstanding_amount") == 0: entries.append(res) continue + else: break From 24234d584d55e5a5a25d27ac2e072d353e1c03ff Mon Sep 17 00:00:00 2001 From: nishibakabeer <59765279+nishibakabeer@users.noreply.github.com> Date: Wed, 21 Sep 2022 13:40:03 +0400 Subject: [PATCH 08/27] fix: total value in all keys Gross and net profit report showing wrong values in monthly quarterly and half yearly filters which is the total value @ruthra-kumar added in develop branch as suggested ( https://github.com/frappe/erpnext/pull/32020) (cherry picked from commit 6919f389aa704f21a83d6a4e8cb6e8a754582663) --- .../gross_and_net_profit_report/gross_and_net_profit_report.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py index 9d566785416..cd5f3667071 100644 --- a/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py +++ b/erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py @@ -155,7 +155,6 @@ def adjust_account(data, period_list, consolidated=False): for d in data: for period in period_list: key = period if consolidated else period.key - d[key] = totals[d["account"]] d["total"] = totals[d["account"]] return data From e599b61ac54d9287446287c9c72b1fc436c7731e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 26 Sep 2022 11:37:14 +0530 Subject: [PATCH 09/27] chore: drop 1 runner from stable branch --- .github/workflows/server-tests-mariadb.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml index 64134bc5397..c4ae61091ff 100644 --- a/.github/workflows/server-tests-mariadb.yml +++ b/.github/workflows/server-tests-mariadb.yml @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: - container: [1, 2, 3, 4] + container: [1, 2, 3] name: Python Unit Tests From a403e729d68d919dba762c62faacb96acb8b3eaf Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 22 Sep 2022 15:42:46 -0600 Subject: [PATCH 10/27] fix: typo on opportunity summary by sales stage report (cherry picked from commit c4919cf5ec6ab6c3dd60364e82dd072cf6f4b023) --- .../opportunity_summary_by_sales_stage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js index 116db2f5a27..7cd1710a7f2 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -44,7 +44,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { }, { fieldname: "opportunity_source", - label: __("Oppoturnity Source"), + label: __("Opportunity Source"), fieldtype: "Link", options: "Lead Source", }, @@ -62,4 +62,4 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { default: frappe.defaults.get_user_default("Company") } ] -}; \ No newline at end of file +}; From 2eeff8a00bdac96b1df7e2131ce568ba9d99885f Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 10:13:30 -0600 Subject: [PATCH 11/27] fix: Add strings to translate function (cherry picked from commit 9decebe6e1960687934dc0b99535019c547889c0) --- erpnext/projects/report/project_summary/project_summary.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/projects/report/project_summary/project_summary.py b/erpnext/projects/report/project_summary/project_summary.py index 606c0c2d81d..7a35fd236a0 100644 --- a/erpnext/projects/report/project_summary/project_summary.py +++ b/erpnext/projects/report/project_summary/project_summary.py @@ -91,9 +91,9 @@ def get_chart_data(data): "data": { "labels": labels[:30], "datasets": [ - {"name": "Overdue", "values": overdue[:30]}, - {"name": "Completed", "values": completed[:30]}, - {"name": "Total Tasks", "values": total[:30]}, + {"name": _("Overdue"), "values": overdue[:30]}, + {"name": _("Completed"), "values": completed[:30]}, + {"name": _("Total Tasks"), "values": total[:30]}, ], }, "type": "bar", From e464de3c5dbce204efe3ee60d3a3a12500bdcdf4 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 11:55:13 -0600 Subject: [PATCH 12/27] fix: add translate function to strings (cherry picked from commit aa49ec815a3837ff5777903f929477b836d92472) --- .../report/work_order_summary/work_order_summary.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py index 2368bfdf2c6..41ffcbb1904 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py @@ -83,6 +83,7 @@ def get_chart_based_on_status(data): for d in data: status_wise_data[d.status] += 1 + labels = [_(label) for label in labels] values = [status_wise_data[label] for label in labels] chart = { @@ -95,7 +96,7 @@ def get_chart_based_on_status(data): def get_chart_based_on_age(data): - labels = ["0-30 Days", "30-60 Days", "60-90 Days", "90 Above"] + labels = [_("0-30 Days"), _("30-60 Days"), _("60-90 Days"), _("90 Above")] age_wise_data = {"0-30 Days": 0, "30-60 Days": 0, "60-90 Days": 0, "90 Above": 0} @@ -135,8 +136,8 @@ def get_chart_based_on_qty(data, filters): pending.append(periodic_data.get("Pending").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Pending", "values": pending}) - datasets.append({"name": "Completed", "values": completed}) + datasets.append({"name": _("Pending"), "values": pending}) + datasets.append({"name": _("Completed"), "values": completed}) chart = { "data": {"labels": labels, "datasets": datasets}, From f5a3f8b48649a0cc4bd650c0eee214b4a127fcc9 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Mon, 19 Sep 2022 11:56:42 -0600 Subject: [PATCH 13/27] fix: add translate function to strings (cherry picked from commit 94199b78675e8d20c98bf9d7d95fb3d6d7d2b831) --- .../manufacturing/report/job_card_summary/job_card_summary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py index 5083b7369de..63c2d97d574 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.py +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.py @@ -85,8 +85,8 @@ def get_chart_data(job_card_details, filters): open_job_cards.append(periodic_data.get("Open").get(d)) completed.append(periodic_data.get("Completed").get(d)) - datasets.append({"name": "Open", "values": open_job_cards}) - datasets.append({"name": "Completed", "values": completed}) + datasets.append({"name": _("Open"), "values": open_job_cards}) + datasets.append({"name": _("Completed"), "values": completed}) chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"} From f1d774034cecfeebe9ac92e96391baf3a80710e5 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 09:40:49 +0530 Subject: [PATCH 14/27] refactor: rewrite `Item Price Stock Report` queries in `QB` (cherry picked from commit 22299d2382c912e8bc8dddca3af7d4cf94374339) --- .../item_price_stock/item_price_stock.py | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py index 15218e63a87..1b07f596c7b 100644 --- a/erpnext/stock/report/item_price_stock/item_price_stock.py +++ b/erpnext/stock/report/item_price_stock/item_price_stock.py @@ -62,22 +62,28 @@ def get_data(filters, columns): def get_item_price_qty_data(filters): - conditions = "" - if filters.get("item_code"): - conditions += "where a.item_code=%(item_code)s" + item_price = frappe.qb.DocType("Item Price") + bin = frappe.qb.DocType("Bin") - item_results = frappe.db.sql( - """select a.item_code, a.item_name, a.name as price_list_name, - a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty - from `tabItem Price` a left join `tabBin` b - ON a.item_code = b.item_code - {conditions}""".format( - conditions=conditions - ), - filters, - as_dict=1, + query = ( + frappe.qb.from_(item_price) + .left_join(bin) + .on(item_price.item_code == bin.item_code) + .select( + item_price.item_code, + item_price.item_name, + item_price.name.as_("price_list_name"), + item_price.brand.as_("brand"), + bin.warehouse.as_("warehouse"), + bin.actual_qty.as_("actual_qty"), + ) ) + if filters.get("item_code"): + query = query.where(item_price.item_code == filters.get("item_code")) + + item_results = query.run(as_dict=True) + price_list_names = list(set(item.price_list_name for item in item_results)) buying_price_map = get_price_map(price_list_names, buying=1) From 8c6ddb67219d49e573e7e50a49c310a4f117e2e2 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 26 Sep 2022 09:44:29 +0530 Subject: [PATCH 15/27] refactor: rewrite `Incorrect Stock Value Report` queries in `QB` (cherry picked from commit b93331e844376fb5207acb41b4e7795ea6e1f059) --- .../incorrect_stock_value_report.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py index 23e3c8a97f5..df01b14d11a 100644 --- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py +++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py @@ -4,6 +4,8 @@ import frappe from frappe import _ +from frappe.query_builder import Field +from frappe.query_builder.functions import Min, Timestamp from frappe.utils import add_days, getdate, today import erpnext @@ -28,7 +30,7 @@ def execute(filters=None): def get_unsync_date(filters): date = filters.from_date if not date: - date = frappe.db.sql(""" SELECT min(posting_date) from `tabStock Ledger Entry`""") + date = (frappe.qb.from_("Stock Ledger Entry").select(Min(Field("posting_date")))).run() date = date[0][0] if not date: @@ -54,22 +56,27 @@ def get_data(report_filters): result = [] voucher_wise_dict = {} - data = frappe.db.sql( - """ - SELECT - name, posting_date, posting_time, voucher_type, voucher_no, - stock_value_difference, stock_value, warehouse, item_code - FROM - `tabStock Ledger Entry` - WHERE - posting_date - = %s and company = %s - and is_cancelled = 0 - ORDER BY timestamp(posting_date, posting_time) asc, creation asc - """, - (from_date, report_filters.company), - as_dict=1, - ) + sle = frappe.qb.DocType("Stock Ledger Entry") + data = ( + frappe.qb.from_(sle) + .select( + sle.name, + sle.posting_date, + sle.posting_time, + sle.voucher_type, + sle.voucher_no, + sle.stock_value_difference, + sle.stock_value, + sle.warehouse, + sle.item_code, + ) + .where( + (sle.posting_date == from_date) + & (sle.company == report_filters.company) + & (sle.is_cancelled == 0) + ) + .orderby(Timestamp(sle.posting_date, sle.posting_time), sle.creation) + ).run(as_dict=True) for d in data: voucher_wise_dict.setdefault((d.item_code, d.warehouse), []).append(d) From 931aa620725aa8c8095bca6fafb35fd08229506c Mon Sep 17 00:00:00 2001 From: hendrik Date: Mon, 26 Sep 2022 19:17:24 +0700 Subject: [PATCH 16/27] fix: report chart field type * fix: report chart field type Co-authored-by: Sagar Sharma Co-authored-by: Deepesh Garg (cherry picked from commit f41d1500b0ce024e1321df8de9aab6c4d15be9f1) --- .../buying/report/purchase_order_trends/purchase_order_trends.py | 1 + .../stock/report/delivery_note_trends/delivery_note_trends.py | 1 + .../report/purchase_receipt_trends/purchase_receipt_trends.py | 1 + 3 files changed, 3 insertions(+) diff --git a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py index dbdc62e9ec7..d089473a16a 100644 --- a/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py +++ b/erpnext/buying/report/purchase_order_trends/purchase_order_trends.py @@ -53,4 +53,5 @@ def get_chart_data(data, conditions, filters): }, "type": "line", "lineOptions": {"regionFill": 1}, + "fieldtype": "Currency", } diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py index 7a1b8c0cee9..0ec4e1ce957 100644 --- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py +++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py @@ -45,4 +45,5 @@ def get_chart_data(data, filters): "datasets": [{"name": _("Total Delivered Amount"), "values": datapoints}], }, "type": "bar", + "fieldtype": "Currency", } diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py index fe2d55a3913..b62a6ee6fd8 100644 --- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py +++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py @@ -46,4 +46,5 @@ def get_chart_data(data, filters): }, "type": "bar", "colors": ["#5e64ff"], + "fieldtype": "Currency", } From a83e36b8013f49b74cad41d90d9d0f685bc691e6 Mon Sep 17 00:00:00 2001 From: Dhananjay Palshikar Date: Thu, 1 Sep 2022 19:07:13 +0530 Subject: [PATCH 17/27] Allow Item Templates in Work Order Items Item Variants can be a part of the items tables, however, when BOM items are fetched to be a part of the Work Order items, item variants were being filtered out. The filtering out does not serve a purpose. Having Item variants in BOMs allows for template like behaviour. (cherry picked from commit 75396c02d28c37eeb44d2429e51d3b9f55a39fe9) --- erpnext/manufacturing/doctype/bom/bom.py | 1 - 1 file changed, 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 70637d3ef20..ff84991c36e 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -1019,7 +1019,6 @@ def get_bom_items_as_dict( where bom_item.docstatus < 2 and bom.name = %(bom)s - and ifnull(item.has_variants, 0) = 0 and item.is_stock_item in (1, {is_stock_item}) {where_conditions} group by item_code, stock_uom From 2fd0291afbae4c36c4d1204c616e388340207ed9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 23 Sep 2022 15:21:09 +0530 Subject: [PATCH 18/27] fix: opening entry causing discepancy between stock and trial balance (cherry picked from commit bc3ab45af2be165988b143b62318d865f83ee673) --- erpnext/accounts/report/general_ledger/general_ledger.py | 4 ++-- erpnext/accounts/report/trial_balance/trial_balance.py | 3 ++- .../trial_balance_for_party/trial_balance_for_party.py | 9 +++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index e77e828e166..82f38dacd2a 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -237,9 +237,9 @@ def get_conditions(filters): or filters.get("party") or filters.get("group_by") in ["Group by Account", "Group by Party"] ): - conditions.append("posting_date >=%(from_date)s") + conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')") - conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')") + conditions.append("(posting_date <=%(to_date)s)") if filters.get("project"): conditions.append("project in %(project)s") diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 6bd08ad837a..6d2cd8ed411 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type): query_filters = { "company": filters.company, "from_date": filters.from_date, + "to_date": filters.to_date, "report_type": report_type, "year_start_date": filters.year_start_date, "project": filters.project, @@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type): where company=%(company)s {additional_conditions} - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) and account in (select name from `tabAccount` where report_type=%(report_type)s) and is_cancelled = 0 group by account""".format( diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index 5fcfdff6f1a..ee223484d47 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -104,12 +104,17 @@ def get_opening_balances(filters): where company=%(company)s and is_cancelled=0 and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') + and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) {account_filter} group by party""".format( account_filter=account_filter ), - {"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type}, + { + "company": filters.company, + "from_date": filters.from_date, + "to_date": filters.to_date, + "party_type": filters.party_type, + }, as_dict=True, ) From 305eb02bfcd337e1260acb76e3f2c9571d4e074e Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 27 Sep 2022 13:47:54 +0530 Subject: [PATCH 19/27] fix: allow to return expired batches using purchase return (cherry picked from commit a4a86ee23fb36f448a308c4f3566ba4be05697ab) --- .../purchase_invoice/test_purchase_invoice.py | 31 +++++++++++++++++++ .../purchase_receipt/test_purchase_receipt.py | 31 +++++++++++++++++++ .../stock_ledger_entry/stock_ledger_entry.py | 3 ++ 3 files changed, 65 insertions(+) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 0a4f25b8769..f901257ccf6 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1543,6 +1543,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): pi.save() self.assertEqual(pi.items[0].conversion_factor, 1000) + def test_batch_expiry_for_purchase_invoice(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = self.make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_invoice( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b77c3a51348..62697244bab 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1241,6 +1241,37 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertEqual(query[0].value, 0) + def test_batch_expiry_for_purchase_receipt(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + item = make_item( + "_Test Batch Item For Return Check", + { + "is_purchase_item": 1, + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TBIRC.#####", + }, + ) + + pi = make_purchase_receipt( + qty=1, + item_code=item.name, + update_stock=True, + ) + + pi.load_from_db() + batch_no = pi.items[0].batch_no + self.assertTrue(batch_no) + + frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1)) + + return_pi = make_return_doc(pi.doctype, pi.name) + return_pi.save().submit() + + self.assertTrue(return_pi.docstatus == 1) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py index 329cd7da09b..f7f8cbe4ee0 100644 --- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py +++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py @@ -153,6 +153,9 @@ class StockLedgerEntry(Document): def validate_batch(self): if self.batch_no and self.voucher_type != "Stock Entry": + if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0: + return + expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date") if expiry_date: if getdate(self.posting_date) > getdate(expiry_date): From a28ae4d6534f40cb4b7844c09998e9589e4d4fb5 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 27 Sep 2022 17:07:38 +0530 Subject: [PATCH 20/27] fix: Scrap Asset Accounting Dimensions (backport #31949) (#32376) fix: Scrap Asset Accounting Dimensions (#31949) * fix: Scrap Asset Accounting Dimensions (cherry picked from commit c760ca232365b8e868c8420f877c9c744ec2b4e7) Co-authored-by: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> --- .../doctype/sales_invoice/sales_invoice.py | 4 +- erpnext/assets/doctype/asset/asset.js | 2 +- erpnext/assets/doctype/asset/depreciation.py | 110 ++++++++++++------ .../asset_capitalization.py | 6 +- 4 files changed, 83 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index e51938b27f5..94db9a9adc4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1081,7 +1081,7 @@ class SalesInvoice(SellingController): if self.is_return: fixed_asset_gl_entries = get_gl_entries_on_asset_regain( - asset, item.base_net_amount, item.finance_book + asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", None) @@ -1091,7 +1091,7 @@ class SalesInvoice(SellingController): else: fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, item.base_net_amount, item.finance_book + asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") ) asset.db_set("disposal_date", self.posting_date) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index a43a16c9ec5..5512d4159d8 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -230,7 +230,7 @@ frappe.ui.form.on('Asset', { datasets: [{ color: 'green', values: asset_values, - formatted: asset_values.map(d => d.toFixed(2)) + formatted: asset_values.map(d => d?.toFixed(2)) }] }, type: 'line' diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 74386384c5d..a4bb960d8e1 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -235,7 +235,9 @@ def restore_asset(asset_name): asset.set_status() -def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_regain( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): ( fixed_asset_account, asset, @@ -247,28 +249,45 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): ) = get_asset_details(asset, finance_book) gl_entries = [ - { - "account": fixed_asset_account, - "debit_in_account_currency": asset.gross_purchase_amount, - "debit": asset.gross_purchase_amount, - "cost_center": depreciation_cost_center, - }, - { - "account": accumulated_depr_account, - "credit_in_account_currency": accumulated_depr_amount, - "credit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - }, + asset.get_gl_dict( + { + "account": fixed_asset_account, + "debit_in_account_currency": asset.gross_purchase_amount, + "debit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "credit_in_account_currency": accumulated_depr_amount, + "credit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), ] profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount)) if profit_amount: - get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center + ) + + if voucher_type and voucher_no: + for entry in gl_entries: + entry["voucher_type"] = voucher_type + entry["voucher_no"] = voucher_no return gl_entries -def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_disposal( + asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None +): ( fixed_asset_account, asset, @@ -280,23 +299,38 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) ) = get_asset_details(asset, finance_book) gl_entries = [ - { - "account": fixed_asset_account, - "credit_in_account_currency": asset.gross_purchase_amount, - "credit": asset.gross_purchase_amount, - "cost_center": depreciation_cost_center, - }, - { - "account": accumulated_depr_account, - "debit_in_account_currency": accumulated_depr_amount, - "debit": accumulated_depr_amount, - "cost_center": depreciation_cost_center, - }, + asset.get_gl_dict( + { + "account": fixed_asset_account, + "credit_in_account_currency": asset.gross_purchase_amount, + "credit": asset.gross_purchase_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), + asset.get_gl_dict( + { + "account": accumulated_depr_account, + "debit_in_account_currency": accumulated_depr_amount, + "debit": accumulated_depr_amount, + "cost_center": depreciation_cost_center, + "posting_date": getdate(), + }, + item=asset, + ), ] profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: - get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center) + get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center + ) + + if voucher_type and voucher_no: + for entry in gl_entries: + entry["voucher_type"] = voucher_type + entry["voucher_no"] = voucher_no return gl_entries @@ -333,15 +367,21 @@ def get_asset_details(asset, finance_book=None): ) -def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center): +def get_profit_gl_entries( + asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center +): debit_or_credit = "debit" if profit_amount < 0 else "credit" gl_entries.append( - { - "account": disposal_account, - "cost_center": depreciation_cost_center, - debit_or_credit: abs(profit_amount), - debit_or_credit + "_in_account_currency": abs(profit_amount), - } + asset.get_gl_dict( + { + "account": disposal_account, + "cost_center": depreciation_cost_center, + debit_or_credit: abs(profit_amount), + debit_or_credit + "_in_account_currency": abs(profit_amount), + "posting_date": getdate(), + }, + item=asset, + ) ) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 2e6f0ad7b02..93194c0333a 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -428,7 +428,11 @@ class AssetCapitalization(StockController): asset.reload() fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( - asset, item.asset_value, item.get("finance_book") or self.get("finance_book") + asset, + item.asset_value, + item.get("finance_book") or self.get("finance_book"), + self.get("doctype"), + self.get("name"), ) asset.db_set("disposal_date", self.posting_date) From 4b6e1f489213e4c911d2ee367ae0b4e558c2d93f Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 27 Sep 2022 12:00:50 +0530 Subject: [PATCH 21/27] fix: `For Quantity` error msg in `Stock Entry` (cherry picked from commit 9049db41ae8793cdcb5fb770fe202c096c9ad53c) --- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 738ac330e39..8bcd772d909 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -1073,8 +1073,8 @@ class StockEntry(StockController): # No work order could mean independent Manufacture entry, if so skip validation if self.work_order and self.fg_completed_qty > allowed_qty: frappe.throw( - _("For quantity {0} should not be greater than work order quantity {1}").format( - flt(self.fg_completed_qty), wo_qty + _("For quantity {0} should not be greater than allowed quantity {1}").format( + flt(self.fg_completed_qty), allowed_qty ) ) From 4a29c7fafb2401c7dca6d388f36c394ec9554781 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Tue, 27 Sep 2022 15:30:08 +0530 Subject: [PATCH 22/27] fix: consider overproduction percentage for WO finish button (cherry picked from commit 05392e0918fce4aafffc016f246f21c8afcc5d8a) --- .../doctype/work_order/work_order.js | 65 ++++++++++++------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 20f15039efe..f3640b93b22 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -557,37 +557,52 @@ erpnext.work_order = { if(!frm.doc.skip_transfer){ // If "Material Consumption is check in Manufacturing Settings, allow Material Consumption - if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing)) - && frm.doc.status != 'Stopped') { - frm.has_finish_btn = true; + if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') { + if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { + frm.has_finish_btn = true; - if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { - // Only show "Material Consumption" when required_qty > consumed_qty - var counter = 0; - var tbl = frm.doc.required_items || []; - var tbl_lenght = tbl.length; - for (var i = 0, len = tbl_lenght; i < len; i++) { - let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; - if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { - counter += 1; + if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { + // Only show "Material Consumption" when required_qty > consumed_qty + var counter = 0; + var tbl = frm.doc.required_items || []; + var tbl_lenght = tbl.length; + for (var i = 0, len = tbl_lenght; i < len; i++) { + let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; + if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { + counter += 1; + } + } + if (counter > 0) { + var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { + const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; + erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); + }); + consumption_btn.addClass('btn-primary'); } } - if (counter > 0) { - var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { - const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; - erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); - }); - consumption_btn.addClass('btn-primary'); + + var finish_btn = frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + + if(doc.material_transferred_for_manufacturing>=doc.qty) { + // all materials transferred for manufacturing, make this primary + finish_btn.addClass('btn-primary'); } - } + } else { + frappe.db.get_doc("Manufacturing Settings").then((doc) => { + let allowance_percentage = doc.overproduction_percentage_for_work_order; - var finish_btn = frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); - if(doc.material_transferred_for_manufacturing>=doc.qty) { - // all materials transferred for manufacturing, make this primary - finish_btn.addClass('btn-primary'); + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + } + } + }); } } } else { From 32ad36ad0f5bfe36d76adfbc7d07387edcf1f7c3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 15:37:41 +0530 Subject: [PATCH 23/27] fix: Add return against indexes for POS Invoice (cherry picked from commit 1f6205e1ea569dd25ac298f10812f6e9f44409ef) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.json | 2 +- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index b126d57400a..6f8b3822c2e 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1553,7 +1553,7 @@ "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2022-03-22 13:00:24.166684", + "modified": "2022-09-27 13:00:24.166684", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 6e3a0766f10..a5e4d9a6dd3 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -740,3 +740,7 @@ def add_return_modes(doc, pos_profile): ]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) append_payment(payment_mode[0]) + + +def on_doctype_update(): + frappe.db.add_index("POS Invoice", ["customer", "is_return", "return_against"]) From 3abe4a16af88c0e0e223399a29dbdd3c417d04f3 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 16:22:59 +0530 Subject: [PATCH 24/27] fix: Add return against indexes for POS Invoice (cherry picked from commit cbfe28286acc9319901aeaa2c77fa50e95c0e481) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index a5e4d9a6dd3..9eded8a7770 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -743,4 +743,4 @@ def add_return_modes(doc, pos_profile): def on_doctype_update(): - frappe.db.add_index("POS Invoice", ["customer", "is_return", "return_against"]) + frappe.db.add_index("POS Invoice", ["return_against"]) From b2f280dee60a5b266a0ea0b685716ce5794935e6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 27 Sep 2022 22:12:32 +0530 Subject: [PATCH 25/27] fix: Move subscription process to hourly long quque (cherry picked from commit 82a2f31ada358d896943d04bdb517ac33b9e2d7a) --- erpnext/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a08feb44476..6ef77f3f5ba 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -391,12 +391,12 @@ scheduler_events = { "erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts", ], "hourly": [ - "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization", "erpnext.projects.doctype.project.project.hourly_reminder", "erpnext.projects.doctype.project.project.collect_project_status", ], "hourly_long": [ + "erpnext.accounts.doctype.subscription.subscription.process_all", "erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries", "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction", ], From 29c4a32c335cae5fd2e2de8bc84bd028b5f2c217 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 27 Sep 2022 15:44:38 +0530 Subject: [PATCH 26/27] fix: POS only validate QTY if is_stock_item POS invoice raised " Item not available " validation error even though item is non_stock. (cherry picked from commit e39e088f18177ab4a189da60c18d57b92a6f8aff) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 9eded8a7770..39ddeb0c854 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice): frappe.bold(d.warehouse), frappe.bold(d.qty), ) - if flt(available_stock) <= 0: + if is_stock_item and flt(available_stock) <= 0: frappe.throw( _("Row #{}: Item Code: {} is not available under warehouse {}.").format( d.idx, item_code, warehouse ), title=_("Item Unavailable"), ) - elif flt(available_stock) < flt(d.qty): + elif is_stock_item and flt(available_stock) < flt(d.qty): frappe.throw( _( "Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}." From 6b55a01834ff66ac0ee3ecb3aba17969d14b6d17 Mon Sep 17 00:00:00 2001 From: Maharshi Patel Date: Tue, 27 Sep 2022 23:44:56 +0530 Subject: [PATCH 27/27] fix: POS properly validate stock for bundle products Stock availability was not calculated properly for Product Bundle with non stock item so i have added logic to properly calculate that as well. (cherry picked from commit e392ea1104fee5add5c893c4e092edb6ad21f486) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 9 ++++++--- erpnext/selling/page/point_of_sale/pos_controller.js | 4 ++-- erpnext/selling/page/point_of_sale/pos_item_details.js | 5 +++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 39ddeb0c854..fbe0ef39f83 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -632,11 +632,12 @@ def get_stock_availability(item_code, warehouse): pos_sales_qty = get_pos_reserved_qty(item_code, warehouse) return bin_qty - pos_sales_qty, is_stock_item else: - is_stock_item = False + is_stock_item = True if frappe.db.exists("Product Bundle", item_code): return get_bundle_availability(item_code, warehouse), is_stock_item else: - # Is a service item + is_stock_item = False + # Is a service item or non_stock item return 0, is_stock_item @@ -650,7 +651,9 @@ def get_bundle_availability(bundle_item_code, warehouse): available_qty = item_bin_qty - item_pos_reserved_qty max_available_bundles = available_qty / item.qty - if bundle_bin_qty > max_available_bundles: + if bundle_bin_qty > max_available_bundles and frappe.get_value( + "Item", item.item_code, "is_stock_item" + ): bundle_bin_qty = max_available_bundles pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index da7576e08de..24375d8252d 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -660,7 +660,7 @@ erpnext.PointOfSale.Controller = class { } else { return; } - } else if (available_qty < qty_needed) { + } else if (is_stock_item && available_qty < qty_needed) { frappe.throw({ message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]), indicator: 'orange' @@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class { callback(res) { if (!me.item_stock_map[item_code]) me.item_stock_map[item_code] = {}; - me.item_stock_map[item_code][warehouse] = res.message[0]; + me.item_stock_map[item_code][warehouse] = res.message; } }); } diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js index b75ffb235ed..f9b5bb2e452 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_details.js +++ b/erpnext/selling/page/point_of_sale/pos_item_details.js @@ -242,13 +242,14 @@ erpnext.PointOfSale.ItemDetails = class { if (this.value) { me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => { me.item_stock_map = me.events.get_item_stock_map(); - const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value]; + const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0]; + const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]); if (available_qty === undefined) { me.events.get_available_stock(me.item_row.item_code, this.value).then(() => { // item stock map is updated now reset warehouse me.warehouse_control.set_value(this.value); }) - } else if (available_qty === 0) { + } else if (available_qty === 0 && is_stock_item) { me.warehouse_control.set_value(''); const bold_item_code = me.item_row.item_code.bold(); const bold_warehouse = this.value.bold();
{{ _("Date") }}