From 906ac093e37694c4616cffb961b131ce9002315b Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Fri, 8 Dec 2023 14:56:10 +0100
Subject: [PATCH 01/27] feat: copy emails from lead to customer
---
erpnext/selling/doctype/customer/customer.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index efb9820016..d44532adc5 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -229,6 +229,7 @@ class Customer(TransactionBase):
if self.flags.is_new_doc:
self.link_lead_address_and_contact()
+ self.copy_communication()
self.update_customer_groups()
@@ -290,6 +291,17 @@ class Customer(TransactionBase):
linked_doc.append("links", dict(link_doctype="Customer", link_name=self.name))
linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
+ def copy_communication(self):
+ if not self.lead_name or not frappe.db.get_single_value(
+ "CRM Settings", "carry_forward_communication_and_comments"
+ ):
+ return
+
+ from erpnext.crm.utils import copy_comments, link_communications
+
+ copy_comments("Lead", self.lead_name, self)
+ link_communications("Lead", self.lead_name, self)
+
def validate_name_with_customer_group(self):
if frappe.db.exists("Customer Group", self.name):
frappe.throw(
From c648090b5d18c58af658ec4fe88114741efa60c7 Mon Sep 17 00:00:00 2001
From: Gursheen Anand
Date: Fri, 12 Jan 2024 18:33:06 +0530
Subject: [PATCH 02/27] fix: query for filter by party
---
.../tax_withholding_details.py | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
index d045d91f52..613d6f9fca 100644
--- a/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
@@ -340,9 +340,6 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
if filters.get("to_date"):
query = query.where(gle.posting_date <= filters.get("to_date"))
- if bank_accounts:
- query = query.where(gle.against.notin(bank_accounts))
-
if filters.get("party"):
party = [filters.get("party")]
jv_condition = gle.against.isin(party) | (
@@ -354,7 +351,14 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
(gle.voucher_type == "Journal Entry")
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
)
- query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
+
+ query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
+ if bank_accounts:
+ query = query.where(
+ gle.against.notin(bank_accounts) & (gle.account.isin(tds_accounts) & jv_condition)
+ | gle.party.isin(party)
+ )
+
return query
From e9526b112d2610293411ada5a98ee847dd511306 Mon Sep 17 00:00:00 2001
From: Gursheen Anand
Date: Sun, 21 Jan 2024 18:04:47 +0530
Subject: [PATCH 03/27] test: journals in withholding report
---
.../test_tax_withholding_details.py | 34 +++++++++++++------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
index b3f67378a9..88321e928c 100644
--- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
@@ -5,9 +5,8 @@ import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import today
-from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
-from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import (
create_tax_withholding_category,
@@ -17,7 +16,7 @@ from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.accounts.utils import get_fiscal_year
-class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
+class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.clear_old_entries()
@@ -27,13 +26,25 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
def test_tax_withholding_for_customers(self):
si = create_sales_invoice(rate=1000)
pe = create_tcs_payment_entry()
+ jv = make_journal_entry(
+ "TCS - _TC",
+ "Debtors - _TC",
+ 1000,
+ "_Test Cost Center - _TC",
+ save=False,
+ )
+ jv.accounts[1].party_type = "Customer"
+ jv.accounts[1].party = "_Test Customer"
+ jv.submit()
+
filters = frappe._dict(
company="_Test Company", party_type="Customer", from_date=today(), to_date=today()
)
result = execute(filters)[1]
expected_values = [
+ [jv.name, "TCS", 0.075, 1000, -1000, 1000],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
- [si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
+ [si.name, "TCS", 0.075, 1000, 0.525, 1000.525],
]
self.check_expected_values(result, expected_values)
@@ -41,12 +52,15 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
for i in range(len(result)):
voucher = frappe._dict(result[i])
voucher_expected_values = expected_values[i]
- self.assertEqual(voucher.ref_no, voucher_expected_values[0])
- self.assertEqual(voucher.section_code, voucher_expected_values[1])
- self.assertEqual(voucher.rate, voucher_expected_values[2])
- self.assertEqual(voucher.base_total, voucher_expected_values[3])
- self.assertAlmostEqual(voucher.tax_amount, voucher_expected_values[4])
- self.assertAlmostEqual(voucher.grand_total, voucher_expected_values[5])
+ voucher_actual_values = (
+ voucher.ref_no,
+ voucher.section_code,
+ voucher.rate,
+ voucher.base_total,
+ voucher.tax_amount,
+ voucher.grand_total,
+ )
+ self.assertSequenceEqual(voucher_actual_values, voucher_expected_values)
def tearDown(self):
self.clear_old_entries()
From ddecbeba756b741950f4233a3355b07add285880 Mon Sep 17 00:00:00 2001
From: Gursheen Anand
Date: Tue, 30 Jan 2024 12:39:40 +0530
Subject: [PATCH 04/27] fix: test JV totals using back calculation logic
---
.../test_tax_withholding_details.py | 43 ++++++++++++++-----
1 file changed, 32 insertions(+), 11 deletions(-)
diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
index 88321e928c..6825b4dcf6 100644
--- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
@@ -26,23 +26,15 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
def test_tax_withholding_for_customers(self):
si = create_sales_invoice(rate=1000)
pe = create_tcs_payment_entry()
- jv = make_journal_entry(
- "TCS - _TC",
- "Debtors - _TC",
- 1000,
- "_Test Cost Center - _TC",
- save=False,
- )
- jv.accounts[1].party_type = "Customer"
- jv.accounts[1].party = "_Test Customer"
- jv.submit()
+ jv = create_tcs_journal_entry()
filters = frappe._dict(
company="_Test Company", party_type="Customer", from_date=today(), to_date=today()
)
result = execute(filters)[1]
expected_values = [
- [jv.name, "TCS", 0.075, 1000, -1000, 1000],
+ # Check for JV totals using back calculation logic
+ [jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
[si.name, "TCS", 0.075, 1000, 0.525, 1000.525],
]
@@ -123,3 +115,32 @@ def create_tcs_payment_entry():
)
payment_entry.submit()
return payment_entry
+
+
+def create_tcs_journal_entry():
+ jv = frappe.new_doc("Journal Entry")
+ jv.posting_date = today()
+ jv.company = "_Test Company"
+ jv.set(
+ "accounts",
+ [
+ {
+ "account": "Debtors - _TC",
+ "party_type": "Customer",
+ "party": "_Test Customer",
+ "credit_in_account_currency": 10000,
+ },
+ {
+ "account": "Debtors - _TC",
+ "party_type": "Customer",
+ "party": "_Test Customer",
+ "debit_in_account_currency": 9992.5,
+ },
+ {
+ "account": "TCS - _TC",
+ "debit_in_account_currency": 7.5,
+ },
+ ],
+ )
+ jv.insert()
+ return jv.submit()
From 1ff473b615fddfffb928fcb67fa79de3efe6671b Mon Sep 17 00:00:00 2001
From: RitvikSardana
Date: Wed, 31 Jan 2024 11:34:20 +0530
Subject: [PATCH 05/27] fix: add ignore_permissions flag while creating a
payment entry
---
.../accounts/doctype/journal_entry/journal_entry.py | 6 ++++--
.../accounts/doctype/payment_entry/payment_entry.py | 13 +++++++++----
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index f722c907be..689dbe376b 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1155,7 +1155,9 @@ class JournalEntry(AccountsController):
@frappe.whitelist()
-def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
+def get_default_bank_cash_account(
+ company, account_type=None, mode_of_payment=None, account=None, ignore_permissions=False
+):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
if mode_of_payment:
@@ -1193,7 +1195,7 @@ def get_default_bank_cash_account(company, account_type=None, mode_of_payment=No
return frappe._dict(
{
"account": account,
- "balance": get_balance_on(account),
+ "balance": get_balance_on(account, ignore_account_permission=ignore_permissions),
"account_currency": account_details.account_currency,
"account_type": account_details.account_type,
}
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7e88b6b7c9..309f072d27 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -2220,6 +2220,7 @@ def get_payment_entry(
party_type=None,
payment_type=None,
reference_date=None,
+ ignore_permissions=False,
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
@@ -2242,14 +2243,14 @@ def get_payment_entry(
)
# bank or cash
- bank = get_bank_cash_account(doc, bank_account)
+ bank = get_bank_cash_account(doc, bank_account, ignore_permissions=ignore_permissions)
# if default bank or cash account is not set in company master and party has default company bank account, fetch it
if party_type in ["Customer", "Supplier"] and not bank:
party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
if party_bank_account:
account = frappe.db.get_value("Bank Account", party_bank_account, "account")
- bank = get_bank_cash_account(doc, account)
+ bank = get_bank_cash_account(doc, account, ignore_permissions=ignore_permissions)
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
@@ -2389,9 +2390,13 @@ def update_accounting_dimensions(pe, doc):
pe.set(dimension, doc.get(dimension))
-def get_bank_cash_account(doc, bank_account):
+def get_bank_cash_account(doc, bank_account, ignore_permissions=False):
bank = get_default_bank_cash_account(
- doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"), account=bank_account
+ doc.company,
+ "Bank",
+ mode_of_payment=doc.get("mode_of_payment"),
+ account=bank_account,
+ ignore_permissions=ignore_permissions,
)
if not bank:
From 951023f434a36ef03f874b3dcbd4f995168b7b5a Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Wed, 31 Jan 2024 11:32:17 +0530
Subject: [PATCH 06/27] perf: timeout for auto material request through reorder
level
---
erpnext/controllers/selling_controller.py | 2 +-
.../doctype/stock_entry/test_stock_entry.py | 42 +++++
erpnext/stock/get_item_details.py | 7 +-
erpnext/stock/reorder_item.py | 163 +++++++++++++-----
4 files changed, 172 insertions(+), 42 deletions(-)
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 8c438420ad..dc49023149 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -599,7 +599,7 @@ class SellingController(StockController):
if self.doctype in ["Sales Order", "Quotation"]:
for item in self.items:
item.gross_profit = flt(
- ((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
+ ((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item)
)
def set_customer_address(self):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 23dacc8343..9f3435ec31 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1785,6 +1785,48 @@ class TestStockEntry(FrappeTestCase):
self.assertRaises(frappe.ValidationError, se1.cancel)
+ def test_auto_reorder_level(self):
+ from erpnext.stock.reorder_item import reorder_item
+
+ item_doc = make_item(
+ "Test Auto Reorder Item - 001",
+ properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
+ uoms=[{"uom": "Nos", "conversion_factor": 5}],
+ )
+
+ if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
+ item_doc.append(
+ "reorder_levels",
+ {
+ "warehouse_reorder_level": 0,
+ "warehouse_reorder_qty": 10,
+ "warehouse": "_Test Warehouse - _TC",
+ "material_request_type": "Purchase",
+ },
+ )
+
+ item_doc.save(ignore_permissions=True)
+
+ frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
+
+ mr_list = reorder_item()
+
+ frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
+ mrs = frappe.get_all(
+ "Material Request Item",
+ fields=["qty", "stock_uom", "stock_qty"],
+ filters={"item_code": item_doc.name, "uom": "Nos"},
+ )
+
+ for mri in mrs:
+ self.assertEqual(mri.stock_uom, "Kg")
+ self.assertEqual(mri.stock_qty, 10)
+ self.assertEqual(mri.qty, 2)
+
+ for mr in mr_list:
+ mr.cancel()
+ mr.delete()
+
def make_serialized_item(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index ebcdd11bf1..693ccaa317 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -86,7 +86,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
get_party_item_code(args, item, out)
- set_valuation_rate(out, args)
+ if args.get("doctype") in ["Sales Order", "Quotation"]:
+ set_valuation_rate(out, args)
update_party_blanket_order(args, out)
@@ -269,7 +270,9 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not item:
item = frappe.get_doc("Item", args.get("item_code"))
- if item.variant_of and not item.taxes:
+ if (
+ item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
+ ):
item.update_template_tables()
item_defaults = get_item_defaults(item.name, args.company)
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 276531ab5c..59f8b20b41 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -34,73 +34,157 @@ def _reorder_item():
erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
)
- items_to_consider = frappe.db.sql_list(
- """select name from `tabItem` item
- where is_stock_item=1 and has_variants=0
- and disabled=0
- and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
- and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
- or (variant_of is not null and variant_of != ''
- and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
- )""",
- {"today": nowdate()},
- )
+ items_to_consider = get_items_for_reorder()
if not items_to_consider:
return
item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
- def add_to_material_request(
- item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
- ):
- if warehouse not in warehouse_company:
+ def add_to_material_request(**kwargs):
+ if isinstance(kwargs, dict):
+ kwargs = frappe._dict(kwargs)
+
+ if kwargs.warehouse not in warehouse_company:
# a disabled warehouse
return
- reorder_level = flt(reorder_level)
- reorder_qty = flt(reorder_qty)
+ reorder_level = flt(kwargs.reorder_level)
+ reorder_qty = flt(kwargs.reorder_qty)
# projected_qty will be 0 if Bin does not exist
- if warehouse_group:
- projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
+ if kwargs.warehouse_group:
+ projected_qty = flt(
+ item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
+ )
else:
- projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
+ projected_qty = flt(
+ item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
+ )
if (reorder_level or reorder_qty) and projected_qty <= reorder_level:
deficiency = reorder_level - projected_qty
if deficiency > reorder_qty:
reorder_qty = deficiency
- company = warehouse_company.get(warehouse) or default_company
+ company = warehouse_company.get(kwargs.warehouse) or default_company
- material_requests[material_request_type].setdefault(company, []).append(
- {"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
+ material_requests[kwargs.material_request_type].setdefault(company, []).append(
+ {
+ "item_code": kwargs.item_code,
+ "warehouse": kwargs.warehouse,
+ "reorder_qty": reorder_qty,
+ "item_details": kwargs.item_details,
+ }
)
- for item_code in items_to_consider:
- item = frappe.get_doc("Item", item_code)
+ for item_code, reorder_levels in items_to_consider.items():
+ for d in reorder_levels:
+ if d.has_variants:
+ continue
- if item.variant_of and not item.get("reorder_levels"):
- item.update_template_tables()
-
- if item.get("reorder_levels"):
- for d in item.get("reorder_levels"):
- add_to_material_request(
- item_code,
- d.warehouse,
- d.warehouse_reorder_level,
- d.warehouse_reorder_qty,
- d.material_request_type,
- warehouse_group=d.warehouse_group,
- )
+ add_to_material_request(
+ item_code=item_code,
+ warehouse=d.warehouse,
+ reorder_level=d.warehouse_reorder_level,
+ reorder_qty=d.warehouse_reorder_qty,
+ material_request_type=d.material_request_type,
+ warehouse_group=d.warehouse_group,
+ item_details=frappe._dict(
+ {
+ "item_code": item_code,
+ "name": item_code,
+ "item_name": d.item_name,
+ "item_group": d.item_group,
+ "brand": d.brand,
+ "description": d.description,
+ "stock_uom": d.stock_uom,
+ "purchase_uom": d.purchase_uom,
+ }
+ ),
+ )
if material_requests:
return create_material_request(material_requests)
+def get_items_for_reorder() -> dict[str, list]:
+ reorder_table = frappe.qb.DocType("Item Reorder")
+ item_table = frappe.qb.DocType("Item")
+
+ query = (
+ frappe.qb.from_(reorder_table)
+ .inner_join(item_table)
+ .on(reorder_table.parent == item_table.name)
+ .select(
+ reorder_table.warehouse,
+ reorder_table.warehouse_group,
+ reorder_table.material_request_type,
+ reorder_table.warehouse_reorder_level,
+ reorder_table.warehouse_reorder_qty,
+ item_table.name,
+ item_table.stock_uom,
+ item_table.purchase_uom,
+ item_table.description,
+ item_table.item_name,
+ item_table.item_group,
+ item_table.brand,
+ item_table.variant_of,
+ item_table.has_variants,
+ )
+ .where(
+ (item_table.disabled == 0)
+ & (item_table.is_stock_item == 1)
+ & (
+ (item_table.end_of_life.isnull())
+ | (item_table.end_of_life > nowdate())
+ | (item_table.end_of_life == "0000-00-00")
+ )
+ )
+ )
+
+ data = query.run(as_dict=True)
+ itemwise_reorder = frappe._dict({})
+ for d in data:
+ itemwise_reorder.setdefault(d.name, []).append(d)
+
+ itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)
+
+ return itemwise_reorder
+
+
+def get_reorder_levels_for_variants(itemwise_reorder):
+ item_table = frappe.qb.DocType("Item")
+
+ query = (
+ frappe.qb.from_(item_table)
+ .select(
+ item_table.name,
+ item_table.variant_of,
+ )
+ .where(
+ (item_table.disabled == 0)
+ & (item_table.is_stock_item == 1)
+ & (
+ (item_table.end_of_life.isnull())
+ | (item_table.end_of_life > nowdate())
+ | (item_table.end_of_life == "0000-00-00")
+ )
+ & (item_table.variant_of.notnull())
+ )
+ )
+
+ variants_item = query.run(as_dict=True)
+ for row in variants_item:
+ if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
+ itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))
+
+ return itemwise_reorder
+
+
def get_item_warehouse_projected_qty(items_to_consider):
item_warehouse_projected_qty = {}
+ items_to_consider = list(items_to_consider.keys())
for item_code, warehouse, projected_qty in frappe.db.sql(
"""select item_code, warehouse, projected_qty
@@ -164,7 +248,7 @@ def create_material_request(material_requests):
for d in items:
d = frappe._dict(d)
- item = frappe.get_doc("Item", d.item_code)
+ item = d.get("item_details")
uom = item.stock_uom
conversion_factor = 1.0
@@ -190,6 +274,7 @@ def create_material_request(material_requests):
"item_code": d.item_code,
"schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
"qty": qty,
+ "conversion_factor": conversion_factor,
"uom": uom,
"stock_uom": item.stock_uom,
"warehouse": d.warehouse,
From d78a1e78148a3702990ae347a2b1c31b580c308e Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Thu, 1 Feb 2024 19:29:06 +0530
Subject: [PATCH 07/27] fix: incorrect landed cost voucher amount
---
.../doctype/landed_cost_voucher/landed_cost_voucher.py | 7 +++++++
.../stock/doctype/purchase_receipt/purchase_receipt.py | 10 +++++-----
2 files changed, 12 insertions(+), 5 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 b4f7708e0b..dec75066ec 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -149,6 +149,13 @@ class LandedCostVoucher(Document):
self.get("items")[item_count - 1].applicable_charges += diff
def validate_applicable_charges_for_item(self):
+ if self.distribute_charges_based_on == "Distribute Manually" and len(self.taxes) > 1:
+ frappe.throw(
+ _(
+ "Please keep one Applicable Charges, when 'Distribute Charges Based On' is 'Distribute Manually'. For more charges, please create another Landed Cost Voucher."
+ )
+ )
+
based_on = self.distribute_charges_based_on.lower()
if based_on != "distribute manually":
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index fcb7a6d510..c2890d8d86 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -1360,16 +1360,16 @@ def get_item_account_wise_additional_cost(purchase_document):
for lcv in landed_cost_vouchers:
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
+ based_on_field = None
# Use amount field for total item cost for manually cost distributed LCVs
- if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually":
- based_on_field = "amount"
- else:
+ if landed_cost_voucher_doc.distribute_charges_based_on != "Distribute Manually":
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
total_item_cost = 0
- for item in landed_cost_voucher_doc.items:
- total_item_cost += item.get(based_on_field)
+ if based_on_field:
+ for item in landed_cost_voucher_doc.items:
+ total_item_cost += item.get(based_on_field)
for item in landed_cost_voucher_doc.items:
if item.receipt_document == purchase_document:
From 7c6a5a0f23b948953815870b726c30b0fd076338 Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Fri, 2 Feb 2024 13:18:52 +0530
Subject: [PATCH 08/27] fix: remove pricing rule
---
erpnext/controllers/accounts_controller.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index a0569ec63e..e2b0ee5fe4 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -693,7 +693,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted
- ret = get_item_details(args, self, for_validate=True, overwrite_warehouse=False)
+ ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
From 2caa2d677c09793f7097c3e76ebb4180a3f2a336 Mon Sep 17 00:00:00 2001
From: ruthra kumar
Date: Fri, 2 Feb 2024 17:45:50 +0530
Subject: [PATCH 09/27] refactor: ensure unique accounts for each Bank
Account's
---
.../accounts/doctype/bank_account/bank_account.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/erpnext/accounts/doctype/bank_account/bank_account.py b/erpnext/accounts/doctype/bank_account/bank_account.py
index ace4bb193d..df4bd5655a 100644
--- a/erpnext/accounts/doctype/bank_account/bank_account.py
+++ b/erpnext/accounts/doctype/bank_account/bank_account.py
@@ -9,6 +9,7 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact,
)
from frappe.model.document import Document
+from frappe.utils import comma_and, get_link_to_form
class BankAccount(Document):
@@ -52,6 +53,17 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
+ self.validate_account()
+
+ def validate_account(self):
+ if self.account:
+ if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, as_list=1):
+ frappe.throw(
+ _("'{0}' account is already used by {1}. Use another account.").format(
+ frappe.bold(self.account),
+ frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
+ )
+ )
def validate_company(self):
if self.is_company_account and not self.company:
From 93259cab1d65c2e0c92be4c7eff3c67e75afa5d9 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Sat, 3 Feb 2024 04:42:28 +0100
Subject: [PATCH 10/27] fix(Bank Statement Import): scheduler not needed in dev
mode (#39678)
---
.../doctype/bank_statement_import/bank_statement_import.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index 1a4747c55b..30e564c803 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -80,7 +80,8 @@ class BankStatementImport(DataImport):
from frappe.utils.background_jobs import is_job_enqueued
from frappe.utils.scheduler import is_scheduler_inactive
- if is_scheduler_inactive() and not frappe.flags.in_test:
+ run_now = frappe.flags.in_test or frappe.conf.developer_mode
+ if is_scheduler_inactive() and not run_now:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
job_id = f"bank_statement_import::{self.name}"
@@ -97,7 +98,7 @@ class BankStatementImport(DataImport):
google_sheets_url=self.google_sheets_url,
bank=self.bank,
template_options=self.template_options,
- now=frappe.conf.developer_mode or frappe.flags.in_test,
+ now=run_now,
)
return True
From a9a2ec81de73cde0995c837e12cd5dc79a584841 Mon Sep 17 00:00:00 2001
From: ruthra kumar
Date: Sat, 3 Feb 2024 10:58:31 +0530
Subject: [PATCH 11/27] refactor(test): generate uniq GL acc and Bank acc for
each test case
---
.../bank_transaction/test_bank_transaction.py | 72 ++++++++++++-------
1 file changed, 46 insertions(+), 26 deletions(-)
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 7bb3f4183b..1fe3608f56 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -32,8 +32,16 @@ class TestBankTransaction(FrappeTestCase):
frappe.db.delete(dt)
clear_loan_transactions()
make_pos_profile()
- add_transactions()
- add_vouchers()
+
+ # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
+ uniq_identifier = frappe.generate_hash(length=10)
+ gl_account = create_gl_account("_Test Bank " + uniq_identifier)
+ bank_account = create_bank_account(
+ gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
+ )
+
+ add_transactions(bank_account=bank_account)
+ add_vouchers(gl_account=gl_account)
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
@@ -219,7 +227,9 @@ def clear_loan_transactions():
frappe.db.delete("Loan Repayment")
-def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
+def create_bank_account(
+ bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
+):
try:
frappe.get_doc(
{
@@ -231,21 +241,35 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
pass
try:
- frappe.get_doc(
+ bank_account = frappe.get_doc(
{
"doctype": "Bank Account",
- "account_name": "Checking Account",
+ "account_name": bank_account_name,
"bank": bank_name,
- "account": account_name,
+ "account": gl_account,
}
).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
+ return bank_account.name
-def add_transactions():
- create_bank_account()
+def create_gl_account(gl_account_name="_Test Bank - _TC"):
+ gl_account = frappe.get_doc(
+ {
+ "doctype": "Account",
+ "company": "_Test Company",
+ "parent_account": "Current Assets - _TC",
+ "account_type": "Bank",
+ "is_group": 0,
+ "account_name": gl_account_name,
+ }
+ ).insert()
+ return gl_account.name
+
+
+def add_transactions(bank_account="_Test Bank - _TC"):
doc = frappe.get_doc(
{
"doctype": "Bank Transaction",
@@ -253,7 +277,7 @@ def add_transactions():
"date": "2018-10-23",
"deposit": 1200,
"currency": "INR",
- "bank_account": "Checking Account - Citi Bank",
+ "bank_account": bank_account,
}
).insert()
doc.submit()
@@ -265,7 +289,7 @@ def add_transactions():
"date": "2018-10-23",
"deposit": 1700,
"currency": "INR",
- "bank_account": "Checking Account - Citi Bank",
+ "bank_account": bank_account,
}
).insert()
doc.submit()
@@ -277,7 +301,7 @@ def add_transactions():
"date": "2018-10-26",
"withdrawal": 690,
"currency": "INR",
- "bank_account": "Checking Account - Citi Bank",
+ "bank_account": bank_account,
}
).insert()
doc.submit()
@@ -289,7 +313,7 @@ def add_transactions():
"date": "2018-10-27",
"deposit": 3900,
"currency": "INR",
- "bank_account": "Checking Account - Citi Bank",
+ "bank_account": bank_account,
}
).insert()
doc.submit()
@@ -301,13 +325,13 @@ def add_transactions():
"date": "2018-10-27",
"withdrawal": 109080,
"currency": "INR",
- "bank_account": "Checking Account - Citi Bank",
+ "bank_account": bank_account,
}
).insert()
doc.submit()
-def add_vouchers():
+def add_vouchers(gl_account="_Test Bank - _TC"):
try:
frappe.get_doc(
{
@@ -323,7 +347,7 @@ def add_vouchers():
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
- pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+ pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
@@ -342,14 +366,14 @@ def add_vouchers():
pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
- pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+ pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24"
pe.insert()
pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
- pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+ pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01"
pe.insert()
@@ -380,10 +404,10 @@ def add_vouchers():
pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
- pi.cash_bank_account = "_Test Bank - _TC"
+ pi.cash_bank_account = gl_account
pi.insert()
pi.submit()
- pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
+ pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.paid_amount = 690
@@ -392,7 +416,7 @@ def add_vouchers():
pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
- pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+ pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account)
pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28"
pe.insert()
@@ -415,16 +439,12 @@ def add_vouchers():
if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
- mode_of_payment.append(
- "accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
- )
+ mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1
- si.append(
- "payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
- )
+ si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
si.insert()
si.submit()
From 322cdbaccf0b8697000aae4e56efa659a34fa8e5 Mon Sep 17 00:00:00 2001
From: ruthra kumar
Date: Sat, 3 Feb 2024 12:08:01 +0530
Subject: [PATCH 12/27] refactor(test): make use of test fixtures in Payment
Order
---
.../payment_order/test_payment_order.py | 32 ++++++++++++-------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.py b/erpnext/accounts/doctype/payment_order/test_payment_order.py
index 0dcb1794b9..60f288e1f0 100644
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.py
+++ b/erpnext/accounts/doctype/payment_order/test_payment_order.py
@@ -4,9 +4,13 @@
import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import getdate
-from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
+from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
+ create_bank_account,
+ create_gl_account,
+)
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_payment_entry,
make_payment_order,
@@ -14,28 +18,32 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
-class TestPaymentOrder(unittest.TestCase):
+class TestPaymentOrder(FrappeTestCase):
def setUp(self):
- create_bank_account()
+ # generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
+ uniq_identifier = frappe.generate_hash(length=10)
+ self.gl_account = create_gl_account("_Test Bank " + uniq_identifier)
+ self.bank_account = create_bank_account(
+ gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier
+ )
def tearDown(self):
- for bt in frappe.get_all("Payment Order"):
- doc = frappe.get_doc("Payment Order", bt.name)
- doc.cancel()
- doc.delete()
+ frappe.db.rollback()
def test_payment_order_creation_against_payment_entry(self):
purchase_invoice = make_purchase_invoice()
payment_entry = get_payment_entry(
- "Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC"
+ "Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account
)
payment_entry.reference_no = "_Test_Payment_Order"
payment_entry.reference_date = getdate()
- payment_entry.party_bank_account = "Checking Account - Citi Bank"
+ payment_entry.party_bank_account = self.bank_account
payment_entry.insert()
payment_entry.submit()
- doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
+ doc = create_payment_order_against_payment_entry(
+ payment_entry, "Payment Entry", self.bank_account
+ )
reference_doc = doc.get("references")[0]
self.assertEqual(reference_doc.reference_name, payment_entry.name)
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
@@ -43,13 +51,13 @@ class TestPaymentOrder(unittest.TestCase):
self.assertEqual(reference_doc.amount, 250)
-def create_payment_order_against_payment_entry(ref_doc, order_type):
+def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
payment_order = frappe.get_doc(
dict(
doctype="Payment Order",
company="_Test Company",
payment_order_type=order_type,
- company_bank_account="Checking Account - Citi Bank",
+ company_bank_account=bank_account,
)
)
doc = make_payment_order(ref_doc.name, payment_order)
From d9a72c1e614a8d103ecb8eb13db26cff18981a52 Mon Sep 17 00:00:00 2001
From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
Date: Sat, 3 Feb 2024 13:05:41 +0530
Subject: [PATCH 13/27] feat: reference for POS SI payments (#39523)
* feat: reference field in SI payment
* fix: document link for pos si
* refactor: pos invoice queries
---
.../doctype/bank_clearance/bank_clearance.py | 89 ++++++++++++-------
.../sales_invoice_payment.json | 11 ++-
.../sales_invoice_payment.py | 1 +
3 files changed, 67 insertions(+), 34 deletions(-)
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 4b97619f29..8a505a8dee 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -5,7 +5,9 @@
import frappe
from frappe import _, msgprint
from frappe.model.document import Document
+from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, getdate
+from pypika import Order
import erpnext
@@ -179,39 +181,62 @@ def get_payment_entries_for_bank_clearance(
pos_sales_invoices, pos_purchase_invoices = [], []
if include_pos_transactions:
- pos_sales_invoices = frappe.db.sql(
- """
- select
- "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
- si.posting_date, si.customer as against_account, sip.clearance_date,
- account.account_currency, 0 as credit
- from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
- where
- sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
- and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
- order by
- si.posting_date ASC, si.name DESC
- """,
- {"account": account, "from": from_date, "to": to_date},
- as_dict=1,
- )
+ si_payment = frappe.qb.DocType("Sales Invoice Payment")
+ si = frappe.qb.DocType("Sales Invoice")
+ acc = frappe.qb.DocType("Account")
- pos_purchase_invoices = frappe.db.sql(
- """
- select
- "Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
- pi.posting_date, pi.supplier as against_account, pi.clearance_date,
- account.account_currency, 0 as debit
- from `tabPurchase Invoice` pi, `tabAccount` account
- where
- pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
- and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
- order by
- pi.posting_date ASC, pi.name DESC
- """,
- {"account": account, "from": from_date, "to": to_date},
- as_dict=1,
- )
+ pos_sales_invoices = (
+ frappe.qb.from_(si_payment)
+ .inner_join(si)
+ .on(si_payment.parent == si.name)
+ .inner_join(acc)
+ .on(si_payment.account == acc.name)
+ .select(
+ ConstantColumn("Sales Invoice").as_("payment_document"),
+ si.name.as_("payment_entry"),
+ si_payment.reference_no.as_("cheque_number"),
+ si_payment.amount.as_("debit"),
+ si.posting_date,
+ si.customer.as_("against_account"),
+ si_payment.clearance_date,
+ acc.account_currency,
+ ConstantColumn(0).as_("credit"),
+ )
+ .where(
+ (si.docstatus == 1)
+ & (si_payment.account == account)
+ & (si.posting_date >= from_date)
+ & (si.posting_date <= to_date)
+ )
+ .orderby(si.posting_date)
+ .orderby(si.name, order=Order.desc)
+ ).run(as_dict=True)
+
+ pi = frappe.qb.DocType("Purchase Invoice")
+
+ pos_purchase_invoices = (
+ frappe.qb.from_(pi)
+ .inner_join(acc)
+ .on(pi.cash_bank_account == acc.name)
+ .select(
+ ConstantColumn("Purchase Invoice").as_("payment_document"),
+ pi.name.as_("payment_entry"),
+ pi.paid_amount.as_("credit"),
+ pi.posting_date,
+ pi.supplier.as_("against_account"),
+ pi.clearance_date,
+ acc.account_currency,
+ ConstantColumn(0).as_("debit"),
+ )
+ .where(
+ (pi.docstatus == 1)
+ & (pi.cash_bank_account == account)
+ & (pi.posting_date >= from_date)
+ & (pi.posting_date <= to_date)
+ )
+ .orderby(pi.posting_date)
+ .orderby(pi.name, order=Order.desc)
+ ).run(as_dict=True)
entries = (
list(payment_entries)
diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
index 5ab46b7fd5..bd59f65dd4 100644
--- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
+++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
@@ -8,6 +8,7 @@
"default",
"mode_of_payment",
"amount",
+ "reference_no",
"column_break_3",
"account",
"type",
@@ -75,11 +76,16 @@
"hidden": 1,
"label": "Default",
"read_only": 1
+ },
+ {
+ "fieldname": "reference_no",
+ "fieldtype": "Data",
+ "label": "Reference No"
}
],
"istable": 1,
"links": [],
- "modified": "2020-08-03 12:45:39.986598",
+ "modified": "2024-01-23 16:20:06.436979",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
@@ -87,5 +93,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py
index 57d0142406..e460a01155 100644
--- a/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py
+++ b/erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.py
@@ -23,6 +23,7 @@ class SalesInvoicePayment(Document):
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
+ reference_no: DF.Data | None
type: DF.ReadOnly | None
# end: auto-generated types
From c81d597ca510706cf3ca7230f21b1630e7515bf5 Mon Sep 17 00:00:00 2001
From: Vishnu VS
Date: Sun, 4 Feb 2024 12:17:18 +0530
Subject: [PATCH 14/27] fix(work order): resolve type error during job card
creation (#39713)
fix: type error
---
erpnext/manufacturing/doctype/work_order/work_order.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index aa7bc5bf76..39beb361de 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -1511,14 +1511,14 @@ def get_serial_nos_for_work_order(work_order, production_item):
def validate_operation_data(row):
- if row.get("qty") <= 0:
+ if flt(row.get("qty")) <= 0:
frappe.throw(
_("Quantity to Manufacture can not be zero for the operation {0}").format(
frappe.bold(row.get("operation"))
)
)
- if row.get("qty") > row.get("pending_qty"):
+ if flt(row.get("qty")) > flt(row.get("pending_qty")):
frappe.throw(
_("For operation {0}: Quantity ({1}) can not be greater than pending quantity({2})").format(
frappe.bold(row.get("operation")),
From 407045a1dee4d5d1e4f08f0e78871942ded9e709 Mon Sep 17 00:00:00 2001
From: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
Date: Sun, 4 Feb 2024 23:01:51 +0530
Subject: [PATCH 15/27] fix: production plan date filters for orders (#39702)
---
.../doctype/production_plan/production_plan.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 5dc5c38376..f0392be01c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1334,10 +1334,10 @@ def get_sales_orders(self):
)
date_field_mapper = {
- "from_date": self.from_date >= so.transaction_date,
- "to_date": self.to_date <= so.transaction_date,
- "from_delivery_date": self.from_delivery_date >= so_item.delivery_date,
- "to_delivery_date": self.to_delivery_date <= so_item.delivery_date,
+ "from_date": so.transaction_date >= self.from_date,
+ "to_date": so.transaction_date <= self.to_date,
+ "from_delivery_date": so_item.delivery_date >= self.from_delivery_date,
+ "to_delivery_date": so_item.delivery_date <= self.to_delivery_date,
}
for field, value in date_field_mapper.items():
From b70f3de16be169a723842a3a99c046a3809d0768 Mon Sep 17 00:00:00 2001
From: rohitwaghchaure
Date: Mon, 5 Feb 2024 11:46:39 +0530
Subject: [PATCH 16/27] perf: memory consumption for the stock balance report
(#39626)
---
.../report/stock_balance/stock_balance.py | 32 ++++++++++++-------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index ed84a5c2d5..269323810b 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -90,8 +90,7 @@ class StockBalanceReport(object):
self.opening_data.setdefault(group_by_key, entry)
def prepare_new_data(self):
- if not self.sle_entries:
- return
+ self.item_warehouse_map = self.get_item_warehouse_map()
if self.filters.get("show_stock_ageing_data"):
self.filters["show_warehouse_wise_stock"] = True
@@ -99,7 +98,8 @@ class StockBalanceReport(object):
_func = itemgetter(1)
- self.item_warehouse_map = self.get_item_warehouse_map()
+ del self.sle_entries
+
sre_details = self.get_sre_reserved_qty_details()
variant_values = {}
@@ -143,15 +143,22 @@ class StockBalanceReport(object):
item_warehouse_map = {}
self.opening_vouchers = self.get_opening_vouchers()
- for entry in self.sle_entries:
- group_by_key = self.get_group_by_key(entry)
- if group_by_key not in item_warehouse_map:
- self.initialize_data(item_warehouse_map, group_by_key, entry)
+ if self.filters.get("show_stock_ageing_data"):
+ self.sle_entries = self.sle_query.run(as_dict=True)
- self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
+ with frappe.db.unbuffered_cursor():
+ if not self.filters.get("show_stock_ageing_data"):
+ self.sle_entries = self.sle_query.run(as_dict=True, as_iterator=True)
- if self.opening_data.get(group_by_key):
- del self.opening_data[group_by_key]
+ for entry in self.sle_entries:
+ group_by_key = self.get_group_by_key(entry)
+ if group_by_key not in item_warehouse_map:
+ self.initialize_data(item_warehouse_map, group_by_key, entry)
+
+ self.prepare_item_warehouse_map(item_warehouse_map, entry, group_by_key)
+
+ if self.opening_data.get(group_by_key):
+ del self.opening_data[group_by_key]
for group_by_key, entry in self.opening_data.items():
if group_by_key not in item_warehouse_map:
@@ -252,7 +259,8 @@ class StockBalanceReport(object):
.where(
(table.docstatus == 1)
& (table.company == self.filters.company)
- & ((table.to_date <= self.from_date))
+ & (table.to_date <= self.from_date)
+ & (table.status == "Completed")
)
.orderby(table.to_date, order=Order.desc)
.limit(1)
@@ -305,7 +313,7 @@ class StockBalanceReport(object):
if self.filters.get("company"):
query = query.where(sle.company == self.filters.get("company"))
- self.sle_entries = query.run(as_dict=True)
+ self.sle_query = query
def apply_inventory_dimensions_filters(self, query, sle) -> str:
inventory_dimension_fields = self.get_inventory_dimension_fields()
From 7a04f0f7bab1bc36774d41b236e8c4421a6305bd Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Mon, 5 Feb 2024 12:35:26 +0530
Subject: [PATCH 17/27] fix: update company in serial no doc
---
erpnext/stock/serial_batch_bundle.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 4cfe5d817e..78df755d74 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -283,6 +283,7 @@ class SerialBatchBundle:
if (sn_table.purchase_document_no != self.sle.voucher_no and self.sle.is_cancelled != 1)
else "Inactive",
)
+ .set(sn_table.company, self.sle.company)
.where(sn_table.name.isin(serial_nos))
).run()
From 25c2b79864ba96b43388a235b34c5b55e2898a8a Mon Sep 17 00:00:00 2001
From: Gursheen Anand
Date: Mon, 5 Feb 2024 13:03:21 +0530
Subject: [PATCH 18/27] fix: precision for tds amount
---
.../tax_withholding_details/test_tax_withholding_details.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
index 6825b4dcf6..af55ba6639 100644
--- a/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
+++ b/erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
@@ -36,7 +36,7 @@ class TestTaxWithholdingDetails(AccountsTestMixin, FrappeTestCase):
# Check for JV totals using back calculation logic
[jv.name, "TCS", 0.075, -10000.0, -7.5, -10000.0],
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
- [si.name, "TCS", 0.075, 1000, 0.525, 1000.525],
+ [si.name, "TCS", 0.075, 1000, 0.52, 1000.52],
]
self.check_expected_values(result, expected_values)
From b834ed10d6b6faa1f76e1343ca8b347ca393b7dd Mon Sep 17 00:00:00 2001
From: Deepesh Garg
Date: Mon, 5 Feb 2024 14:05:01 +0530
Subject: [PATCH 19/27] perf: Move dimension validation out of GL Entry doctype
(#39730)
---
erpnext/accounts/doctype/gl_entry/gl_entry.py | 46 +------------------
erpnext/accounts/general_ledger.py | 42 +++++++++++++++++
2 files changed, 43 insertions(+), 45 deletions(-)
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 777a5bb91c..def2838b75 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -13,16 +13,9 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
-from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
- get_dimension_filter_map,
-)
from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
-from erpnext.exceptions import (
- InvalidAccountCurrency,
- InvalidAccountDimensionError,
- MandatoryAccountDimensionError,
-)
+from erpnext.exceptions import InvalidAccountCurrency
exclude_from_linked_with = True
@@ -98,7 +91,6 @@ class GLEntry(Document):
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
- self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
@@ -208,42 +200,6 @@ class GLEntry(Document):
)
)
- def validate_allowed_dimensions(self):
- dimension_filter_map = get_dimension_filter_map()
- for key, value in dimension_filter_map.items():
- dimension = key[0]
- account = key[1]
-
- if self.account == account:
- if value["is_mandatory"] and not self.get(dimension):
- frappe.throw(
- _("{0} is mandatory for account {1}").format(
- frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
- ),
- MandatoryAccountDimensionError,
- )
-
- if value["allow_or_restrict"] == "Allow":
- if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
- frappe.throw(
- _("Invalid value {0} for {1} against account {2}").format(
- frappe.bold(self.get(dimension)),
- frappe.bold(frappe.unscrub(dimension)),
- frappe.bold(self.account),
- ),
- InvalidAccountDimensionError,
- )
- else:
- if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
- frappe.throw(
- _("Invalid value {0} for {1} against account {2}").format(
- frappe.bold(self.get(dimension)),
- frappe.bold(frappe.unscrub(dimension)),
- frappe.bold(self.account),
- ),
- InvalidAccountDimensionError,
- )
-
def check_pl_account(self):
if (
self.is_opening == "Yes"
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 1c8ac2f164..2e82886755 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -13,9 +13,13 @@ import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
+from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
+ get_dimension_filter_map,
+)
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.utils import create_payment_ledger_entry
+from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
def make_gl_entries(
@@ -355,6 +359,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
process_debit_credit_difference(gl_map)
+ dimension_filter_map = get_dimension_filter_map()
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
@@ -362,6 +367,7 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
for entry in gl_map:
+ validate_allowed_dimensions(entry, dimension_filter_map)
make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -700,3 +706,39 @@ def set_as_cancel(voucher_type, voucher_no):
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
(now(), frappe.session.user, voucher_type, voucher_no),
)
+
+
+def validate_allowed_dimensions(gl_entry, dimension_filter_map):
+ for key, value in dimension_filter_map.items():
+ dimension = key[0]
+ account = key[1]
+
+ if gl_entry.account == account:
+ if value["is_mandatory"] and not gl_entry.get(dimension):
+ frappe.throw(
+ _("{0} is mandatory for account {1}").format(
+ frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
+ ),
+ MandatoryAccountDimensionError,
+ )
+
+ if value["allow_or_restrict"] == "Allow":
+ if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
+ frappe.throw(
+ _("Invalid value {0} for {1} against account {2}").format(
+ frappe.bold(gl_entry.get(dimension)),
+ frappe.bold(frappe.unscrub(dimension)),
+ frappe.bold(gl_entry.account),
+ ),
+ InvalidAccountDimensionError,
+ )
+ else:
+ if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
+ frappe.throw(
+ _("Invalid value {0} for {1} against account {2}").format(
+ frappe.bold(gl_entry.get(dimension)),
+ frappe.bold(frappe.unscrub(dimension)),
+ frappe.bold(gl_entry.account),
+ ),
+ InvalidAccountDimensionError,
+ )
From 6e6c818084ee3fbbb2d44a58acb5c78c9f5b61ac Mon Sep 17 00:00:00 2001
From: Deepesh Garg
Date: Mon, 5 Feb 2024 14:10:42 +0530
Subject: [PATCH 20/27] feat: Period-wise closing entries for TB (#39712)
---
erpnext/accounts/report/trial_balance/trial_balance.js | 10 ++++++++--
erpnext/accounts/report/trial_balance/trial_balance.py | 4 ++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.js b/erpnext/accounts/report/trial_balance/trial_balance.js
index 2c4c762073..5374ac16d1 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.js
+++ b/erpnext/accounts/report/trial_balance/trial_balance.js
@@ -78,8 +78,14 @@ frappe.query_reports["Trial Balance"] = {
"options": erpnext.get_presentation_currency_list()
},
{
- "fieldname": "with_period_closing_entry",
- "label": __("Period Closing Entry"),
+ "fieldname": "with_period_closing_entry_for_opening",
+ "label": __("With Period Closing Entry For Opening Balances"),
+ "fieldtype": "Check",
+ "default": 1
+ },
+ {
+ "fieldname": "with_period_closing_entry_for_current_period",
+ "label": __("Period Closing Entry For Current Period"),
"fieldtype": "Check",
"default": 1
},
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 8b7f0bbc00..2ff0eff662 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -116,7 +116,7 @@ def get_data(filters):
max_rgt,
filters,
gl_entries_by_account,
- ignore_closing_entries=not flt(filters.with_period_closing_entry),
+ ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
ignore_opening_entries=True,
)
@@ -249,7 +249,7 @@ def get_opening_balance(
):
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
- if not flt(filters.with_period_closing_entry):
+ if not flt(filters.with_period_closing_entry_for_opening):
if doctype == "Account Closing Balance":
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
else:
From 5ce5c352e4ab2293b3f5b5dac9bc1a2a1912e620 Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Mon, 5 Feb 2024 11:25:25 +0530
Subject: [PATCH 21/27] fix: disable no-copy for blanket order in PO
---
.../doctype/purchase_order_item/purchase_order_item.json | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 5a24cc2e92..e3e8def7ff 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -545,7 +545,6 @@
"fieldname": "blanket_order",
"fieldtype": "Link",
"label": "Blanket Order",
- "no_copy": 1,
"options": "Blanket Order"
},
{
@@ -553,7 +552,6 @@
"fieldname": "blanket_order_rate",
"fieldtype": "Currency",
"label": "Blanket Order Rate",
- "no_copy": 1,
"print_hide": 1,
"read_only": 1
},
@@ -917,7 +915,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-24 13:24:41.298416",
+ "modified": "2024-02-05 11:23:24.859435",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
From 61ded697a7384d8ef133a42424d8a14763bb6061 Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Mon, 5 Feb 2024 11:42:17 +0530
Subject: [PATCH 22/27] fix: update BO Ordered Quantity on PO Close/Open
---
erpnext/buying/doctype/purchase_order/purchase_order.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 4efbb270e9..656ee9b91c 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -457,6 +457,7 @@ class PurchaseOrder(BuyingController):
self.update_ordered_qty()
self.update_reserved_qty_for_subcontract()
self.update_subcontracting_order_status()
+ self.update_blanket_order()
self.notify_update()
clear_doctype_notifications(self)
From 27d6c8b6d52ada292eef5c42506e95bcf933eec8 Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Mon, 5 Feb 2024 14:08:57 +0530
Subject: [PATCH 23/27] test: BO on PO Close/Open
---
.../purchase_order/test_purchase_order.py | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 5405799b4e..a30de68a00 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -822,6 +822,30 @@ class TestPurchaseOrder(FrappeTestCase):
# To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None)
+ def test_blanket_order_on_po_close_and_open(self):
+ # Step - 1: Create Blanket Order
+ bo = make_blanket_order(blanket_order_type="Purchasing", quantity=10, rate=10)
+
+ # Step - 2: Create Purchase Order
+ po = create_purchase_order(
+ item_code="_Test Item", qty=5, against_blanket_order=1, against_blanket=bo.name
+ )
+
+ bo.load_from_db()
+ self.assertEqual(bo.items[0].ordered_qty, 5)
+
+ # Step - 3: Close Purchase Order
+ po.update_status("Closed")
+
+ bo.load_from_db()
+ self.assertEqual(bo.items[0].ordered_qty, 0)
+
+ # Step - 4: Re-Open Purchase Order
+ po.update_status("Re-open")
+
+ bo.load_from_db()
+ self.assertEqual(bo.items[0].ordered_qty, 5)
+
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
@@ -1148,6 +1172,7 @@ def create_purchase_order(**args):
"schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get("include_exploded_items", 1),
"against_blanket_order": args.against_blanket_order,
+ "against_blanket": args.against_blanket,
"material_request": args.material_request,
"material_request_item": args.material_request_item,
},
From 1fa62333773a59feb5a0a39d274ebcae362ed286 Mon Sep 17 00:00:00 2001
From: Rohit Waghchaure
Date: Mon, 5 Feb 2024 19:26:31 +0530
Subject: [PATCH 24/27] perf: timeout while submitting the purchase receipt
entry
---
erpnext/buying/doctype/purchase_order/purchase_order.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index b830e7d204..32b73dd482 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -640,6 +640,7 @@ class PurchaseOrder(BuyingController):
update_sco_status(sco, "Closed" if self.status == "Closed" else None)
+@frappe.request_cache
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
"""get last purchase rate for an item"""
From ee14faaa39bcb0fb8d813aee7e00eedc8d3a38c1 Mon Sep 17 00:00:00 2001
From: s-aga-r
Date: Mon, 5 Feb 2024 21:53:25 +0530
Subject: [PATCH 25/27] fix: show warehouse title field in sales docs
---
erpnext/controllers/queries.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index e234eec1a6..c46ef50f58 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -729,17 +729,24 @@ def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
conditions, bin_conditions = [], []
filter_dict = get_doctype_wise_filters(filters)
- query = """select `tabWarehouse`.name,
+ warehouse_field = "name"
+ meta = frappe.get_meta("Warehouse")
+ if meta.get("show_title_field_in_link") and meta.get("title_field"):
+ searchfield = meta.get("title_field")
+ warehouse_field = meta.get("title_field")
+
+ query = """select `tabWarehouse`.`{warehouse_field}`,
CONCAT_WS(' : ', 'Actual Qty', ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
from `tabWarehouse` left join `tabBin`
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
where
`tabWarehouse`.`{key}` like {txt}
{fcond} {mcond}
- order by ifnull(`tabBin`.actual_qty, 0) desc
+ order by ifnull(`tabBin`.actual_qty, 0) desc, `tabWarehouse`.`{warehouse_field}` asc
limit
{page_len} offset {start}
""".format(
+ warehouse_field=warehouse_field,
bin_conditions=get_filters_cond(
doctype, filter_dict.get("Bin"), bin_conditions, ignore_permissions=True
),
From 56e5611337faf174771122b45a772a52105ce515 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 6 Feb 2024 11:31:11 +0530
Subject: [PATCH 26/27] chore: update CI badges (#39753)
---
README.md | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 710187ad2f..4f65ceb70b 100644
--- a/README.md
+++ b/README.md
@@ -7,8 +7,7 @@
ERP made simple
-[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
-[](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
+[](https://github.com/frappe/erpnext/actions/workflows/server-tests-mariadb.yml)
[](https://www.codetriage.com/frappe/erpnext)
[](https://codecov.io/gh/frappe/erpnext)
[](https://hub.docker.com/r/frappe/erpnext-worker)
From 798a0510e695ed0556c2ba8713f968a21fe49ef1 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 6 Feb 2024 13:34:28 +0530
Subject: [PATCH 27/27] refactor: change server_action args (#39756)
args are now flat, no need to accept them as dict
ref: https://github.com/frappe/frappe/pull/24782
---
erpnext/selling/doctype/customer/customer.py | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 415216c480..3744922a1a 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -572,15 +572,14 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
@frappe.whitelist()
-def send_emails(args):
- args = json.loads(args)
- subject = _("Credit limit reached for customer {0}").format(args.get("customer"))
+def send_emails(customer, customer_outstanding, credit_limit, credit_controller_users_list):
+ if isinstance(credit_controller_users_list, str):
+ credit_controller_users_list = json.loads(credit_controller_users_list)
+ subject = _("Credit limit reached for customer {0}").format(customer)
message = _("Credit limit has been crossed for customer {0} ({1}/{2})").format(
- args.get("customer"), args.get("customer_outstanding"), args.get("credit_limit")
- )
- frappe.sendmail(
- recipients=args.get("credit_controller_users_list"), subject=subject, message=message
+ customer, customer_outstanding, credit_limit
)
+ frappe.sendmail(recipients=credit_controller_users_list, subject=subject, message=message)
def get_customer_outstanding(