From a3a9cd517459d3b75e73c7808f3ea749af2c7111 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 21 Feb 2023 11:55:42 +0530 Subject: [PATCH 01/30] fix: incorrect leave balance after carry-forwarded leave expiry --- .../doctype/leave_application/leave_application.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 7edcd516fcb..cd1c7f24df1 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -832,6 +832,7 @@ def get_leave_balance_on( def get_leave_allocation_records(employee, date, leave_type=None): """Returns the total allocated leaves and carry forwarded leaves based on ledger entries""" Ledger = frappe.qb.DocType("Leave Ledger Entry") + LeaveAllocation = frappe.qb.DocType("Leave Allocation") cf_leave_case = ( frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0) @@ -845,6 +846,8 @@ def get_leave_allocation_records(employee, date, leave_type=None): query = ( frappe.qb.from_(Ledger) + .inner_join(LeaveAllocation) + .on(Ledger.transaction_name == LeaveAllocation.name) .select( sum_cf_leaves, sum_new_leaves, @@ -854,12 +857,21 @@ def get_leave_allocation_records(employee, date, leave_type=None): ) .where( (Ledger.from_date <= date) - & (Ledger.to_date >= date) & (Ledger.docstatus == 1) & (Ledger.transaction_type == "Leave Allocation") & (Ledger.employee == employee) & (Ledger.is_expired == 0) & (Ledger.is_lwp == 0) + & ( + # newly allocated leave's end date is same as the leave allocation's to date + ((Ledger.is_carry_forward == 0) & (Ledger.to_date >= date)) + # carry forwarded leave's end date won't be same as the leave allocation's to date + # it's between the leave allocation's from and to date + | ( + (Ledger.is_carry_forward == 1) + & (Ledger.to_date.between(LeaveAllocation.from_date, LeaveAllocation.to_date)) + ) + ) ) ) From aea9d82672d0fe1f7a97223bf73b0de41ee2db4f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 21 Feb 2023 12:20:28 +0530 Subject: [PATCH 02/30] test: leaves allocated before and after cf leave expiry is same --- .../leave_application/test_leave_application.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index f36d0f2da86..d0a89a56afb 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -993,16 +993,21 @@ class TestLeaveApplication(unittest.TestCase): @set_holiday_list("Salary Slip Test Holiday List", "_Test Company") def test_get_leave_allocation_records(self): + """Tests if total leaves allocated before and after carry forwarded leave expiry is same""" employee = get_employee() leave_type = create_leave_type( leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, expire_carry_forwarded_leaves_after_days=90, - ) - leave_type.insert() + ).insert() leave_alloc = create_carry_forwarded_allocation(employee, leave_type) - details = get_leave_allocation_records(employee.name, getdate(), leave_type.name) + cf_expiry = frappe.db.get_value( + "Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date" + ) + + # test total leaves allocated before cf leave expiry + details = get_leave_allocation_records(employee.name, add_days(cf_expiry, -1), leave_type.name) expected_data = { "from_date": getdate(leave_alloc.from_date), "to_date": getdate(leave_alloc.to_date), @@ -1013,6 +1018,11 @@ class TestLeaveApplication(unittest.TestCase): } self.assertEqual(details.get(leave_type.name), expected_data) + # test leaves allocated after carry forwarded leaves expiry, should be same thoroughout allocation period + # cf leaves should show up under expired or taken leaves later + details = get_leave_allocation_records(employee.name, add_days(cf_expiry, 1), leave_type.name) + self.assertEqual(details.get(leave_type.name), expected_data) + def create_carry_forwarded_allocation(employee, leave_type): # initial leave allocation From d82ba4e86f023b2d436de7b44f157d94596234a8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 21 Feb 2023 17:12:21 +0530 Subject: [PATCH 03/30] fix: ignore remaining leaves calculation for cf leaves after expiry - calculate correct cf expiry in the entire allocation period --- .../doctype/leave_application/leave_application.py | 12 +++++++++--- .../leave_application/test_leave_application.py | 3 +-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index cd1c7f24df1..fff0967f25a 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -817,7 +817,9 @@ def get_leave_balance_on( allocation = allocation_records.get(leave_type, frappe._dict()) end_date = allocation.to_date if cint(consider_all_leaves_in_the_allocation_period) else date - cf_expiry = get_allocation_expiry_for_cf_leaves(employee, leave_type, to_date, date) + cf_expiry = get_allocation_expiry_for_cf_leaves( + employee, leave_type, to_date, allocation.from_date + ) leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date) @@ -937,8 +939,12 @@ def get_remaining_leaves( # balance for carry forwarded leaves if cf_expiry and allocation.unused_leaves: - cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken) - remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry) + if getdate(date) > getdate(cf_expiry): + # carry forwarded leave expiry date passed + cf_leaves = remaining_cf_leaves = 0 + else: + cf_leaves = flt(allocation.unused_leaves) + flt(leaves_taken) + remaining_cf_leaves = _get_remaining_leaves(cf_leaves, cf_expiry) leave_balance = flt(allocation.new_leaves_allocated) + flt(cf_leaves) leave_balance_for_consumption = flt(allocation.new_leaves_allocated) + flt(remaining_cf_leaves) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index d0a89a56afb..749e4c2d4f2 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -698,8 +698,7 @@ class TestLeaveApplication(unittest.TestCase): leave_type_name="_Test_CF_leave_expiry", is_carry_forward=1, expire_carry_forwarded_leaves_after_days=90, - ) - leave_type.insert() + ).insert() create_carry_forwarded_allocation(employee, leave_type) details = get_leave_balance_on( From b848b77815a21e60fa2e7082b238a84b91adec41 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 22 Feb 2023 09:26:33 +0530 Subject: [PATCH 04/30] test: leave details with expired cf leaves --- .../test_leave_application.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index 749e4c2d4f2..45e9a87428e 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -990,6 +990,35 @@ class TestLeaveApplication(unittest.TestCase): } self.assertEqual(leave_allocation, expected) + @set_holiday_list("Salary Slip Test Holiday List", "_Test Company") + def test_leave_details_with_expired_cf_leaves(self): + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + expire_carry_forwarded_leaves_after_days=90, + ).insert() + + leave_alloc = create_carry_forwarded_allocation(employee, leave_type) + cf_expiry = frappe.db.get_value( + "Leave Ledger Entry", {"transaction_name": leave_alloc.name, "is_carry_forward": 1}, "to_date" + ) + + # all leaves available before cf leave expiry + leave_details = get_leave_details(employee.name, add_days(cf_expiry, -1)) + self.assertEqual(leave_details["leave_allocation"][leave_type.name]["remaining_leaves"], 30.0) + + # cf leaves expired + leave_details = get_leave_details(employee.name, add_days(cf_expiry, 1)) + expected_data = { + "total_leaves": 30.0, + "expired_leaves": 15.0, + "leaves_taken": 0.0, + "leaves_pending_approval": 0.0, + "remaining_leaves": 15.0, + } + self.assertEqual(leave_details["leave_allocation"][leave_type.name], expected_data) + @set_holiday_list("Salary Slip Test Holiday List", "_Test Company") def test_get_leave_allocation_records(self): """Tests if total leaves allocated before and after carry forwarded leave expiry is same""" From e98b34617f194892fae1feaaef99b3551a478b83 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 22 Feb 2023 14:24:18 +0530 Subject: [PATCH 05/30] fix: incorrect color in the BOM Stock Report (cherry picked from commit a8f03ebf7f61c14d2cff45510acd670f0671a1b5) --- .../manufacturing/report/bom_stock_report/bom_stock_report.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js index 7beecaceedf..e7f67caf249 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.js @@ -25,8 +25,9 @@ frappe.query_reports["BOM Stock Report"] = { ], "formatter": function(value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); + if (column.id == "item") { - if (data["enough_parts_to_build"] > 0) { + if (data["in_stock_qty"] >= data["required_qty"]) { value = `${data['item']}`; } else { value = `${data['item']}`; From d1b611d37f7d7fbb8fc1c4e304aaf9ac10fa4757 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Feb 2023 09:36:21 +0530 Subject: [PATCH 06/30] fix: ui freeze on item selection in sales invoice (cherry picked from commit 6412583e9825bd3847f64b43f44409b57236e557) --- erpnext/selling/sales_common.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js index cab67c98d60..24646c7d37a 100644 --- a/erpnext/selling/sales_common.js +++ b/erpnext/selling/sales_common.js @@ -418,8 +418,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({ callback: function(r) { if(r.message) { frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message); - } else { - frappe.model.set_value(doc.doctype, doc.name, 'batch_no', r.message); } } }); From 91a95adcb6180016f87cf3c2b1b1f28d375401b6 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Wed, 22 Feb 2023 13:29:06 +0530 Subject: [PATCH 07/30] fix: zero division error while making LCV (cherry picked from commit 80e94a08cfccd888b91e921a3c8b2418b6aed6c0) --- .../landed_cost_voucher.py | 9 ++- .../test_landed_cost_voucher.py | 55 ++++++++++++++++++- .../purchase_receipt/purchase_receipt.py | 24 ++++++-- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py index b3af309359a..111a0861b71 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py @@ -55,7 +55,6 @@ class LandedCostVoucher(Document): self.get_items_from_purchase_receipts() self.set_applicable_charges_on_item() - self.validate_applicable_charges_for_item() def check_mandatory(self): if not self.get("purchase_receipts"): @@ -115,6 +114,13 @@ class LandedCostVoucher(Document): total_item_cost += item.get(based_on_field) for item in self.get("items"): + if not total_item_cost and not item.get(based_on_field): + frappe.throw( + _( + "It's not possible to distribute charges equally when total amount is zero, please set 'Distribute Charges Based On' as 'Quantity'" + ) + ) + item.applicable_charges = flt( flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)), item.precision("applicable_charges"), @@ -162,6 +168,7 @@ class LandedCostVoucher(Document): ) def on_submit(self): + self.validate_applicable_charges_for_item() self.update_landed_cost() def on_cancel(self): diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py index 790d284a3dc..11d90d6c1b8 100644 --- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py +++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py @@ -175,6 +175,59 @@ class TestLandedCostVoucher(FrappeTestCase): ) self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0) + def test_landed_cost_voucher_for_zero_purchase_rate(self): + "Test impact of LCV on future stock balances." + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item("LCV Stock Item", {"is_stock_item": 1}) + warehouse = "Stores - _TC" + + pr = make_purchase_receipt( + item_code=item.name, + warehouse=warehouse, + qty=10, + rate=0, + posting_date=add_days(frappe.utils.nowdate(), -2), + ) + + self.assertEqual( + frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + "stock_value_difference", + ), + 0, + ) + + lcv = make_landed_cost_voucher( + company=pr.company, + receipt_document_type="Purchase Receipt", + receipt_document=pr.name, + charges=100, + distribute_charges_based_on="Distribute Manually", + do_not_save=True, + ) + + lcv.get_items_from_purchase_receipts() + lcv.items[0].applicable_charges = 100 + lcv.save() + lcv.submit() + + self.assertTrue( + frappe.db.exists( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + ) + ) + self.assertEqual( + frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "is_cancelled": 0}, + "stock_value_difference", + ), + 100, + ) + def test_landed_cost_voucher_against_purchase_invoice(self): pi = make_purchase_invoice( @@ -516,7 +569,7 @@ def make_landed_cost_voucher(**args): lcv = frappe.new_doc("Landed Cost Voucher") lcv.company = args.company or "_Test Company" - lcv.distribute_charges_based_on = "Amount" + lcv.distribute_charges_based_on = args.distribute_charges_based_on or "Amount" lcv.set( "purchase_receipts", diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 390704fa3ed..aea9ff1a526 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -1064,13 +1064,25 @@ def get_item_account_wise_additional_cost(purchase_document): account.expense_account, {"amount": 0.0, "base_amount": 0.0} ) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][ - "amount" - ] += (account.amount * item.get(based_on_field) / total_item_cost) + if total_item_cost > 0: + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["amount"] += ( + account.amount * item.get(based_on_field) / total_item_cost + ) - item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][ - "base_amount" - ] += (account.base_amount * item.get(based_on_field) / total_item_cost) + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["base_amount"] += ( + account.base_amount * item.get(based_on_field) / total_item_cost + ) + else: + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["amount"] += item.applicable_charges + item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][ + account.expense_account + ]["base_amount"] += item.applicable_charges return item_account_wise_cost From 642692a04049c35cfc8d84769ee79104d65b06dd Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 3 Feb 2023 16:24:52 +0530 Subject: [PATCH 08/30] perf: fetch SLE's on demand and memoize (cherry picked from commit 3e5691072aa1243e932c31e7514dafcd9cd92a43) --- .../report/gross_profit/gross_profit.py | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 77bc82590b8..fc7af04ee2b 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -364,6 +364,7 @@ def get_column_names(): class GrossProfitGenerator(object): def __init__(self, filters=None): + self.sle = {} self.data = [] self.average_buying_rate = {} self.filters = frappe._dict(filters) @@ -373,7 +374,6 @@ class GrossProfitGenerator(object): if filters.group_by == "Invoice": self.group_items_by_invoice() - self.load_stock_ledger_entries() self.load_product_bundle() self.load_non_stock_items() self.get_returned_invoice_items() @@ -563,7 +563,7 @@ class GrossProfitGenerator(object): return flt(row.qty) * item_rate else: - my_sle = self.sle.get((item_code, row.warehouse)) + my_sle = self.get_stock_ledger_entries(item_code, row.warehouse) if (row.update_stock or row.dn_detail) and my_sle: parenttype, parent = row.parenttype, row.parent if row.dn_detail: @@ -581,7 +581,7 @@ class GrossProfitGenerator(object): dn["item_row"], dn["warehouse"], ) - my_sle = self.sle.get((item_code, warehouse)) + my_sle = self.get_stock_ledger_entries(item_code, row.warehouse) return self.calculate_buying_amount_from_sle( row, my_sle, parenttype, parent, item_row, item_code ) @@ -840,24 +840,36 @@ class GrossProfitGenerator(object): "Item", item_code, ["item_name", "description", "item_group", "brand"] ) - def load_stock_ledger_entries(self): - res = frappe.db.sql( - """select item_code, voucher_type, voucher_no, - voucher_detail_no, stock_value, warehouse, actual_qty as qty - from `tabStock Ledger Entry` - where company=%(company)s and is_cancelled = 0 - order by - item_code desc, warehouse desc, posting_date desc, - posting_time desc, creation desc""", - self.filters, - as_dict=True, - ) - self.sle = {} - for r in res: - if (r.item_code, r.warehouse) not in self.sle: - self.sle[(r.item_code, r.warehouse)] = [] + def get_stock_ledger_entries(self, item_code, warehouse): + if item_code and warehouse: + if (item_code, warehouse) not in self.sle: + sle = qb.DocType("Stock Ledger Entry") + res = ( + qb.from_(sle) + .select( + sle.item_code, + sle.voucher_type, + sle.voucher_no, + sle.voucher_detail_no, + sle.stock_value, + sle.warehouse, + sle.actual_qty.as_("qty"), + ) + .where( + (sle.company == self.filters.company) + & (sle.item_code == item_code) + & (sle.warehouse == warehouse) + & (sle.is_cancelled == 0) + ) + .orderby(sle.item_code) + .orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc) + .run(as_dict=True) + ) - self.sle[(r.item_code, r.warehouse)].append(r) + self.sle[(item_code, warehouse)] = res + + return self.sle[(item_code, warehouse)] + return [] def load_product_bundle(self): self.product_bundles = {} From c40aa580c5323f5fb4f0de213e03c0370146035a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 23 Feb 2023 11:15:56 +0530 Subject: [PATCH 09/30] refactor: use docstatus from Delivery Note Item (cherry picked from commit 88d888d9d08f5649e142ce2f5b376900cb851a4a) --- erpnext/accounts/report/gross_profit/gross_profit.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index fc7af04ee2b..c73cb050f01 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -597,15 +597,12 @@ class GrossProfitGenerator(object): def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code): from frappe.query_builder.functions import Sum - delivery_note = frappe.qb.DocType("Delivery Note") delivery_note_item = frappe.qb.DocType("Delivery Note Item") query = ( - frappe.qb.from_(delivery_note) - .inner_join(delivery_note_item) - .on(delivery_note.name == delivery_note_item.parent) + frappe.qb.from_(delivery_note_item) .select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty)) - .where(delivery_note.docstatus == 1) + .where(delivery_note_item.docstatus == 1) .where(delivery_note_item.item_code == item_code) .where(delivery_note_item.against_sales_order == sales_order) .where(delivery_note_item.so_detail == so_detail) From 69f1247fabd1e645d7a9fbc96d64cca6111fd5a0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 23 Feb 2023 20:04:14 +0530 Subject: [PATCH 10/30] fix: user shouldn't able to make item price for item template (cherry picked from commit 6417ae0ee83d3a2b384987cc3747917e92d6a4ab) --- .../stock/doctype/item_price/item_price.js | 13 ++++++++++- .../stock/doctype/item_price/item_price.py | 9 +++++++- .../doctype/item_price/test_item_price.py | 22 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.js b/erpnext/stock/doctype/item_price/item_price.js index 12cf6cf84d5..ce489ff52b4 100644 --- a/erpnext/stock/doctype/item_price/item_price.js +++ b/erpnext/stock/doctype/item_price/item_price.js @@ -2,7 +2,18 @@ // License: GNU General Public License v3. See license.txt frappe.ui.form.on("Item Price", { - onload: function (frm) { + setup(frm) { + frm.set_query("item_code", function() { + return { + filters: { + "disabled": 0, + "has_variants": 0 + } + }; + }); + }, + + onload(frm) { // Fetch price list details frm.add_fetch("price_list", "buying", "buying"); frm.add_fetch("price_list", "selling", "selling"); diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 562f7b9e12f..01dd51004ae 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -3,7 +3,7 @@ import frappe -from frappe import _ +from frappe import _, bold from frappe.model.document import Document from frappe.utils import getdate @@ -19,6 +19,7 @@ class ItemPrice(Document): self.update_price_list_details() self.update_item_details() self.check_duplicates() + self.validate_item_template() def validate_item(self): if not frappe.db.exists("Item", self.item_code): @@ -47,6 +48,12 @@ class ItemPrice(Document): "Item", self.item_code, ["item_name", "description"] ) + def validate_item_template(self): + if frappe.get_cached_value("Item", self.item_code, "has_variants"): + msg = f"Item Price cannot be created for the template item {bold(self.item_code)}" + + frappe.throw(_(msg)) + def check_duplicates(self): conditions = ( """where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s""" diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py index d3988743f6d..6b7e9253439 100644 --- a/erpnext/stock/doctype/item_price/test_item_price.py +++ b/erpnext/stock/doctype/item_price/test_item_price.py @@ -16,6 +16,28 @@ class TestItemPrice(FrappeTestCase): frappe.db.sql("delete from `tabItem Price`") make_test_records_for_doctype("Item Price", force=True) + def test_template_item_price(self): + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item( + "Test Template Item 1", + { + "has_variants": 1, + "variant_based_on": "Manufacturer", + }, + ) + + doc = frappe.get_doc( + { + "doctype": "Item Price", + "price_list": "_Test Price List", + "item_code": item.name, + "price_list_rate": 100, + } + ) + + self.assertRaises(frappe.ValidationError, doc.save) + def test_duplicate_item(self): doc = frappe.copy_doc(test_records[0]) self.assertRaises(ItemPriceDuplicateItem, doc.save) From 59c6eb591bb0ed10bb1d80cd7fcdc6bf347e9c6b Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 17 Feb 2023 20:46:09 +0530 Subject: [PATCH 11/30] feat: provision to convert transaction based reposting to item warehouse based reposting (cherry picked from commit f1383b5ef91a536dc13d2b3638823597a9954f14) --- .../stock_reposting_settings.js | 19 +++++- .../stock_reposting_settings.py | 61 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js index 42d0723d427..5f81679bade 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.js @@ -2,7 +2,22 @@ // For license information, please see license.txt frappe.ui.form.on('Stock Reposting Settings', { - // refresh: function(frm) { + refresh: function(frm) { + frm.trigger('convert_to_item_based_reposting'); + }, - // } + convert_to_item_based_reposting: function(frm) { + frm.add_custom_button(__('Convert to Item Based Reposting'), function() { + frm.call({ + method: 'convert_to_item_wh_reposting', + frezz: true, + doc: frm.doc, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + } + }) + }) + } }); diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py index e0c8ed12e7d..51fb5ac4c40 100644 --- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py +++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py @@ -1,6 +1,8 @@ # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors # For license information, please see license.txt +import frappe +from frappe import _ from frappe.model.document import Document from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_hours @@ -24,3 +26,62 @@ class StockRepostingSettings(Document): if diff < 10: self.end_time = get_time_str(add_to_date(self.start_time, hours=10, as_datetime=True)) + + @frappe.whitelist() + def convert_to_item_wh_reposting(self): + """Convert Transaction reposting to Item Warehouse based reposting if Item Based Reposting has enabled.""" + + reposting_data = get_reposting_entries() + + vouchers = [d.voucher_no for d in reposting_data] + + item_warehouses = {} + + for ledger in get_stock_ledgers(vouchers): + key = (ledger.item_code, ledger.warehouse) + if key not in item_warehouses: + item_warehouses[key] = ledger.posting_date + elif frappe.utils.getdate(item_warehouses.get(key)) > frappe.utils.getdate(ledger.posting_date): + item_warehouses[key] = ledger.posting_date + + for key, posting_date in item_warehouses.items(): + item_code, warehouse = key + create_repost_item_valuation(item_code, warehouse, posting_date) + + for row in reposting_data: + frappe.db.set_value("Repost Item Valuation", row.name, "status", "Skipped") + + self.db_set("item_based_reposting", 1) + frappe.msgprint(_("Item Warehouse based reposting has been enabled.")) + + +def get_reposting_entries(): + return frappe.get_all( + "Repost Item Valuation", + fields=["voucher_no", "name"], + filters={"status": ("in", ["Queued", "In Progress"]), "docstatus": 1, "based_on": "Transaction"}, + ) + + +def get_stock_ledgers(vouchers): + return frappe.get_all( + "Stock Ledger Entry", + fields=["item_code", "warehouse", "posting_date"], + filters={"voucher_no": ("in", vouchers)}, + ) + + +def create_repost_item_valuation(item_code, warehouse, posting_date): + frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "company": frappe.get_cached_value("Warehouse", warehouse, "company"), + "posting_date": posting_date, + "based_on": "Item and Warehouse", + "posting_time": "00:00:01", + "item_code": item_code, + "warehouse": warehouse, + "allow_negative_stock": True, + "status": "Queued", + } + ).submit() From 806f7e5eef1fb220ee00889e3aba765f7d4f0deb Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 24 Feb 2023 11:32:46 +0530 Subject: [PATCH 12/30] fix(patch): create only 80G custom fields instead of running the whole setup (#34183) --- ...fields_for_80g_certificate_and_donation.py | 59 ++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py index 1c36b536841..ae76c3d023c 100644 --- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py +++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py @@ -1,16 +1,61 @@ import frappe - -from erpnext.regional.india.setup import make_custom_fields +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields def execute(): if frappe.get_all("Company", filters={"country": "India"}): - frappe.reload_doc("accounts", "doctype", "POS Invoice") - frappe.reload_doc("accounts", "doctype", "POS Invoice Item") - - make_custom_fields() + custom_fields = get_non_profit_custom_fields() + create_custom_fields(custom_fields, update=True) if not frappe.db.exists("Party Type", "Donor"): frappe.get_doc( {"doctype": "Party Type", "party_type": "Donor", "account_type": "Receivable"} - ).insert(ignore_permissions=True) + ).insert(ignore_permissions=True, ignore_mandatory=True) + + +def get_non_profit_custom_fields(): + return { + "Company": [ + { + "fieldname": "non_profit_section", + "label": "Non Profit Settings", + "fieldtype": "Section Break", + "insert_after": "asset_received_but_not_billed", + "collapsible": 1, + }, + { + "fieldname": "company_80g_number", + "label": "80G Number", + "fieldtype": "Data", + "insert_after": "non_profit_section", + }, + { + "fieldname": "with_effect_from", + "label": "80G With Effect From", + "fieldtype": "Date", + "insert_after": "company_80g_number", + }, + { + "fieldname": "pan_details", + "label": "PAN Number", + "fieldtype": "Data", + "insert_after": "with_effect_from", + }, + ], + "Member": [ + { + "fieldname": "pan_number", + "label": "PAN Details", + "fieldtype": "Data", + "insert_after": "email_id", + }, + ], + "Donor": [ + { + "fieldname": "pan_number", + "label": "PAN Details", + "fieldtype": "Data", + "insert_after": "email", + }, + ], + } From 59d579764d2fcecf66ba2b7d704808d41d4f976d Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 24 Feb 2023 14:42:55 +0530 Subject: [PATCH 13/30] fix: conversion factor not set (cherry picked from commit 8e46aebc50110fab1a3f123aa22cb8e06907d451) --- erpnext/stock/doctype/item/item.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 0ffc18c15a3..65df345ed05 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -33,6 +33,9 @@ frappe.ui.form.on("Item", { 'Material Request': () => { open_form(frm, "Material Request", "Material Request Item", "items"); }, + 'Stock Entry': () => { + open_form(frm, "Stock Entry", "Stock Entry Detail", "items"); + }, }; }, @@ -848,6 +851,9 @@ function open_form(frm, doctype, child_doctype, parentfield) { new_child_doc.item_name = frm.doc.item_name; new_child_doc.uom = frm.doc.stock_uom; new_child_doc.description = frm.doc.description; + if (!new_child_doc.qty) { + new_child_doc.qty = 1.0; + } frappe.run_serially([ () => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc), From 2039bd066d6723b10d5bee0b4944a6c5b417a644 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 24 Feb 2023 17:50:00 +0530 Subject: [PATCH 14/30] fix: not able to repost gl entries (cherry picked from commit 7d10dd9ea8671c27709e88350c6799ba187c4929) --- erpnext/stock/doctype/purchase_receipt/purchase_receipt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index aea9ff1a526..314a3549cc1 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -436,7 +436,7 @@ class PurchaseReceipt(BuyingController): ) divisional_loss = flt( - valuation_amount_as_per_doc - stock_value_diff, d.precision("base_net_amount") + valuation_amount_as_per_doc - flt(stock_value_diff), d.precision("base_net_amount") ) if divisional_loss: From 71767994a77349797af70676b1ff42bdc902bc2b Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Fri, 24 Feb 2023 16:38:39 +0530 Subject: [PATCH 15/30] fix: manual depr schedule (cherry picked from commit 971c0720e52b5120ab3497f3fc5bd5964b0deff7) --- erpnext/assets/doctype/asset/asset.js | 18 +++++++++++------- erpnext/assets/doctype/asset/asset.py | 15 +++++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index ccd8d8171a8..4def1ccc72d 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -519,19 +519,23 @@ frappe.ui.form.on('Depreciation Schedule', { }, depreciation_amount: function(frm, cdt, cdn) { - erpnext.asset.set_accumulated_depreciation(frm); + erpnext.asset.set_accumulated_depreciation(frm, locals[cdt][cdn].finance_book_id); } -}) +}); -erpnext.asset.set_accumulated_depreciation = function(frm) { - if(frm.doc.depreciation_method != "Manual") return; +erpnext.asset.set_accumulated_depreciation = function(frm, finance_book_id) { + var depreciation_method = frm.doc.finance_books[Number(finance_book_id) - 1].depreciation_method; + + if(depreciation_method != "Manual") return; var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation); + $.each(frm.doc.schedules || [], function(i, row) { - accumulated_depreciation += flt(row.depreciation_amount); - frappe.model.set_value(row.doctype, row.name, - "accumulated_depreciation_amount", accumulated_depreciation); + if (row.finance_book_id === finance_book_id) { + accumulated_depreciation += flt(row.depreciation_amount); + frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation); + }; }) }; diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 2bd30c6cbd4..fd34f071dcb 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -84,8 +84,11 @@ class Asset(AccountsController): if self.calculate_depreciation: self.value_after_depreciation = 0 self.set_depreciation_rate() - self.make_depreciation_schedule(date_of_disposal) - self.set_accumulated_depreciation(date_of_disposal, date_of_return) + if not ( + self.get("schedules") and "Manual" in [d.depreciation_method for d in self.finance_books] + ): + self.make_depreciation_schedule(date_of_disposal) + self.set_accumulated_depreciation(date_of_disposal, date_of_return) else: self.finance_books = [] self.value_after_depreciation = flt(self.gross_purchase_amount) - flt( @@ -225,9 +228,7 @@ class Asset(AccountsController): ) def make_depreciation_schedule(self, date_of_disposal): - if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get( - "schedules" - ): + if not self.get("schedules"): self.schedules = [] if not self.available_for_use_date: @@ -546,7 +547,9 @@ class Asset(AccountsController): self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False ): straight_line_idx = [ - d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line" + d.idx + for d in self.get("schedules") + if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual" ] finance_books = [] From 4a557b47d7a4f892a652f561b763e52b32149cd7 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Fri, 24 Feb 2023 20:34:36 +0530 Subject: [PATCH 16/30] chore: handle change in opening_accumulated_depreciation properly (cherry picked from commit b0d670a51da32ebafef0a9eef39083bd2d8fdf43) --- erpnext/assets/doctype/asset/asset.js | 4 ---- erpnext/assets/doctype/asset/asset.py | 5 ++++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 4def1ccc72d..6d0b77abcd7 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -296,10 +296,6 @@ frappe.ui.form.on('Asset', { // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); }, - opening_accumulated_depreciation: function(frm) { - erpnext.asset.set_accumulated_depreciation(frm); - }, - make_schedules_editable: function(frm) { if (frm.doc.finance_books) { var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0 diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index fd34f071dcb..59ed2f6d54a 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -85,7 +85,10 @@ class Asset(AccountsController): self.value_after_depreciation = 0 self.set_depreciation_rate() if not ( - self.get("schedules") and "Manual" in [d.depreciation_method for d in self.finance_books] + self.get("schedules") + and "Manual" in [d.depreciation_method for d in self.finance_books] + and self.get_doc_before_save().opening_accumulated_depreciation + == self.opening_accumulated_depreciation ): self.make_depreciation_schedule(date_of_disposal) self.set_accumulated_depreciation(date_of_disposal, date_of_return) From 304e6bb9963649f5e0802c63771a6e6b295aea54 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sat, 25 Feb 2023 14:43:24 +0530 Subject: [PATCH 17/30] fix: incorrect acc depr amount if multiple FBs with straight line or manual method (cherry picked from commit dda6baea3ed18546ce8493ad8ccc519f280d5b29) --- erpnext/assets/doctype/asset/asset.py | 52 +++++++++++++++++++++------ 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 59ed2f6d54a..b2d1c7349bb 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -84,12 +84,7 @@ class Asset(AccountsController): if self.calculate_depreciation: self.value_after_depreciation = 0 self.set_depreciation_rate() - if not ( - self.get("schedules") - and "Manual" in [d.depreciation_method for d in self.finance_books] - and self.get_doc_before_save().opening_accumulated_depreciation - == self.opening_accumulated_depreciation - ): + if self.should_prepare_depreciation_schedule(): self.make_depreciation_schedule(date_of_disposal) self.set_accumulated_depreciation(date_of_disposal, date_of_return) else: @@ -98,6 +93,39 @@ class Asset(AccountsController): self.opening_accumulated_depreciation ) + def should_prepare_depreciation_schedule(self): + if not self.get("schedules"): + return True + + old_asset_doc = self.get_doc_before_save() + + if ( + old_asset_doc.gross_purchase_amount != self.gross_purchase_amount + or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation + or old_asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked + ): + return True + + manual_fb_idx = -1 + for d in self.finance_books: + if d.depreciation_method == "Manual": + manual_fb_idx = d.idx + + if ( + manual_fb_idx == -1 + or old_asset_doc.finance_books[manual_fb_idx - 1].total_number_of_depreciations + != self.finance_books[manual_fb_idx - 1].total_number_of_depreciations + or old_asset_doc.finance_books[manual_fb_idx - 1].frequency_of_depreciation + != self.finance_books[manual_fb_idx - 1].frequency_of_depreciation + or old_asset_doc.finance_books[manual_fb_idx - 1].depreciation_start_date + != getdate(self.finance_books[manual_fb_idx - 1].depreciation_start_date) + or old_asset_doc.finance_books[manual_fb_idx - 1].expected_value_after_useful_life + != self.finance_books[manual_fb_idx - 1].expected_value_after_useful_life + ): + return True + + return False + def validate_item(self): item = frappe.get_cached_value( "Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1 @@ -549,11 +577,7 @@ class Asset(AccountsController): def set_accumulated_depreciation( self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False ): - straight_line_idx = [ - d.idx - for d in self.get("schedules") - if d.depreciation_method == "Straight Line" or d.depreciation_method == "Manual" - ] + straight_line_idx = [] finance_books = [] for i, d in enumerate(self.get("schedules")): @@ -561,6 +585,12 @@ class Asset(AccountsController): continue if int(d.finance_book_id) not in finance_books: + straight_line_idx = [ + s.idx + for s in self.get("schedules") + if s.finance_book_id == d.finance_book_id + and (s.depreciation_method == "Straight Line" or s.depreciation_method == "Manual") + ] accumulated_depreciation = flt(self.opening_accumulated_depreciation) value_after_depreciation = flt( self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation From 9a607b9bd072b3496e865ec188581dc016dca899 Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sat, 25 Feb 2023 21:17:18 +0530 Subject: [PATCH 18/30] chore: should prepare schedule if not draft (cherry picked from commit 75386e3653657ec26d444b73e3032cbf51f123c3) --- erpnext/assets/doctype/asset/asset.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index b2d1c7349bb..15c20a59a88 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -99,6 +99,9 @@ class Asset(AccountsController): old_asset_doc = self.get_doc_before_save() + if not old_asset_doc: + return True + if ( old_asset_doc.gross_purchase_amount != self.gross_purchase_amount or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation From 9942a9d40a300661a4c3c2f5fe0756c4715f7e9d Mon Sep 17 00:00:00 2001 From: anandbaburajan Date: Sun, 26 Feb 2023 15:03:16 +0530 Subject: [PATCH 19/30] chore: refactor long if conditions (cherry picked from commit d56ca011fef658c4030e3c326bc5658c86100716) --- erpnext/assets/doctype/asset/asset.py | 30 +++++++++++++++------------ 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 15c20a59a88..d44e2ec0c5d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -102,29 +102,33 @@ class Asset(AccountsController): if not old_asset_doc: return True - if ( + have_asset_details_been_modified = ( old_asset_doc.gross_purchase_amount != self.gross_purchase_amount or old_asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation or old_asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked - ): + ) + + if have_asset_details_been_modified: return True manual_fb_idx = -1 for d in self.finance_books: if d.depreciation_method == "Manual": - manual_fb_idx = d.idx + manual_fb_idx = d.idx - 1 - if ( + no_manual_depr_or_have_manual_depr_details_been_modified = ( manual_fb_idx == -1 - or old_asset_doc.finance_books[manual_fb_idx - 1].total_number_of_depreciations - != self.finance_books[manual_fb_idx - 1].total_number_of_depreciations - or old_asset_doc.finance_books[manual_fb_idx - 1].frequency_of_depreciation - != self.finance_books[manual_fb_idx - 1].frequency_of_depreciation - or old_asset_doc.finance_books[manual_fb_idx - 1].depreciation_start_date - != getdate(self.finance_books[manual_fb_idx - 1].depreciation_start_date) - or old_asset_doc.finance_books[manual_fb_idx - 1].expected_value_after_useful_life - != self.finance_books[manual_fb_idx - 1].expected_value_after_useful_life - ): + or old_asset_doc.finance_books[manual_fb_idx].total_number_of_depreciations + != self.finance_books[manual_fb_idx].total_number_of_depreciations + or old_asset_doc.finance_books[manual_fb_idx].frequency_of_depreciation + != self.finance_books[manual_fb_idx].frequency_of_depreciation + or old_asset_doc.finance_books[manual_fb_idx].depreciation_start_date + != getdate(self.finance_books[manual_fb_idx].depreciation_start_date) + or old_asset_doc.finance_books[manual_fb_idx].expected_value_after_useful_life + != self.finance_books[manual_fb_idx].expected_value_after_useful_life + ) + + if no_manual_depr_or_have_manual_depr_details_been_modified: return True return False From 3d7b2b1a6d41b9de3d40e67493a2223b86b2960f Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Feb 2023 14:11:53 +0530 Subject: [PATCH 20/30] fix: permission error while calling get_work_order_items (cherry picked from commit b6bad728cdff7fd79f175dbd5b631f9bd5234ed7) --- .../doctype/sales_order/sales_order.js | 23 ++-- .../doctype/sales_order/sales_order.py | 102 ++++++++++-------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 1a40725552d..dd88b6c498f 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -280,9 +280,12 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( make_work_order() { var me = this; - this.frm.call({ - doc: this.frm.doc, - method: 'get_work_order_items', + me.frm.call({ + method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items", + args: { + sales_order: this.frm.docname, + }, + freeze: true, callback: function(r) { if(!r.message) { frappe.msgprint({ @@ -292,14 +295,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( }); return; } - else if(!r.message) { - frappe.msgprint({ - title: __('Work Order not created'), - message: __('Work Order already created for all items with BOM'), - indicator: 'orange' - }); - return; - } else { + else { const fields = [{ label: 'Items', fieldtype: 'Table', @@ -400,9 +396,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( make_raw_material_request: function() { var me = this; this.frm.call({ - doc: this.frm.doc, - method: 'get_work_order_items', + method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items", args: { + sales_order: this.frm.docname, for_raw_material_request: 1 }, callback: function(r) { @@ -421,6 +417,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( }, make_raw_material_request_dialog: function(r) { + var me = this; var fields = [ {fieldtype:'Check', fieldname:'include_exploded_items', label: __('Include Exploded Items')}, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 8b28e02797d..865b9585618 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -6,11 +6,12 @@ import json import frappe import frappe.utils -from frappe import _ +from frappe import _, qb from frappe.contacts.doctype.address.address import get_company_address from frappe.desk.notifications import clear_doctype_notifications from frappe.model.mapper import get_mapped_doc from frappe.model.utils import get_fetch_values +from frappe.query_builder.functions import Sum from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html from six import string_types @@ -481,51 +482,6 @@ class SalesOrder(SellingController): self.indicator_color = "green" self.indicator_title = _("Paid") - @frappe.whitelist() - def get_work_order_items(self, for_raw_material_request=0): - """Returns items with BOM that already do not have a linked work order""" - items = [] - item_codes = [i.item_code for i in self.items] - product_bundle_parents = [ - pb.new_item_code - for pb in frappe.get_all( - "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"] - ) - ] - - for table in [self.items, self.packed_items]: - for i in table: - bom = get_default_bom(i.item_code) - stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty - - if not for_raw_material_request: - total_work_order_qty = flt( - frappe.db.sql( - """select sum(qty) from `tabWork Order` - where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""", - (i.item_code, self.name, i.name), - )[0][0] - ) - pending_qty = stock_qty - total_work_order_qty - else: - pending_qty = stock_qty - - if pending_qty and i.item_code not in product_bundle_parents: - items.append( - dict( - name=i.name, - item_code=i.item_code, - description=i.description, - bom=bom or "", - warehouse=i.warehouse, - pending_qty=pending_qty, - required_qty=pending_qty if for_raw_material_request else 0, - sales_order_item=i.name, - ) - ) - - return items - def on_recurring(self, reference_doc, auto_repeat_doc): def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date): delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date) @@ -1399,3 +1355,57 @@ def update_produced_qty_in_so_item(sales_order, sales_order_item): return frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty) + + +@frappe.whitelist() +def get_work_order_items(sales_order, for_raw_material_request=0): + """Returns items with BOM that already do not have a linked work order""" + if sales_order: + so = frappe.get_doc("Sales Order", sales_order) + + wo = qb.DocType("Work Order") + + items = [] + item_codes = [i.item_code for i in so.items] + product_bundle_parents = [ + pb.new_item_code + for pb in frappe.get_all( + "Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"] + ) + ] + + for table in [so.items, so.packed_items]: + for i in table: + bom = get_default_bom(i.item_code) + stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty + + if not for_raw_material_request: + total_work_order_qty = flt( + qb.from_(wo) + .select(Sum(wo.qty)) + .where( + (wo.production_item == i.item_code) + & (wo.sales_order == so.name) * (wo.sales_order_item == i.name) + & (wo.docstatus.lte(2)) + ) + .run()[0][0] + ) + pending_qty = stock_qty - total_work_order_qty + else: + pending_qty = stock_qty + + if pending_qty and i.item_code not in product_bundle_parents: + items.append( + dict( + name=i.name, + item_code=i.item_code, + description=i.description, + bom=bom or "", + warehouse=i.warehouse, + pending_qty=pending_qty, + required_qty=pending_qty if for_raw_material_request else 0, + sales_order_item=i.name, + ) + ) + + return items From 7971c149eda88f4f8c10cfd847f48401fb92764a Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Fri, 24 Feb 2023 20:21:58 +0530 Subject: [PATCH 21/30] fix(test): use standalone method to fetch work orders from SO (cherry picked from commit a11d3327dfb5b016b2ff7ca33a5a0431d6e91019) --- .../selling/doctype/sales_order/test_sales_order.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index a1c6cb5cd6c..e551f83db86 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -1211,6 +1211,8 @@ class TestSalesOrder(FrappeTestCase): self.assertTrue(si.get("payment_schedule")) def test_make_work_order(self): + from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items + # Make a new Sales Order so = make_sales_order( **{ @@ -1224,7 +1226,7 @@ class TestSalesOrder(FrappeTestCase): # Raise Work Orders po_items = [] so_item_name = {} - for item in so.get_work_order_items(): + for item in get_work_order_items(so.name): po_items.append( { "warehouse": item.get("warehouse"), @@ -1415,6 +1417,7 @@ class TestSalesOrder(FrappeTestCase): from erpnext.controllers.item_variant import create_variant from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items make_item( # template item "Test-WO-Tshirt", @@ -1454,7 +1457,7 @@ class TestSalesOrder(FrappeTestCase): ] } ) - wo_items = so.get_work_order_items() + wo_items = get_work_order_items(so.name) self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R") self.assertEqual(wo_items[0].get("bom"), red_var_bom.name) @@ -1464,6 +1467,8 @@ class TestSalesOrder(FrappeTestCase): self.assertEqual(wo_items[1].get("bom"), template_bom.name) def test_request_for_raw_materials(self): + from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items + item = make_item( "_Test Finished Item", { @@ -1496,7 +1501,7 @@ class TestSalesOrder(FrappeTestCase): so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]}) so.submit() mr_dict = frappe._dict() - items = so.get_work_order_items(1) + items = get_work_order_items(so.name, 1) mr_dict["items"] = items mr_dict["include_exploded_items"] = 0 mr_dict["ignore_existing_ordered_qty"] = 1 From d44da6c8206ea9564331e9df823b6867a248c968 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 11:50:12 +0530 Subject: [PATCH 22/30] fix: german translations (#31732) fix: german translations (#31732) (cherry picked from commit 6b510546ae52ee62da359f53df8204de0b160e34) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/accounts/doctype/sales_invoice/sales_invoice.js | 2 +- erpnext/translations/de.csv | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index c765e4f07ff..462233524f8 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -1078,7 +1078,7 @@ var select_loyalty_program = function(frm, loyalty_programs) { ] }); - dialog.set_primary_action(__("Set"), function() { + dialog.set_primary_action(__("Set Loyalty Program"), function() { dialog.hide(); return frappe.call({ method: "frappe.client.set_value", diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index 0b173b65a9e..41eb62b3f26 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -4047,7 +4047,7 @@ Server Error,Serverfehler, Service Level Agreement has been changed to {0}.,Service Level Agreement wurde in {0} geändert., Service Level Agreement was reset.,Service Level Agreement wurde zurückgesetzt., Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Service Level Agreement mit Entitätstyp {0} und Entität {1} ist bereits vorhanden., -Set,Menge, +Set Loyalty Program,Treueprogramm eintragen, Set Meta Tags,Festlegen von Meta-Tags, Set {0} in company {1},{0} in Firma {1} festlegen, Setup,Einstellungen, @@ -4227,10 +4227,8 @@ To date cannot be before From date,Bis-Datum kann nicht vor Von-Datum liegen, Write Off,Abschreiben, {0} Created,{0} Erstellt, Email Id,E-Mail-ID, -No,Kein, Reference Doctype,Referenz-DocType, User Id,Benutzeridentifikation, -Yes,Ja, Actual ,Tatsächlich, Add to cart,In den Warenkorb legen, Budget,Budget, From 45645c10644beca26e4d1787adc09a1a0ae2f45a Mon Sep 17 00:00:00 2001 From: Brian Pond <19827963+brian-pond@users.noreply.github.com> Date: Sat, 25 Feb 2023 12:41:23 -0800 Subject: [PATCH 23/30] fix: Remove missing DocField in fetch_from (cherry picked from commit 83f3e317e1ad29a1c8303a56070d601c3d102608) --- .../maintenance_visit_purpose.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json index 158f143ae86..ba053555531 100644 --- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json +++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json @@ -64,8 +64,6 @@ "fieldtype": "Section Break" }, { - "fetch_from": "prevdoc_detail_docname.sales_person", - "fetch_if_empty": 1, "fieldname": "service_person", "fieldtype": "Link", "in_list_view": 1, @@ -110,13 +108,15 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2021-05-27 17:47:21.474282", + "modified": "2023-02-27 11:09:33.114458", "modified_by": "Administrator", "module": "Maintenance", "name": "Maintenance Visit Purpose", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file From b1ecca3a164ac849653ff86aacfc986c7aac572c Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Thu, 23 Feb 2023 12:26:19 +0530 Subject: [PATCH 24/30] fix: set `from_warehouse` and `to_warehouse` while mapping SE (cherry picked from commit c09a61f360bad2faa12ac1118665d71203061361) # Conflicts: # erpnext/stock/doctype/material_request/material_request.py --- .../material_request/material_request.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index c1201ef8f9a..1f9451a87d2 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -594,6 +594,9 @@ def make_stock_entry(source_name, target_doc=None): def set_missing_values(source, target): target.purpose = source.material_request_type + target.from_warehouse = source.set_from_warehouse + target.to_warehouse = source.set_warehouse + if source.job_card: target.purpose = "Material Transfer for Manufacture" @@ -721,3 +724,18 @@ def create_pick_list(source_name, target_doc=None): doc.set_item_locations() return doc +<<<<<<< HEAD +======= + + +@frappe.whitelist() +def make_in_transit_stock_entry(source_name, in_transit_warehouse): + ste_doc = make_stock_entry(source_name) + ste_doc.add_to_transit = 1 + ste_doc.to_warehouse = in_transit_warehouse + + for row in ste_doc.items: + row.t_warehouse = in_transit_warehouse + + return ste_doc +>>>>>>> c09a61f360 (fix: set `from_warehouse` and `to_warehouse` while mapping SE) From a71a336e593fead77b914736622df9168521bcdd Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Mon, 27 Feb 2023 17:32:05 +0530 Subject: [PATCH 25/30] chore: `conflicts` --- .../doctype/material_request/material_request.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 1f9451a87d2..4697e36778d 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -724,18 +724,3 @@ def create_pick_list(source_name, target_doc=None): doc.set_item_locations() return doc -<<<<<<< HEAD -======= - - -@frappe.whitelist() -def make_in_transit_stock_entry(source_name, in_transit_warehouse): - ste_doc = make_stock_entry(source_name) - ste_doc.add_to_transit = 1 - ste_doc.to_warehouse = in_transit_warehouse - - for row in ste_doc.items: - row.t_warehouse = in_transit_warehouse - - return ste_doc ->>>>>>> c09a61f360 (fix: set `from_warehouse` and `to_warehouse` while mapping SE) From de631e65cc0d15ed6616d20000a5d3248095006d Mon Sep 17 00:00:00 2001 From: Vishal Date: Tue, 14 Feb 2023 18:43:48 +0530 Subject: [PATCH 26/30] fix: multiple pos conversion issue resolved (cherry picked from commit 1de531e56e8820b19f3aa85cc194e7174dc4d678) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 6 +++--- erpnext/selling/page/point_of_sale/pos_controller.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 6f1434d429b..0c2700eaa21 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -161,7 +161,7 @@ class POSInvoice(SalesInvoice): bold_item_name = frappe.bold(item.item_name) bold_extra_batch_qty_needed = frappe.bold( - abs(available_batch_qty - reserved_batch_qty - item.qty) + abs(available_batch_qty - reserved_batch_qty - item.qty * item.conversion_factor) ) bold_invalid_batch_no = frappe.bold(item.batch_no) @@ -172,7 +172,7 @@ class POSInvoice(SalesInvoice): ).format(item.idx, bold_invalid_batch_no, bold_item_name), title=_("Item Unavailable"), ) - elif (available_batch_qty - reserved_batch_qty - item.qty) < 0: + elif (available_batch_qty - reserved_batch_qty - item.qty * item.conversion_factor) < 0: frappe.throw( _( "Row #{}: Batch No. {} of item {} has less than required stock available, {} more required" @@ -246,7 +246,7 @@ class POSInvoice(SalesInvoice): ), title=_("Item Unavailable"), ) - elif is_stock_item and flt(available_stock) < flt(d.qty): + elif is_stock_item and flt(available_stock) < flt(d.qty * d.conversion_factor): frappe.throw( _( "Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}." diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 595b9196e84..77d8fd8f403 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -522,7 +522,7 @@ erpnext.PointOfSale.Controller = class { const from_selector = field === 'qty' && value === "+1"; if (from_selector) - value = flt(item_row.qty) + flt(value); + value = flt(item_row.qty * item_row.conversion_factor) + flt(value); if (item_row_exists) { if (field === 'qty') From 1ebf2dd2bf525d99273e9022cb6b0b8904937ce0 Mon Sep 17 00:00:00 2001 From: Vishal Date: Thu, 23 Feb 2023 12:23:05 +0530 Subject: [PATCH 27/30] chore: minor changes added to code (cherry picked from commit 3ebe7d861d45a6e0b83bcf5df275923bb882c5e0) --- erpnext/accounts/doctype/pos_invoice/pos_invoice.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 0c2700eaa21..85d8efe2c49 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -161,7 +161,7 @@ class POSInvoice(SalesInvoice): bold_item_name = frappe.bold(item.item_name) bold_extra_batch_qty_needed = frappe.bold( - abs(available_batch_qty - reserved_batch_qty - item.qty * item.conversion_factor) + abs(available_batch_qty - reserved_batch_qty - item.stock_qty) ) bold_invalid_batch_no = frappe.bold(item.batch_no) @@ -172,7 +172,7 @@ class POSInvoice(SalesInvoice): ).format(item.idx, bold_invalid_batch_no, bold_item_name), title=_("Item Unavailable"), ) - elif (available_batch_qty - reserved_batch_qty - item.qty * item.conversion_factor) < 0: + elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0: frappe.throw( _( "Row #{}: Batch No. {} of item {} has less than required stock available, {} more required" @@ -652,7 +652,7 @@ def get_bundle_availability(bundle_item_code, warehouse): item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse) available_qty = item_bin_qty - item_pos_reserved_qty - max_available_bundles = available_qty / item.qty + max_available_bundles = available_qty / item.stock_qty if bundle_bin_qty > max_available_bundles and frappe.get_value( "Item", item.item_code, "is_stock_item" ): From c66dc5658f6421719a1c1cea918dd31aea187773 Mon Sep 17 00:00:00 2001 From: Vishal Date: Thu, 23 Feb 2023 12:26:42 +0530 Subject: [PATCH 28/30] chore: minor change (cherry picked from commit a51bec02690120c55bd265bae23d34e113a652bd) --- 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 85d8efe2c49..64071597eab 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -246,7 +246,7 @@ class POSInvoice(SalesInvoice): ), title=_("Item Unavailable"), ) - elif is_stock_item and flt(available_stock) < flt(d.qty * d.conversion_factor): + elif is_stock_item and flt(available_stock) < flt(d.stock_qty): frappe.throw( _( "Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}." From fd1d2cd203a758890aeb5c95f8bc66ca4e41c174 Mon Sep 17 00:00:00 2001 From: Vishal Date: Tue, 28 Feb 2023 11:01:54 +0530 Subject: [PATCH 29/30] chore: minor changes in pos_controller (cherry picked from commit f18ae5856f0bd8594dd7141b2b85b7ab6a02497b) --- erpnext/selling/page/point_of_sale/pos_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js index 77d8fd8f403..da798ab6d2d 100644 --- a/erpnext/selling/page/point_of_sale/pos_controller.js +++ b/erpnext/selling/page/point_of_sale/pos_controller.js @@ -522,7 +522,7 @@ erpnext.PointOfSale.Controller = class { const from_selector = field === 'qty' && value === "+1"; if (from_selector) - value = flt(item_row.qty * item_row.conversion_factor) + flt(value); + value = flt(item_row.stock_qty) + flt(value); if (item_row_exists) { if (field === 'qty') From f6607a60501297dca70322cbe0d375323fba3fc4 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Tue, 28 Feb 2023 12:05:10 +0530 Subject: [PATCH 30/30] fix: pos return throwing amount greater than grand total (cherry picked from commit 35c70f39fa28860b6c22dc3091eadb79c727a4de) --- erpnext/public/js/controllers/taxes_and_totals.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index be4eab03386..79196c976f8 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -124,8 +124,8 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item)); } else { - let qty = item.qty || 1; - qty = me.frm.doc.is_return ? -1 * qty : qty; + // allow for '0' qty on Credit/Debit notes + let qty = item.qty || -1 item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item)); }