From bdb5cc8ad46585cc78deace8e97785b93776c4fb Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 15 Jun 2023 11:39:22 +0530 Subject: [PATCH 01/33] fix: update `Stock Reconciliation` diff qty while reposting (cherry picked from commit 6a1b0a2fab64b4192063a529bfef33e97aeea47e) --- erpnext/stock/stock_ledger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index be2beb1bfb5..9c399168175 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -821,6 +821,7 @@ class update_entries_after(object): item.current_amount = flt(item.current_qty) * flt(item.current_valuation_rate) item.amount = flt(item.qty) * flt(item.valuation_rate) + item.quantity_difference = item.qty - item.current_qty item.amount_difference = item.amount - item.current_amount else: sr.difference_amount = sum([item.amount_difference for item in sr.items]) From 77b0c5f722b1976d333edd9c053c1d216c7604b0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 17:37:12 +0530 Subject: [PATCH 02/33] fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport #35714) (#35715) fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (#35714) fix: on asset scrap, don't add gl entry for acc. depr. if no acc. depr. (cherry picked from commit bb39a2cac7f714fe30e77ced870e15ec69620801) Co-authored-by: Anand Baburajan --- erpnext/assets/doctype/asset/depreciation.py | 24 ++++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 0c8d5f527fd..543c75a195f 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -483,18 +483,22 @@ def get_gl_entries_on_asset_disposal( }, 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": date, - }, - item=asset, - ), ] + if accumulated_depr_amount: + gl_entries.append( + 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": date, + }, + item=asset, + ), + ) + profit_amount = flt(selling_amount) - flt(value_after_depreciation) if profit_amount: get_profit_gl_entries( From 3355dc2a41137a0fd00a9a37c9c00b5691181faa Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jun 2023 14:45:19 +0530 Subject: [PATCH 03/33] fix: incorrect gl entries for standalone debit note with update stock (cherry picked from commit 6e198188ff90ad7291a48f2fbfaac069eaee8381) # Conflicts: # erpnext/controllers/buying_controller.py --- erpnext/controllers/buying_controller.py | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index fa566f62ef0..7645266ebb5 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -30,6 +30,8 @@ class BuyingController(SubcontractingController): return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) def validate(self): + self.set_rate_for_standalone_debit_note() + super(BuyingController, self).validate() if getattr(self, "supplier", None) and not self.supplier_name: self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name") @@ -72,6 +74,61 @@ class BuyingController(SubcontractingController): ), ) +<<<<<<< HEAD +======= + def create_package_for_transfer(self) -> None: + """Create serial and batch package for Sourece Warehouse in case of inter transfer.""" + + if self.is_internal_transfer() and ( + self.doctype == "Purchase Receipt" or (self.doctype == "Purchase Invoice" and self.update_stock) + ): + field = "delivery_note_item" if self.doctype == "Purchase Receipt" else "sales_invoice_item" + + doctype = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item" + + ids = [d.get(field) for d in self.get("items") if d.get(field)] + bundle_ids = {} + if ids: + for bundle in frappe.get_all( + doctype, filters={"name": ("in", ids)}, fields=["serial_and_batch_bundle", "name"] + ): + bundle_ids[bundle.name] = bundle.serial_and_batch_bundle + + if not bundle_ids: + return + + for item in self.get("items"): + if item.get(field) and not item.serial_and_batch_bundle and bundle_ids.get(item.get(field)): + item.serial_and_batch_bundle = self.make_package_for_transfer( + bundle_ids.get(item.get(field)), + item.from_warehouse, + type_of_transaction="Outward", + do_not_submit=True, + ) + + def set_rate_for_standalone_debit_note(self): + if self.get("is_return") and self.get("update_stock") and not self.return_against: + for row in self.items: + row.rate = get_incoming_rate( + { + "item_code": row.item_code, + "warehouse": row.warehouse, + "posting_date": self.get("posting_date"), + "posting_time": self.get("posting_time"), + "qty": row.qty, + "serial_and_batch_bundle": row.get("serial_and_batch_bundle"), + "company": self.company, + "voucher_type": self.doctype, + "voucher_no": self.name, + }, + raise_error_if_no_rate=False, + ) + + row.discount_percentage = 0.0 + row.discount_amount = 0.0 + row.margin_rate_or_amount = 0.0 + +>>>>>>> 6e198188ff (fix: incorrect gl entries for standalone debit note with update stock) def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) From e2c4e16d7202011d8d88bd9b212820a7e9ecc6d0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 15 Jun 2023 15:01:15 +0530 Subject: [PATCH 04/33] test: added test case (cherry picked from commit f9f662679fc7ebcbfb96198b931c449cecab6ec2) --- .../purchase_invoice/test_purchase_invoice.py | 22 +++++++++++++------ erpnext/controllers/buying_controller.py | 2 ++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index a6d7df6971f..e8766275f0b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -637,13 +637,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): gle_filters={"account": "Stock In Hand - TCP1"}, ) - # assert loss booked in COGS - self.assertGLEs( - return_pi, - [{"credit": 0, "debit": 200}], - gle_filters={"account": "Cost of Goods Sold - TCP1"}, - ) - def test_return_with_lcv(self): from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import ( @@ -1662,6 +1655,21 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin): self.assertTrue(return_pi.docstatus == 1) + def test_gl_entries_for_standalone_debit_note(self): + make_purchase_invoice(qty=5, rate=500, update_stock=True) + + returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True) + + # override the rate with valuation rate + sle = frappe.get_all( + "Stock Ledger Entry", + fields=["stock_value_difference", "actual_qty"], + filters={"voucher_no": returned_inv.name}, + )[0] + + rate = flt(sle.stock_value_difference) / flt(sle.actual_qty) + self.assertAlmostEqual(returned_inv.items[0].rate, rate) + def check_gl_entries(doc, voucher_no, expected_gle, posting_date): gl_entries = frappe.db.sql( diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 7645266ebb5..6ca7533b084 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -109,6 +109,8 @@ class BuyingController(SubcontractingController): def set_rate_for_standalone_debit_note(self): if self.get("is_return") and self.get("update_stock") and not self.return_against: for row in self.items: + + # override the rate with valuation rate row.rate = get_incoming_rate( { "item_code": row.item_code, From 697fcef98bbbf5b2980cbfc817bceac34c99ff18 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Thu, 15 Jun 2023 19:14:41 +0530 Subject: [PATCH 05/33] fix: conflicts --- erpnext/controllers/buying_controller.py | 33 ------------------------ 1 file changed, 33 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 6ca7533b084..677e510d169 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -74,38 +74,6 @@ class BuyingController(SubcontractingController): ), ) -<<<<<<< HEAD -======= - def create_package_for_transfer(self) -> None: - """Create serial and batch package for Sourece Warehouse in case of inter transfer.""" - - if self.is_internal_transfer() and ( - self.doctype == "Purchase Receipt" or (self.doctype == "Purchase Invoice" and self.update_stock) - ): - field = "delivery_note_item" if self.doctype == "Purchase Receipt" else "sales_invoice_item" - - doctype = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item" - - ids = [d.get(field) for d in self.get("items") if d.get(field)] - bundle_ids = {} - if ids: - for bundle in frappe.get_all( - doctype, filters={"name": ("in", ids)}, fields=["serial_and_batch_bundle", "name"] - ): - bundle_ids[bundle.name] = bundle.serial_and_batch_bundle - - if not bundle_ids: - return - - for item in self.get("items"): - if item.get(field) and not item.serial_and_batch_bundle and bundle_ids.get(item.get(field)): - item.serial_and_batch_bundle = self.make_package_for_transfer( - bundle_ids.get(item.get(field)), - item.from_warehouse, - type_of_transaction="Outward", - do_not_submit=True, - ) - def set_rate_for_standalone_debit_note(self): if self.get("is_return") and self.get("update_stock") and not self.return_against: for row in self.items: @@ -130,7 +98,6 @@ class BuyingController(SubcontractingController): row.discount_amount = 0.0 row.margin_rate_or_amount = 0.0 ->>>>>>> 6e198188ff (fix: incorrect gl entries for standalone debit note with update stock) def set_missing_values(self, for_validate=False): super(BuyingController, self).set_missing_values(for_validate) From 3f62e854e58346b86bf510a60712ae1a364a3e9c Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Thu, 15 Jun 2023 19:26:38 +0530 Subject: [PATCH 06/33] fix: consider field precision while setting sle actual_qty (#35717) --- erpnext/controllers/buying_controller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index fa566f62ef0..25f32147f35 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -445,7 +445,7 @@ class BuyingController(SubcontractingController): continue if d.warehouse: - pr_qty = flt(d.qty) * flt(d.conversion_factor) + pr_qty = flt(flt(d.qty) * flt(d.conversion_factor), d.precision("stock_qty")) if pr_qty: @@ -507,7 +507,7 @@ class BuyingController(SubcontractingController): d, { "warehouse": d.rejected_warehouse, - "actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor), + "actual_qty": flt(flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")), "serial_no": cstr(d.rejected_serial_no).strip(), "incoming_rate": 0.0, }, From 55a8be5cad3932dec839e4da131b4a86550536ee Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 19:42:53 +0530 Subject: [PATCH 07/33] fix: `Process Loss Report` (backport #35712) (#35719) fix: `Process Loss Report` (cherry picked from commit d176d86e2c9a96ec0f7d884b89a170b7dc8e020e) Co-authored-by: s-aga-r --- .../process_loss_report.py | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py index ce8f4f35a3f..c3dd9cf9b1a 100644 --- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py +++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py @@ -33,10 +33,9 @@ def get_data(filters: Filters) -> Data: wo.name, wo.status, wo.production_item, - wo.qty, wo.produced_qty, wo.process_loss_qty, - (wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"), + wo.qty.as_("qty_to_manufacture"), Sum(se.total_incoming_value).as_("total_fg_value"), Sum(se.total_outgoing_value).as_("total_rm_value"), ) @@ -44,6 +43,7 @@ def get_data(filters: Filters) -> Data: (wo.process_loss_qty > 0) & (wo.company == filters.company) & (se.docstatus == 1) + & (se.purpose == "Manufacture") & (se.posting_date.between(filters.from_date, filters.to_date)) ) .groupby(se.work_order) @@ -79,20 +79,30 @@ def get_columns() -> Columns: "width": "100", }, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"}, + { + "label": _("Qty To Manufacture"), + "fieldname": "qty_to_manufacture", + "fieldtype": "Float", + "width": "150", + }, { "label": _("Manufactured Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": "150", }, - {"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"}, { - "label": _("Actual Manufactured Qty"), - "fieldname": "actual_produced_qty", + "label": _("Process Loss Qty"), + "fieldname": "process_loss_qty", + "fieldtype": "Float", + "width": "150", + }, + { + "label": _("Process Loss Value"), + "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150", }, - {"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"}, {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"}, { "label": _("Raw Material Value"), @@ -105,5 +115,5 @@ def get_columns() -> Columns: def update_data_with_total_pl_value(data: Data) -> None: for row in data: - value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"] + value_per_unit_fg = row["total_fg_value"] / row["qty_to_manufacture"] row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg From 0e57f4dd3ce44504a059be1a4d5f8f869ffe34b9 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:22:44 +0530 Subject: [PATCH 08/33] perf: Index sales_order_item in Pick list item (backport #35735) (#35736) * perf: Index sales_order_item in Pick list item (#35735) - `get_picked_items_qty` does full table scan - because it also locks, it does full table lock. (cherry picked from commit 07d748c290453696590c723c58ba90736fe4423d) # Conflicts: # erpnext/stock/doctype/pick_list_item/pick_list_item.json * chore: conflicts --------- Co-authored-by: Ankush Menat --- erpnext/stock/doctype/pick_list_item/pick_list_item.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json index a6f8c0db458..b887f795640 100644 --- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -149,7 +149,8 @@ "fieldtype": "Data", "hidden": 1, "label": "Sales Order Item", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "serial_no_and_batch_section", @@ -191,7 +192,7 @@ ], "istable": 1, "links": [], - "modified": "2022-04-22 05:27:38.497997", + "modified": "2023-06-16 14:05:51.719959", "modified_by": "Administrator", "module": "Stock", "name": "Pick List Item", From 01ac54d65da057257734dd0ab399e8ffb5659c35 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:29:20 +0530 Subject: [PATCH 09/33] perf: Ignore cancelled pick lists while fetching picked items (backport #35737) (#35740) perf: Ignore cancelled pick lists while fetching picked items (#35737) (cherry picked from commit 81f916b7d38ea9d78a398ccd2e70a0651973d594) Co-authored-by: Ankush Menat --- erpnext/stock/doctype/pick_list/pick_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index d3af6205083..0e440a1f586 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -360,6 +360,7 @@ class PickList(Document): (pi_item.item_code.isin([x.item_code for x in items])) & ((pi_item.picked_qty > 0) | (pi_item.stock_qty > 0)) & (pi.status != "Completed") + & (pi.status != "Cancelled") & (pi_item.docstatus != 2) ) .groupby( From b261242792e5cf88384fbe03890e4afb4b8e1736 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 16 Jun 2023 16:28:49 +0530 Subject: [PATCH 10/33] fix(ux): set route options for new `Batch` --- erpnext/public/js/controllers/transaction.js | 9 +++++++++ erpnext/stock/doctype/stock_entry/stock_entry.js | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 83630df3a0b..26dd13c3e3f 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -123,6 +123,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) { return me.set_query_for_batch(doc, cdt, cdn); }); + + let batch_field = this.frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } } if( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index bc5ac2addb0..dd08ef4d1e3 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -101,6 +101,14 @@ frappe.ui.form.on('Stock Entry', { } }); + let batch_field = frm.get_docfield('items', 'batch_no'); + if (batch_field) { + batch_field.get_route_options_for_new_doc = (row) => { + return { + 'item': row.doc.item_code + } + }; + } frm.add_fetch("bom_no", "inspection_required", "inspection_required"); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); From 632b67cbc84fc4f478fc4a4e4b363134dcc7b500 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:40:40 +0530 Subject: [PATCH 11/33] perf: Duplicate queries for UOM (backport #35744) (#35745) perf: Duplicate queries for UOM (#35744) This query repeats for every item, UOMs rarely if ever change (cherry picked from commit 29da1db516b3e07c236b47a60b11de00c8ed9349) Co-authored-by: Ankush Menat --- erpnext/stock/doctype/pick_list/pick_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 0e440a1f586..99246774aa1 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -36,7 +36,7 @@ class PickList(Document): for location in self.get("locations"): if ( location.sales_order - and frappe.db.get_value("Sales Order", location.sales_order, "per_picked") == 100 + and frappe.db.get_value("Sales Order", location.sales_order, "per_picked", cache=True) == 100 ): frappe.throw( _("Row #{}: item {} has been picked already.").format(location.idx, location.item_code) @@ -474,7 +474,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus) ) qty = stock_qty / (item_doc.conversion_factor or 1) - uom_must_be_whole_number = frappe.db.get_value("UOM", item_doc.uom, "must_be_whole_number") + uom_must_be_whole_number = frappe.get_cached_value("UOM", item_doc.uom, "must_be_whole_number") if uom_must_be_whole_number: qty = floor(qty) stock_qty = qty * item_doc.conversion_factor From a0fc8e252c6f011a5376fa74d8a35203f3dc5cc0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 16 Jun 2023 18:45:23 +0530 Subject: [PATCH 12/33] perf: duplicate queries while checking prevdoc (backport #35746) (#35749) perf: duplicate queries while checking prevdoc (#35746) These values can't change durning DB transaction AFAIK (cherry picked from commit 6086d1a99d2ff266eeb75aa0288a568abd00ec81) Co-authored-by: Ankush Menat --- .../accounts/doctype/sales_invoice/sales_invoice.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 0dcd2e291a6..15774331272 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1012,10 +1012,16 @@ class SalesInvoice(SellingController): def check_prev_docstatus(self): for d in self.get("items"): - if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1: + if ( + d.sales_order + and frappe.db.get_value("Sales Order", d.sales_order, "docstatus", cache=True) != 1 + ): frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order)) - if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: + if ( + d.delivery_note + and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus", cache=True) != 1 + ): throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) def make_gl_entries(self, gl_entries=None, from_repost=False): From 53ec2a9268ca41fd44f58859df02c1f0876ed757 Mon Sep 17 00:00:00 2001 From: Anoop Kurungadam Date: Sat, 17 Jun 2023 11:21:03 +0530 Subject: [PATCH 13/33] fix: cannot start / stop jobs --- erpnext/manufacturing/doctype/job_card/job_card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 496cbfd0a6b..2c17568d1b4 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -161,7 +161,7 @@ class JobCard(Document): self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty")) for row in self.sub_operations: - self.c += row.completed_qty + self.total_completed_qty += row.completed_qty def get_overlap_for(self, args, check_next_available_slot=False): production_capacity = 1 From c11d950fc5ebc6aee4bec2915a8cf78df83d3dc4 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 17 Jun 2023 11:33:17 +0530 Subject: [PATCH 14/33] fix: incorrect stock value for purchase returned with rejected qty (backport #35747) (#35752) fix: incorrect stock value for purchase returned with rejected qty (cherry picked from commit 28dd758aa3e7f2cd68f4658d1c4ca6f591c6df07) Co-authored-by: Rohit Waghchaure --- .../controllers/sales_and_purchase_return.py | 3 ++ .../purchase_receipt/test_purchase_receipt.py | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 15c270e58ad..401c6b03383 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -617,6 +617,9 @@ def get_filters( if reference_voucher_detail_no: filters["voucher_detail_no"] = reference_voucher_detail_no + if item_row and item_row.get("warehouse"): + filters["warehouse"] = item_row.get("warehouse") + return filters diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b5740ca575b..7965864cd43 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -1751,6 +1751,52 @@ class TestPurchaseReceipt(FrappeTestCase): pr.items[0].delivery_note_item = delivery_note_item pr.save() + def test_purchase_return_valuation_with_rejected_qty(self): + item_code = "_Test Item Return Valuation" + create_item(item_code) + + warehouse = create_warehouse("_Test Warehouse Return Valuation") + rejected_warehouse = create_warehouse("_Test Rejected Warehouse Return Valuation") + + # Step 1: Create Purchase Receipt with valuation rate 100 + make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=10, + rate=100, + rejected_qty=2, + rejected_warehouse=rejected_warehouse, + ) + + # Step 2: Create One more Purchase Receipt with valuation rate 200 + pr = make_purchase_receipt( + item_code=item_code, + warehouse=warehouse, + qty=10, + rate=200, + rejected_qty=2, + rejected_warehouse=rejected_warehouse, + ) + + # Step 3: Create Purchase Return for 2 qty + from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return + + pr_return = make_purchase_return(pr.name) + pr_return.items[0].qty = 2 * -1 + pr_return.items[0].received_qty = 2 * -1 + pr_return.items[0].rejected_qty = 0 + pr_return.items[0].rejected_warehouse = "" + pr_return.save() + pr_return.submit() + + data = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_no": pr_return.name, "docstatus": 1}, + fields=["SUM(stock_value_difference) as stock_value_difference"], + )[0] + + self.assertEqual(abs(data["stock_value_difference"]), 400.00) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier From 59ab13c34f29ec3ccf1f55fbaf9b8486b79e4419 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 17 Jun 2023 12:52:18 +0530 Subject: [PATCH 15/33] fix: add validation for QI in PR (backport #35677) (#35757) fix: add validation for QI in PR (cherry picked from commit 2c1ab569a784d482779f29dc2f7085d78e2a5403) Co-authored-by: s-aga-r --- .../purchase_receipt/purchase_receipt.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 3373d8ac8c5..c793529e843 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -124,6 +124,7 @@ class PurchaseReceipt(BuyingController): self.set_status() self.po_required() + self.validate_items_quality_inspection() self.validate_with_previous_doc() self.validate_uom_is_integer("uom", ["qty", "received_qty"]) self.validate_uom_is_integer("stock_uom", "stock_qty") @@ -197,6 +198,26 @@ class PurchaseReceipt(BuyingController): if not d.purchase_order: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) + def validate_items_quality_inspection(self): + for item in self.get("items"): + if item.quality_inspection: + qi = frappe.db.get_value( + "Quality Inspection", + item.quality_inspection, + ["reference_type", "reference_name", "item_code"], + as_dict=True, + ) + + if qi.reference_type != self.doctype or qi.reference_name != self.name: + msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Reference Type + {frappe.bold(self.doctype)} and Reference Name {frappe.bold(self.name)}.""" + frappe.throw(_(msg)) + + if qi.item_code != item.item_code: + msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Item Code + {frappe.bold(item.item_code)}.""" + frappe.throw(_(msg)) + def get_already_received_qty(self, po, po_detail): qty = frappe.db.sql( """select sum(qty) from `tabPurchase Receipt Item` From ce2bf5fb1c55d3bb4b566e50b9b12f3d89676ccd Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 17 Jun 2023 12:14:32 +0530 Subject: [PATCH 16/33] fix: validation of job card in stock entry (cherry picked from commit df8c3f0888e06c28da0937190e97c3589d67c44d) --- 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 3f5457c633e..ae9adc826c6 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -268,10 +268,10 @@ class StockEntry(StockController): return for row in self.items: - if row.job_card_item: + if row.job_card_item or not row.s_warehouse: continue - msg = f"""Row #{0}: The job card item reference + msg = f"""Row #{row.idx}: The job card item reference is missing. Kindly create the stock entry from the job card. If you have added the row manually then you won't be able to add job card item reference.""" From b875de6fb7998bbd7f089343b7da4dd358ac0ced Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 17 Jun 2023 21:25:30 +0530 Subject: [PATCH 17/33] perf: Index pick list field in stock entry and DN (backport #35738) (#35742) * perf: Index pick list field in stock entry and DN (#35738) We check if pick list is created against them but there's no index so we end up reading entire table. ``` +------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | r_rows | filtered | r_filtered | Extra | +------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+ | 1 | SIMPLE | tabDelivery Note | index | NULL | modified | 9 | NULL | 207015 | 348940.00 | 100.00 | 0.00 | Using where | +------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+ ``` After ``` +------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+-------------------------------> | id | select_type | table | type | possible_keys | key | key_len | ref | rows | r_rows | filtered | r_filtered | Extra > +------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+-------------------------------> | 1 | SIMPLE | tabDelivery Note | ref | pick_list_index | pick_list_index | 563 | const | 1 | 0.00 | 100.00 | 100.00 | Using index condition; Using w> +------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+-------------------------------> ``` (cherry picked from commit 433489a9e6822b127b2a62ea2bca0da872c1ab1b) # Conflicts: # erpnext/stock/doctype/delivery_note/delivery_note.json * chore: conflict --------- Co-authored-by: Ankush Menat --- erpnext/stock/doctype/delivery_note/delivery_note.json | 5 +++-- erpnext/stock/doctype/stock_entry/stock_entry.json | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 2adf9c310f9..ff0e1b66bf8 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1222,7 +1222,8 @@ "hidden": 1, "label": "Pick List", "options": "Pick List", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "default": "0", @@ -1398,7 +1399,7 @@ "idx": 146, "is_submittable": 1, "links": [], - "modified": "2023-04-21 11:15:23.931084", + "modified": "2023-06-16 14:58:55.066602", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index 9bf679b8955..fe42b1f135c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -577,7 +577,8 @@ "fieldtype": "Link", "label": "Pick List", "options": "Pick List", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "print_settings_col_break", @@ -677,7 +678,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-09 15:46:28.418339", + "modified": "2023-06-16 14:59:10.917235", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", @@ -738,7 +739,6 @@ "read": 1, "report": 1, "role": "Stock Manager", - "set_user_permissions": 1, "share": 1, "submit": 1, "write": 1 From 3c790c12f2f91ed5a6b1c95319af582aaa13008b Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Sat, 17 Jun 2023 23:21:30 +0530 Subject: [PATCH 18/33] fix(patch): enable existing serial no in stock settings (#35762) --- erpnext/patches.txt | 1 + erpnext/patches/v14_0/enable_allow_existing_serial_no.py | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 erpnext/patches/v14_0/enable_allow_existing_serial_no.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index a2c5ca9b893..205047602a5 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -333,3 +333,4 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger erpnext.patches.v14_0.update_company_in_ldc erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes erpnext.patches.v14_0.cleanup_workspaces +erpnext.patches.v14_0.enable_allow_existing_serial_no diff --git a/erpnext/patches/v14_0/enable_allow_existing_serial_no.py b/erpnext/patches/v14_0/enable_allow_existing_serial_no.py new file mode 100644 index 00000000000..4210e9c9353 --- /dev/null +++ b/erpnext/patches/v14_0/enable_allow_existing_serial_no.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + + +def execute(): + frappe.reload_doc("stock", "doctype", frappe.scrub("Stock Settings")) + frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1, update_modified=False) From 58a6bbcf6d95f59821484ff29b585c10529a0fe4 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Sun, 18 Jun 2023 23:00:01 +0530 Subject: [PATCH 19/33] fix: unsupported operand type(s) for //: 'float' and 'NoneType' for POS Barcode search (#35710) --- erpnext/selling/page/point_of_sale/point_of_sale.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py index 6ccf45f476c..6744a5d49e7 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.py +++ b/erpnext/selling/page/point_of_sale/point_of_sale.py @@ -49,7 +49,6 @@ def search_by_term(search_term, warehouse, price_list): ) item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse) - item_stock_qty = item_stock_qty // item.get("conversion_factor") item_stock_qty = item_stock_qty // item.get("conversion_factor", 1) item.update({"actual_qty": item_stock_qty}) @@ -59,7 +58,7 @@ def search_by_term(search_term, warehouse, price_list): "price_list": price_list, "item_code": item_code, }, - fields=["uom", "stock_uom", "currency", "price_list_rate"], + fields=["uom", "currency", "price_list_rate"], ) def __sort(p): From 070df97663f763660e3f4a1b3d251f14a35f65a6 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 09:14:21 +0530 Subject: [PATCH 20/33] fix: loan interest accrual date (#35695) fix: loan interest accrual date (#35695) fix: loan interest accrual date --------- Co-authored-by: Abhinav Raut Co-authored-by: Deepesh Garg (cherry picked from commit 2a24423ad2cb6733359fc2b45f39e676e6f9ec24) Co-authored-by: Abhinav Raut --- .../doctype/loan_interest_accrual/loan_interest_accrual.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py index 9d31ee195fc..e72d694b0f1 100644 --- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py +++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py @@ -290,8 +290,8 @@ def get_last_accrual_date(loan, posting_date): # interest for last interest accrual date is already booked, so add 1 day last_disbursement_date = get_last_disbursement_date(loan, posting_date) - if last_disbursement_date and getdate(last_disbursement_date) > getdate( - last_interest_accrual_date + if last_disbursement_date and getdate(last_disbursement_date) > add_days( + getdate(last_interest_accrual_date), 1 ): last_interest_accrual_date = last_disbursement_date From 5541d68477d5d8c71ce19ff46343c285bb408455 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:52:06 +0530 Subject: [PATCH 21/33] fix: Allocated amount validation for other party types (#35741) fix: Allocated amount validation for other party types (#35741) * fix: Allocated amount validation for other party types * chore: Validation for return allocations * chore: minor typo --------- Co-authored-by: anandbaburajan (cherry picked from commit 9d27a25e5f2335386402f96f83a10729695b20c8) Co-authored-by: Deepesh Garg --- .../doctype/payment_entry/payment_entry.py | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 118d5636769..63a1370de81 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -151,6 +151,19 @@ class PaymentEntry(AccountsController): if self.payment_type == "Internal Transfer": return + if self.party_type in ("Customer", "Supplier"): + self.validate_allocated_amount_with_latest_data() + else: + fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") + for d in self.get("references"): + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) + + # Check for negative outstanding invoices as well + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) + + def validate_allocated_amount_with_latest_data(self): latest_references = get_outstanding_reference_documents( { "posting_date": self.posting_date, @@ -168,7 +181,7 @@ class PaymentEntry(AccountsController): d = frappe._dict(d) latest_lookup.update({(d.voucher_type, d.voucher_no): d}) - for d in self.get("references").copy(): + for d in self.get("references"): latest = latest_lookup.get((d.reference_doctype, d.reference_name)) # The reference has already been fully paid @@ -187,18 +200,14 @@ class PaymentEntry(AccountsController): ).format(d.reference_doctype, d.reference_name) ) - d.outstanding_amount = latest.outstanding_amount - fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") - if (flt(d.allocated_amount)) > 0: - if flt(d.allocated_amount) > flt(d.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) # Check for negative outstanding invoices as well - if flt(d.allocated_amount) < 0: - if flt(d.allocated_amount) < flt(d.outstanding_amount): - frappe.throw(fail_message.format(d.idx)) + if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): + frappe.throw(fail_message.format(d.idx)) def delink_advance_entry_references(self): for reference in self.references: From 42e25d4cdf3e3ed1ac61427ceeaf009523d1c1ab Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 20:17:56 +0530 Subject: [PATCH 22/33] fix: fix get outstanding invoices btn and add get outstanding orders btn (backport #35776) (#35787) fix: fix get outstanding invoices btn and add get outstanding orders btn (#35776) * fix: fix get outstanding invoices btn and add get outstanding orders btn * chore: remove unnecessary arg (cherry picked from commit c1da3ddbbf5e57a0f96733ef05682056051f9ebd) Co-authored-by: Anand Baburajan --- .../doctype/payment_entry/payment_entry.js | 32 +++++- .../doctype/payment_entry/payment_entry.json | 23 ++-- .../doctype/payment_entry/payment_entry.py | 103 ++++++++++-------- 3 files changed, 102 insertions(+), 56 deletions(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index cc72c313238..ace34e052cd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -613,7 +613,7 @@ frappe.ui.form.on('Payment Entry', { frm.events.set_unallocated_amount(frm); }, - get_outstanding_invoice: function(frm) { + get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) { const today = frappe.datetime.get_today(); const fields = [ {fieldtype:"Section Break", label: __("Posting Date")}, @@ -643,12 +643,29 @@ frappe.ui.form.on('Payment Entry', { {fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1}, ]; + let btn_text = ""; + + if (get_outstanding_invoices) { + btn_text = "Get Outstanding Invoices"; + } + else if (get_orders_to_be_billed) { + btn_text = "Get Outstanding Orders"; + } + frappe.prompt(fields, function(filters){ frappe.flags.allocate_payment_amount = true; frm.events.validate_filters_data(frm, filters); frm.doc.cost_center = filters.cost_center; - frm.events.get_outstanding_documents(frm, filters); - }, __("Filters"), __("Get Outstanding Documents")); + frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed); + }, __("Filters"), __(btn_text)); + }, + + get_outstanding_invoices: function(frm) { + frm.events.get_outstanding_invoices_or_orders(frm, true, false); + }, + + get_outstanding_orders: function(frm) { + frm.events.get_outstanding_invoices_or_orders(frm, false, true); }, validate_filters_data: function(frm, filters) { @@ -674,7 +691,7 @@ frappe.ui.form.on('Payment Entry', { } }, - get_outstanding_documents: function(frm, filters) { + get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) { frm.clear_table("references"); if(!frm.doc.party) { @@ -698,6 +715,13 @@ frappe.ui.form.on('Payment Entry', { args[key] = filters[key]; } + if (get_outstanding_invoices) { + args["get_outstanding_invoices"] = true; + } + else if (get_orders_to_be_billed) { + args["get_orders_to_be_billed"] = true; + } + frappe.flags.allocate_payment_amount = filters['allocate_payment_amount']; return frappe.call({ diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 3927ecae43d..6224d4038d6 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -48,7 +48,8 @@ "base_received_amount", "base_received_amount_after_tax", "section_break_14", - "get_outstanding_invoice", + "get_outstanding_invoices", + "get_outstanding_orders", "references", "section_break_34", "total_allocated_amount", @@ -355,12 +356,6 @@ "fieldtype": "Section Break", "label": "Reference" }, - { - "depends_on": "eval:doc.docstatus==0", - "fieldname": "get_outstanding_invoice", - "fieldtype": "Button", - "label": "Get Outstanding Invoice" - }, { "fieldname": "references", "fieldtype": "Table", @@ -728,12 +723,24 @@ "fieldname": "section_break_60", "fieldtype": "Section Break", "hide_border": 1 + }, + { + "depends_on": "eval:doc.docstatus==0", + "fieldname": "get_outstanding_invoices", + "fieldtype": "Button", + "label": "Get Outstanding Invoices" + }, + { + "depends_on": "eval:doc.docstatus==0", + "fieldname": "get_outstanding_orders", + "fieldtype": "Button", + "label": "Get Outstanding Orders" } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-02-14 04:52:30.478523", + "modified": "2023-06-19 11:38:04.387219", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 63a1370de81..6e6505fd6dd 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -172,6 +172,8 @@ class PaymentEntry(AccountsController): "payment_type": self.payment_type, "party": self.party, "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, + "get_outstanding_invoices": True, + "get_orders_to_be_billed": True, } ) @@ -196,7 +198,7 @@ class PaymentEntry(AccountsController): ): frappe.throw( _( - "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount." + "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." ).format(d.reference_doctype, d.reference_name) ) @@ -1356,62 +1358,75 @@ def get_outstanding_reference_documents(args): condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) common_filter.append(ple.company == args.get("company")) - outstanding_invoices = get_outstanding_invoices( - args.get("party_type"), - args.get("party"), - args.get("party_account"), - common_filter=common_filter, - posting_date=posting_and_due_date, - min_outstanding=args.get("outstanding_amt_greater_than"), - max_outstanding=args.get("outstanding_amt_less_than"), - accounting_dimensions=accounting_dimensions_filter, - ) - - outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) - - for d in outstanding_invoices: - d["exchange_rate"] = 1 - if party_account_currency != company_currency: - if d.voucher_type in frappe.get_hooks("invoice_doctypes"): - d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") - elif d.voucher_type == "Journal Entry": - d["exchange_rate"] = get_exchange_rate( - party_account_currency, company_currency, d.posting_date - ) - if d.voucher_type in ("Purchase Invoice"): - d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") - - # Get all SO / PO which are not fully billed or against which full advance not paid - orders_to_be_billed = [] - orders_to_be_billed = get_orders_to_be_billed( - args.get("posting_date"), - args.get("party_type"), - args.get("party"), - args.get("company"), - party_account_currency, - company_currency, - filters=args, - ) - - # Get negative outstanding sales /purchase invoices + outstanding_invoices = [] negative_outstanding_invoices = [] - if args.get("party_type") != "Employee" and not args.get("voucher_no"): - negative_outstanding_invoices = get_negative_outstanding_invoices( + + if args.get("get_outstanding_invoices"): + outstanding_invoices = get_outstanding_invoices( args.get("party_type"), args.get("party"), args.get("party_account"), + common_filter=common_filter, + posting_date=posting_and_due_date, + min_outstanding=args.get("outstanding_amt_greater_than"), + max_outstanding=args.get("outstanding_amt_less_than"), + accounting_dimensions=accounting_dimensions_filter, + ) + + outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) + + for d in outstanding_invoices: + d["exchange_rate"] = 1 + if party_account_currency != company_currency: + if d.voucher_type in frappe.get_hooks("invoice_doctypes"): + d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate") + elif d.voucher_type == "Journal Entry": + d["exchange_rate"] = get_exchange_rate( + party_account_currency, company_currency, d.posting_date + ) + if d.voucher_type in ("Purchase Invoice"): + d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") + + # Get negative outstanding sales /purchase invoices + if args.get("party_type") != "Employee" and not args.get("voucher_no"): + negative_outstanding_invoices = get_negative_outstanding_invoices( + args.get("party_type"), + args.get("party"), + args.get("party_account"), + party_account_currency, + company_currency, + condition=condition, + ) + + # Get all SO / PO which are not fully billed or against which full advance not paid + orders_to_be_billed = [] + if args.get("get_orders_to_be_billed"): + orders_to_be_billed = get_orders_to_be_billed( + args.get("posting_date"), + args.get("party_type"), + args.get("party"), + args.get("company"), party_account_currency, company_currency, - condition=condition, + filters=args, ) data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: + if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"): + ref_document_type = "invoices or orders" + elif args.get("get_outstanding_invoices"): + ref_document_type = "invoices" + elif args.get("get_orders_to_be_billed"): + ref_document_type = "orders" + frappe.msgprint( _( - "No outstanding invoices found for the {0} {1} which qualify the filters you have specified." - ).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))) + "No outstanding {0} found for the {1} {2} which qualify the filters you have specified." + ).format( + ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party")) + ) ) return data From 50a8907e8c05a913bf3b1778760795db85f9d675 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 19 Jun 2023 19:22:06 +0530 Subject: [PATCH 23/33] fix: work order serial no issue --- .../doctype/work_order/test_work_order.py | 55 +++++++++++++++++++ erpnext/stock/doctype/serial_no/serial_no.py | 9 +++ 2 files changed, 64 insertions(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index f1ac5d7b308..7ae6a0fef71 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1657,6 +1657,60 @@ class TestWorkOrder(FrappeTestCase): job_card2.time_logs = [] job_card2.save() + def test_make_serial_no_batch_from_work_order_for_serial_no(self): + item_code = "Test Serial No Item For Work Order" + warehouse = "_Test Warehouse - _TC" + raw_materials = [ + "Test RM Item 1 for Serial No Item In Work Order", + ] + + make_item( + item_code, + { + "has_stock_item": 1, + "has_serial_no": 1, + "serial_no_series": "TSNIFWO-.#####", + }, + ) + + for rm_item in raw_materials: + make_item( + rm_item, + { + "has_stock_item": 1, + }, + ) + + test_stock_entry.make_stock_entry(item_code=rm_item, target=warehouse, qty=10, basic_rate=100) + + bom = make_bom(item=item_code, raw_materials=raw_materials) + + frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 1) + + wo_order = make_wo_order_test_record( + item=item_code, + bom_no=bom.name, + qty=5, + skip_transfer=1, + from_wip_warehouse=1, + ) + + serial_nos = frappe.get_all( + "Serial No", + filters={"item_code": item_code, "work_order": wo_order.name}, + ) + + self.assertEqual(len(serial_nos), 5) + + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) + + stock_entry.submit() + for row in stock_entry.items: + if row.is_finished_item: + self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos))) + + frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 0) + def prepare_data_for_workstation_type_check(): from erpnext.manufacturing.doctype.operation.test_operation import make_operation @@ -1886,6 +1940,7 @@ def make_wo_order_test_record(**args): wo_order.sales_order = args.sales_order or None wo_order.planned_start_date = args.planned_start_date or now() wo_order.transfer_material_against = args.transfer_material_against or "Work Order" + wo_order.from_wip_warehouse = args.from_wip_warehouse or None if args.source_warehouse: for item in wo_order.get("required_items"): diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 7d97f1667bb..a9989956ebb 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -307,6 +307,11 @@ def validate_serial_no(sle, item_det): allow_existing_serial_no = cint( frappe.get_cached_value("Stock Settings", "None", "allow_existing_serial_no") ) + + work_order = None + if sle.voucher_no and sle.voucher_type == "Stock Entry": + work_order = frappe.get_cached_value("Stock Entry", sle.voucher_no, "work_order") + for serial_no in serial_nos: if frappe.db.exists("Serial No", serial_no): sr = frappe.db.get_value( @@ -324,6 +329,7 @@ def validate_serial_no(sle, item_det): "purchase_document_no", "company", "status", + "work_order", ], as_dict=1, ) @@ -335,6 +341,9 @@ def validate_serial_no(sle, item_det): SerialNoItemError, ) + if sr.work_order and work_order and sr.work_order == work_order: + allow_existing_serial_no = True + if not allow_existing_serial_no and sle.voucher_type in [ "Stock Entry", "Purchase Receipt", From 3bac2a88bd0022dc595621d08993a124b8080f20 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 22:22:50 +0530 Subject: [PATCH 24/33] perf: index `purpose` in `Stock Entry` (backport #35782) (#35783) perf: index `purpose` in `Stock Entry` (cherry picked from commit 4f941ac5c07a60aa52d1a1ddfc37e55ec3bea886) Co-authored-by: s-aga-r --- erpnext/stock/doctype/stock_entry/stock_entry.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index fe42b1f135c..564c380017b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -125,7 +125,8 @@ "oldfieldname": "purpose", "oldfieldtype": "Select", "options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor", - "read_only": 1 + "read_only": 1, + "search_index": 1 }, { "fieldname": "company", @@ -678,7 +679,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2023-06-16 14:59:10.917235", + "modified": "2023-06-19 18:23:40.748114", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", From 4af0a9b192c309b4d8d76f3fcf3cd9875de3d97b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jun 2023 11:42:02 +0530 Subject: [PATCH 25/33] fix: test case --- erpnext/manufacturing/doctype/work_order/test_work_order.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 7ae6a0fef71..97ece8fc3d9 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1700,6 +1700,7 @@ class TestWorkOrder(FrappeTestCase): filters={"item_code": item_code, "work_order": wo_order.name}, ) + serial_nos = [d.name for d in serial_nos] self.assertEqual(len(serial_nos), 5) stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5)) From 8b57ecd8efba65af59904586685a391fb803958e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:52:18 +0530 Subject: [PATCH 26/33] fix: date and finance book fixes in fixed asset register (backport #35751) (#35799) * fix: date and finance book fixes in fixed asset register (#35751) * fix: handle finance books properly and show all assets by default in fixed asset register * chore: rename value to depr amount * chore: get asset value for correct fb properly * chore: rename include_default_book_entries to include_default_book_assets (cherry picked from commit 0d125885836f0ae5015195401dd41653314fddfe) # Conflicts: # erpnext/assets/report/fixed_asset_register/fixed_asset_register.py * chore: resolving conflicts and renaming entries to assets --------- Co-authored-by: Anand Baburajan --- .../fixed_asset_register.js | 119 +++++++-------- .../fixed_asset_register.py | 142 +++++++++++------- 2 files changed, 145 insertions(+), 116 deletions(-) diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js index 4f7b8361077..b788a32d6ab 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js @@ -19,56 +19,6 @@ frappe.query_reports["Fixed Asset Register"] = { options: "\nIn Location\nDisposed", default: 'In Location' }, - { - "fieldname":"filter_based_on", - "label": __("Period Based On"), - "fieldtype": "Select", - "options": ["Fiscal Year", "Date Range"], - "default": "Fiscal Year", - "reqd": 1 - }, - { - "fieldname":"from_date", - "label": __("Start Date"), - "fieldtype": "Date", - "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12), - "depends_on": "eval: doc.filter_based_on == 'Date Range'", - "reqd": 1 - }, - { - "fieldname":"to_date", - "label": __("End Date"), - "fieldtype": "Date", - "default": frappe.datetime.nowdate(), - "depends_on": "eval: doc.filter_based_on == 'Date Range'", - "reqd": 1 - }, - { - "fieldname":"from_fiscal_year", - "label": __("Start Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", - "reqd": 1 - }, - { - "fieldname":"to_fiscal_year", - "label": __("End Year"), - "fieldtype": "Link", - "options": "Fiscal Year", - "default": frappe.defaults.get_user_default("fiscal_year"), - "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", - "reqd": 1 - }, - { - "fieldname":"date_based_on", - "label": __("Date Based On"), - "fieldtype": "Select", - "options": ["Purchase Date", "Available For Use Date"], - "default": "Purchase Date", - "reqd": 1 - }, { fieldname:"asset_category", label: __("Asset Category"), @@ -89,22 +39,67 @@ frappe.query_reports["Fixed Asset Register"] = { default: "--Select a group--", reqd: 1 }, - { - fieldname:"finance_book", - label: __("Finance Book"), - fieldtype: "Link", - options: "Finance Book", - depends_on: "eval: doc.filter_by_finance_book == 1", - }, - { - fieldname:"filter_by_finance_book", - label: __("Filter by Finance Book"), - fieldtype: "Check" - }, { fieldname:"only_existing_assets", label: __("Only existing assets"), fieldtype: "Check" }, + { + fieldname:"finance_book", + label: __("Finance Book"), + fieldtype: "Link", + options: "Finance Book", + }, + { + "fieldname": "include_default_book_assets", + "label": __("Include Default Book Assets"), + "fieldtype": "Check", + "default": 1 + }, + { + "fieldname":"filter_based_on", + "label": __("Period Based On"), + "fieldtype": "Select", + "options": ["--Select a period--", "Fiscal Year", "Date Range"], + "default": "--Select a period--", + }, + { + "fieldname":"from_date", + "label": __("Start Date"), + "fieldtype": "Date", + "default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12), + "depends_on": "eval: doc.filter_based_on == 'Date Range'", + }, + { + "fieldname":"to_date", + "label": __("End Date"), + "fieldtype": "Date", + "default": frappe.datetime.nowdate(), + "depends_on": "eval: doc.filter_based_on == 'Date Range'", + }, + { + "fieldname":"from_fiscal_year", + "label": __("Start Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", + }, + { + "fieldname":"to_fiscal_year", + "label": __("End Year"), + "fieldtype": "Link", + "options": "Fiscal Year", + "default": frappe.defaults.get_user_default("fiscal_year"), + "depends_on": "eval: doc.filter_based_on == 'Fiscal Year'", + }, + { + "fieldname":"date_based_on", + "label": __("Date Based On"), + "fieldtype": "Select", + "options": ["Purchase Date", "Available For Use Date"], + "default": "Purchase Date", + "depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'", + }, ] }; diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py index c6fbc6c58e8..f810819b4fc 100644 --- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py +++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py @@ -2,9 +2,11 @@ # For license information, please see license.txt +from itertools import chain + import frappe from frappe import _ -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cstr, flt, formatdate, getdate from erpnext.accounts.report.financial_statements import ( @@ -13,7 +15,6 @@ from erpnext.accounts.report.financial_statements import ( validate_fiscal_year, ) from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation -from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts def execute(filters=None): @@ -64,11 +65,9 @@ def get_conditions(filters): def get_data(filters): - data = [] conditions = get_conditions(filters) - depreciation_amount_map = get_finance_book_value_map(filters) pr_supplier_map = get_purchase_receipt_supplier_map() pi_supplier_map = get_purchase_invoice_supplier_map() @@ -102,20 +101,27 @@ def get_data(filters): ] assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields) - assets_linked_to_fb = None + assets_linked_to_fb = get_assets_linked_to_fb(filters) - if filters.filter_by_finance_book: - assets_linked_to_fb = frappe.db.get_all( - doctype="Asset Finance Book", - filters={"finance_book": filters.finance_book or ("is", "not set")}, - pluck="parent", - ) + company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") + + if filters.include_default_book_assets and company_fb: + finance_book = company_fb + elif filters.finance_book: + finance_book = filters.finance_book + else: + finance_book = None + + depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book) for asset in assets_record: if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb: continue - asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book) + asset_value = get_asset_value_after_depreciation( + asset.asset_id, finance_book + ) or get_asset_value_after_depreciation(asset.asset_id) + row = { "asset_id": asset.asset_id, "asset_name": asset.asset_name, @@ -126,7 +132,7 @@ def get_data(filters): or pi_supplier_map.get(asset.purchase_invoice), "gross_purchase_amount": asset.gross_purchase_amount, "opening_accumulated_depreciation": asset.opening_accumulated_depreciation, - "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters), + "depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map), "available_for_use_date": asset.available_for_use_date, "location": asset.location, "asset_category": asset.asset_category, @@ -140,14 +146,23 @@ def get_data(filters): def prepare_chart_data(data, filters): labels_values_map = {} - date_field = frappe.scrub(filters.date_based_on) + if filters.filter_based_on not in ("Date Range", "Fiscal Year"): + filters_filter_based_on = "Date Range" + date_field = "purchase_date" + filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field) + filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field) + else: + filters_filter_based_on = filters.filter_based_on + date_field = frappe.scrub(filters.date_based_on) + filters_from_date = filters.from_date + filters_to_date = filters.to_date period_list = get_period_list( filters.from_fiscal_year, filters.to_fiscal_year, - filters.from_date, - filters.to_date, - filters.filter_based_on, + filters_from_date, + filters_to_date, + filters_filter_based_on, "Monthly", company=filters.company, ignore_fiscal_year=True, @@ -184,57 +199,76 @@ def prepare_chart_data(data, filters): } -def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters): - if asset.calculate_depreciation: - depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0 - else: - depr_amount = get_manual_depreciation_amount_of_asset(asset, filters) +def get_assets_linked_to_fb(filters): + afb = frappe.qb.DocType("Asset Finance Book") - return flt(depr_amount, 2) - - -def get_finance_book_value_map(filters): - date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date - - return frappe._dict( - frappe.db.sql( - """ Select - parent, SUM(depreciation_amount) - FROM `tabDepreciation Schedule` - WHERE - parentfield='schedules' - AND schedule_date<=%s - AND journal_entry IS NOT NULL - AND ifnull(finance_book, '')=%s - GROUP BY parent""", - (date, cstr(filters.finance_book or "")), - ) + query = frappe.qb.from_(afb).select( + afb.parent, ) + if filters.include_default_book_assets: + company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book") -def get_manual_depreciation_amount_of_asset(asset, filters): + if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb): + frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'")) + + query = query.where( + (afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""])) + | (afb.finance_book.isnull()) + ) + else: + query = query.where( + (afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull()) + ) + + assets_linked_to_fb = list(chain(*query.run(as_list=1))) + + return assets_linked_to_fb + + +def get_depreciation_amount_of_asset(asset, depreciation_amount_map): + return depreciation_amount_map.get(asset.asset_id) or 0.0 + + +def get_asset_depreciation_amount_map(filters, finance_book): date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date - (_, _, depreciation_expense_account) = get_depreciation_accounts(asset) - + asset = frappe.qb.DocType("Asset") gle = frappe.qb.DocType("GL Entry") + aca = frappe.qb.DocType("Asset Category Account") + company = frappe.qb.DocType("Company") - result = ( + query = ( frappe.qb.from_(gle) - .select(Sum(gle.debit)) - .where(gle.against_voucher == asset.asset_id) - .where(gle.account == depreciation_expense_account) + .join(asset) + .on(gle.against_voucher == asset.name) + .join(aca) + .on((aca.parent == asset.asset_category) & (aca.company_name == asset.company)) + .join(company) + .on(company.name == asset.company) + .select(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount")) + .where( + gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account) + ) .where(gle.debit != 0) .where(gle.is_cancelled == 0) - .where(gle.posting_date <= date) - ).run() + .where(asset.docstatus == 1) + .groupby(asset.name) + ) - if result and result[0] and result[0][0]: - depr_amount = result[0][0] + if finance_book: + query = query.where( + (gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull()) + ) else: - depr_amount = 0 + query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull())) - return depr_amount + if filters.filter_based_on in ("Date Range", "Fiscal Year"): + query = query.where(gle.posting_date <= date) + + asset_depr_amount_map = query.run() + + return dict(asset_depr_amount_map) def get_purchase_receipt_supplier_map(): From 7da461b8624b4b49da54e172f75527820af79056 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:19:59 +0530 Subject: [PATCH 27/33] fix: for zero bal accounts, dr/cr only on currency that has balance (cherry picked from commit 1b33afd69987f04c1c92e236c8eeb86bf9e3e76d) --- .../exchange_rate_revaluation.py | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 043fbdd5d6c..2e2a34ad835 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -373,6 +373,24 @@ class ExchangeRateRevaluation(Document): "credit": 0, } ) + + journal_entry_accounts.append(journal_account) + + journal_entry_accounts.append( + { + "account": unrealized_exchange_gain_loss_account, + "balance": get_balance_on(unrealized_exchange_gain_loss_account), + "debit": 0, + "credit": 0, + "debit_in_account_currency": abs(d.gain_loss) if d.gain_loss < 0 else 0, + "credit_in_account_currency": abs(d.gain_loss) if d.gain_loss > 0 else 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "exchange_rate": 1, + "reference_type": "Exchange Rate Revaluation", + "reference_name": self.name, + } + ) + elif d.get("balance_in_base_currency") and not d.get("new_balance_in_base_currency"): # Base currency has balance dr_or_cr = "credit" if d.get("balance_in_base_currency") > 0 else "debit" @@ -388,22 +406,22 @@ class ExchangeRateRevaluation(Document): } ) - journal_entry_accounts.append(journal_account) + journal_entry_accounts.append(journal_account) - journal_entry_accounts.append( - { - "account": unrealized_exchange_gain_loss_account, - "balance": get_balance_on(unrealized_exchange_gain_loss_account), - "debit": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0, - "credit": abs(self.gain_loss_booked) if self.gain_loss_booked > 0 else 0, - "debit_in_account_currency": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0, - "credit_in_account_currency": self.gain_loss_booked if self.gain_loss_booked > 0 else 0, - "cost_center": erpnext.get_default_cost_center(self.company), - "exchange_rate": 1, - "reference_type": "Exchange Rate Revaluation", - "reference_name": self.name, - } - ) + journal_entry_accounts.append( + { + "account": unrealized_exchange_gain_loss_account, + "balance": get_balance_on(unrealized_exchange_gain_loss_account), + "debit": abs(d.gain_loss) if d.gain_loss < 0 else 0, + "credit": abs(d.gain_loss) if d.gain_loss > 0 else 0, + "debit_in_account_currency": 0, + "credit_in_account_currency": 0, + "cost_center": erpnext.get_default_cost_center(self.company), + "exchange_rate": 1, + "reference_type": "Exchange Rate Revaluation", + "reference_name": self.name, + } + ) journal_entry.set("accounts", journal_entry_accounts) journal_entry.set_total_debit_credit() From 146d41ee81103b774fdaa3b73bf73343f7a2787e Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:21:51 +0530 Subject: [PATCH 28/33] refactor: allow higher precision for new exchange rate (cherry picked from commit 9d04af9ecc865b77e9408f621eed0c4d933aa543) --- .../exchange_rate_revaluation_account.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json b/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json index 2968359a0d0..0a7d0579b15 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json +++ b/erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json @@ -92,6 +92,7 @@ "fieldtype": "Float", "in_list_view": 1, "label": "New Exchange Rate", + "precision": "9", "reqd": 1 }, { @@ -147,7 +148,7 @@ ], "istable": 1, "links": [], - "modified": "2022-12-29 19:38:52.915295", + "modified": "2023-06-20 07:21:40.743460", "modified_by": "Administrator", "module": "Accounts", "name": "Exchange Rate Revaluation Account", From 6c9c3426f8175e756e806ac79485282a5499b0d4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:23:51 +0530 Subject: [PATCH 29/33] refactor: allow '0' rounding allowance (cherry picked from commit 4567474418d159e6334c7b69c3a7f7f6ccc9741d) --- .../exchange_rate_revaluation/exchange_rate_revaluation.js | 4 ++-- .../exchange_rate_revaluation/exchange_rate_revaluation.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js index 733a7616b21..1ef5c837402 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js @@ -36,8 +36,8 @@ frappe.ui.form.on('Exchange Rate Revaluation', { }, validate_rounding_loss: function(frm) { - allowance = frm.doc.rounding_loss_allowance; - if (!(allowance > 0 && allowance < 1)) { + let allowance = frm.doc.rounding_loss_allowance; + if (!(allowance >= 0 && allowance < 1)) { frappe.throw(__("Rounding Loss Allowance should be between 0 and 1")); } }, diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index 2e2a34ad835..b161cc7fcc4 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -22,7 +22,7 @@ class ExchangeRateRevaluation(Document): self.set_total_gain_loss() def validate_rounding_loss_allowance(self): - if not (self.rounding_loss_allowance > 0 and self.rounding_loss_allowance < 1): + if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1): frappe.throw(_("Rounding Loss Allowance should be between 0 and 1")) def set_total_gain_loss(self): From 65d24ea9ea0c415ed972b2b2a911cbd47e080ecd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 20 Jun 2023 07:29:21 +0530 Subject: [PATCH 30/33] refactor: higher precision for rounding loss and allow '0' (cherry picked from commit 6694175a517dc942f056e5b9b9918e55419f85b0) --- .../exchange_rate_revaluation.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json index 2310d1272cd..79428d591b4 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json @@ -100,15 +100,16 @@ }, { "default": "0.05", - "description": "Only values between 0 and 1 are allowed. \nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account", + "description": "Only values between [0,1) are allowed. Like {0.00, 0.04, 0.09, ...}\nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account", "fieldname": "rounding_loss_allowance", "fieldtype": "Float", - "label": "Rounding Loss Allowance" + "label": "Rounding Loss Allowance", + "precision": "9" } ], "is_submittable": 1, "links": [], - "modified": "2023-06-12 21:02:09.818208", + "modified": "2023-06-20 07:29:06.972434", "modified_by": "Administrator", "module": "Accounts", "name": "Exchange Rate Revaluation", From 703e4f4f5da73fc56ef1ff805864f065c9db455f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jun 2023 14:39:47 +0530 Subject: [PATCH 31/33] fix: Duplicate addresses are creating while using the E-commerce --- erpnext/e_commerce/shopping_cart/cart.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py index 4c823936848..4f9088e8c08 100644 --- a/erpnext/e_commerce/shopping_cart/cart.py +++ b/erpnext/e_commerce/shopping_cart/cart.py @@ -3,7 +3,7 @@ import frappe import frappe.defaults -from frappe import _, throw +from frappe import _, bold, throw from frappe.contacts.doctype.address.address import get_address_display from frappe.contacts.doctype.contact.contact import get_contact_name from frappe.utils import cint, cstr, flt, get_fullname @@ -201,6 +201,11 @@ def get_shopping_cart_menu(context=None): @frappe.whitelist() def add_new_address(doc): doc = frappe.parse_json(doc) + address_title = doc.get("address_title") + if frappe.db.exists("Address", {"address_title": address_title}): + msg = f"The address with the title {bold(address_title)} already exists. Please change the title accordingly." + frappe.throw(_(msg), title=_("Address Already Exists")) + doc.update({"doctype": "Address"}) address = frappe.get_doc(doc) address.save(ignore_permissions=True) From baf014fc61ef643e10efa969ef36f436ddc26c74 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jun 2023 15:55:18 +0530 Subject: [PATCH 32/33] fix: keyerror while checking the stock balance report (cherry picked from commit a627d2a38cc6b194694066da256dda28d3c7ddba) --- erpnext/stock/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 67233dd44da..a7e37d5961a 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -475,7 +475,7 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors) for row_idx, row in enumerate(result): for convertible_col, data in convertible_column_map.items(): - conversion_factor = conversion_factors[row.get("item_code")] or 1 + conversion_factor = conversion_factors.get(row.get("item_code")) or 1.0 for_type = data.for_type value_before_conversion = row.get(convertible_col) if for_type == "rate": From 2bbea63de178d54e146bb29bc89fdf9d4e39a4b4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 20 Jun 2023 16:27:23 +0530 Subject: [PATCH 33/33] fix: stock error for service item (cherry picked from commit 32965f1af9403b20b8d783c6e8f609a565b2864c) --- erpnext/e_commerce/variant_selector/utils.py | 1 + erpnext/templates/generators/item/item_configure.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py index 1a3e7379281..4466c457436 100644 --- a/erpnext/e_commerce/variant_selector/utils.py +++ b/erpnext/e_commerce/variant_selector/utils.py @@ -162,6 +162,7 @@ def get_next_attribute_and_values(item_code, selected_attributes): product_info = get_item_variant_price_dict(exact_match[0], cart_settings) if product_info: + product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item") product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock) else: product_info = None diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js index 613c967e3d6..9beba3fd01e 100644 --- a/erpnext/templates/generators/item/item_configure.js +++ b/erpnext/templates/generators/item/item_configure.js @@ -219,7 +219,8 @@ class ItemConfigure { : '' } - ${available_qty === 0 ? '(' + __('Out of Stock') + ')' : ''} + ${available_qty === 0 && product_info && product_info?.is_stock_item + ? '(' + __('Out of Stock') + ')' : ''} @@ -236,7 +237,8 @@ class ItemConfigure { `; /* eslint-disable indent */ - if (!product_info?.allow_items_not_in_stock && available_qty === 0) { + if (!product_info?.allow_items_not_in_stock && available_qty === 0 + && product_info && product_info?.is_stock_item) { item_add_to_cart = ''; }