Compare commits

..

3 Commits

Author SHA1 Message Date
MochaMind
abff82a4b2 fix: Bosnian translations 2026-07-04 03:02:42 +05:30
MochaMind
3a63c74382 fix: Croatian translations 2026-07-04 03:02:38 +05:30
MochaMind
6bd2f29ab5 fix: Swedish translations 2026-07-04 03:02:30 +05:30
36 changed files with 181 additions and 861 deletions

View File

@@ -1,59 +1,10 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
aggregate_with_last_account_closing_balance,
generate_key,
)
# import frappe
from erpnext.tests.utils import ERPNextTestSuite
def entry(**overrides):
row = {"debit": 0, "credit": 0, "debit_in_account_currency": 0, "credit_in_account_currency": 0}
row.update(overrides)
return row
class TestAccountClosingBalance(ERPNextTestSuite):
"""The closing-balance snapshot is built by merging this period's entries with the
previous period's. These lock the merge/key logic that drives that carry-forward."""
def test_matching_entries_are_summed(self):
# this is how a prior-period balance carries forward into the current one
merged = aggregate_with_last_account_closing_balance(
[
entry(account="Cash - _TC", debit=100, debit_in_account_currency=100),
entry(
account="Cash - _TC",
debit=50,
credit=20,
debit_in_account_currency=50,
credit_in_account_currency=20,
),
],
[],
)
self.assertEqual(len(merged), 1)
row = next(iter(merged.values()))
self.assertEqual(row["debit"], 150)
self.assertEqual(row["credit"], 20)
# the account-currency columns are accumulated in the same pass
self.assertEqual(row["debit_in_account_currency"], 150)
self.assertEqual(row["credit_in_account_currency"], 20)
def test_entries_are_kept_separate_per_dimension(self):
merged = aggregate_with_last_account_closing_balance(
[
entry(account="Cash - _TC", cost_center="CC1", debit=100, debit_in_account_currency=100),
entry(account="Cash - _TC", cost_center="CC2", debit=40, debit_in_account_currency=40),
],
[],
)
self.assertEqual(len(merged), 2)
def test_period_closing_flag_is_part_of_the_key(self):
# a P&L reversal (flag 0) and a closing-account entry (flag 1) for the same
# account must not merge, so the flag has to distinguish their keys
key_reversal, _ = generate_key(entry(account="Sales - _TC", is_period_closing_voucher_entry=0), [])
key_closing, _ = generate_key(entry(account="Sales - _TC", is_period_closing_voucher_entry=1), [])
self.assertNotEqual(key_reversal, key_closing)
pass

View File

@@ -1,76 +1,8 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import flt
from erpnext.accounts.doctype.bank_guarantee.bank_guarantee import get_voucher_details
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.tests.utils import ERPNextTestSuite
BANK = "_Test BG Bank"
class TestBankGuarantee(ERPNextTestSuite):
"""Bank Guarantee records a guarantee issued/received against a customer or
supplier. validate() needs a party; on_submit() needs the bank details filled in."""
def setUp(self):
frappe.set_user("Administrator")
if not frappe.db.exists("Bank", BANK):
frappe.get_doc({"doctype": "Bank", "bank_name": BANK}).insert()
def make_bg(self, **args):
args = frappe._dict(args)
doc = frappe.new_doc("Bank Guarantee")
doc.bg_type = args.bg_type or "Receiving"
doc.amount = args.amount if args.amount is not None else 1000
doc.start_date = args.start_date or "2026-06-01"
if args.end_date:
doc.end_date = args.end_date
doc.customer = args.get("customer", "_Test Customer")
doc.supplier = args.get("supplier")
# fields on_submit requires — present by default, cleared per-test to assert the guard
doc.bank_guarantee_number = args.get("bank_guarantee_number", "BG-001")
doc.name_of_beneficiary = args.get("name_of_beneficiary", "Test Beneficiary")
doc.bank = args.get("bank", BANK)
return doc
def test_validate_requires_customer_or_supplier(self):
doc = self.make_bg(customer=None)
self.assertRaises(frappe.ValidationError, doc.insert)
def test_submit_requires_guarantee_number(self):
doc = self.make_bg(bank_guarantee_number="")
doc.insert()
self.assertRaises(frappe.ValidationError, doc.submit)
def test_submit_requires_beneficiary_name(self):
doc = self.make_bg(name_of_beneficiary="")
doc.insert()
self.assertRaises(frappe.ValidationError, doc.submit)
def test_submit_requires_bank(self):
doc = self.make_bg(bank="")
doc.insert()
self.assertRaises(frappe.ValidationError, doc.submit)
def test_valid_guarantee_submits(self):
doc = self.make_bg()
doc.insert()
doc.submit()
self.assertEqual(frappe.db.get_value("Bank Guarantee", doc.name, "docstatus"), 1)
def test_get_voucher_details_for_receiving(self):
so = make_sales_order()
details = get_voucher_details("Receiving", so.name)
self.assertEqual(details.customer, so.customer)
self.assertEqual(flt(details.grand_total), flt(so.grand_total))
def test_end_date_before_start_date_is_not_validated(self):
# SUSPECTED BUG: validate() never checks that end_date >= start_date, so a
# guarantee that expires before it starts saves cleanly. Locking the current
# (wrong) behaviour so a future fix that adds the check trips this test.
doc = self.make_bg(start_date="2026-06-30", end_date="2026-06-01")
doc.insert()
self.assertTrue(frappe.db.exists("Bank Guarantee", doc.name))
pass

View File

@@ -1,67 +1,8 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.tests.utils import ERPNextTestSuite
DATE = "2026-06-15"
class TestCashierClosing(ERPNextTestSuite):
"""Cashier Closing reconciles a shift: it pulls outstanding invoices in a
date/time window and rolls payments, expense, custody and returns into net_amount."""
def setUp(self):
frappe.set_user("Administrator")
def make_invoice_in_window(self, rate=100):
si = create_sales_invoice(rate=rate, qty=1, posting_date=DATE, do_not_submit=True)
si.posting_time = "10:30:00"
si.submit()
si.reload() # read outstanding_amount as persisted after submit
return si
def make_closing(self, user="Administrator", payments=None, **args):
doc = frappe.new_doc("Cashier Closing")
doc.user = user
doc.date = args.get("date", DATE)
doc.from_time = args.get("from_time", "09:00:00")
doc.time = args.get("time", "18:00:00")
for amount in payments or []:
doc.append("payments", {"mode_of_payment": "Cash", "amount": amount})
doc.expense = args.get("expense", 0)
doc.custody = args.get("custody", 0)
doc.returns = args.get("returns", 0)
return doc
def test_from_time_must_be_before_to_time(self):
doc = self.make_closing(from_time="18:00:00", time="09:00:00")
self.assertRaises(frappe.ValidationError, doc.save)
def test_equal_from_and_to_time_is_rejected(self):
# validate_time uses >=, so a zero-length window is also blocked
doc = self.make_closing(from_time="09:00:00", time="09:00:00")
self.assertRaises(frappe.ValidationError, doc.save)
def test_net_amount_rolls_up_outstanding_and_adjustments(self):
si = self.make_invoice_in_window(rate=100)
doc = self.make_closing(payments=[500], expense=50, custody=30, returns=20)
doc.save()
# the in-window invoice is picked up as outstanding
self.assertEqual(doc.outstanding_amount, si.outstanding_amount)
# net = payments + outstanding + expense - custody + returns
self.assertEqual(doc.net_amount, 500 + si.outstanding_amount + 50 - 30 + 20)
def test_outstanding_is_scoped_to_the_invoice_owner(self):
# The invoice is created by Administrator; a closing for a different user does
# not see it. NOTE: get_outstanding keys on Sales Invoice.owner (the document
# creator) rather than an explicit cashier/POS-user field, which is fragile when
# invoices are created by a shared or system user.
self.make_invoice_in_window(rate=100)
doc = self.make_closing(user="Guest", payments=[500])
doc.save()
self.assertEqual(doc.outstanding_amount, 0)
self.assertEqual(doc.net_amount, 500)
pass

View File

