Compare commits

...

6 Commits

Author SHA1 Message Date
mergify[bot]
651af67b26 fix(payment_entry): fix paid/received amount calculation for multi-currency accounts (backport #54963) (#54969)
* fix(payment_entry): `paid_amount` and `received_amount` calculation depending upon `account_currency`

(cherry picked from commit 69642860ee)

# Conflicts:
#	erpnext/accounts/doctype/payment_entry/payment_entry.json

* chore: resolve conflicts

---------

Co-authored-by: diptanilsaha <diptanil@frappe.io>
2026-05-15 10:33:07 +00:00
ruthra kumar
2b2eb2fa27 Merge pull request #54956 from frappe/mergify/bp/version-15-hotfix/pr-54941
fix: flag to disable opening balance calculation in general ledger (backport #54941)
2026-05-15 13:01:01 +05:30
ruthra kumar
d1d4480187 refactor: flag to disable opening balance calculation
(cherry picked from commit 28a2230d02)
2026-05-15 06:50:58 +00:00
mergify[bot]
48ed07816d fix(stock): add whole number quantity validation in Stock Reconciliation (backport #54922) (#54924)
fix(stock): add whole number quantity validation in Stock Reconciliation (#54922)

(cherry picked from commit f9dec73042)

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
2026-05-13 15:20:13 +00:00
mergify[bot]
6d3cd7d38a Revert "fix: debit credit not equal in purchase transactions for mult… (backport #54906) (#54907)
* Revert "fix: debit credit not equal in purchase transactions for mult… (#54906)

* Revert "fix: debit credit not equal in purchase transactions for multi currency"

This reverts commit 75bcea57f4.

* Revert "test: add test case"

This reverts commit 1d30a202c3.

* Revert "fix: include rejected qty in tax (purchase receipt)"

This reverts commit 8c9a88abbe.

(cherry picked from commit cf5e8ce878)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: resolve conflicts

---------

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-13 09:58:45 +00:00
mergify[bot]
b969662b6c fix: add warehouse vaildation for repack entry (backport #54866) (#54900)
* fix: add warehouse vaildation for repack entry (#54866)

(cherry picked from commit bc07b2d3e5)

# Conflicts:
#	erpnext/stock/doctype/stock_entry/stock_entry.py

* chore: resolve conflicts

---------

Co-authored-by: Pandiyan P <pandiyanpalani37@gmail.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2026-05-13 06:42:18 +00:00
10 changed files with 72 additions and 132 deletions

View File

@@ -725,31 +725,12 @@ frappe.ui.form.on("Payment Entry", {
if (!frm.doc.paid_from_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
from_currency: frm.doc.paid_from_account_currency,
to_currency: company_currency,
transaction_date: frm.doc.posting_date,
},
callback: function (r, rt) {
frm.set_value("source_exchange_rate", r.message);
},
});
} else {
frm.events.set_current_exchange_rate(
frm,
"source_exchange_rate",
frm.doc.paid_from_account_currency,
company_currency
);
}
}
frm.events.set_current_exchange_rate(
frm,
"source_exchange_rate",
frm.doc.paid_from_account_currency,
company_currency
);
},
paid_to_account_currency: function (frm) {
@@ -781,49 +762,24 @@ frappe.ui.form.on("Payment Entry", {
posting_date: function (frm) {
frm.events.paid_from_account_currency(frm);
frm.events.paid_to_account_currency(frm);
},
source_exchange_rate: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_amount) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
// target exchange rate should always be same as source if both account currencies is same
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
} else if (company_currency == frm.doc.paid_to_account_currency) {
frm.set_value("received_amount", frm.doc.base_paid_amount);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
// set_unallocated_amount is called by below method,
// no need trigger separately
frm.events.set_total_allocated_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},
target_exchange_rate: function (frm) {
frm.set_paid_amount_based_on_received_amount = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.received_amount) {
frm.set_value(
"base_received_amount",
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
);
if (frm.doc.base_received_amount && frm.doc.source_exchange_rate) {
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
if (
!frm.doc.source_exchange_rate &&
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
} else if (company_currency == frm.doc.paid_from_account_currency) {
frm.set_value("paid_amount", frm.doc.base_received_amount);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
// target exchange rate should always be same as source if both account currencies is same
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
} else {
frm.set_value(
"paid_amount",
flt(frm.doc.base_paid_amount) / flt(frm.doc.source_exchange_rate)
);
}
// set_unallocated_amount is called by below method,
@@ -832,6 +788,32 @@ frappe.ui.form.on("Payment Entry", {
}
frm.set_paid_amount_based_on_received_amount = false;
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},
target_exchange_rate: function (frm) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.base_paid_amount && frm.doc.target_exchange_rate) {
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
if (
!frm.doc.source_exchange_rate &&
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
} else {
frm.set_value(
"received_amount",
flt(frm.doc.base_received_amount) / flt(frm.doc.target_exchange_rate)
);
}
// set_unallocated_amount is called by below method,
// no need trigger separately
frm.events.set_total_allocated_amount(frm);
}
// Make read only if Accounts Settings doesn't allow stale rates
frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
},

View File

@@ -350,7 +350,7 @@
"reqd": 1
},
{
"depends_on": "doc.received_amount",
"depends_on": "eval:doc.received_amount;",
"fieldname": "base_received_amount",
"fieldtype": "Currency",
"label": "Received Amount (Company Currency)",
@@ -800,7 +800,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2025-05-15 18:01:04.013025",
"modified": "2026-05-15 13:31:01.166010",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -176,10 +176,16 @@ frappe.query_reports["General Ledger"] = {
fieldtype: "Check",
default: 1,
},
{
fieldname: "disable_opening_balance_calculation",
label: __("Disable Opening Balance Calculation"),
fieldtype: "Check",
},
{
fieldname: "show_opening_entries",
label: __("Show Opening Entries"),
fieldtype: "Check",
depends_on: "eval: !doc.disable_opening_balance_calculation",
},
{
fieldname: "include_default_book_entries",

View File

@@ -279,7 +279,15 @@ def get_conditions(filters):
if filters.get("party"):
conditions.append("party in %(party)s")
if not (
if filters.get("disable_opening_balance_calculation"):
if not ignore_is_opening:
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
else:
conditions.append("posting_date >=%(from_date)s")
# opening balance calculation is done only if filtered on account/party
# so from_date filter is not applied
elif not (
filters.get("account")
or filters.get("party")
or filters.get("categorize_by") in ["Categorize by Account", "Categorize by Party"]
@@ -528,7 +536,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot
group_by_value = gle.get(group_by)
gle.voucher_type = gle.voucher_type
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
if gle.posting_date < from_date or (
cstr(gle.is_opening) == "Yes"
and not show_opening_entries
and not filters.disable_opening_balance_calculation
):
if not group_by_voucher_consolidated:
update_value_in_dict(gle_map[group_by_value].totals, "opening", gle, True)
update_value_in_dict(gle_map[group_by_value].totals, "closing", gle, True)

View File

@@ -364,17 +364,7 @@ class BuyingController(SubcontractingController):
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
)
net_rate = (
flt(
(item.base_net_amount / item.received_qty) * item.qty,
item.precision("base_net_amount"),
)
if item.received_qty
and frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
else item.base_net_amount
)
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate

View File

@@ -183,10 +183,6 @@ class calculate_taxes_and_totals:
return
if not self.discount_amount_applied:
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
do_not_round_fields = ["valuation_rate", "incoming_rate"]
for item in self.doc.items:
@@ -244,13 +240,7 @@ class calculate_taxes_and_totals:
elif not item.qty and self.doc.get("is_debit_note"):
item.amount = flt(item.rate, item.precision("amount"))
else:
qty = (
(item.qty + item.rejected_qty)
if bill_for_rejected_quantity_in_purchase_invoice
and self.doc.doctype == "Purchase Receipt"
else item.qty
)
item.amount = flt(item.rate * qty, item.precision("amount"))
item.amount = flt(item.rate * item.qty, item.precision("amount"))
item.net_amount = item.amount
@@ -382,16 +372,9 @@ class calculate_taxes_and_totals:
self.doc.total
) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
for item in self._items:
self.doc.total += item.amount
self.doc.total_qty += (
(item.qty + item.rejected_qty)
if bill_for_rejected_quantity_in_purchase_invoice and self.doc.doctype == "Purchase Receipt"
else item.qty
)
self.doc.total_qty += item.qty
self.doc.base_total += item.base_amount
self.doc.net_total += item.net_amount
self.doc.base_net_total += item.base_net_amount

View File

@@ -510,14 +510,7 @@ class PurchaseReceipt(BuyingController):
else flt(item.net_amount, item.precision("net_amount"))
)
outgoing_amount = (
flt((item.base_net_amount / item.received_qty) * item.qty, item.precision("base_net_amount"))
if item.received_qty
and frappe.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
else item.base_net_amount
)
outgoing_amount = item.base_net_amount
if self.is_internal_transfer() and item.valuation_rate:
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
credit_amount = outgoing_amount

View File

@@ -4539,7 +4539,7 @@ class TestPurchaseReceipt(FrappeTestCase):
self.assertEqual(srbnb_cost, 1500)
def test_valuation_rate_for_rejected_materials_without_accepted_materials(self):
def test_valuation_rate_for_rejected_materials_withoout_accepted_materials(self):
item = make_item("Test Item with Rej Material Valuation WO Accepted", {"is_stock_item": 1})
company = "_Test Company with perpetual inventory"
@@ -5106,33 +5106,6 @@ class TestPurchaseReceipt(FrappeTestCase):
self.assertEqual(row.warehouse, "_Test Warehouse 1 - _TC")
self.assertEqual(row.incoming_rate, 100)
def test_bill_for_rejected_quantity_in_purchase_invoice(self):
item_code = make_item("Test Rejected Qty", {"is_stock_item": 1}).name
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
pr = make_purchase_receipt(
item_code=item_code,
qty=10,
rejected_qty=2,
rate=10,
warehouse="_Test Warehouse - _TC",
)
self.assertEqual(pr.total_qty, 10)
self.assertEqual(pr.total, 100)
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)
pr = make_purchase_receipt(
item_code=item_code,
qty=10,
rejected_qty=2,
rate=10,
warehouse="_Test Warehouse - _TC",
)
self.assertEqual(pr.total_qty, 12)
self.assertEqual(pr.total, 120)
def test_different_exchange_rate_in_pr_and_pi(self):
from erpnext.accounts.doctype.account.test_account import create_account

View File

@@ -774,7 +774,7 @@ class StockEntry(StockController):
else:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
if self.purpose == "Manufacture":
if self.purpose in ["Manufacture", "Repack"]:
if d.is_finished_item or d.is_scrap_item:
d.s_warehouse = None
if not d.t_warehouse:

View File

@@ -82,6 +82,7 @@ class StockReconciliation(StockController):
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
self.validate_inventory_dimension()
self.validate_uom_is_integer("stock_uom", "qty")
if self._action == "submit":
self.validate_reserved_stock()