mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-03 21:50:53 +00:00
Compare commits
3 Commits
chore/test
...
l10n_devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abff82a4b2 | ||
|
|
3a63c74382 | ||
|
|
6bd2f29ab5 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
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
File diff suppressed because one or more lines are too long
@@ -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."
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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():
|
||||
|
||||
@@ -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), [])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user