mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 03:09:09 +00:00
Merge pull request #43668 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -538,7 +538,7 @@ def make_payment_request(**args):
|
|||||||
)
|
)
|
||||||
|
|
||||||
party_type = args.get("party_type") or "Customer"
|
party_type = args.get("party_type") or "Customer"
|
||||||
party_account_currency = ref_doc.party_account_currency
|
party_account_currency = ref_doc.get("party_account_currency")
|
||||||
|
|
||||||
if not party_account_currency:
|
if not party_account_currency:
|
||||||
party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
|
party_account = get_party_account(party_type, ref_doc.get(party_type.lower()), ref_doc.company)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
)
|
)
|
||||||
if gle_count > 5000:
|
if gle_count > 5000:
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
make_reverse_gl_entries,
|
process_cancellation,
|
||||||
voucher_type="Period Closing Voucher",
|
voucher_type="Period Closing Voucher",
|
||||||
voucher_no=self.name,
|
voucher_no=self.name,
|
||||||
queue="long",
|
queue="long",
|
||||||
@@ -51,9 +51,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
alert=True,
|
alert=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
|
process_cancellation(voucher_type="Period Closing Voucher", voucher_no=self.name)
|
||||||
|
|
||||||
self.delete_closing_entries()
|
|
||||||
|
|
||||||
def validate_future_closing_vouchers(self):
|
def validate_future_closing_vouchers(self):
|
||||||
if frappe.db.exists(
|
if frappe.db.exists(
|
||||||
@@ -66,12 +64,6 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete_closing_entries(self):
|
|
||||||
closing_balance = frappe.qb.DocType("Account Closing Balance")
|
|
||||||
frappe.qb.from_(closing_balance).delete().where(
|
|
||||||
closing_balance.period_closing_voucher == self.name
|
|
||||||
).run()
|
|
||||||
|
|
||||||
def validate_account_head(self):
|
def validate_account_head(self):
|
||||||
closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
|
closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
|
||||||
|
|
||||||
@@ -136,14 +128,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||||
if len(gl_entries + closing_entries) > 3000:
|
if len(gl_entries + closing_entries) > 3000:
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
process_gl_entries,
|
process_gl_and_closing_entries,
|
||||||
gl_entries=gl_entries,
|
|
||||||
voucher_name=self.name,
|
|
||||||
timeout=3000,
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.enqueue(
|
|
||||||
process_closing_entries,
|
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
closing_entries=closing_entries,
|
closing_entries=closing_entries,
|
||||||
voucher_name=self.name,
|
voucher_name=self.name,
|
||||||
@@ -157,8 +142,9 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
alert=True,
|
alert=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
process_gl_entries(gl_entries, self.name)
|
process_gl_and_closing_entries(
|
||||||
process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
gl_entries, closing_entries, self.name, self.company, self.posting_date
|
||||||
|
)
|
||||||
|
|
||||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||||
closing_entries = []
|
closing_entries = []
|
||||||
@@ -323,9 +309,10 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
|
|
||||||
if get_opening_entries:
|
if get_opening_entries:
|
||||||
query = query.where(
|
query = query.where(
|
||||||
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
|
( # noqa: UP034
|
||||||
| gl_entry.is_opening
|
(gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date))
|
||||||
== "Yes"
|
| (gl_entry.is_opening == "Yes")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query = query.where(
|
query = query.where(
|
||||||
@@ -343,12 +330,16 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
return query.run(as_dict=1)
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def process_gl_entries(gl_entries, voucher_name):
|
def process_gl_and_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||||
|
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||||
|
make_closing_entries,
|
||||||
|
)
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
make_gl_entries(gl_entries, merge_entries=False)
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
|
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
@@ -356,25 +347,21 @@ def process_gl_entries(gl_entries, voucher_name):
|
|||||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||||
|
|
||||||
|
|
||||||
def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
def process_cancellation(voucher_type, voucher_no):
|
||||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
|
||||||
make_closing_entries,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
|
||||||
except Exception as e:
|
|
||||||
frappe.db.rollback()
|
|
||||||
frappe.log_error(e)
|
|
||||||
|
|
||||||
|
|
||||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
|
||||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no)
|
make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no)
|
||||||
|
delete_closing_entries(voucher_no)
|
||||||
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed")
|
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
frappe.log_error(e)
|
frappe.log_error(e)
|
||||||
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed")
|
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_closing_entries(voucher_no):
|
||||||
|
closing_balance = frappe.qb.DocType("Account Closing Balance")
|
||||||
|
frappe.qb.from_(closing_balance).delete().where(
|
||||||
|
closing_balance.period_closing_voucher == voucher_no
|
||||||
|
).run()
|
||||||
|
|||||||
@@ -981,6 +981,12 @@ class TestPricingRule(FrappeTestCase):
|
|||||||
self.assertEqual(so.items[1].item_code, "_Test Item")
|
self.assertEqual(so.items[1].item_code, "_Test Item")
|
||||||
self.assertEqual(so.items[1].qty, 3)
|
self.assertEqual(so.items[1].qty, 3)
|
||||||
|
|
||||||
|
so = make_sales_order(item_code="_Test Item", qty=5, do_not_submit=1)
|
||||||
|
so.items[0].qty = 1
|
||||||
|
del so.items[-1]
|
||||||
|
so.save()
|
||||||
|
self.assertEqual(len(so.items), 1)
|
||||||
|
|
||||||
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
|
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
|
||||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
|
||||||
|
|||||||
@@ -644,6 +644,9 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
|||||||
if pricing_rule.round_free_qty:
|
if pricing_rule.round_free_qty:
|
||||||
qty = math.floor(qty)
|
qty = math.floor(qty)
|
||||||
|
|
||||||
|
if not qty:
|
||||||
|
return
|
||||||
|
|
||||||
free_item_data_args = {
|
free_item_data_args = {
|
||||||
"item_code": free_item,
|
"item_code": free_item,
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
|
from frappe.query_builder.functions import IfNull
|
||||||
|
|
||||||
pricing_rule_fields = [
|
pricing_rule_fields = [
|
||||||
"apply_on",
|
"apply_on",
|
||||||
@@ -91,22 +93,50 @@ class PromotionalScheme(Document):
|
|||||||
if self.is_new():
|
if self.is_new():
|
||||||
return
|
return
|
||||||
|
|
||||||
transaction_exists = False
|
invalid_pricing_rule = self.get_invalid_pricing_rules()
|
||||||
docnames = []
|
|
||||||
|
|
||||||
# If user has changed applicable for
|
if not invalid_pricing_rule:
|
||||||
if self.get_doc_before_save() and self.get_doc_before_save().applicable_for == self.applicable_for:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name})
|
if frappe.db.exists(
|
||||||
|
"Pricing Rule Detail",
|
||||||
|
{
|
||||||
|
"pricing_rule": ["in", invalid_pricing_rule],
|
||||||
|
"docstatus": ["<", 2],
|
||||||
|
},
|
||||||
|
):
|
||||||
|
raise_for_transaction_exists(self.name)
|
||||||
|
|
||||||
for docname in docnames:
|
for doc in invalid_pricing_rule:
|
||||||
if frappe.db.exists("Pricing Rule Detail", {"pricing_rule": docname.name, "docstatus": ("<", 2)}):
|
frappe.delete_doc("Pricing Rule", doc)
|
||||||
raise_for_transaction_exists(self.name)
|
|
||||||
|
|
||||||
if docnames and not transaction_exists:
|
frappe.msgprint(
|
||||||
for docname in docnames:
|
_("The following invalid Pricing Rules are deleted:")
|
||||||
frappe.delete_doc("Pricing Rule", docname.name)
|
+ "<br><br><ul><li>"
|
||||||
|
+ "</li><li>".join(invalid_pricing_rule)
|
||||||
|
+ "</li></ul>"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_invalid_pricing_rules(self):
|
||||||
|
pr = frappe.qb.DocType("Pricing Rule")
|
||||||
|
conditions = []
|
||||||
|
conditions.append(pr.promotional_scheme == self.name)
|
||||||
|
|
||||||
|
if self.applicable_for:
|
||||||
|
applicable_for = frappe.scrub(self.applicable_for)
|
||||||
|
applicable_for_list = [d.get(applicable_for) for d in self.get(applicable_for)]
|
||||||
|
|
||||||
|
conditions.append(
|
||||||
|
(IfNull(pr.applicable_for, "") != self.applicable_for)
|
||||||
|
| (
|
||||||
|
(IfNull(pr.applicable_for, "") == self.applicable_for)
|
||||||
|
& IfNull(pr[applicable_for], "").notin(applicable_for_list)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
conditions.append(IfNull(pr.applicable_for, "") != "")
|
||||||
|
|
||||||
|
return frappe.qb.from_(pr).select(pr.name).where(Criterion.all(conditions)).run(pluck=True)
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.validate()
|
self.validate()
|
||||||
|
|||||||
@@ -90,6 +90,31 @@ class TestPromotionalScheme(unittest.TestCase):
|
|||||||
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
||||||
self.assertEqual(price_rules, [])
|
self.assertEqual(price_rules, [])
|
||||||
|
|
||||||
|
def test_change_applicable_for_values_in_promotional_scheme(self):
|
||||||
|
ps = make_promotional_scheme(applicable_for="Customer", customer="_Test Customer")
|
||||||
|
ps.append("customer", {"customer": "_Test Customer 2"})
|
||||||
|
ps.save()
|
||||||
|
|
||||||
|
price_rules = frappe.get_all(
|
||||||
|
"Pricing Rule", filters={"promotional_scheme": ps.name, "applicable_for": "Customer"}
|
||||||
|
)
|
||||||
|
self.assertTrue(len(price_rules), 2)
|
||||||
|
|
||||||
|
ps.set("customer", [])
|
||||||
|
ps.append("customer", {"customer": "_Test Customer 2"})
|
||||||
|
ps.save()
|
||||||
|
|
||||||
|
price_rules = frappe.get_all(
|
||||||
|
"Pricing Rule",
|
||||||
|
filters={
|
||||||
|
"promotional_scheme": ps.name,
|
||||||
|
"applicable_for": "Customer",
|
||||||
|
"customer": "_Test Customer",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(price_rules, [])
|
||||||
|
frappe.delete_doc("Promotional Scheme", ps.name)
|
||||||
|
|
||||||
def test_min_max_amount_configuration(self):
|
def test_min_max_amount_configuration(self):
|
||||||
ps = make_promotional_scheme()
|
ps = make_promotional_scheme()
|
||||||
ps.price_discount_slabs[0].min_amount = 10
|
ps.price_discount_slabs[0].min_amount = 10
|
||||||
|
|||||||
@@ -578,8 +578,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
|||||||
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||||
conditions.append(ple.company == inv.company)
|
conditions.append(ple.company == inv.company)
|
||||||
|
|
||||||
(qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1))
|
|
||||||
|
|
||||||
advance_amt = (
|
advance_amt = (
|
||||||
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
|
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
"allow_rename": 1,
|
|
||||||
"autoname": "format:UNREC-{#####}",
|
|
||||||
"creation": "2023-08-22 10:26:34.421423",
|
"creation": "2023-08-22 10:26:34.421423",
|
||||||
"default_view": "List",
|
"default_view": "List",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -58,11 +56,10 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-28 17:42:50.261377",
|
"modified": "2024-10-10 12:03:50.022444",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Unreconcile Payment",
|
"name": "Unreconcile Payment",
|
||||||
"naming_rule": "Expression",
|
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
formatter: function (value, row, column, data, default_formatter, filter) {
|
formatter: function (value, row, column, data, default_formatter, filter) {
|
||||||
if (column.fieldname == "payment_entry" && value == "Cheques and Deposits incorrectly cleared") {
|
if (column.fieldname == "payment_entry" && value == __("Cheques and Deposits incorrectly cleared")) {
|
||||||
column.link_onclick =
|
column.link_onclick =
|
||||||
"frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()";
|
"frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -474,10 +474,13 @@ def update_parent_account_names(accounts):
|
|||||||
|
|
||||||
for d in accounts:
|
for d in accounts:
|
||||||
if d.account_number:
|
if d.account_number:
|
||||||
account_name = d.account_number + " - " + d.account_name
|
account_key = d.account_number + " - " + d.account_name
|
||||||
else:
|
else:
|
||||||
account_name = d.account_name
|
account_key = d.account_name
|
||||||
name_to_account_map[d.name] = account_name
|
|
||||||
|
d.account_key = account_key
|
||||||
|
|
||||||
|
name_to_account_map[d.name] = account_key
|
||||||
|
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
if account.parent_account:
|
if account.parent_account:
|
||||||
@@ -510,33 +513,26 @@ def get_subsidiary_companies(company):
|
|||||||
|
|
||||||
def get_accounts(root_type, companies):
|
def get_accounts(root_type, companies):
|
||||||
accounts = []
|
accounts = []
|
||||||
added_accounts = []
|
|
||||||
|
|
||||||
for company in companies:
|
for company in companies:
|
||||||
for account in frappe.get_all(
|
accounts.extend(
|
||||||
"Account",
|
frappe.get_all(
|
||||||
fields=[
|
"Account",
|
||||||
"name",
|
fields=[
|
||||||
"is_group",
|
"name",
|
||||||
"company",
|
"is_group",
|
||||||
"parent_account",
|
"company",
|
||||||
"lft",
|
"parent_account",
|
||||||
"rgt",
|
"lft",
|
||||||
"root_type",
|
"rgt",
|
||||||
"report_type",
|
"root_type",
|
||||||
"account_name",
|
"report_type",
|
||||||
"account_number",
|
"account_name",
|
||||||
],
|
"account_number",
|
||||||
filters={"company": company, "root_type": root_type},
|
],
|
||||||
):
|
filters={"company": company, "root_type": root_type},
|
||||||
if account.account_number:
|
)
|
||||||
account_key = account.account_number + "-" + account.account_name
|
)
|
||||||
else:
|
|
||||||
account_key = account.account_name
|
|
||||||
|
|
||||||
if account_key not in added_accounts:
|
|
||||||
accounts.append(account)
|
|
||||||
added_accounts.append(account_key)
|
|
||||||
|
|
||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
@@ -775,15 +771,17 @@ def add_total_row(out, root_type, balance_must_be, companies, company_currency):
|
|||||||
def filter_accounts(accounts, depth=10):
|
def filter_accounts(accounts, depth=10):
|
||||||
parent_children_map = {}
|
parent_children_map = {}
|
||||||
accounts_by_name = {}
|
accounts_by_name = {}
|
||||||
for d in accounts:
|
added_accounts = []
|
||||||
if d.account_number:
|
|
||||||
account_name = d.account_number + " - " + d.account_name
|
|
||||||
else:
|
|
||||||
account_name = d.account_name
|
|
||||||
d["company_wise_opening_bal"] = defaultdict(float)
|
|
||||||
accounts_by_name[account_name] = d
|
|
||||||
|
|
||||||
parent_children_map.setdefault(d.parent_account or None, []).append(d)
|
for d in accounts:
|
||||||
|
if d.account_key in added_accounts:
|
||||||
|
continue
|
||||||
|
|
||||||
|
added_accounts.append(d.account_key)
|
||||||
|
d["company_wise_opening_bal"] = defaultdict(float)
|
||||||
|
accounts_by_name[d.account_key] = d
|
||||||
|
|
||||||
|
parent_children_map.setdefault(d.parent_account_name or None, []).append(d)
|
||||||
|
|
||||||
filtered_accounts = []
|
filtered_accounts = []
|
||||||
|
|
||||||
@@ -795,7 +793,7 @@ def filter_accounts(accounts, depth=10):
|
|||||||
for child in children:
|
for child in children:
|
||||||
child.indent = level
|
child.indent = level
|
||||||
filtered_accounts.append(child)
|
filtered_accounts.append(child)
|
||||||
add_to_list(child.name, level + 1)
|
add_to_list(child.account_key, level + 1)
|
||||||
|
|
||||||
add_to_list(None, 0)
|
add_to_list(None, 0)
|
||||||
|
|
||||||
|
|||||||
@@ -392,6 +392,7 @@ def create_rfq_items(sq_doc, supplier, data):
|
|||||||
"material_request",
|
"material_request",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
|
"uom",
|
||||||
]:
|
]:
|
||||||
args[field] = data.get(field)
|
args[field] = data.get(field)
|
||||||
|
|
||||||
|
|||||||
@@ -456,6 +456,16 @@ class SellingController(StockController):
|
|||||||
raise_error_if_no_rate=False,
|
raise_error_if_no_rate=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
not d.incoming_rate
|
||||||
|
and self.get("return_against")
|
||||||
|
and self.get("is_return")
|
||||||
|
and get_valuation_method(d.item_code) == "Moving Average"
|
||||||
|
):
|
||||||
|
d.incoming_rate = get_rate_for_return(
|
||||||
|
self.doctype, self.name, d.item_code, self.return_against, item_row=d
|
||||||
|
)
|
||||||
|
|
||||||
# For internal transfers use incoming rate as the valuation rate
|
# For internal transfers use incoming rate as the valuation rate
|
||||||
if self.is_internal_transfer():
|
if self.is_internal_transfer():
|
||||||
if self.doctype == "Delivery Note" or self.get("update_stock"):
|
if self.doctype == "Delivery Note" or self.get("update_stock"):
|
||||||
|
|||||||
@@ -50,6 +50,21 @@ class StockController(AccountsController):
|
|||||||
self.set_rate_of_stock_uom()
|
self.set_rate_of_stock_uom()
|
||||||
self.validate_internal_transfer()
|
self.validate_internal_transfer()
|
||||||
self.validate_putaway_capacity()
|
self.validate_putaway_capacity()
|
||||||
|
self.reset_conversion_factor()
|
||||||
|
|
||||||
|
def reset_conversion_factor(self):
|
||||||
|
for row in self.get("items"):
|
||||||
|
if row.uom != row.stock_uom:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if row.conversion_factor != 1.0:
|
||||||
|
row.conversion_factor = 1.0
|
||||||
|
frappe.msgprint(
|
||||||
|
_(
|
||||||
|
"Conversion factor for item {0} has been reset to 1.0 as the uom {1} is same as stock uom {2}."
|
||||||
|
).format(bold(row.item_code), bold(row.uom), bold(row.stock_uom)),
|
||||||
|
alert=True,
|
||||||
|
)
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False):
|
def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
|||||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||||
from erpnext.stock.get_item_details import get_default_bom, get_price_list_rate
|
from erpnext.stock.get_item_details import get_bin_details, get_default_bom, get_price_list_rate
|
||||||
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
|
from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty
|
||||||
|
|
||||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||||
@@ -607,6 +607,9 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
target.project = source_parent.project
|
target.project = source_parent.project
|
||||||
target.qty = get_remaining_qty(source)
|
target.qty = get_remaining_qty(source)
|
||||||
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
|
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
|
||||||
|
target.actual_qty = get_bin_details(
|
||||||
|
target.item_code, target.warehouse, source_parent.company, True
|
||||||
|
).get("actual_qty", 0)
|
||||||
|
|
||||||
args = target.as_dict().copy()
|
args = target.as_dict().copy()
|
||||||
args.update(
|
args.update(
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from erpnext.selling.doctype.sales_order.sales_order import (
|
|||||||
)
|
)
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
from erpnext.stock.get_item_details import get_bin_details
|
||||||
|
|
||||||
|
|
||||||
class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||||
@@ -99,6 +100,12 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.assertEqual(mr.material_request_type, "Purchase")
|
self.assertEqual(mr.material_request_type, "Purchase")
|
||||||
self.assertEqual(len(mr.get("items")), len(so.get("items")))
|
self.assertEqual(len(mr.get("items")), len(so.get("items")))
|
||||||
|
|
||||||
|
for item in mr.get("items"):
|
||||||
|
actual_qty = get_bin_details(item.item_code, item.warehouse, mr.company, True).get(
|
||||||
|
"actual_qty", 0
|
||||||
|
)
|
||||||
|
self.assertEqual(flt(item.actual_qty), actual_qty)
|
||||||
|
|
||||||
def test_make_delivery_note(self):
|
def test_make_delivery_note(self):
|
||||||
so = make_sales_order(do_not_submit=True)
|
so = make_sales_order(do_not_submit=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1449,6 +1449,46 @@ class TestDeliveryNote(FrappeTestCase):
|
|||||||
self.assertEqual(so.items[0].rate, rate)
|
self.assertEqual(so.items[0].rate, rate)
|
||||||
self.assertEqual(dn.items[0].rate, so.items[0].rate)
|
self.assertEqual(dn.items[0].rate, so.items[0].rate)
|
||||||
|
|
||||||
|
def test_batch_return_dn(self):
|
||||||
|
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Batch Return DN Item 1",
|
||||||
|
properties={
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"valuation_method": "Moving Average",
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TBRDN1-.#####",
|
||||||
|
"is_stock_item": 1,
|
||||||
|
},
|
||||||
|
).name
|
||||||
|
|
||||||
|
se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=5, basic_rate=100)
|
||||||
|
|
||||||
|
batch_no = se.items[0].batch_no
|
||||||
|
dn = create_delivery_note(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=5,
|
||||||
|
rate=500,
|
||||||
|
batch_no=batch_no,
|
||||||
|
)
|
||||||
|
|
||||||
|
dn_return = make_sales_return(dn.name)
|
||||||
|
dn_return.save().submit()
|
||||||
|
|
||||||
|
self.assertEqual(dn_return.items[0].qty, 5 * -1)
|
||||||
|
|
||||||
|
returned_batch_no = dn_return.items[0].batch_no
|
||||||
|
self.assertEqual(batch_no, returned_batch_no)
|
||||||
|
|
||||||
|
stock_value_difference = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"voucher_no": dn_return.name, "voucher_type": "Delivery Note"},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(stock_value_difference, 100.0 * 5)
|
||||||
|
|
||||||
|
|
||||||
def create_delivery_note(**args):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
dn = frappe.new_doc("Delivery Note")
|
||||||
|
|||||||
@@ -2668,6 +2668,21 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(data[0].get("bal_qty"), 50.0)
|
self.assertEqual(data[0].get("bal_qty"), 50.0)
|
||||||
|
|
||||||
|
def test_same_stock_and_transaction_uom_conversion_factor(self):
|
||||||
|
item_code = "Test Item for Same Stock and Transaction UOM Conversion Factor"
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
stock_uom="Nos",
|
||||||
|
transaction_uom="Nos",
|
||||||
|
conversion_factor=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(pr.items[0].conversion_factor, 1.0)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -775,6 +775,15 @@ class update_entries_after:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not rate and sle.voucher_type in ["Delivery Note", "Sales Invoice"]:
|
||||||
|
rate = get_rate_for_return(
|
||||||
|
sle.voucher_type,
|
||||||
|
sle.voucher_no,
|
||||||
|
sle.item_code,
|
||||||
|
voucher_detail_no=sle.voucher_detail_no,
|
||||||
|
sle=sle,
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
rate = get_rate_for_return(
|
rate = get_rate_for_return(
|
||||||
sle.voucher_type,
|
sle.voucher_type,
|
||||||
|
|||||||
Reference in New Issue
Block a user