@@ -1,62 +1,8 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
TAX_ACCOUNT = "_Test Account VAT - _TC"
RECEIVABLE_ACCOUNT = "Debtors - _TC"
class TestItemTaxTemplate(ERPNextTestSuite):
"""Item Tax Template validates its tax rows: each account must belong to the
company, be a tax-like account type, and appear only once."""
def setUp(self):
frappe.set_user("Administrator")
def make_template(self, rows, title="_Test ITT"):
doc = frappe.new_doc("Item Tax Template")
doc.title = f"{title} {frappe.generate_hash(length=6)}"
doc.company = COMPANY
for account, rate, not_applicable in rows:
doc.append(
"taxes",
{"tax_type": account, "tax_rate": rate, "not_applicable": not_applicable},
)
return doc
def test_valid_template_saves_and_is_named_with_abbr(self):
doc = self.make_template([(TAX_ACCOUNT, 9, 0)])
doc.insert()
self.assertTrue(doc.name.endswith(" - _TC"))
self.assertTrue(doc.name.startswith(doc.title))
def test_duplicate_tax_type_throws(self):
doc = self.make_template([(TAX_ACCOUNT, 9, 0), (TAX_ACCOUNT, 5, 0)])
self.assertRaises(frappe.ValidationError, doc.insert)
def test_account_of_wrong_company_throws(self):
other_account = frappe.db.get_value("Account", {"company": "_Test Company 1", "is_group": 0}, "name")
self.assertTrue(other_account, "need a non-group account in _Test Company 1")
doc = self.make_template([(other_account, 9, 0)])
self.assertRaises(frappe.ValidationError, doc.insert)
def test_disallowed_account_type_throws(self):
# a Receivable account is not Tax/Chargeable/Income/Expense
doc = self.make_template([(RECEIVABLE_ACCOUNT, 9, 0)])
self.assertRaises(frappe.ValidationError, doc.insert)
def test_not_applicable_row_has_rate_zeroed(self):
doc = self.make_template([(TAX_ACCOUNT, 18, 1)])
doc.insert()
self.assertEqual(doc.taxes[0].tax_rate, 0)
def test_negative_tax_rate_is_accepted(self):
# SUSPECTED BUG: validate never bounds tax_rate, so a negative (or >100) rate
# saves silently. Locking the current (wrong) behaviour.
doc = self.make_template([(TAX_ACCOUNT, -5, 0)])
doc.insert()
self.assertEqual(doc.taxes[0].tax_rate, -5)
pass

View File

@@ -45,20 +45,6 @@ class JournalEntryTemplate(Document):
def validate(self):
self.validate_party()
self.validate_account_company()
def validate_account_company(self):
"""Each row's account must belong to the template's company."""
for account in self.accounts:
if (
account.account
and frappe.get_cached_value("Account", account.account, "company") != self.company
):
frappe.throw(
_("Row {0}: Account {1} does not belong to company {2}").format(
account.idx, account.account, self.company
)
)
def validate_party(self):
"""

View File

@@ -1,45 +1,9 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
# import frappe
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
class TestJournalEntryTemplate(ERPNextTestSuite):
"""Journal Entry Template's only real rule is validate_party: party_type is
allowed only on Receivable/Payable accounts, and a party needs a party_type."""
def setUp(self):
frappe.set_user("Administrator")
def make_template(self, rows, company=COMPANY):
doc = frappe.new_doc("Journal Entry Template")
doc.template_title = f"_Test JET {frappe.generate_hash(length=6)}"
doc.company = company
doc.voucher_type = "Journal Entry"
doc.naming_series = frappe.get_meta("Journal Entry").get_field("naming_series").options.split("\n")[0]
for row in rows:
doc.append("accounts", row)
return doc
def test_party_type_only_on_receivable_or_payable_account(self):
# Cash is neither Receivable nor Payable, so a party_type here is invalid
doc = self.make_template([{"account": "Cash - _TC", "party_type": "Customer"}])
self.assertRaises(frappe.ValidationError, doc.validate)
def test_party_requires_party_type(self):
doc = self.make_template([{"account": "Debtors - _TC", "party": "_Test Customer"}])
self.assertRaises(frappe.ValidationError, doc.validate)
def test_account_from_other_company_is_rejected(self):
other_receivable = frappe.db.get_value(
"Account", {"company": "_Test Company 1", "account_type": "Receivable", "is_group": 0}, "name"
)
self.assertTrue(other_receivable, "need a receivable account in _Test Company 1")
doc = self.make_template(
[{"account": other_receivable, "party_type": "Customer", "party": "_Test Customer"}]
)
self.assertRaises(frappe.ValidationError, doc.insert)
pass

View File

@@ -5,59 +5,9 @@ import frappe
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
class TestModeofPayment(ERPNextTestSuite):
"""Mode of Payment validates its per-company default accounts (account company
must match the row, no company twice) and blocks disabling while a POS Profile
still references it."""
def setUp(self):
frappe.set_user("Administrator")
def make_mop(self, accounts=None, enabled=1):
doc = frappe.new_doc("Mode of Payment")
doc.mode_of_payment = f"_Test MoP {frappe.generate_hash(length=6)}"
doc.type = "General"
doc.enabled = enabled
for company, account in accounts or []:
doc.append("accounts", {"company": company, "default_account": account})
return doc
def test_valid_mode_of_payment_saves(self):
doc = self.make_mop(accounts=[(COMPANY, "Cash - _TC")])
doc.insert()
self.assertTrue(doc.name)
def test_account_of_wrong_company_throws(self):
other_account = frappe.db.get_value("Account", {"company": "_Test Company 1", "is_group": 0}, "name")
self.assertTrue(other_account, "need a non-group account in _Test Company 1")
doc = self.make_mop(accounts=[(COMPANY, other_account)])
self.assertRaises(frappe.ValidationError, doc.insert)
def test_repeating_company_throws(self):
doc = self.make_mop(accounts=[(COMPANY, "Cash - _TC"), (COMPANY, "Debtors - _TC")])
self.assertRaises(frappe.ValidationError, doc.insert)
def test_disabling_mode_referenced_by_pos_profile_is_not_blocked(self):
# SUSPECTED BUG: validate_pos_mode_of_payment queries "Sales Invoice Payment"
# rows with parenttype "POS Profile", but a POS Profile's payments are stored
# as "POS Payment Method" rows. The filter never matches, so the guard is dead
# and a mode still referenced by a POS Profile disables without complaint.
# Locking the current (wrong) behaviour so a fix to the guard trips this test.
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
make_pos_profile() # its payments row references the "Cash" mode of payment
cash = frappe.get_doc("Mode of Payment", "Cash")
cash.enabled = 0
cash.save()
self.assertEqual(frappe.db.get_value("Mode of Payment", "Cash", "enabled"), 0)
def test_disabling_unreferenced_mode_succeeds(self):
doc = self.make_mop(accounts=[(COMPANY, "Cash - _TC")], enabled=0)
doc.insert()
self.assertEqual(doc.enabled, 0)
pass
def set_default_account_for_mode_of_payment(mode_of_payment, company, account):

View File

@@ -1,67 +1,8 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt
import frappe
from frappe.utils import getdate
from erpnext.accounts.doctype.monthly_distribution.monthly_distribution import (
get_percentage,
get_periodwise_distribution_data,
)
from erpnext.tests.utils import ERPNextTestSuite
class TestMonthlyDistribution(ERPNextTestSuite):
"""Monthly Distribution spreads an amount across months. validate() enforces a
100% total; get_percentage() sums the months that fall inside a period window."""
def setUp(self):
frappe.set_user("Administrator")
def make_distribution(self, allocations):
doc = frappe.new_doc("Monthly Distribution")
doc.distribution_id = f"_Test MD {frappe.generate_hash(length=6)}"
for month, pct in allocations:
doc.append("percentages", {"month": month, "percentage_allocation": pct})
return doc
def test_get_months_populates_twelve_even_rows(self):
doc = frappe.new_doc("Monthly Distribution")
doc.distribution_id = "_Test MD Even"
doc.get_months()
self.assertEqual(len(doc.percentages), 12)
self.assertEqual(doc.percentages[0].month, "January")
self.assertEqual(doc.percentages[-1].month, "December")
self.assertEqual([d.idx for d in doc.percentages], list(range(1, 13)))
for d in doc.percentages:
self.assertAlmostEqual(d.percentage_allocation, 100.0 / 12, places=4)
# the auto-populated rows round to exactly 100 and pass validation
doc.validate()
def test_validate_rejects_total_other_than_100(self):
doc = self.make_distribution([("January", 50), ("February", 30)]) # sums to 80
self.assertRaises(frappe.ValidationError, doc.insert)
def test_get_percentage_sums_period_window(self):
doc = self.make_distribution([("January", 50), ("February", 30), ("March", 20)])
doc.insert() # total is 100, so validate passes
# a quarter starting in January covers Jan+Feb+Mar
self.assertEqual(get_percentage(doc, getdate("2026-01-01"), 3), 100)
# a single month picks up only that month
self.assertEqual(get_percentage(doc, getdate("2026-02-01"), 1), 30)
# months with no row simply contribute 0 (there is no guard that all 12 exist)
self.assertEqual(get_percentage(doc, getdate("2026-04-01"), 1), 0)
def test_periodwise_distribution_maps_each_period(self):
doc = self.make_distribution([("January", 50), ("February", 30), ("March", 20)])
doc.insert()
period_list = [
frappe._dict(key="q1", from_date=getdate("2026-01-01")),
frappe._dict(key="q2", from_date=getdate("2026-04-01")),
]
data = get_periodwise_distribution_data(doc.name, period_list, "Quarterly")
self.assertEqual(data["q1"], 100) # Jan+Feb+Mar
self.assertEqual(data["q2"], 0) # Apr+May+Jun carry no allocation
pass

View File

@@ -1,67 +1,9 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
import frappe
from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.tests.utils import ERPNextTestSuite
CUSTOMER = "_Test Customer"
SUPPLIER = "_Test Supplier"
SUPPLIER_2 = "_Test Supplier 1"
class TestPartyLink(ERPNextTestSuite):
"""Party Link ties a Customer and a Supplier together as one underlying party.
validate() constrains the primary role and blocks duplicate links."""
def setUp(self):
frappe.set_user("Administrator")
def test_create_party_link_with_customer_primary(self):
link = create_party_link("Customer", CUSTOMER, SUPPLIER)
self.assertEqual(link.primary_role, "Customer")
self.assertEqual(link.secondary_role, "Supplier")
self.assertEqual(link.primary_party, CUSTOMER)
self.assertEqual(link.secondary_party, SUPPLIER)
self.assertTrue(frappe.db.exists("Party Link", link.name))
def test_create_party_link_with_supplier_primary(self):
link = create_party_link("Supplier", SUPPLIER, CUSTOMER)
self.assertEqual(link.primary_role, "Supplier")
self.assertEqual(link.secondary_role, "Customer")
self.assertEqual(link.primary_party, SUPPLIER)
self.assertEqual(link.secondary_party, CUSTOMER)
self.assertTrue(frappe.db.exists("Party Link", link.name))
def test_primary_role_must_be_customer_or_supplier(self):
doc = frappe.new_doc("Party Link")
doc.primary_role = "Employee"
doc.primary_party = CUSTOMER
doc.secondary_role = "Supplier"
doc.secondary_party = SUPPLIER
# validate() alone isolates the role rule from the dynamic-link checks
self.assertRaises(frappe.ValidationError, doc.validate)
def test_duplicate_link_throws(self):
create_party_link("Customer", CUSTOMER, SUPPLIER)
dup = frappe.new_doc("Party Link")
dup.primary_role = "Customer"
dup.primary_party = CUSTOMER
dup.secondary_role = "Supplier"
dup.secondary_party = SUPPLIER
self.assertRaises(frappe.ValidationError, dup.insert)
def test_party_can_wrongly_be_primary_in_two_links(self):
# SUSPECTED BUG: the uniqueness checks are asymmetric - a party already a
# *primary* in another link isn't blocked, so one customer can be linked to two
# different suppliers, breaking the 1:1 mapping. Locking the current (wrong)
# behaviour so a fix that blocks primary reuse trips this test.
create_party_link("Customer", CUSTOMER, SUPPLIER)
link2 = frappe.new_doc("Party Link")
link2.primary_role = "Customer"
link2.primary_party = CUSTOMER
link2.secondary_role = "Supplier"
link2.secondary_party = SUPPLIER_2
link2.insert()
self.assertTrue(frappe.db.exists("Party Link", link2.name))
pass

View File

@@ -2317,65 +2317,3 @@ def create_customer(name="_Test Customer 2 USD", currency="USD"):
customer.save()
customer = customer.name
return customer
class TestPaymentEntryValidation(ERPNextTestSuite):
"""Field-level validations invoked on the document directly, covering branches the
integration suite above doesn't reach (no GL / reconciliation setup needed)."""
def make_pe(self, **fields):
doc = frappe.new_doc("Payment Entry")
doc.update(fields)
return doc
def test_payment_type_must_be_a_known_value(self):
self.assertRaises(frappe.ValidationError, self.make_pe(payment_type="Foo").validate_payment_type)
self.make_pe(payment_type="Receive").validate_payment_type() # valid value passes
def test_nonexistent_party_is_rejected(self):
doc = self.make_pe(party_type="Customer", party="__No Such Customer__")
self.assertRaises(frappe.ValidationError, doc.validate_party_details)
def test_amount_and_exchange_rate_fields_are_mandatory(self):
# every field but target_exchange_rate is set, so that missing one raises
doc = self.make_pe(
paid_amount=100, received_amount=100, source_exchange_rate=1, target_exchange_rate=0
)
self.assertRaises(frappe.ValidationError, doc.validate_mandatory)
def test_received_amount_cannot_exceed_paid_in_same_currency(self):
doc = self.make_pe(
paid_from_account_currency="INR",
paid_to_account_currency="INR",
paid_amount=100,
received_amount=150,
)
self.assertRaises(frappe.ValidationError, doc.validate_received_amount)
# received <= paid is fine
doc.received_amount = 50
doc.validate_received_amount()
def test_duplicate_reference_rows_are_rejected(self):
doc = self.make_pe()
for _ in range(2):
doc.append(
"references",
{"reference_doctype": "Sales Invoice", "reference_name": "SI-X", "allocated_amount": 100},
)
self.assertRaises(frappe.ValidationError, doc.validate_duplicate_entry)
def test_receive_from_customer_against_negative_outstanding_is_rejected(self):
doc = self.make_pe(party_type="Customer", payment_type="Receive")
doc.append(
"references",
{"reference_doctype": "Sales Invoice", "reference_name": "SI-Y", "allocated_amount": -100},
)
self.assertRaises(frappe.ValidationError, doc.validate_payment_type_with_outstanding)
def test_bank_transaction_requires_a_reference_number(self):
doc = self.make_pe(payment_type="Pay", paid_from="_Test Bank - _TC")
self.assertRaises(frappe.ValidationError, doc.validate_transaction_reference)
# supplying the reference details clears the requirement
doc.reference_no = "TXN-1"
doc.reference_date = "2026-06-15"
doc.validate_transaction_reference()

