mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-27 17:04:47 +00:00
Merge branch 'version-16-hotfix' into mergify/bp/version-16-hotfix/pr-55242
This commit is contained in:
@@ -8,6 +8,7 @@ from frappe.utils import today
|
|||||||
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.tests.utils import ERPNextTestSuite
|
from erpnext.tests.utils import ERPNextTestSuite
|
||||||
|
|
||||||
@@ -334,6 +335,48 @@ class TestPeriodClosingVoucher(ERPNextTestSuite):
|
|||||||
|
|
||||||
return pcv
|
return pcv
|
||||||
|
|
||||||
|
@ERPNextTestSuite.change_settings(
|
||||||
|
"Accounts Settings",
|
||||||
|
{"enable_immutable_ledger": 1},
|
||||||
|
)
|
||||||
|
def test_immutable_ledger_reverse_entry_uses_passed_posting_date_after_pcv(self):
|
||||||
|
company = create_company()
|
||||||
|
cost_center = create_cost_center("Test Cost Center 1")
|
||||||
|
|
||||||
|
jv = make_journal_entry(
|
||||||
|
posting_date="2021-03-15",
|
||||||
|
amount=400,
|
||||||
|
account1="Cash - TPC",
|
||||||
|
account2="Sales - TPC",
|
||||||
|
cost_center=cost_center,
|
||||||
|
company=company,
|
||||||
|
save=False,
|
||||||
|
)
|
||||||
|
jv.company = company
|
||||||
|
jv.save()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
self.make_period_closing_voucher(posting_date="2021-03-31")
|
||||||
|
|
||||||
|
# Passed posting_date is after PCV end date, so cancellation should not fail.
|
||||||
|
make_reverse_gl_entries(
|
||||||
|
voucher_type="Journal Entry",
|
||||||
|
voucher_no=jv.name,
|
||||||
|
posting_date="2022-01-01",
|
||||||
|
)
|
||||||
|
|
||||||
|
totals_after_cancel = frappe.db.sql(
|
||||||
|
"""
|
||||||
|
select sum(debit) as total_debit, sum(credit) as total_credit
|
||||||
|
from `tabGL Entry`
|
||||||
|
where voucher_type=%s and voucher_no=%s and is_cancelled=0
|
||||||
|
""",
|
||||||
|
("Journal Entry", jv.name),
|
||||||
|
as_dict=True,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
self.assertEqual(totals_after_cancel.total_debit, totals_after_cancel.total_credit)
|
||||||
|
|
||||||
|
|
||||||
def create_company():
|
def create_company():
|
||||||
company = frappe.get_doc(
|
company = frappe.get_doc(
|
||||||
|
|||||||
@@ -968,9 +968,6 @@ class SalesInvoice(SellingController):
|
|||||||
if selling_price_list:
|
if selling_price_list:
|
||||||
self.set("selling_price_list", selling_price_list)
|
self.set("selling_price_list", selling_price_list)
|
||||||
|
|
||||||
if not for_validate:
|
|
||||||
self.update_stock = cint(pos.get("update_stock"))
|
|
||||||
|
|
||||||
# set pos values in items
|
# set pos values in items
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.get("item_code"):
|
if item.get("item_code"):
|
||||||
@@ -981,6 +978,10 @@ class SalesInvoice(SellingController):
|
|||||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||||
item.set(fname, val)
|
item.set(fname, val)
|
||||||
|
|
||||||
|
if not for_validate:
|
||||||
|
dn_flag = any(d.get("dn_detail") for d in self.get("items"))
|
||||||
|
self.update_stock = 0 if dn_flag else cint(pos.get("update_stock"))
|
||||||
|
|
||||||
# fetch terms
|
# fetch terms
|
||||||
if self.tc_name and not self.terms:
|
if self.tc_name and not self.terms:
|
||||||
self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms")
|
self.terms = frappe.db.get_value("Terms and Conditions", self.tc_name, "terms")
|
||||||
|
|||||||
@@ -719,7 +719,12 @@ def make_reverse_gl_entries(
|
|||||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||||
|
|
||||||
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
|
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
|
||||||
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
|
|
||||||
|
# For reverse entries, use the posting_date parameter if provided and valid
|
||||||
|
# Otherwise fall back to original posting_date
|
||||||
|
validation_date = posting_date if posting_date else gl_entries[0]["posting_date"]
|
||||||
|
validate_against_pcv(is_opening, validation_date, gl_entries[0]["company"])
|
||||||
|
|
||||||
if partial_cancel:
|
if partial_cancel:
|
||||||
# Partial cancel is only used by `Advance` in separate account feature.
|
# Partial cancel is only used by `Advance` in separate account feature.
|
||||||
# Only cancel GL entries for unlinked reference using `voucher_detail_no`
|
# Only cancel GL entries for unlinked reference using `voucher_detail_no`
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ frappe.ui.form.on("Employee", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
frm.fields_dict.date_of_birth.datepicker.update({ maxDate: new Date() });
|
frm.fields_dict.date_of_birth.datepicker?.update({ maxDate: new Date() });
|
||||||
|
|
||||||
if (!frm.is_new() && !frm.doc.user_id) {
|
if (!frm.is_new() && !frm.doc.user_id) {
|
||||||
frm.add_custom_button(__("Create User"), () => {
|
frm.add_custom_button(__("Create User"), () => {
|
||||||
|
|||||||
@@ -1374,7 +1374,7 @@ def get_billed_qty_amount_against_purchase_receipt(pr_doc):
|
|||||||
.on(parent_table.name == table.parent)
|
.on(parent_table.name == table.parent)
|
||||||
.select(
|
.select(
|
||||||
table.pr_detail,
|
table.pr_detail,
|
||||||
fn.Sum(table.amount * parent_table.conversion_rate).as_("amount"),
|
fn.Sum(table.base_net_amount).as_("amount"),
|
||||||
fn.Sum(table.qty).as_("qty"),
|
fn.Sum(table.qty).as_("qty"),
|
||||||
)
|
)
|
||||||
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
||||||
@@ -1420,7 +1420,7 @@ def get_billed_qty_amount_against_purchase_order(pr_doc):
|
|||||||
.select(
|
.select(
|
||||||
table.po_detail,
|
table.po_detail,
|
||||||
fn.Sum(table.qty).as_("qty"),
|
fn.Sum(table.qty).as_("qty"),
|
||||||
fn.Sum(table.amount * parent_table.conversion_rate).as_("amount"),
|
fn.Sum(table.base_net_amount).as_("amount"),
|
||||||
)
|
)
|
||||||
.where((table.po_detail.isin(po_names)) & (table.docstatus == 1) & (table.pr_detail.isnull()))
|
.where((table.po_detail.isin(po_names)) & (table.docstatus == 1) & (table.pr_detail.isnull()))
|
||||||
.groupby(table.po_detail)
|
.groupby(table.po_detail)
|
||||||
|
|||||||
@@ -5606,6 +5606,157 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
|||||||
self.assertIn(company.default_inventory_account, gl_accounts)
|
self.assertIn(company.default_inventory_account, gl_accounts)
|
||||||
pr.cancel()
|
pr.cancel()
|
||||||
|
|
||||||
|
@ERPNextTestSuite.change_settings(
|
||||||
|
"Buying Settings", {"set_landed_cost_based_on_purchase_invoice_rate": 1, "maintain_same_rate": 0}
|
||||||
|
)
|
||||||
|
def test_srbnb_with_inclusive_tax_and_rate_change_in_pi(self):
|
||||||
|
"""
|
||||||
|
When 'Set Landed Cost Based on PI Rate' is enabled and PI has an inclusive tax:
|
||||||
|
- PR: qty=2, rate=1000 INR → base_net_amount=2000
|
||||||
|
- PI: rate changed to 2000, 5% tax included in basic rate
|
||||||
|
→ PI base_net_amount = 2 * 2000 / 1.05 ≈ 3809.52
|
||||||
|
|
||||||
|
The system must use PI's base_net_amount (not amount=4000) so that
|
||||||
|
SRBNB credit on PR = 3809.52, not 4000.
|
||||||
|
"""
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
warehouse = "Stores - TCP1"
|
||||||
|
cost_center = "Main - TCP1"
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Item for SRBNB Inclusive Tax Rate Change",
|
||||||
|
{"is_stock_item": 1},
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=2,
|
||||||
|
rate=1000,
|
||||||
|
company=company,
|
||||||
|
warehouse=warehouse,
|
||||||
|
cost_center=cost_center,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(pr.name)
|
||||||
|
pi.items[0].rate = 2000
|
||||||
|
pi.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account VAT - TCP1",
|
||||||
|
"category": "Total",
|
||||||
|
"add_deduct_tax": "Add",
|
||||||
|
"included_in_print_rate": 1,
|
||||||
|
"rate": 5,
|
||||||
|
"description": "Test Inclusive Tax",
|
||||||
|
"cost_center": cost_center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pr.reload()
|
||||||
|
|
||||||
|
# PI base_net_amount = qty * (rate / (1 + tax_rate/100)) = 2 * (2000 / 1.05)
|
||||||
|
pi_base_net_amount = flt(2 * 2000 / 1.05, 2)
|
||||||
|
pr_base_net_amount = flt(pr.items[0].amount, 2) # 2 * 1000 = 2000
|
||||||
|
expected_diff = flt(pi_base_net_amount - pr_base_net_amount, 2)
|
||||||
|
|
||||||
|
self.assertAlmostEqual(pr.items[0].amount_difference_with_purchase_invoice, expected_diff, places=2)
|
||||||
|
|
||||||
|
# Total SRBNB credit = PR base_net_amount + amount_difference = PI base_net_amount
|
||||||
|
srbnb_account = "Stock Received But Not Billed - TCP1"
|
||||||
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True)
|
||||||
|
srbnb_credit = sum(flt(row.credit) for row in gl_entries if row.account == srbnb_account)
|
||||||
|
self.assertAlmostEqual(srbnb_credit, pi_base_net_amount, places=2)
|
||||||
|
|
||||||
|
@ERPNextTestSuite.change_settings(
|
||||||
|
"Buying Settings", {"set_landed_cost_based_on_purchase_invoice_rate": 1, "maintain_same_rate": 0}
|
||||||
|
)
|
||||||
|
def test_srbnb_with_inclusive_tax_and_exchange_rate_change_in_pi(self):
|
||||||
|
"""
|
||||||
|
When 'Set Landed Cost Based on PI Rate' is enabled, PI has an inclusive tax, and only
|
||||||
|
the exchange rate changes on the PI (rate stays the same):
|
||||||
|
- PR: qty=2, rate=100 USD, conversion_rate=70 → base_net_amount=14000 INR
|
||||||
|
- PI: same rate=100 USD, conversion_rate changed to 90, 5% tax included in basic rate
|
||||||
|
→ PI base_net_amount = 2 * (100 / 1.05) * 90 ≈ 17142.86 INR
|
||||||
|
|
||||||
|
The system must use PI's base_net_amount (not amount = 2*100*90 = 18000) so that
|
||||||
|
SRBNB credit on PR = 17142.86, not 18000.
|
||||||
|
"""
|
||||||
|
from erpnext.accounts.doctype.account.test_account import create_account
|
||||||
|
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
warehouse = "Stores - TCP1"
|
||||||
|
cost_center = "Main - TCP1"
|
||||||
|
|
||||||
|
party_account = create_account(
|
||||||
|
account_name="USD Payable For SRBNB Exchange Rate Test",
|
||||||
|
parent_account="Accounts Payable - TCP1",
|
||||||
|
account_type="Payable",
|
||||||
|
company=company,
|
||||||
|
account_currency="USD",
|
||||||
|
)
|
||||||
|
|
||||||
|
supplier = create_supplier(
|
||||||
|
supplier_name="_Test USD Supplier for SRBNB Exchange Rate",
|
||||||
|
default_currency="USD",
|
||||||
|
party_account=party_account,
|
||||||
|
).name
|
||||||
|
|
||||||
|
item_code = make_item(
|
||||||
|
"Test Item for SRBNB Inclusive Tax Exchange Rate Change",
|
||||||
|
{"is_stock_item": 1},
|
||||||
|
).name
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item_code,
|
||||||
|
qty=2,
|
||||||
|
rate=100,
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=70,
|
||||||
|
company=company,
|
||||||
|
warehouse=warehouse,
|
||||||
|
supplier=supplier,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(pr.name)
|
||||||
|
pi.conversion_rate = 90
|
||||||
|
pi.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "_Test Account VAT - TCP1",
|
||||||
|
"category": "Total",
|
||||||
|
"add_deduct_tax": "Add",
|
||||||
|
"included_in_print_rate": 1,
|
||||||
|
"rate": 5,
|
||||||
|
"description": "Test Inclusive Tax",
|
||||||
|
"cost_center": cost_center,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pi.save()
|
||||||
|
pi.submit()
|
||||||
|
|
||||||
|
pr.reload()
|
||||||
|
|
||||||
|
# PI base_net_amount = qty * (rate / (1 + tax_rate/100)) * new_conversion_rate
|
||||||
|
# = 2 * (100 / 1.05) * 90 ≈ 17142.86 INR
|
||||||
|
# PR base_net_amount = qty * rate * pr_conversion_rate = 2 * 100 * 70 = 14000 INR
|
||||||
|
tax_amount_pr = (200 - flt(200 / 1.05, 2)) * 90
|
||||||
|
|
||||||
|
pi_base_net_amount = flt(2 * 100 * 90) - flt(tax_amount_pr)
|
||||||
|
pr_base_net_amount = flt(2 * 100 * 70)
|
||||||
|
expected_diff = flt(pi_base_net_amount - pr_base_net_amount)
|
||||||
|
|
||||||
|
self.assertAlmostEqual(pr.items[0].amount_difference_with_purchase_invoice, expected_diff, places=2)
|
||||||
|
|
||||||
|
# Total SRBNB credit = PR base_net_amount + amount_difference = PI base_net_amount
|
||||||
|
srbnb_account = "Stock Received But Not Billed - TCP1"
|
||||||
|
gl_entries = get_gl_entries("Purchase Receipt", pr.name, skip_cancelled=True)
|
||||||
|
srbnb_credit = sum(flt(row.credit) for row in gl_entries if row.account == srbnb_account)
|
||||||
|
self.assertAlmostEqual(srbnb_credit, pi_base_net_amount, places=2)
|
||||||
|
|
||||||
|
|
||||||
def create_asset_category_for_pr_test():
|
def create_asset_category_for_pr_test():
|
||||||
category_name = "Test Asset Category for PR"
|
category_name = "Test Asset Category for PR"
|
||||||
|
|||||||
@@ -3638,12 +3638,12 @@ class StockEntry(StockController, SubcontractingInwardController):
|
|||||||
args = {
|
args = {
|
||||||
"source_dt": "Stock Entry Detail",
|
"source_dt": "Stock Entry Detail",
|
||||||
"target_field": "transferred_qty",
|
"target_field": "transferred_qty",
|
||||||
"target_ref_field": "qty",
|
"target_ref_field": "transfer_qty",
|
||||||
"target_dt": "Stock Entry Detail",
|
"target_dt": "Stock Entry Detail",
|
||||||
"join_field": "ste_detail",
|
"join_field": "ste_detail",
|
||||||
"target_parent_dt": "Stock Entry",
|
"target_parent_dt": "Stock Entry",
|
||||||
"target_parent_field": "per_transferred",
|
"target_parent_field": "per_transferred",
|
||||||
"source_field": "qty",
|
"source_field": "transfer_qty",
|
||||||
"percent_join_field": "against_stock_entry",
|
"percent_join_field": "against_stock_entry",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Submodule frappe-semgrep-rules deleted from a05bce32ad
Reference in New Issue
Block a user