View File

@@ -106,8 +106,6 @@ def get_pr_instance(doc: str):
"party",
"receivable_payable_account",
"default_advance_account",
"bank_cash_account",
"cost_center",
"from_invoice_date",
"to_invoice_date",
"from_payment_date",

View File

@@ -1,73 +1,11 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
# import frappe
from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import (
get_pr_instance,
)
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
class TestProcessPaymentReconciliation(ERPNextTestSuite):
"""Process Payment Reconciliation validates its accounts against the company,
moves to Queued on submit, and hands its filters to a Payment Reconciliation run."""
def setUp(self):
frappe.set_user("Administrator")
def make_ppr(self, **args):
args = frappe._dict(args)
doc = frappe.new_doc("Process Payment Reconciliation")
doc.company = COMPANY
doc.party_type = "Customer"
doc.party = "_Test Customer"
doc.receivable_payable_account = args.get("receivable_payable_account", "Debtors - _TC")
doc.bank_cash_account = args.get("bank_cash_account")
doc.from_invoice_date = args.get("from_invoice_date")
doc.to_invoice_date = args.get("to_invoice_date")
return doc
def other_company_account(self, **extra):
filters = {"company": "_Test Company 1", "is_group": 0, **extra}
account = frappe.db.get_value("Account", filters, "name")
self.assertTrue(account, "need a matching account in _Test Company 1")
return account
def test_receivable_account_must_belong_to_company(self):
doc = self.make_ppr(receivable_payable_account=self.other_company_account(account_type="Receivable"))
self.assertRaises(frappe.ValidationError, doc.insert)
def test_bank_cash_account_must_belong_to_company(self):
doc = self.make_ppr(bank_cash_account=self.other_company_account())
self.assertRaises(frappe.ValidationError, doc.insert)
def test_submit_sets_status_to_queued(self):
doc = self.make_ppr()
doc.insert()
doc.submit()
self.assertEqual(doc.status, "Queued")
def test_get_pr_instance_copies_filters_and_caps_limits(self):
doc = self.make_ppr(from_invoice_date="2026-01-01", to_invoice_date="2026-06-30")
doc.insert()
pr = get_pr_instance(doc.name)
self.assertEqual(pr.company, COMPANY)
self.assertEqual(pr.party, "_Test Customer")
self.assertEqual(pr.receivable_payable_account, "Debtors - _TC")
self.assertEqual(str(pr.from_invoice_date), "2026-01-01")
# the tool run is capped so a single process can't fetch unbounded rows
self.assertEqual(pr.invoice_limit, 1000)
self.assertEqual(pr.payment_limit, 1000)
def test_get_pr_instance_copies_bank_cash_and_cost_center(self):
doc = self.make_ppr(bank_cash_account="Cash - _TC")
doc.cost_center = "_Test Cost Center - _TC"
doc.insert()
pr = get_pr_instance(doc.name)
self.assertEqual(pr.bank_cash_account, "Cash - _TC")
self.assertEqual(pr.cost_center, "_Test Cost Center - _TC")
pass

View File

@@ -79,9 +79,7 @@ def get_plan_rate(
start_date = getdate(start_date)
end_date = getdate(end_date)
delta = relativedelta.relativedelta(end_date, start_date)
# include the years component so cross-year spans aren't under-counted
no_of_months = delta.years * 12 + delta.months + 1
no_of_months = relativedelta.relativedelta(end_date, start_date).months + 1
cost = plan.cost * no_of_months
# Adjust cost if start or end date is not month start or end

View File

@@ -1,54 +1,8 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
from erpnext.tests.utils import ERPNextTestSuite
class TestSubscriptionPlan(ERPNextTestSuite):
"""Subscription Plan validates its interval and computes a rate. The Monthly
Rate branch multiplies cost by the number of months in the billing window."""
def setUp(self):
frappe.set_user("Administrator")
def make_plan(self, **args):
args = frappe._dict(args)
plan = frappe.new_doc("Subscription Plan")
plan.plan_name = f"_Test Plan {frappe.generate_hash(length=6)}"
plan.item = args.item or "_Test Item"
plan.currency = args.currency or "INR"
plan.price_determination = args.price_determination
plan.cost = args.cost or 0
plan.billing_interval = args.billing_interval or "Month"
plan.billing_interval_count = (
args.billing_interval_count if args.billing_interval_count is not None else 1
)
return plan
def test_billing_interval_count_must_be_positive(self):
plan = self.make_plan(price_determination="Fixed Rate", cost=100, billing_interval_count=0)
self.assertRaises(frappe.ValidationError, plan.insert)
def test_fixed_rate_applies_prorate_factor(self):
plan = self.make_plan(price_determination="Fixed Rate", cost=100)
plan.insert()
self.assertEqual(get_plan_rate(plan.name), 100)
self.assertEqual(get_plan_rate(plan.name, prorate_factor=0.5), 50)
def test_monthly_rate_within_year(self):
plan = self.make_plan(price_determination="Monthly Rate", cost=100)
plan.insert()
# Jan 1 - Mar 31 is 3 whole months; month-aligned so proration is 0
rate = get_plan_rate(plan.name, start_date="2026-01-01", end_date="2026-03-31")
self.assertEqual(rate, 300)
def test_monthly_rate_across_year_boundary(self):
# a 14-month span (Jan 2026 to Feb 2027) bills all 14 months, not just the
# 2-month remainder that relativedelta.months alone would give
plan = self.make_plan(price_determination="Monthly Rate", cost=100)
plan.insert()
rate = get_plan_rate(plan.name, start_date="2026-01-01", end_date="2027-02-28")
self.assertEqual(rate, 1400)
pass

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-06-28 10:20+0000\n"
"PO-Revision-Date: 2026-07-01 20:39\n"
"PO-Revision-Date: 2026-07-03 21:32\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Bosnian\n"
"MIME-Version: 1.0\n"
@@ -8214,7 +8214,7 @@ msgstr "Šarža {0} artikla {1} je onemogućena."
#: erpnext/stock/workspace/stock/stock.json
#: erpnext/workspace_sidebar/stock.json
msgid "Batch-Wise Balance History"
msgstr "Istorija Stanja na osnovu Šarže"
msgstr "Historija Stanja na osnovu Šarže"
#: erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py:164
#: erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py:183
@@ -18764,7 +18764,7 @@ msgstr "Obuka Personala"
#. Name of a DocType
#: erpnext/setup/doctype/employee_external_work_history/employee_external_work_history.json
msgid "Employee External Work History"
msgstr "Eksterna radna istorija Personala"
msgstr "Eksterna Radna Historija Personala"
#. Label of the employee_group (Link) field in DocType 'Communication Medium
#. Timeslot'
@@ -18786,7 +18786,7 @@ msgstr "ID Personala"
#. Name of a DocType
#: erpnext/setup/doctype/employee_internal_work_history/employee_internal_work_history.json
msgid "Employee Internal Work History"
msgstr "Interna radna istorija Personala"
msgstr "Eksterna Radna Historija Personala"
#. Label of the employee_name (Data) field in DocType 'Activity Cost'
#. Label of the employee_name (Data) field in DocType 'Timesheet'
@@ -20111,7 +20111,7 @@ msgstr "Prošireni Bankovni Izvod"
#. Label of the external_work_history (Table) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "External Work History"
msgstr "Eksterna Radna Istorija"
msgstr "Eksterna RadnaHstorija"
#: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py:148
msgid "Extra Consumed Qty"
@@ -20288,7 +20288,7 @@ msgstr "Greška: {0}"
#. Label of the family_background (Small Text) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "Family Background"
msgstr "Porodična Istorija"
msgstr "Porodična Historija"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -23173,7 +23173,7 @@ msgstr "Što je veći broj, veći je prioritet"
#. Label of the history_in_company (Section Break) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "History In Company"
msgstr "Istorija u Poduzeću"
msgstr "Historija u Poduzeću"
#: erpnext/buying/doctype/purchase_order/purchase_order.js:314
#: erpnext/selling/doctype/sales_order/sales_order.js:1033
@@ -25234,7 +25234,7 @@ msgstr "Interni Prenosi"
#. Label of the internal_work_history (Table) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "Internal Work History"
msgstr "Interna Radna Istorija"
msgstr "Interna Radna Historija"
#. Description of the 'Customer Details' (Text) field in DocType 'Customer'
#: erpnext/selling/doctype/customer/customer.json
@@ -27986,7 +27986,7 @@ msgstr "Nabavni Registar po Artiklu"
#: erpnext/selling/workspace/selling/selling.json
#: erpnext/workspace_sidebar/selling.json
msgid "Item-wise Sales History"
msgstr "Istorija Prodaje po Artiklu"
msgstr "Historija Prodaje po Artiklu"
#. Name of a report
#. Label of a Workspace Sidebar Item
@@ -47609,7 +47609,7 @@ msgstr "Prodajna Faktura {0} mora se izbrisati prije otkazivanja ovog Prodajnog
#. Label of the sales_monthly_history (Small Text) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
msgid "Sales Monthly History"
msgstr "Mjesečna Istorija Prodaje"
msgstr "Mjesečna Historija Prodaje"
#: erpnext/selling/page/sales_funnel/sales_funnel.js:153
msgid "Sales Opportunities by Campaign"
@@ -57913,7 +57913,7 @@ msgstr "Transakcije"
#. Label of the transactions_annual_history (Code) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
msgid "Transactions Annual History"
msgstr "Godišnja Istorija Transakcije"
msgstr "Godišnja Historija Transakcije"
#: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:117
msgid "Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-06-28 10:20+0000\n"
"PO-Revision-Date: 2026-07-01 20:39\n"
"PO-Revision-Date: 2026-07-03 21:32\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Croatian\n"
"MIME-Version: 1.0\n"
@@ -8214,7 +8214,7 @@ msgstr "Šarža {0} artikla {1} je onemogućena."
#: erpnext/stock/workspace/stock/stock.json
#: erpnext/workspace_sidebar/stock.json
msgid "Batch-Wise Balance History"
msgstr "Istorija Stanja na osnovu Šarže"
msgstr "Povijest Stanja na temelju Šarže"
#: erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py:164
#: erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py:183
@@ -18764,7 +18764,7 @@ msgstr "Obrazovanje Osoblja"
#. Name of a DocType
#: erpnext/setup/doctype/employee_external_work_history/employee_external_work_history.json
msgid "Employee External Work History"
msgstr "Eksterna radna povijest Osoblja"
msgstr "Vanjska radna povijest Osoblja"
#. Label of the employee_group (Link) field in DocType 'Communication Medium
#. Timeslot'
@@ -18786,7 +18786,7 @@ msgstr "ID Osoblja"
#. Name of a DocType
#: erpnext/setup/doctype/employee_internal_work_history/employee_internal_work_history.json
msgid "Employee Internal Work History"
msgstr "Interna radna povijest Osoblja"
msgstr "Unutarnja radna povijest Osoblja"
#. Label of the employee_name (Data) field in DocType 'Activity Cost'
#. Label of the employee_name (Data) field in DocType 'Timesheet'
@@ -20111,7 +20111,7 @@ msgstr "Prošireni Bankovni Izvod"
#. Label of the external_work_history (Table) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "External Work History"
msgstr "Eksterna Radna Istorija"
msgstr "Vanjska Radna Povijest"
#: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py:148
msgid "Extra Consumed Qty"
@@ -25234,7 +25234,7 @@ msgstr "Interni Prenosi"
#. Label of the internal_work_history (Table) field in DocType 'Employee'
#: erpnext/setup/doctype/employee/employee.json
msgid "Internal Work History"
msgstr "Interna Radna Istorija"
msgstr "Unutarnja Radna Povijest"
#. Description of the 'Customer Details' (Text) field in DocType 'Customer'
#: erpnext/selling/doctype/customer/customer.json
@@ -27986,7 +27986,7 @@ msgstr "Registar Nabave po Artiklu"
#: erpnext/selling/workspace/selling/selling.json
#: erpnext/workspace_sidebar/selling.json
msgid "Item-wise Sales History"
msgstr "Istorija Prodaje po Artiklu"
msgstr "Povijest Prodaje po Artiklu"
#. Name of a report
#. Label of a Workspace Sidebar Item
@@ -47609,7 +47609,7 @@ msgstr "Prodajna Faktura {0} mora se izbrisati prije otkazivanja ovog Prodajnog
#. Label of the sales_monthly_history (Small Text) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
msgid "Sales Monthly History"
msgstr "Mjesečna Istorija Prodaje"
msgstr "Mjesečna Povijest Prodaje"
#: erpnext/selling/page/sales_funnel/sales_funnel.js:153
msgid "Sales Opportunities by Campaign"
@@ -57913,7 +57913,7 @@ msgstr "Transakcije"
#. Label of the transactions_annual_history (Code) field in DocType 'Company'
#: erpnext/setup/doctype/company/company.json
msgid "Transactions Annual History"
msgstr "Godišnja Istorija Transakcije"
msgstr "Godišnja Povijest Transakcija"
#: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:117
msgid "Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: frappe\n"
"Report-Msgid-Bugs-To: hello@frappe.io\n"
"POT-Creation-Date: 2026-06-28 10:20+0000\n"
"PO-Revision-Date: 2026-07-01 20:39\n"
"PO-Revision-Date: 2026-07-03 21:32\n"
"Last-Translator: hello@frappe.io\n"
"Language-Team: Swedish\n"
"MIME-Version: 1.0\n"
@@ -5054,7 +5054,7 @@ msgstr "Fel uppstod under uppdatering process"
#: erpnext/stock/reorder_item.py:368
msgid "An error occurred for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :"
msgstr "Fel uppstod för vissa artiklar när Material Begäran skapades baserat på beställning nivå. Vänligen åtgärda dessa problem:"
msgstr "Fel uppstod för vissa artiklar när Material Begäran skapades baserat på återbeställning nivå. Vänligen åtgärda dessa problem:"
#: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.html:124
msgid "Analysis Chart"
@@ -6451,7 +6451,7 @@ msgstr "Automatiskt Skapad"
#. Request'
#: erpnext/stock/doctype/material_request/material_request.json
msgid "Auto Created (Reorder)"
msgstr "Skapas automatiskt (ombeställning)"
msgstr "Skapas automatiskt (återbeställning)"
#. Label of the auto_created_serial_and_batch_bundle (Check) field in DocType
#. 'Stock Ledger Entry'
@@ -6569,7 +6569,7 @@ msgstr "Automatiskt avstämning av Parti i Bank Transaktioner"
#. Label of the reorder_section (Section Break) field in DocType 'Item'
#: erpnext/stock/doctype/item/item.json
msgid "Auto re-order"
msgstr "Automatisk Ombeställning"
msgstr "Automatisk Återbeställning"
#. Label of the auto_reconcile_payments (Check) field in DocType 'Accounts
#. Settings'
@@ -8221,7 +8221,7 @@ msgstr "Parti {0} av Artikel {1} är Inaktiverad."
#: erpnext/stock/workspace/stock/stock.json
#: erpnext/workspace_sidebar/stock.json
msgid "Batch-Wise Balance History"
msgstr "Saldo Historik per Parti"
msgstr "Partibaserad Saldo Historik"
#: erpnext/stock/report/fifo_queue_vs_qty_after_transaction_comparison/fifo_queue_vs_qty_after_transaction_comparison.py:164
#: erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py:183
@@ -9814,7 +9814,7 @@ msgstr "Kan inte demontera {0} mot Lager Post {1}. Endast {2} tillgängliga för
#: erpnext/setup/doctype/company/company.py:233
msgid "Cannot enable Item-wise Inventory Account, as there are existing Stock Ledger Entries for the company {0} with Warehouse-wise Inventory Account. Please cancel the stock transactions first and try again."
msgstr "Kan inte aktivera Lager Konto per Lager, eftersom det redan finns befintliga Lager Register Poster för {0} med Lager Konto per Lager. Avbryt lager transaktioner först och försök igen."
msgstr "Kan inte aktivera Artikelbaserad Lager Konto, eftersom det redan finns befintliga Lager Register Poster för {0} med Lagerbaserad Lager Konto. Avbryt lager transaktioner först och försök igen."
#: erpnext/crm/doctype/crm_settings/crm_settings.py:43
msgid "Cannot enable Opportunity creation from Contact Us because the Contact Us form is disabled."
@@ -10213,7 +10213,7 @@ msgstr "Kategori Detaljer"
#: erpnext/assets/dashboard_fixtures.py:93
msgid "Category-wise Asset Value"
msgstr "Tillgång Värde per Kategori"
msgstr "Kategoribaserad Tillgång Värde"
#: erpnext/buying/doctype/purchase_order/purchase_order.py:289
#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:140
@@ -15118,7 +15118,7 @@ msgstr "Kund eller Artikel"
#: erpnext/setup/doctype/authorization_rule/authorization_rule.py:93
msgid "Customer required for 'Customerwise Discount'"
msgstr "Kund erfordras för \"Kund Rabatt\""
msgstr "Kund erfordras för \"Kundbaserad Rabatt\""
#: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:885
#: erpnext/selling/doctype/sales_order/sales_order.py:392
@@ -15171,7 +15171,7 @@ msgstr "Kundens Leverantör"
#. Name of a report
#: erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.json
msgid "Customer-wise Item Price"
msgstr "Artikel Pris per Kund"
msgstr "Kundbaserad Artikel Pris"
#: erpnext/crm/report/lost_opportunity/lost_opportunity.py:43
msgid "Customer/Lead Name"
@@ -15206,7 +15206,7 @@ msgstr "Kunder inte valda."
#. Option for the 'Based On' (Select) field in DocType 'Authorization Rule'
#: erpnext/setup/doctype/authorization_rule/authorization_rule.json
msgid "Customerwise Discount"
msgstr "Rabatt per Kund"
msgstr "Kundbaserad Rabatt"
#. Name of a DocType
#. Label of the customs_tariff_number (Link) field in DocType 'Item'
@@ -17168,7 +17168,7 @@ msgstr "Dimension Namn"
#. Name of a report
#: erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.json
msgid "Dimension-wise Accounts Balance Report"
msgstr "Bokföring Saldo Rapport per Dimension"
msgstr "Dimension baserad Bokföring Saldo Rapport"
#. Label of the dimensions_section (Section Break) field in DocType 'GL Entry'
#: erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -17872,7 +17872,7 @@ msgstr "Utvidga Ej"
#: erpnext/stock/doctype/stock_settings/stock_settings.py:129
msgid "Do Not Use Batchwise Valuation"
msgstr "Använd inte Parti baserad Värdering"
msgstr "Använd inte Partibaserad Värdering"
#. Label of the do_not_fetch_incoming_rate_from_serial_no (Check) field in
#. DocType 'Stock Reposting Settings'
@@ -18890,7 +18890,7 @@ msgstr "Aktivera Automatisk E-post"
#: erpnext/stock/doctype/item/item.py:1171
msgid "Enable Auto Re-Order"
msgstr "Aktivera Automatisk Ombeställning"
msgstr "Aktivera Automatisk Återbeställning"
#. Label of the enable_party_matching (Check) field in DocType 'Accounts
#. Settings'
@@ -18969,7 +18969,7 @@ msgstr "Aktivera Oförenderlig Bokföring"
#. 'Company'
#: erpnext/setup/doctype/company/company.json
msgid "Enable Item-wise Inventory Account"
msgstr "Aktivera Lager Konto per Artikel"
msgstr "Aktivera Artikelbaserad Lager Konto"
#. Label of the enable_loyalty_point_program (Check) field in DocType 'Accounts
#. Settings'
@@ -21056,7 +21056,7 @@ msgstr "Följ Kalender Månader"
#: erpnext/templates/emails/reorder_item.html:1
msgid "Following Material Requests have been raised automatically based on Item's re-order level"
msgstr "Följande Material Begäran skapades automatiskt baserat på Artikel beställning nivå"
msgstr "Följande Material Begäran skapades automatiskt baserat på Artikel återbeställning nivå"
#: erpnext/selling/doctype/customer/mapper.py:173
msgid "Following fields are mandatory to create address:"
@@ -23792,7 +23792,7 @@ msgstr "Om artikel handlas som Noll Värdering Pris i denna post, aktivera 'Till
#. Request Item'
#: erpnext/stock/doctype/material_request_item/material_request_item.json
msgid "If the reorder check is set at the Group warehouse level, the available quantity becomes the sum of the projected quantities of all its child warehouses."
msgstr "Om ombeställning kontroll är angiven på grupp lager nivå blir tillgänglig kvantitet summa av planerad kvantitet för alla underordnade lager."
msgstr "Om återbeställning kontroll är angiven på grupp lager nivå blir tillgänglig kvantitet summa av planerad kvantitet för alla underordnade lager."
#: erpnext/manufacturing/doctype/work_order/work_order.js:1286
msgid "If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed."
@@ -24688,7 +24688,7 @@ msgstr "Felaktig Parti Förbrukad"
#: erpnext/stock/doctype/item/item.py:602
msgid "Incorrect Check in (group) Warehouse for Reorder"
msgstr "Felaktig vald (grupp) Lager för Ombeställning"
msgstr "Felaktig vald (grupp) Lager för Återbeställning"
#: erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py:148
msgid "Incorrect Company"
@@ -27177,7 +27177,7 @@ msgstr "Artikel Grupp inte angiven i Artikel Inställningar för Artikel {0}"
#. Option for the 'Based On' (Select) field in DocType 'Authorization Rule'
#: erpnext/setup/doctype/authorization_rule/authorization_rule.json
msgid "Item Group wise Discount"
msgstr "Rabatt per Artikel Grupp"
msgstr "Artikel Grupp baserad Rabatt"
#. Label of the item_groups (Table) field in DocType 'POS Profile'
#: erpnext/accounts/doctype/pos_profile/pos_profile.json
@@ -27519,7 +27519,7 @@ msgstr "Artikel Referens"
#: erpnext/stock/doctype/item_reorder/item_reorder.json
#: erpnext/stock/doctype/material_request_item/material_request_item.json
msgid "Item Reorder"
msgstr "Artikel Ombeställning"
msgstr "Artikel Återbeställning"
#. Label of the item_row (Data) field in DocType 'Item Wise Tax Detail'
#: erpnext/accounts/doctype/item_wise_tax_detail/item_wise_tax_detail.json
@@ -27730,12 +27730,12 @@ msgstr "Var Används Artikel"
#: erpnext/stock/report/item_wise_consumption/item_wise_consumption.json
#: erpnext/workspace_sidebar/buying.json
msgid "Item Wise Consumption"
msgstr "Artikelvis Förbrukning"
msgstr "Artikelbaserad Förbrukning"
#. Name of a DocType
#: erpnext/accounts/doctype/item_wise_tax_detail/item_wise_tax_detail.json
msgid "Item Wise Tax Detail"
msgstr "Moms Detalj per Artikel"
msgstr "Artikelbaserad Moms Detalj"
#. Label of the item_wise_tax_details (Table) field in DocType 'POS Invoice'
#. Label of the item_wise_tax_details (Table) field in DocType 'Purchase
@@ -27759,11 +27759,11 @@ msgstr "Moms Detalj per Artikel"
#: erpnext/stock/doctype/delivery_note/delivery_note.json
#: erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
msgid "Item Wise Tax Details"
msgstr "Artikel Moms Detaljer"
msgstr "Artikelbaserade Moms Detaljer"
#: erpnext/controllers/taxes_and_totals.py:562
msgid "Item Wise Tax Details do not match with Taxes and Charges at the following rows:"
msgstr "Artikel Moms Detaljer stämmer inte överens med Moms och Avgifter på följande rader:"
msgstr "Artikelbaserade Moms Detaljer stämmer inte med Moms och Avgifter på följande rader:"
#. Label of the section_break_rrrx (Section Break) field in DocType 'Sales
#. Forecast'
@@ -27967,7 +27967,7 @@ msgstr "Artikel {0}: {1} Kvantitet producerad ."
#. Name of a report
#: erpnext/stock/report/item_wise_price_list_rate/item_wise_price_list_rate.json
msgid "Item-wise Price List Rate"
msgstr "Prislista Pris per Artikel"
msgstr "Artikelbaserad Prislista Pris "
#. Name of a report
#. Label of a Link in the Buying Workspace
@@ -27976,14 +27976,14 @@ msgstr "Prislista Pris per Artikel"
#: erpnext/buying/workspace/buying/buying.json
#: erpnext/workspace_sidebar/buying.json
msgid "Item-wise Purchase History"
msgstr "Inköp Historik per Artikel"
msgstr "Artikelbaserad Inköp Historik"
#. Name of a report
#. Label of a Workspace Sidebar Item
#: erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.json
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Item-wise Purchase Register"
msgstr "Inköp Register per Artikel"
msgstr "Artikelbaserad Inköp Register"
#. Name of a report
#. Label of a Link in the Selling Workspace
@@ -27992,19 +27992,19 @@ msgstr "Inköp Register per Artikel"
#: erpnext/selling/workspace/selling/selling.json
#: erpnext/workspace_sidebar/selling.json
msgid "Item-wise Sales History"
msgstr "Försäljning Historik per Artikel"
msgstr "Artikelbaserad Försäljning Historik"
#. Name of a report
#. Label of a Workspace Sidebar Item
#: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.json
#: erpnext/workspace_sidebar/selling.json
msgid "Item-wise Sales Register"
msgstr "Försäljning Register per Artikel"
msgstr "Artikelbaserad Försäljning Register"
#. Label of a Workspace Sidebar Item
#: erpnext/workspace_sidebar/financial_reports.json
msgid "Item-wise sales Register"
msgstr "Försäljning Register per Artikel"
msgstr "Artikelbaserad Försäljning Register"
#: erpnext/stock/get_item_details.py:769
msgid "Item/Item Code required to get Item Tax Template."
@@ -28111,7 +28111,7 @@ msgstr "Artikel {0} saknas i Artikel Register."
#. Option for the 'Based On' (Select) field in DocType 'Authorization Rule'
#: erpnext/setup/doctype/authorization_rule/authorization_rule.json
msgid "Itemwise Discount"
msgstr "Rabatt per Artikel"
msgstr "Artikelbaserad Rabatt"
#. Name of a report
#. Label of a Link in the Stock Workspace
@@ -28120,7 +28120,7 @@ msgstr "Rabatt per Artikel"
#: erpnext/stock/workspace/stock/stock.json
#: erpnext/workspace_sidebar/stock.json
msgid "Itemwise Recommended Reorder Level"
msgstr "Rekommenderad Ombeställning Nivå per Artikel"
msgstr "Artikelbaserad Rekommenderad Återbeställning Nivå"
#. Option for the 'Barcode Type' (Select) field in DocType 'Item Barcode'
#: erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -40686,16 +40686,16 @@ msgstr "Projekt kommer att vara tillgänglig på hemsida till dessa Användare"
#: erpnext/projects/workspace/projects/projects.json
#: erpnext/workspace_sidebar/projects.json
msgid "Project wise Stock Tracking"
msgstr "Lager Spårning per Projekt"
msgstr "Projektbaserad Lager Spårning"
#. Name of a report
#: erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.json
msgid "Project wise Stock Tracking "
msgstr "Lager Spårning per Projekt"
msgstr "Projektbaserad Lager Spårning "
#: erpnext/controllers/trends.py:457
msgid "Project-wise data is not available for Quotation"
msgstr "Data per Projekt finns inte tillgängligt för Försäljning Offert"
msgstr "Projektbaserad data är inte tillgängligt för Försäljning Offert"
#. Label of the projected_on_hand (Float) field in DocType 'Material Request
#. Item'
@@ -41821,7 +41821,7 @@ msgstr "Kvantitet att Producera"
#: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py:56
msgid "Qty Wise Chart"
msgstr "Kvantitet Diagram"
msgstr "Kvantitetbaserad Diagram"
#. Label of the section_break_6 (Section Break) field in DocType 'Asset
#. Capitalization Service Item'
@@ -42669,7 +42669,7 @@ msgstr "Inköp Offerter är inte tillåtna för {0} på grund av Resultat Kort v
#. Label of the auto_indent (Check) field in DocType 'Stock Settings'
#: erpnext/stock/doctype/stock_settings/stock_settings.json
msgid "Raise Material Request when stock reaches re-order level"
msgstr "Skapa Material Begäran när Lager når ombeställning nivå"
msgstr "Skapa Material Begäran när Lager når återbeställning nivå"
#. Label of the complaint_raised_by (Data) field in DocType 'Warranty Claim'
#: erpnext/support/doctype/warranty_claim/warranty_claim.json
@@ -43168,12 +43168,12 @@ msgstr "Återöppna"
#. Label of the warehouse_reorder_level (Float) field in DocType 'Item Reorder'
#: erpnext/stock/doctype/item_reorder/item_reorder.json
msgid "Re-order Level"
msgstr "Ombeställning Nivå"
msgstr "Återbeställning Nivå"
#. Label of the warehouse_reorder_qty (Float) field in DocType 'Item Reorder'
#: erpnext/stock/doctype/item_reorder/item_reorder.json
msgid "Re-order Qty"
msgstr "Ombeställning Kvantitet"
msgstr "Återbeställning Kvantitet"
#: erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py:227
msgid "Reached Root"
@@ -44313,18 +44313,18 @@ msgstr "Hyrd"
#: erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py:64
#: erpnext/stock/report/stock_projected_qty/stock_projected_qty.py:213
msgid "Reorder Level"
msgstr "Ombeställning Nivå"
msgstr "Återbeställning Nivå"
#. Label of the reorder_qty (Float) field in DocType 'Material Request Item'
#: erpnext/stock/doctype/material_request_item/material_request_item.json
#: erpnext/stock/report/stock_projected_qty/stock_projected_qty.py:220
msgid "Reorder Qty"
msgstr "Ombeställning Kvantitet"
msgstr "Återbeställning Kvantitet"
#. Label of the reorder_levels (Table) field in DocType 'Item'
#: erpnext/stock/doctype/item/item.json
msgid "Reorder level based on Warehouse"
msgstr "Ombeställning Nivå Baserad på Lager"
msgstr "Återbeställning Nivå Baserad på Lager"
#. Option for the 'Purpose' (Select) field in DocType 'Stock Entry'
#. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type'
@@ -46411,7 +46411,7 @@ msgstr "Rad #{0}: Välj Underenhet Lager"
#: erpnext/stock/doctype/item/item.py:590
msgid "Row #{0}: Please set reorder quantity"
msgstr "Rad # {0}: Ange Ombeställning Kvantitet"
msgstr "Rad #{0}: Ange Återbeställning Kvantitet"
#: erpnext/controllers/accounts_controller.py:522
msgid "Row #{0}: Please update deferred revenue/expense account in item row or default account in company master"
@@ -48044,7 +48044,7 @@ msgstr "Säljare Mål"
#: erpnext/selling/workspace/selling/selling.json
#: erpnext/workspace_sidebar/selling.json
msgid "Sales Person-wise Transaction Summary"
msgstr "Transaktion Översikt per Säljare"
msgstr "Säljarebaserad Transaktion Översikt"
#. Label of a Workspace Sidebar Item
#: erpnext/selling/page/sales_funnel/sales_funnel.js:50
@@ -49993,7 +49993,7 @@ msgstr "Ange Total Summa till Standard Betalning Metod"
#. 'Territory'
#: erpnext/setup/doctype/territory/territory.json
msgid "Set Item Group-wise budgets on this Territory. You can also include seasonality by setting the Distribution."
msgstr "Ange Budget per Artikel Grupp för detta Distrikt. Man kan även inkludera säsongvariationer genom att ange Fördelning."
msgstr "Ange Artikel Grupp baserad Budget för detta Distrikt. Inkludera även säsongvariationer genom att ange Fördelning."
#. Label of the set_landed_cost_based_on_purchase_invoice_rate (Check) field in
#. DocType 'Buying Settings'
@@ -50726,7 +50726,7 @@ msgstr "Visa Kumulativ Belopp"
#: erpnext/stock/report/stock_balance/stock_balance.js:143
msgid "Show Dimension Wise Stock"
msgstr "Visa Lager per Dimension"
msgstr "Visa Dimensionbaserad Lager"
#: erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js:29
msgid "Show Disabled Items"
@@ -50863,7 +50863,7 @@ msgstr "Visa Varianter"
#: erpnext/stock/report/stock_ageing/stock_ageing.js:64
msgid "Show Warehouse-wise Stock"
msgstr "Visa Lager Värde per Lager"
msgstr "Visa Lagerbaserad Lager Värde"
#: erpnext/manufacturing/report/bom_stock_analysis/bom_stock_analysis.js:26
msgid "Show availability of exploded items"
@@ -55085,7 +55085,7 @@ msgstr "Distrikt Mål"
#. Name of a report
#: erpnext/selling/report/territory_wise_sales/territory_wise_sales.json
msgid "Territory-wise Sales"
msgstr "Försäljning per Distrikt"
msgstr "Distriktbaserad Försäljning"
#. Name of a UOM
#: erpnext/setup/setup_wizard/data/uom_data.json
@@ -55656,7 +55656,7 @@ msgstr "{0} innehåller Enhet Pris Artiklar."
#: erpnext/stock/doctype/item/item.py:491
msgid "The {0} prefix '{1}' already exists. Please change the Serial No Series, otherwise you will get a Duplicate Entry error."
msgstr "Prefix {0} '{1}' finns redan. Ändra serienummer, annars blir det dubblett post."
msgstr "Prefix {0} '{1}' finns redan. Ändra serie nummer, annars blir det Dubbel Post."
#: erpnext/stock/doctype/material_request/material_request.py:572
msgid "The {0} {1} created successfully"
@@ -60435,7 +60435,7 @@ msgstr "Verifikat {0} är övertilldelad av {1}"
#. Name of a report
#: erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.json
msgid "Voucher-wise Balance"
msgstr "Saldo per Verifikat"
msgstr "Verifikatbaserad Saldo"
#. Label of the vouchers (Table) field in DocType 'Repost Accounting Ledger'
#. Label of the selected_vouchers_section (Section Break) field in DocType
@@ -60563,7 +60563,7 @@ msgstr "Lager Typ"
#: erpnext/stock/workspace/stock/stock.json
#: erpnext/workspace_sidebar/stock.json
msgid "Warehouse Wise Stock Balance"
msgstr "Lager Saldo per Lager"
msgstr "Lagerbaserad Lager Saldo"
#. Label of the warehouse_and_reference (Section Break) field in DocType
#. 'Request for Quotation Item'
@@ -60616,7 +60616,7 @@ msgstr "Lager erfodras för Lager Artikel {0}"
#. Name of a report
#: erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.json
msgid "Warehouse wise Item Balance Age and Value"
msgstr "Artikel Saldo Ålder och Värde per Lager"
msgstr "Lagerbaserad Artikel Saldo, Ålder och Värde"
#: erpnext/stock/doctype/warehouse/warehouse.py:95
msgid "Warehouse {0} can not be deleted as quantity exists for Item {1}"
@@ -61926,7 +61926,7 @@ msgstr "Du har inte utfört några avstämningar i denna sessionen ännu."
#: erpnext/stock/doctype/item/item.py:1170
msgid "You have to enable auto re-order in Stock Settings to maintain re-order levels."
msgstr "Du måste aktivera automatisk ombeställning i lager inställningar för att behålla ombeställning nivåer."
msgstr "Du måste aktivera automatisk återbeställning i Lager Inställningar för att behålla återbeställning nivåer."
#: erpnext/selling/page/point_of_sale/pos_controller.js:272
msgid "You have unsaved changes. Do you want to save the invoice?"
@@ -62020,7 +62020,7 @@ msgstr "Zip Fil"
#: erpnext/stock/reorder_item.py:364
msgid "[Important] [ERPNext] Auto Reorder Errors"
msgstr "[Viktigt] [System] Automatisk Ombeställning Fel"
msgstr "[Viktigt] [System] Automatisk Återbeställning Fel"
#: erpnext/controllers/status_updater.py:306
msgid "`Allow Negative rates for Items`"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -20,7 +20,6 @@ SLE_FIELDS = (
"outgoing_rate",
"stock_queue",
"batch_no",
"serial_no",
"stock_value",
"stock_value_difference",
"valuation_rate",
@@ -53,16 +52,16 @@ def add_invariant_check_fields(sles, filters):
balance_qty = 0.0
balance_stock_value = 0.0
incorrect_idx = None
float_precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) or 3
currency_precision = (
cint(frappe.db.get_single_value("System Settings", "currency_precision")) or float_precision
)
incorrect_idx = 0
precision = frappe.get_precision("Stock Ledger Entry", "actual_qty")
for idx, sle in enumerate(sles):
if sle.batch_no:
sle.use_batchwise_valuation = frappe.db.get_value(
"Batch", sle.batch_no, "use_batchwise_valuation", cache=True
)
queue = json.loads(sle.stock_queue) if sle.stock_queue else []
fifo_qty = 0.0
fifo_value = 0.0
for qty, rate in queue:
fifo_qty += qty
fifo_value += qty * rate
if sle.actual_qty < 0:
sle.consumption_rate = sle.stock_value_difference / sle.actual_qty
@@ -78,67 +77,57 @@ def add_invariant_check_fields(sles, filters):
if balance_qty is None:
balance_qty = sle.qty_after_transaction
sle.fifo_queue_qty = fifo_qty
sle.fifo_stock_value = fifo_value
sle.fifo_valuation_rate = fifo_value / fifo_qty if fifo_qty else None
sle.balance_value_by_qty = (
sle.stock_value / sle.qty_after_transaction if sle.qty_after_transaction else None
)
sle.expected_qty_after_transaction = balance_qty
sle.stock_value_from_diff = balance_stock_value
# set difference fields
sle.difference_in_qty = sle.qty_after_transaction - sle.expected_qty_after_transaction
sle.fifo_qty_diff = sle.qty_after_transaction - fifo_qty
sle.fifo_value_diff = sle.stock_value - fifo_value
sle.fifo_valuation_diff = (
sle.valuation_rate - sle.fifo_valuation_rate if sle.fifo_valuation_rate else None
)
sle.valuation_diff = (
sle.valuation_rate - sle.balance_value_by_qty if sle.balance_value_by_qty else None
)
sle.diff_value_diff = sle.stock_value_from_diff - sle.stock_value
if maintains_fifo_queue(sle):
add_fifo_fields(sle, sles[idx - 1] if idx else None)
if not incorrect_idx and filters.get("show_incorrect_entries"):
if is_sle_has_correct_data(sle, precision):
continue
else:
incorrect_idx = idx
if incorrect_idx is None and not is_sle_has_correct_data(sle, float_precision, currency_precision):
incorrect_idx = idx
if idx > 0:
sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value
sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference
if sle.batch_no:
sle.use_batchwise_valuation = frappe.db.get_value(
"Batch", sle.batch_no, "use_batchwise_valuation", cache=True
)
if filters.get("show_incorrect_entries"):
if incorrect_idx is None:
return []
return sles[max(incorrect_idx - 1, 0) :]
if incorrect_idx > 0:
sles = sles[cint(incorrect_idx) - 1 :]
return []
return sles
def maintains_fifo_queue(sle):
# no queue is maintained for serialized/batchwise-valued stock
return not (
sle.serial_and_batch_bundle or sle.serial_no or (sle.batch_no and sle.use_batchwise_valuation)
)
def is_sle_has_correct_data(sle, precision):
if flt(sle.difference_in_qty, precision) != 0.0 or flt(sle.diff_value_diff, precision) != 0:
print(flt(sle.difference_in_qty, precision), flt(sle.diff_value_diff, precision))
return False
def add_fifo_fields(sle, prev_sle):
queue = json.loads(sle.stock_queue) if sle.stock_queue else []
fifo_qty = 0.0
fifo_value = 0.0
for qty, rate in queue:
fifo_qty += qty
fifo_value += qty * rate
sle.fifo_queue_qty = fifo_qty
sle.fifo_stock_value = fifo_value
sle.fifo_valuation_rate = fifo_value / fifo_qty if fifo_qty else None
sle.fifo_qty_diff = sle.qty_after_transaction - fifo_qty
sle.fifo_value_diff = sle.stock_value - fifo_value
sle.fifo_valuation_diff = (
sle.valuation_rate - sle.fifo_valuation_rate if sle.fifo_valuation_rate else None
)
# prev row may not maintain a queue; H and H - F stay blank across the gap
if prev_sle and prev_sle.fifo_stock_value is not None:
sle.fifo_stock_diff = sle.fifo_stock_value - prev_sle.fifo_stock_value
sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference
def is_sle_has_correct_data(sle, float_precision, currency_precision):
return (
flt(sle.difference_in_qty, float_precision) == 0.0
and flt(sle.diff_value_diff, currency_precision) == 0.0
)
return True
def get_columns():

View File

@@ -42,35 +42,3 @@ class TestStockLedgerInvariantCheck(ERPNextTestSuite):
data = self.run_report(item_code=item)
self.assertEqual(data[-1].qty_after_transaction, 11)
def test_show_incorrect_entries(self):
item = self.make_movements()
self.assertEqual(self.run_report(item_code=item, show_incorrect_entries=1), [])
sle = frappe.get_last_doc(
"Stock Ledger Entry", {"item_code": item, "warehouse": WAREHOUSE, "is_cancelled": 0}
)
frappe.db.set_value(
"Stock Ledger Entry", sle.name, "qty_after_transaction", sle.qty_after_transaction + 5
)
data = self.run_report(item_code=item, show_incorrect_entries=1)
self.assertEqual(len(data), 2) # incorrect entry + one before it for context
self.assertEqual(data[-1].name, sle.name)
def test_batch_item_skips_fifo_queue_checks(self):
from erpnext.stock.doctype.item.test_item import make_item
item = make_item(
properties={"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "SLIC-BAT-.####"}
).name
make_stock_entry(item_code=item, to_warehouse=WAREHOUSE, qty=10, rate=100)
data = self.run_report(item_code=item)
self.assertTrue(data)
for row in data:
self.assertIsNone(row.fifo_qty_diff)
self.assertIsNone(row.fifo_value_diff)
self.assertEqual(self.run_report(item_code=item, show_incorrect_entries=1), [])

View File

@@ -205,10 +205,7 @@ def get_data(filters=None):
data = []
if item_warehouse_map:
float_precision = cint(frappe.db.get_single_value("System Settings", "float_precision")) or 3
currency_precision = (
cint(frappe.db.get_single_value("System Settings", "currency_precision")) or float_precision
)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
for item_warehouse in item_warehouse_map:
report_data = stock_ledger_invariant_check(item_warehouse)
@@ -218,11 +215,7 @@ def get_data(filters=None):
for row in report_data:
if has_difference(
row,
float_precision,
currency_precision,
filters.difference_in,
item_warehouse.valuation_method or valuation_method,
row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method
):
row.update(
{
@@ -268,26 +261,23 @@ def get_item_warehouse_combinations(filters: dict | None = None) -> dict:
return query.run(as_dict=1)
def has_difference(row, float_precision, currency_precision, difference_in, valuation_method):
def has_difference(row, precision, difference_in, valuation_method):
if valuation_method == "Moving Average":
qty_diff = flt(row.difference_in_qty, float_precision)
value_diff = flt(row.diff_value_diff, currency_precision)
valuation_diff = flt(row.valuation_diff, currency_precision)
qty_diff = flt(row.difference_in_qty, precision)
value_diff = flt(row.diff_value_diff, precision)
valuation_diff = flt(row.valuation_diff, precision)
else:
qty_diff = flt(row.difference_in_qty, float_precision)
value_diff = flt(row.diff_value_diff, currency_precision)
qty_diff = flt(row.difference_in_qty, precision)
value_diff = flt(row.diff_value_diff, precision)
if row.stock_queue and json.loads(row.stock_queue):
value_diff = value_diff or (
flt(row.fifo_value_diff, currency_precision)
or flt(row.fifo_difference_diff, currency_precision)
flt(row.fifo_value_diff, precision) or flt(row.fifo_difference_diff, precision)
)
qty_diff = qty_diff or flt(row.fifo_qty_diff, float_precision)
qty_diff = qty_diff or flt(row.fifo_qty_diff, precision)
valuation_diff = flt(row.valuation_diff, currency_precision) or flt(
row.fifo_valuation_diff, currency_precision
)
valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
if difference_in == "Qty" and qty_diff:
return True
@@ -297,5 +287,3 @@ def has_difference(row, float_precision, currency_precision, difference_in, valu
return True
elif difference_in not in ["Qty", "Value", "Valuation"] and (qty_diff or value_diff or valuation_diff):
return True
return False

View File

@@ -1204,11 +1204,7 @@ class update_entries_after:
self.wh_data.stock_queue = json.loads(stock_queue[0]) if stock_queue else []
self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + doc.total_amount)
# Replay the immutable qty recorded on the SLE at submission, not the bundle's recomputed
# total_qty. A valuation repost must never rewrite physical quantities; if the bundle's child
# rows were edited after submission, doc.total_qty would silently corrupt qty_after_transaction
# (and every downstream balance). sle.actual_qty is the frozen movement for this entry.
self.wh_data.qty_after_transaction += flt(sle.actual_qty, self.flt_precision)
self.wh_data.qty_after_transaction += flt(doc.total_qty, self.flt_precision)
if flt(self.wh_data.qty_after_transaction, self.flt_precision):
self.wh_data.valuation_rate = flt(self.wh_data.stock_value, self.flt_precision) / flt(
self.wh_data.qty_after_transaction, self.flt_precision