mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-26 12:28:35 +00:00
Compare commits
68 Commits
chore/test
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e2adc0706 | ||
|
|
05e7317375 | ||
|
|
c5700f5df4 | ||
|
|
b9f5a77fa7 | ||
|
|
1ffdfbe86e | ||
|
|
c3ae7a0b95 | ||
|
|
99e85bfd82 | ||
|
|
dca5d2ca3a | ||
|
|
dc59ee8034 | ||
|
|
76da65ab4c | ||
|
|
bde23492fb | ||
|
|
d47bc64576 | ||
|
|
4ac863d653 | ||
|
|
5fe84305fc | ||
|
|
a972ef313a | ||
|
|
245925815e | ||
|
|
7f5f2ccfa3 | ||
|
|
dc4f5ce0ab | ||
|
|
182ef8a8e8 | ||
|
|
67ac8f64e8 | ||
|
|
9158d5f893 | ||
|
|
0e6f50ca24 | ||
|
|
6ceddd7a83 | ||
|
|
293ca4e96f | ||
|
|
05e44ca63a | ||
|
|
0583349ae4 | ||
|
|
0776f7f7fa | ||
|
|
0e54e532ff | ||
|
|
b537e8b183 | ||
|
|
ce5239132c | ||
|
|
8bef2b13a1 | ||
|
|
4a6b189221 | ||
|
|
08ce18fe58 | ||
|
|
7661e5ed96 | ||
|
|
dddaa80f99 | ||
|
|
09be6fed9a | ||
|
|
5790bcf99d | ||
|
|
ea9bf932d8 | ||
|
|
993a011005 | ||
|
|
d18177665b | ||
|
|
507bc0930e | ||
|
|
c3d2ebd734 | ||
|
|
142d80d7de | ||
|
|
c8f86099e2 | ||
|
|
ab1e949752 | ||
|
|
8ad90338a3 | ||
|
|
d2ff0913df | ||
|
|
05dd44246f | ||
|
|
6e22f4b063 | ||
|
|
d737c39131 | ||
|
|
38385432f6 | ||
|
|
78fd06048f | ||
|
|
1b68445313 | ||
|
|
3a3f56350f | ||
|
|
70bd57d3e7 | ||
|
|
548d90df4f | ||
|
|
9ec043ffcb | ||
|
|
10744d1332 | ||
|
|
b161e5aa79 | ||
|
|
4efb43d977 | ||
|
|
f5bf915104 | ||
|
|
33562a6a86 | ||
|
|
95b6cf2847 | ||
|
|
11da80c9c5 | ||
|
|
0a462f8d2f | ||
|
|
f4413ebda3 | ||
|
|
168c24f8f0 | ||
|
|
adfef48a65 |
2
.github/workflows/linters.yml
vendored
2
.github/workflows/linters.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
cache: pip
|
||||
|
||||
- name: Install and Run Pre-commit
|
||||
uses: pre-commit/action@v3.0.0
|
||||
uses: pre-commit/action@v3.0.1
|
||||
|
||||
semgrep:
|
||||
name: semgrep
|
||||
|
||||
@@ -73,7 +73,7 @@ class ExchangeRateRevaluation(Document):
|
||||
|
||||
def validate_mandatory(self):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
frappe.throw(_("Please select Company and Posting Date to get entries"))
|
||||
|
||||
def before_submit(self):
|
||||
self.remove_accounts_without_gain_loss()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import today
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
@@ -63,13 +64,9 @@ class TestLoyaltyPointEntry(ERPNextTestSuite):
|
||||
self.assertEqual(doc.loyalty_points, -7)
|
||||
|
||||
# Check balance
|
||||
balance = frappe.db.sql(
|
||||
"""
|
||||
SELECT SUM(loyalty_points)
|
||||
FROM `tabLoyalty Point Entry`
|
||||
WHERE customer = %s
|
||||
""",
|
||||
(self.customer_name,),
|
||||
)[0][0]
|
||||
lpe = frappe.qb.DocType("Loyalty Point Entry")
|
||||
balance = (
|
||||
frappe.qb.from_(lpe).select(Sum(lpe.loyalty_points)).where(lpe.customer == self.customer_name)
|
||||
).run()[0][0]
|
||||
|
||||
self.assertEqual(balance, 3) # 10 added, 7 redeemed
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, flt, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
@@ -262,14 +263,12 @@ class TestLoyaltyProgram(ERPNextTestSuite):
|
||||
|
||||
def get_points_earned(self):
|
||||
def get_returned_amount():
|
||||
returned_amount = frappe.db.sql(
|
||||
"""
|
||||
select sum(grand_total)
|
||||
from `tabSales Invoice`
|
||||
where docstatus=1 and is_return=1 and ifnull(return_against, '')=%s
|
||||
""",
|
||||
self.name,
|
||||
)
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
returned_amount = (
|
||||
frappe.qb.from_(si)
|
||||
.select(Sum(si.grand_total))
|
||||
.where((si.docstatus == 1) & (si.is_return == 1) & (si.return_against == self.name))
|
||||
).run()
|
||||
return abs(flt(returned_amount[0][0])) if returned_amount else 0
|
||||
|
||||
lp_details = get_loyalty_program_details_with_points(
|
||||
|
||||
@@ -10,9 +10,9 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
|
||||
|
||||
class TestPOSInvoiceMerging(POSInvoiceTestMixin):
|
||||
def clear_pos_data(self):
|
||||
frappe.db.sql("delete from `tabPOS Opening Entry`;")
|
||||
frappe.db.sql("delete from `tabPOS Closing Entry`;")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`;")
|
||||
frappe.db.delete("POS Opening Entry")
|
||||
frappe.db.delete("POS Closing Entry")
|
||||
frappe.db.delete("POS Invoice")
|
||||
|
||||
def setUp(self):
|
||||
self.clear_pos_data()
|
||||
|
||||
@@ -25,15 +25,11 @@ class TestPOSProfile(ERPNextTestSuite):
|
||||
items = get_items_list(doc, doc.company)
|
||||
customers = get_customers_list(doc)
|
||||
|
||||
products_count = frappe.db.sql(
|
||||
""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1
|
||||
)
|
||||
customers_count = frappe.db.sql(
|
||||
""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'"""
|
||||
)
|
||||
products_count = frappe.db.count("Item", {"item_group": "_Test Item Group"})
|
||||
customers_count = frappe.db.count("Customer", {"customer_group": "_Test Customer Group"})
|
||||
|
||||
self.assertEqual(len(items), products_count[0][0])
|
||||
self.assertEqual(len(customers), customers_count[0][0])
|
||||
self.assertEqual(len(items), products_count)
|
||||
self.assertEqual(len(customers), customers_count)
|
||||
|
||||
def test_disabled_pos_profile_creation(self):
|
||||
make_pos_profile(name="_Test POS Profile 001", disabled=1)
|
||||
@@ -83,7 +79,6 @@ class TestPOSProfile(ERPNextTestSuite):
|
||||
def get_customers_list(pos_profile=None):
|
||||
if pos_profile is None:
|
||||
pos_profile = {}
|
||||
cond = "1=1"
|
||||
customer_groups = []
|
||||
if pos_profile.get("customer_groups"):
|
||||
# Get customers based on the customer groups defined in the POS profile
|
||||
@@ -91,14 +86,16 @@ def get_customers_list(pos_profile=None):
|
||||
customer_groups.extend(
|
||||
[d.get("name") for d in get_child_nodes("Customer Group", d.get("customer_group"))]
|
||||
)
|
||||
cond = "customer_group in ({})".format(", ".join(["%s"] * len(customer_groups)))
|
||||
|
||||
filters = {"disabled": 0}
|
||||
if customer_groups:
|
||||
filters["customer_group"] = ["in", customer_groups]
|
||||
|
||||
return (
|
||||
frappe.db.sql(
|
||||
f""" select name, customer_name, customer_group, territory from tabCustomer where disabled = 0
|
||||
and {cond}""",
|
||||
tuple(customer_groups),
|
||||
as_dict=1,
|
||||
frappe.get_all(
|
||||
"Customer",
|
||||
filters=filters,
|
||||
fields=["name", "customer_name", "customer_group", "territory"],
|
||||
)
|
||||
or {}
|
||||
)
|
||||
@@ -135,8 +132,8 @@ def get_items_list(pos_profile, company):
|
||||
|
||||
|
||||
def make_pos_profile(**args):
|
||||
frappe.db.sql("delete from `tabPOS Payment Method`")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
frappe.db.delete("POS Payment Method")
|
||||
frappe.db.delete("POS Profile")
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
|
||||
@@ -91,7 +91,9 @@ class TestPricingRule(ERPNextTestSuite):
|
||||
details = get_item_details(args)
|
||||
self.assertEqual(details.get("discount_percentage"), 5)
|
||||
|
||||
frappe.db.sql("update `tabPricing Rule` set priority=NULL where campaign='_Test Campaign'")
|
||||
frappe.db.set_value(
|
||||
"Pricing Rule", {"campaign": "_Test Campaign"}, "priority", None, update_modified=False
|
||||
)
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import MultiplePricingRuleConflict
|
||||
|
||||
self.assertRaises(MultiplePricingRuleConflict, get_item_details, args)
|
||||
|
||||
@@ -597,11 +597,21 @@ def execute_synced_report(filters):
|
||||
|
||||
def get_data_duckdb(filters, conn):
|
||||
# accounts and all metadata via frappe.db — only GL Entry comes from DuckDB
|
||||
accounts = frappe.db.sql(
|
||||
"""select name, account_number, parent_account, account_name, root_type, report_type, is_group, lft, rgt
|
||||
from `tabAccount` where company=%s order by lft""",
|
||||
filters.company,
|
||||
as_dict=True,
|
||||
accounts = frappe.get_all(
|
||||
"Account",
|
||||
filters={"company": filters.company},
|
||||
fields=[
|
||||
"name",
|
||||
"account_number",
|
||||
"parent_account",
|
||||
"account_name",
|
||||
"root_type",
|
||||
"report_type",
|
||||
"is_group",
|
||||
"lft",
|
||||
"rgt",
|
||||
],
|
||||
order_by="lft",
|
||||
)
|
||||
if not accounts:
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.trial_balance_for_party.trial_balance_for_party import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestTrialBalanceForParty(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"party_type": "Customer",
|
||||
"fiscal_year": "_Test Fiscal Year 2026",
|
||||
"from_date": "2026-01-01",
|
||||
"to_date": "2026-12-31",
|
||||
**extra,
|
||||
}
|
||||
)
|
||||
return execute(filters)[1]
|
||||
|
||||
def party_row(self, party, **extra):
|
||||
return next(row for row in self.run_report(party=party, **extra) if row.get("party") == party)
|
||||
|
||||
def test_sales_invoice_shown_as_period_debit(self):
|
||||
customer = "_Test Customer"
|
||||
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
|
||||
|
||||
row = self.party_row(customer)
|
||||
self.assertEqual(row["opening_debit"], 0)
|
||||
self.assertEqual(row["debit"], 10000)
|
||||
self.assertEqual(row["credit"], 0)
|
||||
self.assertEqual(row["closing_debit"], 10000)
|
||||
self.assertEqual(row["closing_credit"], 0)
|
||||
|
||||
def test_receipt_nets_invoice_in_closing(self):
|
||||
customer = "_Test Customer"
|
||||
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
|
||||
create_payment_entry(
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=customer,
|
||||
paid_from="Debtors - _TC",
|
||||
paid_to="_Test Bank - _TC",
|
||||
paid_amount=4000,
|
||||
save=True,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
row = self.party_row(customer)
|
||||
self.assertEqual(row["debit"], 10000)
|
||||
self.assertEqual(row["credit"], 4000)
|
||||
# closing nets debit against credit: 10000 - 4000
|
||||
self.assertEqual(row["closing_debit"], 6000)
|
||||
self.assertEqual(row["closing_credit"], 0)
|
||||
|
||||
def test_prior_period_invoice_shown_as_opening(self):
|
||||
customer = "_Test Customer"
|
||||
# invoice dated before from_date should land in the opening balance, not within-period
|
||||
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2025-12-01")
|
||||
|
||||
row = self.party_row(customer)
|
||||
self.assertEqual(row["opening_debit"], 10000)
|
||||
self.assertEqual(row["debit"], 0)
|
||||
self.assertEqual(row["closing_debit"], 10000)
|
||||
|
||||
def test_exclude_zero_balance_parties(self):
|
||||
customer = "_Test Customer"
|
||||
create_sales_invoice(customer=customer, qty=1, rate=10000, posting_date="2026-06-01")
|
||||
create_payment_entry(
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=customer,
|
||||
paid_from="Debtors - _TC",
|
||||
paid_to="_Test Bank - _TC",
|
||||
paid_amount=10000,
|
||||
save=True,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
# fully settled party still shows by default ...
|
||||
self.assertEqual(self.party_row(customer)["closing_debit"], 0)
|
||||
# ... but is hidden when zero-balance parties are excluded
|
||||
parties = {row.get("party") for row in self.run_report(exclude_zero_balance_parties=1)}
|
||||
self.assertNotIn(customer, parties)
|
||||
|
||||
def test_purchase_invoice_shown_as_supplier_credit(self):
|
||||
supplier = "_Test Supplier"
|
||||
make_purchase_invoice(supplier=supplier, qty=1, rate=8000, posting_date="2026-06-01")
|
||||
|
||||
row = self.party_row(supplier, party_type="Supplier")
|
||||
self.assertEqual(row["credit"], 8000)
|
||||
self.assertEqual(row["debit"], 0)
|
||||
self.assertEqual(row["closing_credit"], 8000)
|
||||
self.assertEqual(row["closing_debit"], 0)
|
||||
|
||||
def test_totals_row_sums_party_rows(self):
|
||||
create_sales_invoice(customer="_Test Customer 1", qty=1, rate=10000, posting_date="2026-06-01")
|
||||
create_sales_invoice(customer="_Test Customer 2", qty=1, rate=6000, posting_date="2026-06-01")
|
||||
|
||||
data = self.run_report()
|
||||
totals = data[-1] # totals row is appended last
|
||||
party_rows = data[:-1]
|
||||
for column in (
|
||||
"opening_debit",
|
||||
"opening_credit",
|
||||
"debit",
|
||||
"credit",
|
||||
"closing_debit",
|
||||
"closing_credit",
|
||||
):
|
||||
self.assertEqual(totals[column], sum(row[column] for row in party_rows))
|
||||
@@ -238,22 +238,13 @@ class TestAssetRepair(ERPNextTestSuite):
|
||||
submit=1,
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account,
|
||||
sum(debit) as debit,
|
||||
sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
voucher_type='Asset Repair'
|
||||
and voucher_no=%s
|
||||
group by
|
||||
account
|
||||
""",
|
||||
asset_repair.name,
|
||||
as_dict=1,
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
frappe.qb.from_(gle)
|
||||
.select(gle.account, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit"))
|
||||
.where((gle.voucher_type == "Asset Repair") & (gle.voucher_no == asset_repair.name))
|
||||
.groupby(gle.account)
|
||||
).run(as_dict=True)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
@@ -287,22 +278,13 @@ class TestAssetRepair(ERPNextTestSuite):
|
||||
submit=1,
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account,
|
||||
sum(debit) as debit,
|
||||
sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
voucher_type='Asset Repair'
|
||||
and voucher_no=%s
|
||||
group by
|
||||
account
|
||||
""",
|
||||
asset_repair.name,
|
||||
as_dict=1,
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
frappe.qb.from_(gle)
|
||||
.select(gle.account, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit"))
|
||||
.where((gle.voucher_type == "Asset Repair") & (gle.voucher_no == asset_repair.name))
|
||||
.groupby(gle.account)
|
||||
).run(as_dict=True)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
@@ -94,11 +94,12 @@ class TestAssetValueAdjustment(ERPNextTestSuite):
|
||||
("_Test Fixed Asset - _TC", 0.0, 4625.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_no = %s
|
||||
order by account""",
|
||||
adj_doc.journal_entry,
|
||||
gle = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_type": "Journal Entry", "voucher_no": adj_doc.journal_entry},
|
||||
fields=["account", "debit", "credit"],
|
||||
order_by="account",
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
@@ -184,11 +185,12 @@ class TestAssetValueAdjustment(ERPNextTestSuite):
|
||||
("_Test Fixed Asset - _TC", 0.0, 5175.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_no = %s
|
||||
order by account""",
|
||||
adj_doc.journal_entry,
|
||||
gle = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_type": "Journal Entry", "voucher_no": adj_doc.journal_entry},
|
||||
fields=["account", "debit", "credit"],
|
||||
order_by="account",
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
@@ -3,11 +3,26 @@
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
|
||||
from erpnext.assets.doctype.asset.test_asset import AssetSetup, create_asset
|
||||
from erpnext.assets.doctype.asset_capitalization.test_asset_capitalization import (
|
||||
create_asset_capitalization,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_value_adjustment.test_asset_value_adjustment import (
|
||||
make_asset_value_adjustment,
|
||||
)
|
||||
from erpnext.assets.report.fixed_asset_register.fixed_asset_register import execute
|
||||
|
||||
|
||||
class TestFixedAssetRegister(AssetSetup):
|
||||
def run_report(self, **extra):
|
||||
filters = frappe._dict(company="_Test Company", **extra)
|
||||
return execute(filters)[1]
|
||||
|
||||
def report_row(self, asset_name, **extra):
|
||||
return next(row for row in self.run_report(**extra) if row["asset_id"] == asset_name)
|
||||
|
||||
def test_report_lists_submitted_asset(self):
|
||||
"""Exercises the report's converted queries -- including the depreciation aggregate that groups
|
||||
by asset.name (must be valid on Postgres) -- by asserting a submitted asset is listed."""
|
||||
@@ -18,16 +33,170 @@ class TestFixedAssetRegister(AssetSetup):
|
||||
location="Test Location",
|
||||
submit=1,
|
||||
)
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"status": "In Location",
|
||||
"filter_based_on": "Date Range",
|
||||
"from_date": "2020-01-01",
|
||||
"to_date": "2030-12-31",
|
||||
"date_based_on": "Purchase Date",
|
||||
}
|
||||
ids = {
|
||||
row["asset_id"]
|
||||
for row in self.run_report(
|
||||
status="In Location",
|
||||
filter_based_on="Date Range",
|
||||
from_date="2020-01-01",
|
||||
to_date="2030-12-31",
|
||||
date_based_on="Purchase Date",
|
||||
)
|
||||
}
|
||||
self.assertIn(asset.name, ids)
|
||||
|
||||
def test_asset_appears_with_purchase_value(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro", net_purchase_amount=100000, purchase_amount=100000, submit=True
|
||||
)
|
||||
data = execute(filters)[1]
|
||||
asset_ids = {row.get("asset_id") for row in data}
|
||||
self.assertIn(asset.name, asset_ids)
|
||||
|
||||
row = self.report_row(asset.name)
|
||||
self.assertEqual(row["net_purchase_amount"], 100000)
|
||||
self.assertEqual(row["asset_value"], 100000) # no depreciation yet
|
||||
self.assertEqual(row["asset_category"], "Computers")
|
||||
|
||||
def test_asset_value_reduced_by_opening_depreciation(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
net_purchase_amount=100000,
|
||||
purchase_amount=100000,
|
||||
opening_accumulated_depreciation=20000,
|
||||
opening_number_of_booked_depreciations=2,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
row = self.report_row(asset.name)
|
||||
self.assertEqual(row["opening_accumulated_depreciation"], 20000)
|
||||
self.assertEqual(row["asset_value"], 80000) # 100000 - 20000
|
||||
|
||||
def test_status_in_location_filter_shows_active_asset(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro", net_purchase_amount=100000, purchase_amount=100000, submit=True
|
||||
)
|
||||
|
||||
ids = {row["asset_id"] for row in self.run_report(status="In Location")}
|
||||
self.assertIn(asset.name, ids)
|
||||
|
||||
def test_asset_category_filter(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro", net_purchase_amount=100000, purchase_amount=100000, submit=True
|
||||
)
|
||||
|
||||
ids = {row["asset_id"] for row in self.run_report(asset_category="Computers")}
|
||||
self.assertIn(asset.name, ids)
|
||||
|
||||
def test_group_by_asset_category_sums_values(self):
|
||||
before_net, before_value = self.computers_group_totals()
|
||||
|
||||
create_asset(item_code="Macbook Pro", net_purchase_amount=100000, purchase_amount=100000, submit=True)
|
||||
create_asset(
|
||||
item_code="Macbook Pro",
|
||||
asset_name="Macbook Pro 2",
|
||||
net_purchase_amount=50000,
|
||||
purchase_amount=50000,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
after_net, after_value = self.computers_group_totals()
|
||||
# assert on the delta so pre-existing Computers assets don't skew the totals
|
||||
self.assertEqual(after_net - before_net, 150000)
|
||||
self.assertEqual(after_value - before_value, 150000)
|
||||
|
||||
def computers_group_totals(self):
|
||||
row = next(
|
||||
(r for r in self.run_report(group_by="Asset Category") if r["asset_category"] == "Computers"),
|
||||
None,
|
||||
)
|
||||
return (row["net_purchase_amount"], row["asset_value"]) if row else (0, 0)
|
||||
|
||||
def test_booked_depreciation_reduces_asset_value(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2019-12-31",
|
||||
depreciation_start_date="2020-12-31",
|
||||
frequency_of_depreciation=12,
|
||||
total_number_of_depreciations=3,
|
||||
expected_value_after_useful_life=10000,
|
||||
net_purchase_amount=100000,
|
||||
purchase_amount=100000,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
# books one depreciation entry of (100000 - 10000) / 3 = 30000
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
row = self.report_row(asset.name)
|
||||
self.assertEqual(row["depreciated_amount"], 30000)
|
||||
self.assertEqual(row["asset_value"], 70000) # 100000 - 30000
|
||||
|
||||
def test_revaluation_adjusts_asset_value(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro", net_purchase_amount=100000, purchase_amount=100000, submit=True
|
||||
)
|
||||
|
||||
# revalue the asset upwards by 20000
|
||||
make_asset_value_adjustment(
|
||||
asset=asset.name, current_asset_value=100000, new_asset_value=120000
|
||||
).submit()
|
||||
|
||||
row = self.report_row(asset.name)
|
||||
self.assertEqual(row["asset_value"], 120000) # 100000 + 20000 revaluation
|
||||
|
||||
def test_depreciation_and_revaluation_together(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2019-12-31",
|
||||
depreciation_start_date="2020-12-31",
|
||||
frequency_of_depreciation=12,
|
||||
total_number_of_depreciations=3,
|
||||
expected_value_after_useful_life=10000,
|
||||
net_purchase_amount=100000,
|
||||
purchase_amount=100000,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
# books one depreciation entry of (100000 - 10000) / 3 = 30000, leaving 70000
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
# revalue the depreciated asset down from 70000 to 60000
|
||||
make_asset_value_adjustment(
|
||||
asset=asset.name, current_asset_value=70000, new_asset_value=60000
|
||||
).submit()
|
||||
|
||||
row = self.report_row(asset.name)
|
||||
self.assertEqual(row["depreciated_amount"], 30000)
|
||||
self.assertEqual(row["asset_value"], 60000) # 100000 - 30000 depreciation - 10000 revaluation
|
||||
|
||||
def test_sold_asset_hidden_from_in_location_and_shown_in_disposed(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro", net_purchase_amount=100000, purchase_amount=100000, submit=True
|
||||
)
|
||||
|
||||
create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=80000)
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
self.assertNotIn(asset.name, {row["asset_id"] for row in self.run_report(status="In Location")})
|
||||
self.assertIn(asset.name, {row["asset_id"] for row in self.run_report(status="Disposed")})
|
||||
|
||||
def test_capitalized_asset_hidden_from_in_location_and_shown_in_disposed(self):
|
||||
consumed_asset = create_asset(
|
||||
asset_name="Consumed Asset",
|
||||
net_purchase_amount=100000,
|
||||
purchase_amount=100000,
|
||||
submit=True,
|
||||
)
|
||||
composite_asset = create_asset(
|
||||
asset_name="Composite Asset", asset_type="Composite Asset", submit=False
|
||||
)
|
||||
|
||||
create_asset_capitalization(
|
||||
target_asset=composite_asset.name, consumed_asset=consumed_asset.name, submit=1
|
||||
)
|
||||
self.assertEqual(frappe.db.get_value("Asset", consumed_asset.name, "status"), "Capitalized")
|
||||
|
||||
self.assertNotIn(
|
||||
consumed_asset.name, {row["asset_id"] for row in self.run_report(status="In Location")}
|
||||
)
|
||||
self.assertIn(consumed_asset.name, {row["asset_id"] for row in self.run_report(status="Disposed")})
|
||||
|
||||
@@ -274,7 +274,7 @@ class AccountsController(TransactionBase):
|
||||
if invalid_advances := [x for x in self.advances if not x.reference_type or not x.reference_name]:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Rows: {0} in {1} section are Invalid. Reference Name should point to a valid Payment Entry or Journal Entry."
|
||||
"Rows: {0} in {1} section are invalid. Reference Name should point to a valid Payment Entry or Journal Entry."
|
||||
).format(
|
||||
frappe.bold(comma_and([x.idx for x in invalid_advances])),
|
||||
frappe.bold(_("Advance Payments")),
|
||||
@@ -1233,7 +1233,7 @@ class AccountsController(TransactionBase):
|
||||
{"sales_order": None, "sales_order_item": None},
|
||||
)
|
||||
|
||||
frappe.msgprint(_("Purchase Orders {0} are un-linked").format("\n".join(linked_po)))
|
||||
frappe.msgprint(_("Purchase Orders {0} are unlinked").format("\n".join(linked_po)))
|
||||
|
||||
def get_company_default(self, fieldname, ignore_validation=False):
|
||||
from erpnext.accounts.utils import get_company_default
|
||||
|
||||
@@ -129,7 +129,7 @@ class BuyingController(SubcontractingController):
|
||||
msg += f"<li>{po} ({date})</li>"
|
||||
msg += "</ul>"
|
||||
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(msg)
|
||||
|
||||
def create_package_for_transfer(self) -> None:
|
||||
"""Create serial and batch package for Sourece Warehouse in case of inter transfer."""
|
||||
@@ -287,7 +287,7 @@ class BuyingController(SubcontractingController):
|
||||
if self.is_return and len(not_cancelled_asset):
|
||||
frappe.throw(
|
||||
_(
|
||||
"{} has submitted assets linked to it. You need to cancel the assets to create purchase return."
|
||||
"{0} has submitted assets linked to it. You need to cancel the assets to create purchase return."
|
||||
).format(self.return_against),
|
||||
title=_("Not Allowed"),
|
||||
)
|
||||
@@ -738,7 +738,7 @@ class BuyingController(SubcontractingController):
|
||||
frappe.throw(
|
||||
_("Row #{idx}: {field_label} can not be negative for item {item_code}.").format(
|
||||
idx=item_row["idx"],
|
||||
field_label=frappe.get_meta(item_row.doctype).get_label(fieldname),
|
||||
field_label=_(frappe.get_meta(item_row.doctype).get_label(fieldname)),
|
||||
item_code=frappe.bold(item_row["item_code"]),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ def validate_return_against(doc):
|
||||
# validate update stock
|
||||
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
|
||||
frappe.throw(
|
||||
_("'Update Stock' can not be checked because items are not delivered via {0}").format(
|
||||
_("'Update Stock' cannot be checked because items are not delivered via {0}").format(
|
||||
doc.return_against
|
||||
)
|
||||
)
|
||||
|
||||
@@ -297,7 +297,7 @@ class SellingController(StockController):
|
||||
throw(
|
||||
_(
|
||||
"""Row #{0}: Selling rate for item {1} is lower than its {2}.
|
||||
Selling {3} should be atleast {4}.<br><br>Alternatively,
|
||||
Selling {3} should be at least {4}.<br><br>Alternatively,
|
||||
you can disable '{5}' in {6} to bypass
|
||||
this validation."""
|
||||
).format(
|
||||
@@ -869,7 +869,7 @@ class SellingController(StockController):
|
||||
|
||||
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
|
||||
duplicate_items_msg += "<br><br>"
|
||||
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
|
||||
duplicate_items_msg += _("Please enable {0} in {1} to allow same item in multiple rows").format(
|
||||
frappe.bold(_("Allow Item to Be Added Multiple Times in a Transaction")),
|
||||
get_link_to_form("Selling Settings", "Selling Settings"),
|
||||
)
|
||||
@@ -898,7 +898,7 @@ class SellingController(StockController):
|
||||
|
||||
if not self.get("is_internal_customer") and any(d.get("target_warehouse") for d in items):
|
||||
msg = _("Target Warehouse is set for some items but the customer is not an internal customer.")
|
||||
msg += " " + _("This {} will be treated as material transfer.").format(_(self.doctype))
|
||||
msg += " " + _("This {0} will be treated as material transfer.").format(_(self.doctype))
|
||||
frappe.msgprint(msg, title="Internal Transfer", alert=True)
|
||||
|
||||
def validate_items(self):
|
||||
|
||||
@@ -286,10 +286,10 @@ class StatusUpdater(Document):
|
||||
# get unique transactions to update
|
||||
for d in self.get_all_children():
|
||||
if hasattr(d, "qty") and flt(d.qty) < 0 and not self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be positive number").format(d.item_code))
|
||||
frappe.throw(_("For an item {0}, quantity must be a positive number").format(d.item_code))
|
||||
|
||||
if hasattr(d, "qty") and flt(d.qty) > 0 and self.get("is_return"):
|
||||
frappe.throw(_("For an item {0}, quantity must be negative number").format(d.item_code))
|
||||
frappe.throw(_("For an item {0}, quantity must be a negative number").format(d.item_code))
|
||||
|
||||
if (
|
||||
not selling_negative_rate_allowed and self.doctype in ["Sales Invoice", "Delivery Note"]
|
||||
@@ -300,7 +300,7 @@ class StatusUpdater(Document):
|
||||
if hasattr(d, "item_code") and hasattr(d, "rate") and flt(d.rate) < 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"For item {0}, rate must be a positive number. To Allow negative rates, enable {1} in {2}"
|
||||
"For item {0}, rate must be a positive number. To allow negative rates, enable {1} in {2}"
|
||||
).format(
|
||||
frappe.bold(d.item_code),
|
||||
frappe.bold(_("`Allow Negative rates for Items`")),
|
||||
|
||||
@@ -211,7 +211,7 @@ class SubcontractingController(StockController):
|
||||
)
|
||||
if bom_item != item.item_code:
|
||||
frappe.throw(
|
||||
_("Row {0}: Please select an valid BOM for Item {1}.").format(
|
||||
_("Row {0}: Please select a valid BOM for Item {1}.").format(
|
||||
item.idx, item.item_name
|
||||
)
|
||||
)
|
||||
@@ -1053,8 +1053,10 @@ class SubcontractingController(StockController):
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the {self.subcontract_data.order_doctype} {link}'
|
||||
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
|
||||
msg = _("The Batch No {0} has not been supplied against the {1} {2}").format(
|
||||
frappe.bold(row.get("batch_no")), self.subcontract_data.order_doctype, link
|
||||
)
|
||||
frappe.throw(msg, title=_("Incorrect Batch Consumed"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
if row.get("serial_and_batch_bundle") and self.__transferred_items.get(key).get("serial_no"):
|
||||
@@ -1066,8 +1068,10 @@ class SubcontractingController(StockController):
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the {self.subcontract_data.order_doctype} {link}"
|
||||
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
|
||||
msg = _("The Serial Nos {0} have not been supplied against the {1} {2}").format(
|
||||
incorrect_sn, self.subcontract_data.order_doctype, link
|
||||
)
|
||||
frappe.throw(msg, title=_("Incorrect Serial Number Consumed"))
|
||||
|
||||
def __validate_supplied_or_received_items(self):
|
||||
if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
|
||||
@@ -78,7 +78,7 @@ class SubcontractingInwardController:
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Item {1} mismatch. Changing of item code is not permitted, add another row instead."
|
||||
"Row #{0}: Item {1} mismatch. Changing the item code is not permitted, add another row instead."
|
||||
).format(item.idx, get_link_to_form("Item", item.item_code))
|
||||
)
|
||||
|
||||
@@ -126,7 +126,7 @@ class SubcontractingInwardController:
|
||||
or frappe.get_cached_value("Subcontracting Inward Order Item", item.scio_detail, "item_code")
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Item {1} mismatch. Changing of item code is not permitted.").format(
|
||||
_("Row #{0}: Item {1} mismatch. Changing the item code is not permitted.").format(
|
||||
item.idx, get_link_to_form("Item", item.item_code)
|
||||
)
|
||||
)
|
||||
@@ -441,7 +441,7 @@ class SubcontractingInwardController:
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Batch No(s) {1} is not a part of the linked Subcontracting Inward Order. Please select valid Batch No(s)."
|
||||
"Row #{0}: Batch No(s) {1} are not a part of the linked Subcontracting Inward Order. Please select valid Batch No(s)."
|
||||
).format(
|
||||
item.idx,
|
||||
", ".join([get_link_to_form("Batch No", bn) for bn in incorrect_batch_nos]),
|
||||
|
||||
@@ -131,9 +131,9 @@ class calculate_taxes_and_totals:
|
||||
if item.item_tax_template not in taxes:
|
||||
item.item_tax_template = taxes[0]
|
||||
frappe.msgprint(
|
||||
_("Row {0}: Item Tax template updated as per validity and rate applied").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
)
|
||||
_(
|
||||
"Row {0}: Item Tax template for {1} updated as per validity and rate applied"
|
||||
).format(item.idx, frappe.bold(item.item_code))
|
||||
)
|
||||
|
||||
# For correct tax_amount calculation re-computation is required
|
||||
@@ -564,7 +564,7 @@ class calculate_taxes_and_totals:
|
||||
+ "<br>".join(invalid_rows)
|
||||
)
|
||||
|
||||
frappe.throw(_(message))
|
||||
frappe.throw(message)
|
||||
|
||||
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
|
||||
# if just for valuation, do not add the tax amount in total
|
||||
|
||||
@@ -56,10 +56,10 @@ def validate_filters(filters):
|
||||
frappe.throw(_("{0} is mandatory").format(_(f)))
|
||||
|
||||
if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")):
|
||||
frappe.throw(_("Fiscal Year {0} Does Not Exist").format(filters.get("fiscal_year")))
|
||||
frappe.throw(_("Fiscal Year {0} does not exist").format(filters.get("fiscal_year")))
|
||||
|
||||
if filters.get("based_on") == filters.get("group_by"):
|
||||
frappe.throw(_("'Based On' and 'Group By' can not be same"))
|
||||
frappe.throw(_("'Based On' and 'Group By' can not be the same"))
|
||||
|
||||
if filters.get("period_based_on") and filters.period_based_on not in ["bill_date", "posting_date"]:
|
||||
frappe.throw(
|
||||
|
||||
@@ -308,4 +308,4 @@ def add_role_for_portal_user(portal_user, role):
|
||||
return
|
||||
|
||||
user_doc.add_roles(role)
|
||||
frappe.msgprint(_("Added {1} Role to User {0}.").format(frappe.bold(user_doc.name), role), alert=True)
|
||||
frappe.msgprint(_("Added {1} role to user {0}.").format(frappe.bold(user_doc.name), role), alert=True)
|
||||
|
||||
@@ -59,7 +59,7 @@ class AppointmentBookingSettings(Document):
|
||||
err_msg = _("<b>From Time</b> cannot be later than <b>To Time</b> for {0}").format(
|
||||
record.day_of_week
|
||||
)
|
||||
frappe.throw(_(err_msg))
|
||||
frappe.throw(err_msg)
|
||||
|
||||
def duration_is_divisible(self, from_time, to_time):
|
||||
timedelta = to_time - from_time
|
||||
|
||||
@@ -49,7 +49,7 @@ class CRMSettings(Document):
|
||||
if self.enable_frappe_crm_data_synchronization and not self.allowed_users:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please add atleast one user on Allowed Users to allow Data Synchronization from Frappe CRM site."
|
||||
"Please add at least one user on Allowed Users to allow Data Synchronization from Frappe CRM site."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ def add_bank_accounts(response: str | dict, bank: str | dict, company: str):
|
||||
except Exception:
|
||||
frappe.log_error("Plaid Link Error")
|
||||
frappe.throw(
|
||||
_("There was an error updating Bank Account {} while linking with Plaid.").format(
|
||||
_("There was an error updating Bank Account {0} while linking with Plaid.").format(
|
||||
existing_bank_account
|
||||
),
|
||||
title=_("Plaid Link Failed"),
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:01\n"
|
||||
"PO-Revision-Date: 2026-06-23 19:26\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Arabic\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -21009,7 +21009,7 @@ msgstr "للتشغيل"
|
||||
|
||||
#: banking/src/pages/BankStatementImporter.tsx:172
|
||||
msgid "For PDF statements, we auto-detect the tables on each page. You can then confirm each detected table, map its columns, and exclude anything that is not transactions (e.g. ads or summaries). Password-protected PDFs are supported - the password is saved on the bank account and reused."
|
||||
msgstr ""
|
||||
msgstr "بالنسبة لبيانات PDF، نقوم بالكشف التلقائي عن الجداول في كل صفحة. يمكنك بعد ذلك تأكيد كل جدول تم اكتشافه، وتعيين أعمدته، واستبعاد أي شيء لا يمثل معاملات (مثل الإعلانات أو الملخصات). يتم دعم ملفات PDF المحمية بكلمة مرور - يتم حفظ كلمة المرور في الحساب البنكي وإعادة استخدامها."
|
||||
|
||||
#. Label of the for_price_list (Link) field in DocType 'Pricing Rule'
|
||||
#. Label of the for_price_list (Link) field in DocType 'Promotional Scheme
|
||||
|
||||
62892
erpnext/locale/bg.po
Normal file
62892
erpnext/locale/bg.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:03\n"
|
||||
"PO-Revision-Date: 2026-06-24 19:23\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Bosnian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -27,8 +27,8 @@ msgid "\n"
|
||||
"\t\t\tSo please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."
|
||||
msgstr "\n"
|
||||
"\t\t\tŠarža {0} artikla {1} ima negativne zalihe u skladištu {2}{3}.\n"
|
||||
"\t\t\tMolimo dodajte količinu zaliha od {4} da biste nastavili s ovim unosom.\n"
|
||||
"\t\t\tAko nije moguće izvršiti unos prilagođavanja, omogućite 'Dozvoli Negativne Zalihe za Šaržu' ya Šaržu {0} ili u Postavkama Zaliha da biste nastavili.\n"
|
||||
"\t\t\tDodaj količinu zaliha od {4} da biste nastavili s ovim unosom.\n"
|
||||
"\t\t\tAko nije moguće izvršiti unos prilagođavanja, omogućite 'Dozvoli Negativne Zalihe za Šaržu' za Šaržu {0} ili u Postavkama Zaliha da biste nastavili.\n"
|
||||
"\t\t\tMeđutim, omogućavanje ove postavke može dovesti do negativnih zaliha u sistemu.\n"
|
||||
"\t\t\tStoga, molimo vas da osigurate da se nivoi zaliha što prije prilagode kako bi se održala ispravna stopa vrednovanja."
|
||||
|
||||
@@ -12197,7 +12197,7 @@ msgstr "Konsolidirana Prodajna Faktura"
|
||||
#. Name of a report
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.json
|
||||
msgid "Consolidated Trial Balance"
|
||||
msgstr "Konsolidovani Bruto Bilans"
|
||||
msgstr "Konsolidovani Probni Bilans"
|
||||
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:71
|
||||
msgid "Consolidated Trial Balance can be generated for Companies having same root Company."
|
||||
@@ -12205,7 +12205,7 @@ msgstr "Konsolidovani Bruto Bilans može se generirati za poduzeća koje imaju i
|
||||
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:167
|
||||
msgid "Consolidated Trial balance could not be generated as Exchange Rate from {0} to {1} is not available for {2}."
|
||||
msgstr "Konsolidovani Bruto Bilans nije mogao biti generisan jer kurs od {0} do {1} nije dostupan za {2}."
|
||||
msgstr "Konsolidovani Probni Bilans nije mogao biti generisan jer kurs valute od {0} do {1} nije dostupan za {2}."
|
||||
|
||||
#. Option for the 'Lead Type' (Select) field in DocType 'Lead'
|
||||
#: erpnext/crm/doctype/lead/lead.json
|
||||
@@ -58110,7 +58110,7 @@ msgstr "Stablo Procedura"
|
||||
#: erpnext/workspace_sidebar/invoicing.json
|
||||
#: erpnext/workspace_sidebar/payments.json
|
||||
msgid "Trial Balance"
|
||||
msgstr "Bruto Stanje"
|
||||
msgstr "Probni Bilans"
|
||||
|
||||
#. Name of a report
|
||||
#: erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json
|
||||
@@ -58124,7 +58124,7 @@ msgstr "Bruto Stanje (Jednostavno)"
|
||||
#: erpnext/accounts/workspace/financial_reports/financial_reports.json
|
||||
#: erpnext/workspace_sidebar/financial_reports.json
|
||||
msgid "Trial Balance for Party"
|
||||
msgstr "Bruto Stanje Stranke"
|
||||
msgstr "Probni Bilans Stranke"
|
||||
|
||||
#. Label of the trial_period_end (Date) field in DocType 'Subscription'
|
||||
#: erpnext/accounts/doctype/subscription/subscription.json
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:02\n"
|
||||
"PO-Revision-Date: 2026-06-23 19:26\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: German\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -4293,7 +4293,7 @@ msgstr "Verkauf erlauben"
|
||||
#. in DocType 'Selling Settings'
|
||||
#: erpnext/selling/doctype/selling_settings/selling_settings.json
|
||||
msgid "Allow Sales Order creation for expired Quotation"
|
||||
msgstr ""
|
||||
msgstr "Auftragserstellung für abgelaufene Angebote zulassen"
|
||||
|
||||
#. Label of the allow_zero_qty_in_sales_order (Check) field in DocType 'Selling
|
||||
#. Settings'
|
||||
@@ -4409,7 +4409,7 @@ msgstr "Rechnungswährung darf sich von Kontowährung unterscheiden"
|
||||
#. 'Selling Settings'
|
||||
#: erpnext/selling/doctype/selling_settings/selling_settings.json
|
||||
msgid "Allow multiple Sales Orders against a customer's Purchase Order"
|
||||
msgstr ""
|
||||
msgstr "Mehrere Aufträge (je Kunde) mit derselben Bestellnummer erlauben"
|
||||
|
||||
#. Label of the allow_negative_rates_for_items (Check) field in DocType 'Buying
|
||||
#. Settings'
|
||||
@@ -48219,7 +48219,7 @@ msgstr "Einsparungen"
|
||||
#. Name of a UOM
|
||||
#: erpnext/setup/setup_wizard/data/uom_data.json
|
||||
msgid "Sazhen"
|
||||
msgstr ""
|
||||
msgstr "Saschen"
|
||||
|
||||
#. Label of the scan_barcode (Data) field in DocType 'POS Invoice'
|
||||
#. Label of the scan_barcode (Data) field in DocType 'Purchase Invoice'
|
||||
@@ -51463,7 +51463,7 @@ msgstr "Quadratmeile"
|
||||
#. Name of a UOM
|
||||
#: erpnext/setup/setup_wizard/data/uom_data.json
|
||||
msgid "Square Yard"
|
||||
msgstr ""
|
||||
msgstr "Quadratyard"
|
||||
|
||||
#. Label of the stage_name (Data) field in DocType 'Sales Stage'
|
||||
#: erpnext/crm/doctype/sales_stage/sales_stage.json
|
||||
@@ -52544,7 +52544,7 @@ msgstr "Lagerbestände/Konten können nicht eingefroren werden, da die Verarbeit
|
||||
#. Name of a UOM
|
||||
#: erpnext/setup/setup_wizard/data/uom_data.json
|
||||
msgid "Stone"
|
||||
msgstr ""
|
||||
msgstr "Stone"
|
||||
|
||||
#. Label of the stop_reason (Select) field in DocType 'Downtime Entry'
|
||||
#: erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
|
||||
@@ -59818,7 +59818,7 @@ msgstr "Wert oder Menge"
|
||||
#. Name of a UOM
|
||||
#: erpnext/setup/setup_wizard/data/uom_data.json
|
||||
msgid "Vara"
|
||||
msgstr ""
|
||||
msgstr "Vara"
|
||||
|
||||
#. Label of the variable (Data) field in DocType 'Bank Statement Import Log
|
||||
#. Column Map'
|
||||
@@ -61136,7 +61136,7 @@ msgstr "Einbehalt-Dokumenttyp"
|
||||
|
||||
#: banking/src/components/features/Settings/Preferences.tsx:70
|
||||
msgid "Within 1 day"
|
||||
msgstr ""
|
||||
msgstr "Innerhalb eines Tages"
|
||||
|
||||
#: banking/src/components/features/Settings/Preferences.tsx:71
|
||||
msgid "Within 2 days"
|
||||
@@ -62090,7 +62090,7 @@ msgstr ""
|
||||
#. Exchange Settings'
|
||||
#: erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
|
||||
msgid "frankfurter.dev"
|
||||
msgstr ""
|
||||
msgstr "frankfurter.dev"
|
||||
|
||||
#. Option for the 'Service Provider' (Select) field in DocType 'Currency
|
||||
#. Exchange Settings'
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:03\n"
|
||||
"PO-Revision-Date: 2026-06-23 19:26\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Persian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -61985,7 +61985,7 @@ msgstr "frankfurter.dev"
|
||||
#. Exchange Settings'
|
||||
#: erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
|
||||
msgid "frankfurter.dev - v2"
|
||||
msgstr ""
|
||||
msgstr "frankfurter.dev - v2"
|
||||
|
||||
#: erpnext/templates/form_grid/item_grid.html:66
|
||||
#: erpnext/templates/form_grid/item_grid.html:80
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:03\n"
|
||||
"PO-Revision-Date: 2026-06-24 19:23\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Croatian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -25,7 +25,12 @@ msgid "\n"
|
||||
"\t\t\tIf it is not possible to make an adjustment entry, please enable 'Allow Negative Stock for Batch' in the batch {0} or in the Stock Settings to proceed.\n"
|
||||
"\t\t\tHowever, enabling this setting may lead to negative stock in the system.\n"
|
||||
"\t\t\tSo please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."
|
||||
msgstr ""
|
||||
msgstr "\n"
|
||||
"\t\t\tŠarža {0} artikla {1} ima negativne zalihe u skladištu {2}{3}.\n"
|
||||
"\t\t\tDodaj količinu zaliha od {4} da biste nastavili s ovim unosom.\n"
|
||||
"\t\t\tAko nije moguće izvršiti unos prilagođavanja, omogućite 'Dozvoli Negativne Zalihe za Šaržu' za Šaržu {0} ili u Postavkama Zaliha da biste nastavili.\n"
|
||||
"\t\t\tMeđutim, omogućavanje ove postavke može dovesti do negativnih zaliha u ssustavu.\n"
|
||||
"\t\t\tStoga, molimo vas da osigurate da se razina zaliha što prije prilagode kako bi se održala ispravna stopa vrednovanja."
|
||||
|
||||
#. Label of the column_break_32 (Column Break) field in DocType 'Email Digest'
|
||||
#: erpnext/setup/doctype/email_digest/email_digest.json
|
||||
@@ -1067,7 +1072,7 @@ msgstr "Otpremnica se može kreirati samo za nacrt Dostavnice."
|
||||
|
||||
#: erpnext/accounts/services/gl_validator.py:123
|
||||
msgid "A Period Closing Voucher is already submitted and an Opening Entry can no longer be created. {0} to learn more."
|
||||
msgstr ""
|
||||
msgstr "Verifikat Zatvaranje Razdoblja je već podnesen i početni unos se više ne može kreirati. {0} za više informacija."
|
||||
|
||||
#. Description of a DocType
|
||||
#: erpnext/stock/doctype/price_list/price_list.json
|
||||
@@ -2732,7 +2737,7 @@ msgstr "Dodaj više zadataka"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:974
|
||||
msgid "Add Opening Stock"
|
||||
msgstr ""
|
||||
msgstr "Dodaj Početne Zalihe"
|
||||
|
||||
#. Label of the add_deduct_tax (Select) field in DocType 'Advance Taxes and
|
||||
#. Charges'
|
||||
@@ -4024,7 +4029,7 @@ msgstr "Automatski Dodjeli Predujam (FIFO)"
|
||||
#. 'Purchase Taxes and Charges'
|
||||
#: erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
|
||||
msgid "Allocate Full Amount to Stock Items"
|
||||
msgstr ""
|
||||
msgstr "Dodijeli Puni Iznos Artiklima Zaliha"
|
||||
|
||||
#: erpnext/accounts/doctype/payment_entry/payment_entry.js:919
|
||||
msgid "Allocate Payment Amount"
|
||||
@@ -4603,7 +4608,7 @@ msgstr "Također se ne možete vratiti na FIFO nakon što ste za ovu stavku post
|
||||
|
||||
#: erpnext/stock/report/stock_balance/stock_balance.py:644
|
||||
msgid "Alt UOM"
|
||||
msgstr ""
|
||||
msgstr "Alternativna Jedinica"
|
||||
|
||||
#: erpnext/manufacturing/doctype/bom/bom.js:291
|
||||
#: erpnext/manufacturing/doctype/work_order/work_order.js:158
|
||||
@@ -7295,7 +7300,7 @@ msgstr "Količinsko Stanje"
|
||||
|
||||
#: erpnext/stock/report/stock_balance/stock_balance.py:635
|
||||
msgid "Balance Qty (Alt UOM)"
|
||||
msgstr ""
|
||||
msgstr "Količinsko Stanja (Alternativna Jedinica)"
|
||||
|
||||
#: erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js:71
|
||||
msgid "Balance Qty (Stock)"
|
||||
@@ -12192,15 +12197,15 @@ msgstr "Konsolidirana Prodajna Faktura"
|
||||
#. Name of a report
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.json
|
||||
msgid "Consolidated Trial Balance"
|
||||
msgstr "Konsolidirana Bruto Bilanca"
|
||||
msgstr "Konsolidirana Probna Bilanca"
|
||||
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:71
|
||||
msgid "Consolidated Trial Balance can be generated for Companies having same root Company."
|
||||
msgstr "Konsolidirana Bruto Bilanca može se generirati za tvrtke koje imaju istu matičnu tvrtku."
|
||||
msgstr "Konsolidirana Probna Bilanca može se generirati za tvrtke koje imaju istu matičnu tvrtku."
|
||||
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:167
|
||||
msgid "Consolidated Trial balance could not be generated as Exchange Rate from {0} to {1} is not available for {2}."
|
||||
msgstr "Konsolidirana Bruto Bilanca nije mogla biti generirana jer tečaj od {0} do {1} nije dostupan za {2}."
|
||||
msgstr "Konsolidirana Probna Bilanca nije mongla biti generirana jer devizni tečaj od {0} do {1} nije dostupan za {2}."
|
||||
|
||||
#. Option for the 'Lead Type' (Select) field in DocType 'Lead'
|
||||
#: erpnext/crm/doctype/lead/lead.json
|
||||
@@ -13772,7 +13777,7 @@ msgstr "Kreiranje Naloga Knjiženja u toku..."
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:988
|
||||
msgid "Creating Opening Stock Entry..."
|
||||
msgstr ""
|
||||
msgstr "Kreiranje Početnog Unosa Zaliha..."
|
||||
|
||||
#: erpnext/stock/doctype/packing_slip/packing_slip.js:42
|
||||
msgid "Creating Packing Slip ..."
|
||||
@@ -16099,7 +16104,7 @@ msgstr "Standard Predlošci PDV-a za prodaju, nabavu i artikle su kreirani."
|
||||
#: erpnext/stock/doctype/item/item.js:942
|
||||
#: erpnext/stock/doctype/item/item.js:954
|
||||
msgid "Default warehouse from Item Defaults."
|
||||
msgstr ""
|
||||
msgstr "Standard Skladište iz Standard Postavki Artikala."
|
||||
|
||||
#. Description of the 'Time Between Operations (Mins)' (Int) field in DocType
|
||||
#. 'Manufacturing Settings'
|
||||
@@ -16285,7 +16290,7 @@ msgstr "Izbriši Transakcije"
|
||||
|
||||
#: erpnext/setup/doctype/company/company.js:254
|
||||
msgid "Delete all the Transactions for {0}"
|
||||
msgstr ""
|
||||
msgstr "Izbriši sve transakcije za {0}"
|
||||
|
||||
#. Label of a Link in the ERPNext Settings Workspace
|
||||
#: erpnext/setup/workspace/erpnext_settings/erpnext_settings.json
|
||||
@@ -19569,7 +19574,7 @@ msgstr "Prekomjerna Demontaža"
|
||||
|
||||
#: erpnext/stock/doctype/stock_entry/services/material_transfer.py:243
|
||||
msgid "Excess Material Transfer"
|
||||
msgstr ""
|
||||
msgstr "Prijenos Dodatnog Materijala"
|
||||
|
||||
#: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.js:55
|
||||
msgid "Excess Materials Consumed"
|
||||
@@ -23440,7 +23445,7 @@ msgstr "Ako je označeno, odabrana količina neće biti automatski ispunjena pri
|
||||
#. DocType 'Purchase Taxes and Charges'
|
||||
#: erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
|
||||
msgid "If checked, the entire amount (e.g. Freight) is allocated to the valuation of stock & asset items only. If unchecked, the amount is distributed across all items and the portion belonging to non-stock items is not added to valuation."
|
||||
msgstr ""
|
||||
msgstr "Ako je odabrano, cijeli iznos (npr. Vozarina) se dodjeljuje samo za stopu vrijednovanja zaliha i imovine. Ako nije odabrano, iznos se raspoređuje na sve artikle, a dio koji pripada artiklima koje nisu na zalihama se ne dodaje stopi vrijednovanja."
|
||||
|
||||
#. Description of the 'Considered In Paid Amount' (Check) field in DocType
|
||||
#. 'Purchase Taxes and Charges'
|
||||
@@ -23615,7 +23620,7 @@ msgstr "Ako je omogućeno, sustav će dopustiti negativne unose zaliha za šarž
|
||||
#. 'Batch'
|
||||
#: erpnext/stock/doctype/batch/batch.json
|
||||
msgid "If enabled, the system will allow negative stock entries for this batch, overriding the 'Allow negative stock for Batch' setting in Stock Settings. This may lead to incorrect valuation rates, so it is recommended to avoid using this option."
|
||||
msgstr ""
|
||||
msgstr "Ako je omogućeno, sustav će dopustiti unos negativnih zaliha za ovu šaržu, poništavajući postavku 'Dopusti negativne zalihe za Šaržu' u Postavkama Zaliha. To može dovesti do netočnih stopa vrednovanja, stoga se preporučuje izbjegavanje korištenja ove opcije."
|
||||
|
||||
#. Description of the 'Allow UOM with conversion rate defined in Item' (Check)
|
||||
#. field in DocType 'Stock Settings'
|
||||
@@ -25516,7 +25521,7 @@ msgstr "Nevažeći upit pretraživanja"
|
||||
|
||||
#: erpnext/stock/doctype/stock_entry/stock_entry.py:1649
|
||||
msgid "Invalid subcontract order field: {0}"
|
||||
msgstr ""
|
||||
msgstr "Nevažeći nalog podizvođača: {0}"
|
||||
|
||||
#: erpnext/accounts/report/inactive_sales_items/inactive_sales_items.py:99
|
||||
msgid "Invalid value {0} for 'Based On'"
|
||||
@@ -28866,7 +28871,7 @@ msgstr "Odsustvo Isplaćeno?"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:969
|
||||
msgid "Leave as 0 to allow zero valuation rate."
|
||||
msgstr ""
|
||||
msgstr "Ostavite kao 0 kako biste omogućili nultu stopu vrednovanja."
|
||||
|
||||
#. Description of the 'Success Redirect URL' (Data) field in DocType
|
||||
#. 'Appointment Booking Settings'
|
||||
@@ -32343,7 +32348,7 @@ msgstr "Bez Odgovora"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:913
|
||||
msgid "No Company Found"
|
||||
msgstr ""
|
||||
msgstr "Nije pronađenaTvrtka"
|
||||
|
||||
#: erpnext/accounts/doctype/sales_invoice/mapper.py:115
|
||||
msgid "No Customer found for Inter Company Transactions which represents company {0}"
|
||||
@@ -32534,7 +32539,7 @@ msgstr "Nema podataka. Čini se da ste otpremili praznu datoteku"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:943
|
||||
msgid "No default warehouse set for this company. Entry will use Stock Settings default."
|
||||
msgstr ""
|
||||
msgstr "Za ovu tvrtku nije postavljeno standard skladište. Unos će koristiti standard postavke zaliha."
|
||||
|
||||
#: erpnext/templates/generators/bom.html:85
|
||||
msgid "No description given"
|
||||
@@ -32820,7 +32825,7 @@ msgstr "Nisu pronađeni vaučeri za ovu transakciju"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:1734
|
||||
msgid "No warehouse found for company {0}. Please set a Default Warehouse in Item Defaults or Stock Settings."
|
||||
msgstr ""
|
||||
msgstr "Nije pronađeno skladište za {0}. Postavi Standard Skladište u Postavkama Artikala ili Postavkama Zaliha."
|
||||
|
||||
#: erpnext/accounts/doctype/sales_invoice/mapper.py:163
|
||||
msgid "No {0} found for Inter Company Transactions."
|
||||
@@ -33564,7 +33569,7 @@ msgstr "Dozvoljene su samo vrijednosti između [0,1). Kao {0,00, 0,04, 0,09, ...
|
||||
#. 'Repost Item Valuation'
|
||||
#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
|
||||
msgid "Only works for Purchase Receipt, Purchase Invoice and Stock Entry"
|
||||
msgstr ""
|
||||
msgstr "Radi samo za Račun Nabave, Fakturu Nabave i Unos Zaliha"
|
||||
|
||||
#: erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py:43
|
||||
msgid "Only {0} are supported"
|
||||
@@ -33857,24 +33862,24 @@ msgstr "Početna Zaliha"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:1588
|
||||
msgid "Opening Stock can only be set for stock items."
|
||||
msgstr ""
|
||||
msgstr "Početne zalihe mogu se postaviti samo za artikle na zalihi."
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:1595
|
||||
msgid "Opening Stock cannot be created as stock transactions already exist for item {0}."
|
||||
msgstr ""
|
||||
msgstr "Početne zalihe se ne mogu kreirati jer već postoje transakcije zaliha za artikal {0}."
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:1591
|
||||
msgid "Opening Stock for serialised or batch items must be set via the Stock Reconciliation form."
|
||||
msgstr ""
|
||||
msgstr "Početne zalihe za serijske ili šaržne artikle mora se postaviti putem Usklađivanje Zaliha."
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:356
|
||||
msgid "Opening Stock reconciliation created with zero valuation rate: {0}"
|
||||
msgstr ""
|
||||
msgstr "Početno Usklađivanje Zaliha kreirano sa nultom stopom vrednovanja: {0}"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:364
|
||||
#: erpnext/stock/doctype/item/item.py:1637
|
||||
msgid "Opening Stock reconciliation created: {0}"
|
||||
msgstr ""
|
||||
msgstr "Početno Usklađivanje Zaliha kreirano: {0}"
|
||||
|
||||
#. Label of the opening_time (Time) field in DocType 'Issue'
|
||||
#: erpnext/support/doctype/issue/issue.json
|
||||
@@ -33892,7 +33897,7 @@ msgstr "Otvaranje & Zatvaranje"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:199
|
||||
msgid "Opening stock creation has been queued and will be created in the background. Please check the Stock Reconciliation after some time."
|
||||
msgstr ""
|
||||
msgstr "Kreiranje početnih zaliha je stavljeno u red čekanja i bit će kreirano u pozadini. Molimo provjerite usklađivanje zaliha nakon nekog vremena."
|
||||
|
||||
#. Label of the operating_component (Link) field in DocType 'Workstation Cost'
|
||||
#. Label of the operating_component (Data) field in DocType 'Landed Cost Taxes
|
||||
@@ -35646,7 +35651,7 @@ msgstr "Djelomično Rezervisano"
|
||||
#. Option for the 'Status' (Select) field in DocType 'Job Card'
|
||||
#: erpnext/manufacturing/doctype/job_card/job_card.json
|
||||
msgid "Partially Transferred"
|
||||
msgstr ""
|
||||
msgstr "Djelomično Preneseno"
|
||||
|
||||
#. Option for the 'Status' (Select) field in DocType 'Stock Reservation Entry'
|
||||
#: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
|
||||
@@ -37722,7 +37727,7 @@ msgstr "Dodaj barem jednu seriju imenovanja."
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:914
|
||||
msgid "Please add at least one row in Item Defaults with a Company before setting opening stock."
|
||||
msgstr ""
|
||||
msgstr "Dodaj barem jedan red u Postavke Artikala sa tvrtkom prije postavljanja početnih zaliha."
|
||||
|
||||
#: erpnext/public/js/utils/serial_no_batch_selector.js:663
|
||||
msgid "Please add atleast one Serial No / Batch No"
|
||||
@@ -38128,7 +38133,7 @@ msgstr "Potvrdi da datoteka koju koristite ima kolonu 'Nadređeni Račun' u zagl
|
||||
|
||||
#: erpnext/setup/doctype/company/company.js:234
|
||||
msgid "Please make sure you really want to delete all the transactions for {0}. Your master data will remain as it is. This action cannot be undone."
|
||||
msgstr ""
|
||||
msgstr "Da li zaista želiš izbrisati sve transakcije za {0}. Vaši glavni podaci će ostati onakvi kakvi jesu. Ova radnja se ne može poništiti."
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:1025
|
||||
msgid "Please mention 'Weight UOM' along with Weight."
|
||||
@@ -38648,7 +38653,7 @@ msgstr "Postavi Centar Troškova za Imovinu ili postavite Centar Troškova Amort
|
||||
#: erpnext/stock/doctype/item/item.py:339
|
||||
#: erpnext/stock/doctype/item/item.py:1621
|
||||
msgid "Please set a Temporary Opening account for company {0} to create an Opening Stock reconciliation."
|
||||
msgstr ""
|
||||
msgstr "Postavi Privremeni Početni Račun za {0} kako biste kreirali početno usklađivanje zaliha."
|
||||
|
||||
#: erpnext/projects/doctype/project/project.py:806
|
||||
msgid "Please set a default Holiday List for Company {0}"
|
||||
@@ -43157,7 +43162,7 @@ msgstr "Dostignut je Najviši Nivo"
|
||||
|
||||
#: erpnext/accounts/services/gl_validator.py:127
|
||||
msgid "Read the docs"
|
||||
msgstr ""
|
||||
msgstr "Pročitaj dokumentaciju"
|
||||
|
||||
#. Label of the reading_1 (Data) field in DocType 'Quality Inspection Reading'
|
||||
#: erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
|
||||
@@ -43270,7 +43275,7 @@ msgstr "Preračunaj Nabavnu/Prodajnu Cijenu"
|
||||
#. Item Valuation'
|
||||
#: erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
|
||||
msgid "Recalculate Valuation Rate"
|
||||
msgstr ""
|
||||
msgstr "Ponovo izračunaj Stopu Vrednovanja"
|
||||
|
||||
#. Option for the 'Status' (Select) field in DocType 'Asset'
|
||||
#. Option for the 'Purpose' (Select) field in DocType 'Asset Movement'
|
||||
@@ -46051,7 +46056,7 @@ msgstr "Red #{0}: Ne može se prenijeti više od potrebne količine {1} za artik
|
||||
|
||||
#: erpnext/stock/doctype/stock_entry/services/material_transfer.py:233
|
||||
msgid "Row #{0}: Cannot transfer {1} {2} of Item {3}. Maximum transferable quantity is {4} {2}."
|
||||
msgstr ""
|
||||
msgstr "Red #{0}: Ne može se prenijeti {1} {2} artikal {3}. Najveća prenosiva količina je {4} {2}."
|
||||
|
||||
#: erpnext/selling/doctype/product_bundle/product_bundle.py:138
|
||||
msgid "Row #{0}: Child Item should not be a Product Bundle. Please remove Item {1} and Save"
|
||||
@@ -47742,7 +47747,7 @@ msgstr "Prodajni Nalog {0} već postoji naspram Nabavnog Naloga Klijenta {1}. Da
|
||||
|
||||
#: erpnext/projects/doctype/project/project.py:256
|
||||
msgid "Sales Order {0} is already linked to Project {1}, skipping the link."
|
||||
msgstr ""
|
||||
msgstr "Prodajni Nalog {0} je već povezan s projektom {1}, preskoči poveznicu."
|
||||
|
||||
#: erpnext/selling/doctype/sales_order/mapper.py:883
|
||||
#: erpnext/selling/doctype/sales_order/mapper.py:896
|
||||
@@ -50038,7 +50043,7 @@ msgstr "Postavi Novi Datum Izdavanja"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.js:203
|
||||
msgid "Set Opening Stock"
|
||||
msgstr ""
|
||||
msgstr "Postavi Početne Zalihe"
|
||||
|
||||
#. Label of the set_op_cost_and_secondary_items_from_sub_assemblies (Check)
|
||||
#. field in DocType 'Manufacturing Settings'
|
||||
@@ -50732,7 +50737,7 @@ msgstr "Prikažite ukupnu vrijednost iz Podružnica"
|
||||
|
||||
#: erpnext/stock/report/stock_balance/stock_balance.js:115
|
||||
msgid "Show Alternate UOM Balance"
|
||||
msgstr ""
|
||||
msgstr "Prikaži Saldo Alternativne Jedinice"
|
||||
|
||||
#: erpnext/accounts/report/general_ledger/general_ledger.js:199
|
||||
msgid "Show Cancelled Entries"
|
||||
@@ -58105,12 +58110,12 @@ msgstr "Stablo Procedura"
|
||||
#: erpnext/workspace_sidebar/invoicing.json
|
||||
#: erpnext/workspace_sidebar/payments.json
|
||||
msgid "Trial Balance"
|
||||
msgstr "Bruto Stanje"
|
||||
msgstr "Probna Bilanca"
|
||||
|
||||
#. Name of a report
|
||||
#: erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json
|
||||
msgid "Trial Balance (Simple)"
|
||||
msgstr "Bruto Stanje (Jednostavno)"
|
||||
msgstr "Probna Bilanca (Jednostavno)"
|
||||
|
||||
#. Name of a report
|
||||
#. Label of a Link in the Financial Reports Workspace
|
||||
@@ -58119,7 +58124,7 @@ msgstr "Bruto Stanje (Jednostavno)"
|
||||
#: erpnext/accounts/workspace/financial_reports/financial_reports.json
|
||||
#: erpnext/workspace_sidebar/financial_reports.json
|
||||
msgid "Trial Balance for Party"
|
||||
msgstr "Bruto Stanje Stranke"
|
||||
msgstr "Probna Bilanca Stranke"
|
||||
|
||||
#. Label of the trial_period_end (Date) field in DocType 'Subscription'
|
||||
#: erpnext/accounts/doctype/subscription/subscription.json
|
||||
@@ -59685,7 +59690,7 @@ msgstr "Nedostaje Stopa Vrednovanja"
|
||||
|
||||
#: erpnext/stock/doctype/item/item.py:1604
|
||||
msgid "Valuation Rate cannot be negative."
|
||||
msgstr ""
|
||||
msgstr "Stopa Vrednovanja ne može biti negativna."
|
||||
|
||||
#: erpnext/stock/stock_ledger.py:2037
|
||||
msgid "Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}."
|
||||
@@ -62094,7 +62099,7 @@ msgstr "frankfurter.dev"
|
||||
#. Exchange Settings'
|
||||
#: erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
|
||||
msgid "frankfurter.dev - v2"
|
||||
msgstr ""
|
||||
msgstr "frankfurter.dev - v2"
|
||||
|
||||
#: erpnext/templates/form_grid/item_grid.html:66
|
||||
#: erpnext/templates/form_grid/item_grid.html:80
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:02\n"
|
||||
"PO-Revision-Date: 2026-06-24 19:22\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Hungarian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -11865,7 +11865,7 @@ msgstr ""
|
||||
#. Label of the items (Table) field in DocType 'BOM'
|
||||
#: erpnext/manufacturing/doctype/bom/bom.json
|
||||
msgid "Components"
|
||||
msgstr ""
|
||||
msgstr "Komponensek"
|
||||
|
||||
#. Option for the 'Asset Type' (Select) field in DocType 'Asset'
|
||||
#: erpnext/assets/doctype/asset/asset.json
|
||||
@@ -36116,7 +36116,7 @@ msgstr ""
|
||||
|
||||
#: banking/src/components/features/ActionLog/ActionLogDialogBody.tsx:408
|
||||
msgid "Payment Details"
|
||||
msgstr ""
|
||||
msgstr "Fizetési részletek"
|
||||
|
||||
#. Label of the payment_document (Link) field in DocType 'Bank Clearance
|
||||
#. Detail'
|
||||
@@ -59394,7 +59394,7 @@ msgstr ""
|
||||
|
||||
#: erpnext/stock/doctype/item/item_prices.html:86
|
||||
msgid "Valid Upto"
|
||||
msgstr ""
|
||||
msgstr "Valid Upto"
|
||||
|
||||
#. Label of the countries (Table) field in DocType 'Shipping Rule'
|
||||
#: erpnext/accounts/doctype/shipping_rule/shipping_rule.json
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:02\n"
|
||||
"PO-Revision-Date: 2026-06-24 19:23\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -12751,13 +12751,13 @@ msgstr ""
|
||||
#. Label of the cost_allocation (Currency) field in DocType 'BOM'
|
||||
#: erpnext/manufacturing/doctype/bom/bom.json
|
||||
msgid "Cost Allocation"
|
||||
msgstr ""
|
||||
msgstr "porazdelitve stroškov"
|
||||
|
||||
#. Label of the cost_allocation_per (Percent) field in DocType 'BOM Secondary
|
||||
#. Item'
|
||||
#: erpnext/manufacturing/doctype/bom_secondary_item/bom_secondary_item.json
|
||||
msgid "Cost Allocation %"
|
||||
msgstr ""
|
||||
msgstr "porazdelitve stroškov %"
|
||||
|
||||
#. Label of the cost_allocation__process_loss_section (Section Break) field in
|
||||
#. DocType 'BOM'
|
||||
|
||||
@@ -3,7 +3,7 @@ msgstr ""
|
||||
"Project-Id-Version: frappe\n"
|
||||
"Report-Msgid-Bugs-To: hello@frappe.io\n"
|
||||
"POT-Creation-Date: 2026-06-21 10:42+0000\n"
|
||||
"PO-Revision-Date: 2026-06-21 19:02\n"
|
||||
"PO-Revision-Date: 2026-06-24 19:23\n"
|
||||
"Last-Translator: hello@frappe.io\n"
|
||||
"Language-Team: Swedish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -12204,15 +12204,15 @@ msgstr "Konsoliderad Försäljning Faktura"
|
||||
#. Name of a report
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.json
|
||||
msgid "Consolidated Trial Balance"
|
||||
msgstr "Konsoliderat Brutto Saldo"
|
||||
msgstr "Konsoliderad Prov Saldo"
|
||||
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:71
|
||||
msgid "Consolidated Trial Balance can be generated for Companies having same root Company."
|
||||
msgstr "Konsoliderad Brutto Saldo kan skapas för bolag som har samma moderbolag."
|
||||
msgstr "Konsoliderad Prov Saldo kan skapas för bolag som har samma moderbolag."
|
||||
|
||||
#: erpnext/accounts/report/consolidated_trial_balance/consolidated_trial_balance.py:167
|
||||
msgid "Consolidated Trial balance could not be generated as Exchange Rate from {0} to {1} is not available for {2}."
|
||||
msgstr "Konsoliderad Brutto Saldo kunde inte skapas eftersom växelkurs från {0} till {1} inte är tillgänglig för {2}."
|
||||
msgstr "Konsoliderad Prov Saldo kunde inte skapas eftersom växelkurs från {0} till {1} inte är tillgänglig för {2}."
|
||||
|
||||
#. Option for the 'Lead Type' (Select) field in DocType 'Lead'
|
||||
#: erpnext/crm/doctype/lead/lead.json
|
||||
@@ -58117,12 +58117,12 @@ msgstr "Kvalitet Procedur Träd"
|
||||
#: erpnext/workspace_sidebar/invoicing.json
|
||||
#: erpnext/workspace_sidebar/payments.json
|
||||
msgid "Trial Balance"
|
||||
msgstr "Brutto Saldo"
|
||||
msgstr "Prov Saldo"
|
||||
|
||||
#. Name of a report
|
||||
#: erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json
|
||||
msgid "Trial Balance (Simple)"
|
||||
msgstr "Brutto Saldo (Enkel)"
|
||||
msgstr "Prov Saldo (Enkel)"
|
||||
|
||||
#. Name of a report
|
||||
#. Label of a Link in the Financial Reports Workspace
|
||||
@@ -58131,7 +58131,7 @@ msgstr "Brutto Saldo (Enkel)"
|
||||
#: erpnext/accounts/workspace/financial_reports/financial_reports.json
|
||||
#: erpnext/workspace_sidebar/financial_reports.json
|
||||
msgid "Trial Balance for Party"
|
||||
msgstr "Brutto Saldo för Parti"
|
||||
msgstr "Prov Saldo för Parti"
|
||||
|
||||
#. Label of the trial_period_end (Date) field in DocType 'Subscription'
|
||||
#: erpnext/accounts/doctype/subscription/subscription.json
|
||||
|
||||
62892
erpnext/locale/uz.po
Normal file
62892
erpnext/locale/uz.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -97,10 +97,10 @@ class TestBOM(ERPNextTestSuite):
|
||||
update_cost_in_all_boms_in_test()
|
||||
|
||||
# check if new valuation rate updated in all BOMs
|
||||
for d in frappe.db.sql(
|
||||
"""select base_rate from `tabBOM Item`
|
||||
where item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""",
|
||||
as_dict=1,
|
||||
for d in frappe.get_all(
|
||||
"BOM Item",
|
||||
filters={"item_code": "_Test Item 2", "docstatus": 1, "parenttype": "BOM"},
|
||||
fields=["base_rate"],
|
||||
):
|
||||
self.assertEqual(d.base_rate, rm_base_rate + 10)
|
||||
|
||||
@@ -881,12 +881,8 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non
|
||||
warehouse_list = [warehouse_list]
|
||||
|
||||
if not warehouse_list:
|
||||
warehouse_list = frappe.db.sql_list(
|
||||
"""
|
||||
select warehouse from `tabBin`
|
||||
where item_code=%s and actual_qty > 0
|
||||
""",
|
||||
item_code,
|
||||
warehouse_list = frappe.get_all(
|
||||
"Bin", filters={"item_code": item_code, "actual_qty": [">", 0]}, pluck="warehouse"
|
||||
)
|
||||
|
||||
if not warehouse_list:
|
||||
|
||||
@@ -5249,11 +5249,8 @@ def update_job_card(job_card, jc_qty=None, days=None):
|
||||
|
||||
def get_secondary_item_details(bom_no):
|
||||
secondary_items = {}
|
||||
for item in frappe.db.sql(
|
||||
"""select item_code, stock_qty from `tabBOM Secondary Item`
|
||||
where parent = %s""",
|
||||
bom_no,
|
||||
as_dict=1,
|
||||
for item in frappe.get_all(
|
||||
"BOM Secondary Item", filters={"parent": bom_no}, fields=["item_code", "stock_qty"]
|
||||
):
|
||||
secondary_items[item.item_code] = item.stock_qty
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class TestProject(ERPNextTestSuite):
|
||||
|
||||
def test_project_with_template_having_no_parent_and_depend_tasks(self):
|
||||
project_name = "Test Project with Template - No Parent and Dependend Tasks"
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||
frappe.db.delete("Task", {"project": project_name})
|
||||
frappe.delete_doc("Project", project_name)
|
||||
|
||||
task1 = task_exists("Test Template Task with No Parent and Dependency")
|
||||
@@ -82,7 +82,7 @@ class TestProject(ERPNextTestSuite):
|
||||
if frappe.db.get_value("Project", {"project_name": project_name}, "name"):
|
||||
project_name = frappe.db.get_value("Project", {"project_name": project_name}, "name")
|
||||
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||
frappe.db.delete("Task", {"project": project_name})
|
||||
frappe.delete_doc("Project", project_name)
|
||||
|
||||
task1 = task_exists("Test Template Task Parent")
|
||||
@@ -137,7 +137,7 @@ class TestProject(ERPNextTestSuite):
|
||||
|
||||
def test_project_template_having_dependent_tasks(self):
|
||||
project_name = "Test Project with Template - Dependent Tasks"
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||
frappe.db.delete("Task", {"project": project_name})
|
||||
frappe.delete_doc("Project", project_name)
|
||||
|
||||
task1 = task_exists("Test Template Task for Dependency")
|
||||
@@ -252,7 +252,7 @@ class TestProject(ERPNextTestSuite):
|
||||
|
||||
def test_project_having_no_tasks_complete(self):
|
||||
project_name = "Test Project - No Tasks Completion"
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||
frappe.db.delete("Task", {"project": project_name})
|
||||
frappe.delete_doc("Project", project_name)
|
||||
|
||||
project = frappe.get_doc(
|
||||
|
||||
@@ -144,7 +144,7 @@ class Task(NestedSet):
|
||||
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot complete task {0} as its dependant task {1} are not completed / cancelled."
|
||||
"Cannot complete task {0} as its dependent task {1} is not completed / cancelled."
|
||||
).format(frappe.bold(self.name), frappe.bold(d.task))
|
||||
)
|
||||
|
||||
@@ -316,7 +316,7 @@ class Task(NestedSet):
|
||||
|
||||
def on_trash(self):
|
||||
if check_if_child_exists(self.name):
|
||||
throw(_("Child Task exists for this Task. You can not delete this Task."))
|
||||
throw(_("Child Task exists for this Task. You cannot delete this Task."))
|
||||
|
||||
self.update_nsm_model()
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ class TimesheetDetail(Document):
|
||||
def validate_dates(self):
|
||||
"""Validate that to_time is not before from_time."""
|
||||
if self.from_time and self.to_time and time_diff_in_hours(self.to_time, self.from_time) < 0:
|
||||
frappe.throw(_("To Time cannot be before from date"))
|
||||
frappe.throw(_("To Time cannot be before From Time"))
|
||||
|
||||
def validate_parent_project(self, parent_project: str):
|
||||
"""Validate that project is same as Timesheet's parent project."""
|
||||
|
||||
@@ -158,7 +158,7 @@ class Customer(TransactionBase):
|
||||
new_customer_name = f"{self.customer_name} - {cstr(count)}"
|
||||
|
||||
msgprint(
|
||||
_("Changed customer name to '{}' as '{}' already exists.").format(
|
||||
_("Changed customer name to '{0}' as '{1}' already exists.").format(
|
||||
new_customer_name, self.customer_name
|
||||
),
|
||||
title=_("Note"),
|
||||
@@ -356,7 +356,7 @@ class Customer(TransactionBase):
|
||||
if frappe.db.exists("Customer Group", self.name):
|
||||
frappe.throw(
|
||||
_(
|
||||
"A Customer Group exists with same name please change the Customer name or rename the Customer Group"
|
||||
"A Customer Group exists with the same name. Please change the Customer name or rename the Customer Group"
|
||||
),
|
||||
frappe.NameError,
|
||||
)
|
||||
@@ -406,7 +406,7 @@ class Customer(TransactionBase):
|
||||
if flt(limit.credit_limit) < outstanding_amt:
|
||||
frappe.throw(
|
||||
_(
|
||||
"""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}"""
|
||||
"""New credit limit is less than current outstanding amount for the customer. Credit limit has to be at least {0}"""
|
||||
).format(outstanding_amt)
|
||||
)
|
||||
|
||||
@@ -440,7 +440,7 @@ class Customer(TransactionBase):
|
||||
self.loyalty_program = loyalty_program[0]
|
||||
else:
|
||||
frappe.msgprint(
|
||||
_("Multiple Loyalty Programs found for Customer {}. Please select manually.").format(
|
||||
_("Multiple Loyalty Programs found for Customer {0}. Please select manually.").format(
|
||||
frappe.bold(self.customer_name)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -172,7 +172,7 @@ def make_address(args, is_primary_address=1, is_shipping_address=1):
|
||||
if reqd_fields:
|
||||
msg = _("Following fields are mandatory to create address:")
|
||||
frappe.throw(
|
||||
"{} <br><br> <ul>{}</ul>".format(msg, "\n".join(reqd_fields)),
|
||||
msg + " <br><br> <ul>{}</ul>".format("\n".join(reqd_fields)),
|
||||
title=_("Missing Values Required"),
|
||||
)
|
||||
|
||||
|
||||
@@ -32,4 +32,6 @@ class PartySpecificItem(Document):
|
||||
},
|
||||
)
|
||||
if exists:
|
||||
frappe.throw(_("This item filter has already been applied for the {0}").format(self.party_type))
|
||||
frappe.throw(
|
||||
_("This item filter has already been applied for the {0}").format(_(self.party_type))
|
||||
)
|
||||
|
||||
@@ -118,9 +118,9 @@ class ProductBundle(Document):
|
||||
|
||||
if len(invoice_links):
|
||||
frappe.throw(
|
||||
"This Product Bundle is linked with {}. You will have to cancel these documents in order to delete this Product Bundle".format(
|
||||
", ".join(invoice_links)
|
||||
),
|
||||
_(
|
||||
"This Product Bundle is linked with {0}. You will have to cancel these documents in order to delete this Product Bundle"
|
||||
).format(", ".join(invoice_links)),
|
||||
title=_("Not Allowed"),
|
||||
)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from unittest.mock import patch
|
||||
import frappe
|
||||
import frappe.permissions
|
||||
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.tests import change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
|
||||
@@ -907,10 +908,12 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
item_doc.save()
|
||||
else:
|
||||
# update valid from
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabItem Tax` set valid_from = CURRENT_DATE
|
||||
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||
{"item": item, "tax": tax_template},
|
||||
frappe.db.set_value(
|
||||
"Item Tax",
|
||||
{"parent": item, "item_tax_template": tax_template},
|
||||
"valid_from",
|
||||
today(),
|
||||
update_modified=False,
|
||||
)
|
||||
|
||||
so = make_sales_order(item_code=item, qty=1, do_not_save=1)
|
||||
@@ -960,10 +963,12 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
self.assertEqual(so.taxes[1].total, 480)
|
||||
|
||||
# teardown
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabItem Tax` set valid_from = NULL
|
||||
where parent = %(item)s and item_tax_template = %(tax)s""",
|
||||
{"item": item, "tax": tax_template},
|
||||
frappe.db.set_value(
|
||||
"Item Tax",
|
||||
{"parent": item, "item_tax_template": tax_template},
|
||||
"valid_from",
|
||||
None,
|
||||
update_modified=False,
|
||||
)
|
||||
so.cancel()
|
||||
so.delete()
|
||||
@@ -1559,10 +1564,12 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
|
||||
# Check if Work Orders were raised
|
||||
for item in so_item_name:
|
||||
wo_qty = frappe.db.sql(
|
||||
"select sum(qty) from `tabWork Order` where sales_order=%s and sales_order_item=%s",
|
||||
(so.name, item),
|
||||
)
|
||||
wo = frappe.qb.DocType("Work Order")
|
||||
wo_qty = (
|
||||
frappe.qb.from_(wo)
|
||||
.select(Sum(wo.qty))
|
||||
.where((wo.sales_order == so.name) & (wo.sales_order_item == item))
|
||||
).run()
|
||||
self.assertEqual(wo_qty[0][0], so_item_name.get(item))
|
||||
|
||||
def test_advance_payment_entry_unlink_against_sales_order(self):
|
||||
@@ -1742,9 +1749,7 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
mr_dict["include_exploded_items"] = 0
|
||||
mr_dict["ignore_existing_ordered_qty"] = 1
|
||||
make_raw_material_request(mr_dict, so.company, so.name)
|
||||
mr = frappe.db.sql(
|
||||
"""select name from `tabMaterial Request` ORDER BY creation DESC LIMIT 1""", as_dict=1
|
||||
)[0]
|
||||
mr = frappe.get_all("Material Request", fields=["name"], order_by="creation desc", limit=1)[0]
|
||||
mr_doc = frappe.get_doc("Material Request", mr.get("name"))
|
||||
self.assertEqual(mr_doc.items[0].sales_order, so.name)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ def validate_filters(from_date, to_date, company):
|
||||
frappe.throw(_("To Date must be greater than From Date"))
|
||||
|
||||
if not company:
|
||||
frappe.throw(_("Please Select a Company"))
|
||||
frappe.throw(_("Please select a Company"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -47,7 +47,7 @@ class SalesPartnerSummaryReport:
|
||||
frappe.throw(_("Please select the document type first."))
|
||||
|
||||
if self.filters.get("doctype") not in SALES_TRANSACTION_DOCTYPES:
|
||||
frappe.throw(_("DocType can be one of them {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)))
|
||||
frappe.throw(_("DocType can be one of {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)))
|
||||
|
||||
if not self.filters.get("company"):
|
||||
frappe.throw(_("Please select a company."))
|
||||
|
||||
@@ -19,7 +19,7 @@ class SalesPartnerSummaryReportTestMixin(ERPNextTestSuite):
|
||||
|
||||
with self.assertRaisesRegex(
|
||||
frappe.ValidationError,
|
||||
_("DocType can be one of them {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)),
|
||||
_("DocType can be one of {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)),
|
||||
):
|
||||
run(self.report_name, self.filters)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Max
|
||||
from frappe.utils.nestedset import (
|
||||
NestedSetChildExistsError,
|
||||
NestedSetInvalidMergeError,
|
||||
@@ -20,7 +21,8 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
|
||||
def test_basic_tree(self, records=None):
|
||||
min_lft = 1
|
||||
max_rgt = frappe.db.sql("select max(rgt) from `tabItem Group`")[0][0]
|
||||
ig = frappe.qb.DocType("Item Group")
|
||||
max_rgt = frappe.qb.from_(ig).select(Max(ig.rgt)).run()[0][0]
|
||||
|
||||
if not records:
|
||||
records = self.globalTestRecords["Item Group"][2:]
|
||||
@@ -131,12 +133,7 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
frappe.db.get_value("Item Group", parent_item_group, "rgt")
|
||||
|
||||
ancestors = get_ancestors_of("Item Group", "_Test Item Group B - 3")
|
||||
ancestors = frappe.db.sql(
|
||||
"""select name, rgt from `tabItem Group`
|
||||
where name in ({})""".format(", ".join(["%s"] * len(ancestors))),
|
||||
tuple(ancestors),
|
||||
as_dict=True,
|
||||
)
|
||||
ancestors = frappe.get_all("Item Group", filters={"name": ["in", ancestors]}, fields=["name", "rgt"])
|
||||
|
||||
frappe.delete_doc("Item Group", "_Test Item Group B - 3")
|
||||
records_to_test = self.globalTestRecords["Item Group"][2:]
|
||||
@@ -168,9 +165,8 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
self.test_basic_tree()
|
||||
|
||||
# move its children back
|
||||
for name in frappe.db.sql_list(
|
||||
"""select name from `tabItem Group`
|
||||
where parent_item_group='_Test Item Group C'"""
|
||||
for name in frappe.get_all(
|
||||
"Item Group", filters={"parent_item_group": "_Test Item Group C"}, pluck="name"
|
||||
):
|
||||
doc = frappe.get_doc("Item Group", name)
|
||||
doc.parent_item_group = "_Test Item Group B"
|
||||
@@ -218,11 +214,7 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
def get_no_of_children(item_groups, no_of_children):
|
||||
children = []
|
||||
for ig in item_groups:
|
||||
children += frappe.db.sql_list(
|
||||
"""select name from `tabItem Group`
|
||||
where ifnull(parent_item_group, '')=%s""",
|
||||
ig or "",
|
||||
)
|
||||
children += frappe.get_all("Item Group", filters={"parent_item_group": ig}, pluck="name")
|
||||
|
||||
if len(children):
|
||||
return get_no_of_children(children, no_of_children + len(children))
|
||||
|
||||
@@ -390,7 +390,7 @@ def validate_serial_no_with_batch(serial_nos, item_code):
|
||||
|
||||
serial_no_link = ",".join(get_link_to_form("Serial No", sn) for sn in serial_nos)
|
||||
|
||||
message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
|
||||
message = _("Serial Nos") if len(serial_nos) > 1 else _("Serial No")
|
||||
frappe.throw(_("There is no batch found against the {0}: {1}").format(message, serial_no_link))
|
||||
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ def make_sales_invoice(
|
||||
target.run_method("set_po_nos")
|
||||
|
||||
if len(target.get("items")) == 0:
|
||||
frappe.throw(_("All these items have already been Invoiced/Returned"))
|
||||
frappe.throw(_("All these items have already been invoiced/returned"))
|
||||
|
||||
if args and args.get("merge_taxes"):
|
||||
merge_taxes(source, target)
|
||||
|
||||
@@ -216,7 +216,7 @@ class DeliveryTrip(Document):
|
||||
(list of list of str): List of address routes split at locks, if optimize is `True`
|
||||
"""
|
||||
if not self.driver_address:
|
||||
frappe.throw(_("Cannot Calculate Arrival Time as Driver Address is Missing."))
|
||||
frappe.throw(_("Cannot calculate arrival time as the driver address is missing."))
|
||||
|
||||
home_address = get_address_display(frappe.get_doc("Address", self.driver_address).as_dict())
|
||||
|
||||
|
||||
@@ -463,7 +463,7 @@ class Item(Document):
|
||||
|
||||
def validate_item_type(self):
|
||||
if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset:
|
||||
frappe.throw(_("'Has Serial No' can not be 'Yes' for non-stock item"))
|
||||
frappe.throw(_("'Has Serial No' cannot be 'Yes' for non-stock item"))
|
||||
|
||||
if self.has_serial_no == 0 and self.serial_no_series:
|
||||
self.serial_no_series = None
|
||||
@@ -1508,7 +1508,9 @@ def validate_item_default_company_links(item_defaults: list[ItemDefault]) -> Non
|
||||
company = frappe.db.get_value(doctype, item_default.get(field), "company", cache=True)
|
||||
if company and company != item_default.company:
|
||||
frappe.throw(
|
||||
_("Row #{}: {} {} doesn't belong to Company {}. Please select valid {}.").format(
|
||||
_(
|
||||
"Row #{0}: {1} {2} does not belong to Company {3}. Please select valid {4}."
|
||||
).format(
|
||||
item_default.idx,
|
||||
doctype,
|
||||
frappe.bold(item_default.get(field)),
|
||||
|
||||
@@ -33,7 +33,7 @@ class ItemAlternative(Document):
|
||||
|
||||
def has_alternative_item(self):
|
||||
if self.item_code and not frappe.db.get_value("Item", self.item_code, "allow_alternative_item"):
|
||||
frappe.throw(_("Not allow to set alternative item for the item {0}").format(self.item_code))
|
||||
frappe.throw(_("Cannot set alternative item for the item {0}").format(self.item_code))
|
||||
|
||||
def validate_alternative_item(self):
|
||||
if self.item_code == self.alternative_item_code:
|
||||
@@ -65,7 +65,7 @@ class ItemAlternative(Document):
|
||||
indicator="Orange",
|
||||
)
|
||||
|
||||
alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {}")
|
||||
alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {0}")
|
||||
|
||||
if not item_data.allow_alternative_item:
|
||||
frappe.throw(alternate_item_check_msg.format(self.item_code))
|
||||
@@ -81,7 +81,7 @@ class ItemAlternative(Document):
|
||||
"name": ("!=", self.name),
|
||||
},
|
||||
):
|
||||
frappe.throw(_("Already record exists for the item {0}").format(self.item_code))
|
||||
frappe.throw(_("Record already exists for the item {0}").format(self.item_code))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -68,7 +68,7 @@ class ItemPrice(Document):
|
||||
|
||||
if not price_list_details:
|
||||
link = frappe.utils.get_link_to_form("Price List", self.price_list)
|
||||
frappe.throw(f"The price list {link} does not exist or is disabled")
|
||||
frappe.throw(_("The price list {0} does not exist or is disabled").format(link))
|
||||
|
||||
self.buying, self.selling, self.currency = price_list_details
|
||||
|
||||
|
||||
@@ -129,8 +129,10 @@ class LandedCostVoucher(Document):
|
||||
d.receipt_document_type, d.receipt_document, ["docstatus", "company"]
|
||||
)
|
||||
if docstatus != 1:
|
||||
msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
|
||||
frappe.throw(_(msg), title=_("Invalid Document"))
|
||||
msg = _("Row {0}: {1} {2} must be submitted").format(
|
||||
d.idx, d.receipt_document_type, frappe.bold(d.receipt_document)
|
||||
)
|
||||
frappe.throw(msg, title=_("Invalid Document"))
|
||||
|
||||
if company != self.company:
|
||||
frappe.throw(
|
||||
@@ -244,7 +246,7 @@ class LandedCostVoucher(Document):
|
||||
if not total:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'"
|
||||
"Total {0} for all items is zero, maybe you should change 'Distribute Charges Based On'"
|
||||
).format(based_on)
|
||||
)
|
||||
|
||||
@@ -375,8 +377,8 @@ class LandedCostVoucher(Document):
|
||||
if not docs or total_asset_qty < item.qty:
|
||||
frappe.throw(
|
||||
_(
|
||||
"For item <b>{0}</b>, only <b>{1}</b> asset have been created or linked to <b>{2}</b>. "
|
||||
"Please create or link <b>{3}</b> more asset with the respective document."
|
||||
"For item <b>{0}</b>, only <b>{1}</b> assets have been created or linked to <b>{2}</b>. "
|
||||
"Please create or link <b>{3}</b> more assets with the respective document."
|
||||
).format(
|
||||
item.item_code, total_asset_qty, item.receipt_document, item.qty - total_asset_qty
|
||||
)
|
||||
|
||||
@@ -350,7 +350,7 @@ class MaterialRequest(BuyingController):
|
||||
if d.ordered_qty and flt(d.ordered_qty, precision) > flt(allowed_qty, precision):
|
||||
frappe.throw(
|
||||
_(
|
||||
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than allowed requested quantity {2} for Item {3}"
|
||||
"The total Issue / Transfer quantity {0} in Material Request {1} cannot be greater than allowed requested quantity {2} for Item {3}"
|
||||
).format(d.ordered_qty, d.parent, allowed_qty, d.item_code)
|
||||
)
|
||||
|
||||
@@ -576,7 +576,7 @@ def raise_work_orders(material_request: str, company: str):
|
||||
|
||||
if errors:
|
||||
frappe.throw(
|
||||
_("Work Order cannot be created for following reason: <br> {0}").format(new_line_sep(errors))
|
||||
_("Work Order cannot be created for the following reason: <br> {0}").format(new_line_sep(errors))
|
||||
)
|
||||
|
||||
return work_orders
|
||||
|
||||
@@ -746,11 +746,11 @@ class TestMaterialRequest(ERPNextTestSuite):
|
||||
mr = frappe.get_doc("Material Request", mr.name)
|
||||
mr.submit()
|
||||
completed_qty = mr.items[0].ordered_qty
|
||||
requested_qty = frappe.db.sql(
|
||||
"""select indented_qty from `tabBin` where \
|
||||
item_code= %s and warehouse= %s """,
|
||||
(mr.items[0].item_code, mr.items[0].warehouse),
|
||||
)[0][0]
|
||||
requested_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
{"item_code": mr.items[0].item_code, "warehouse": mr.items[0].warehouse},
|
||||
"indented_qty",
|
||||
)
|
||||
|
||||
prod_order = raise_work_orders(mr.name, mr.company)
|
||||
po = frappe.get_doc("Work Order", prod_order[0])
|
||||
@@ -760,11 +760,11 @@ class TestMaterialRequest(ERPNextTestSuite):
|
||||
mr = frappe.get_doc("Material Request", mr.name)
|
||||
self.assertEqual(completed_qty + po.qty, mr.items[0].ordered_qty)
|
||||
|
||||
new_requested_qty = frappe.db.sql(
|
||||
"""select indented_qty from `tabBin` where \
|
||||
item_code= %s and warehouse= %s """,
|
||||
(mr.items[0].item_code, mr.items[0].warehouse),
|
||||
)[0][0]
|
||||
new_requested_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
{"item_code": mr.items[0].item_code, "warehouse": mr.items[0].warehouse},
|
||||
"indented_qty",
|
||||
)
|
||||
|
||||
self.assertEqual(requested_qty - po.qty, new_requested_qty)
|
||||
|
||||
@@ -773,11 +773,11 @@ class TestMaterialRequest(ERPNextTestSuite):
|
||||
mr = frappe.get_doc("Material Request", mr.name)
|
||||
self.assertEqual(completed_qty, mr.items[0].ordered_qty)
|
||||
|
||||
new_requested_qty = frappe.db.sql(
|
||||
"""select indented_qty from `tabBin` where \
|
||||
item_code= %s and warehouse= %s """,
|
||||
(mr.items[0].item_code, mr.items[0].warehouse),
|
||||
)[0][0]
|
||||
new_requested_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
{"item_code": mr.items[0].item_code, "warehouse": mr.items[0].warehouse},
|
||||
"indented_qty",
|
||||
)
|
||||
self.assertEqual(requested_qty, new_requested_qty)
|
||||
|
||||
def test_requested_qty_multi_uom(self):
|
||||
|
||||
@@ -80,15 +80,13 @@ class PackingSlip(StatusUpdater):
|
||||
"""Raises an exception if the `Delivery Note` status is not Draft"""
|
||||
|
||||
if cint(frappe.db.get_value("Delivery Note", self.delivery_note, "docstatus")) != 0:
|
||||
frappe.throw(
|
||||
_("A Packing Slip can only be created for Draft Delivery Note.").format(self.delivery_note)
|
||||
)
|
||||
frappe.throw(_("A Packing Slip can only be created for a Draft Delivery Note."))
|
||||
|
||||
def validate_case_nos(self):
|
||||
"""Validate if case nos overlap. If they do, recommend next case no."""
|
||||
|
||||
if cint(self.from_case_no) <= 0:
|
||||
frappe.throw(_("The 'From Package No.' field must neither be empty nor it's value less than 1."))
|
||||
frappe.throw(_("The 'From Package No.' field must not be empty or have a value less than 1."))
|
||||
elif not self.to_case_no:
|
||||
self.to_case_no = self.from_case_no
|
||||
elif cint(self.to_case_no) < cint(self.from_case_no):
|
||||
|
||||
@@ -286,7 +286,7 @@ def create_stock_entry(pick_list: str | dict):
|
||||
validate_item_locations(pick_list)
|
||||
|
||||
if stock_entry_exists(pick_list.get("name")):
|
||||
return frappe.msgprint(_("Stock Entry has been already created against this Pick List"))
|
||||
return frappe.msgprint(_("Stock Entry has already been created against this Pick List"))
|
||||
|
||||
stock_entry = frappe.new_doc("Stock Entry")
|
||||
stock_entry.pick_list = pick_list.get("name")
|
||||
|
||||
@@ -232,7 +232,7 @@ class PickList(TransactionBase):
|
||||
and frappe.db.get_value("Sales Order", location.sales_order, "per_picked", cache=True) == 100
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row #{}: item {} has been picked already.").format(location.idx, location.item_code)
|
||||
_("Row #{0}: item {1} has been picked already.").format(location.idx, location.item_code)
|
||||
)
|
||||
|
||||
def before_submit(self):
|
||||
@@ -647,7 +647,7 @@ class PickList(TransactionBase):
|
||||
continue
|
||||
|
||||
if not item.item_code:
|
||||
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
|
||||
frappe.throw(_("Row #{0}: Item Code is Mandatory").format(item.idx))
|
||||
if not cint(
|
||||
frappe.get_cached_value("Item", item.item_code, "is_stock_item")
|
||||
) and not get_active_product_bundle(item.item_code):
|
||||
|
||||
@@ -260,7 +260,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.check_for_on_hold_or_closed_status("Purchase Order", "purchase_order")
|
||||
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
throw(_("Posting Date cannot be future date"))
|
||||
throw(_("Posting Date cannot be a future date"))
|
||||
|
||||
self.get_current_stock()
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
@@ -329,14 +329,18 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
if qi.reference_type != self.doctype or qi.reference_name != self.name:
|
||||
msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Reference Type
|
||||
{frappe.bold(self.doctype)} and Reference Name {frappe.bold(self.name)}."""
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Please select a valid Quality Inspection with Reference Type {1} and Reference Name {2}."
|
||||
).format(item.idx, frappe.bold(self.doctype), frappe.bold(self.name))
|
||||
)
|
||||
|
||||
if qi.item_code != item.item_code:
|
||||
msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Item Code
|
||||
{frappe.bold(item.item_code)}."""
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(
|
||||
_("Row #{0}: Please select a valid Quality Inspection with Item Code {1}.").format(
|
||||
item.idx, frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
|
||||
def get_already_received_qty(self, po, po_detail):
|
||||
qty = frappe.get_all(
|
||||
|
||||
@@ -58,7 +58,7 @@ class PutawayRule(Document):
|
||||
|
||||
def validate_priority(self):
|
||||
if self.priority < 1:
|
||||
frappe.throw(_("Priority cannot be lesser than 1."), title=_("Invalid Priority"))
|
||||
frappe.throw(_("Priority cannot be less than 1."), title=_("Invalid Priority"))
|
||||
|
||||
def validate_warehouse_and_company(self):
|
||||
company = frappe.db.get_value("Warehouse", self.warehouse, "company")
|
||||
@@ -303,7 +303,7 @@ def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=N
|
||||
|
||||
|
||||
def show_unassigned_items_message(items_not_accomodated):
|
||||
msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br>"
|
||||
msg = _("The following Items, having Putaway Rules, could not be accommodated:") + "<br><br>"
|
||||
formatted_item_rows = ""
|
||||
|
||||
for entry in items_not_accomodated:
|
||||
|
||||
@@ -134,7 +134,7 @@ class QualityInspection(Document):
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"'Inspection Required before Purchase' has disabled for the item {0}, no need to create the QI"
|
||||
"'Inspection Required before Purchase' is disabled for the item {0}, no need to create the QI"
|
||||
).format(get_link_to_form("Item", self.item_code))
|
||||
)
|
||||
|
||||
@@ -143,7 +143,7 @@ class QualityInspection(Document):
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"'Inspection Required before Delivery' has disabled for the item {0}, no need to create the QI"
|
||||
"'Inspection Required before Delivery' is disabled for the item {0}, no need to create the QI"
|
||||
).format(get_link_to_form("Item", self.item_code))
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:parameter",
|
||||
"creation": "2020-12-28 17:06:00.254129",
|
||||
"doctype": "DocType",
|
||||
@@ -34,7 +35,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:28.861722",
|
||||
"modified": "2026-06-19 10:55:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Quality Inspection Parameter",
|
||||
|
||||
@@ -209,7 +209,7 @@ class RepostItemValuation(Document):
|
||||
):
|
||||
frappe.msgprint(_("Caution: This might alter frozen accounts."))
|
||||
return
|
||||
frappe.throw(_("You cannot repost item valuation before {}").format(acc_frozen_till_date))
|
||||
frappe.throw(_("You cannot repost item valuation before {0}").format(acc_frozen_till_date))
|
||||
|
||||
def reset_field_values(self):
|
||||
if self.based_on == "Transaction":
|
||||
|
||||
@@ -165,7 +165,7 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
if invalid_serial_nos:
|
||||
msg = _(
|
||||
"You cannot outward following {0} as either they are Delivered, Inactive or located in a different warehouse."
|
||||
"You cannot outward the following {0} as they are either Delivered, Inactive or located in a different warehouse."
|
||||
).format(_("Serial Nos") if len(invalid_serial_nos) > 1 else _("Serial No"))
|
||||
msg += "<hr>"
|
||||
msg += ", ".join(sn for sn in invalid_serial_nos)
|
||||
@@ -183,7 +183,7 @@ class SerialandBatchBundle(Document):
|
||||
if self.voucher_type == "POS Invoice":
|
||||
if not frappe.db.exists("POS Invoice Item", self.voucher_detail_no):
|
||||
frappe.throw(
|
||||
_("The serial and batch bundle {0} not linked to {1} {2}").format(
|
||||
_("The serial and batch bundle {0} is not linked to {1} {2}").format(
|
||||
bold(self.name), self.voucher_type, bold(self.voucher_no)
|
||||
)
|
||||
)
|
||||
@@ -195,7 +195,7 @@ class SerialandBatchBundle(Document):
|
||||
return
|
||||
|
||||
frappe.throw(
|
||||
_("The serial and batch bundle {0} not linked to {1} {2}").format(
|
||||
_("The serial and batch bundle {0} is not linked to {1} {2}").format(
|
||||
bold(self.name), self.voucher_type, bold(self.voucher_no)
|
||||
)
|
||||
)
|
||||
@@ -227,7 +227,7 @@ class SerialandBatchBundle(Document):
|
||||
for row in data:
|
||||
frappe.throw(
|
||||
_(
|
||||
"You can't process the serial number {0} as it has already been used in the SABB {1}. {2} if you want to inward same serial number multiple times then enabled 'Allow existing Serial No to be Manufactured/Received again' in the {3}"
|
||||
"You cannot process the serial number {0} as it has already been used in the SABB {1}. {2} If you want to inward the same serial number multiple times, then enable 'Allow existing Serial No to be Manufactured/Received again' in the {3}"
|
||||
).format(
|
||||
row.serial_no,
|
||||
get_link_to_form("Serial and Batch Bundle", row.parent),
|
||||
@@ -376,7 +376,7 @@ class SerialandBatchBundle(Document):
|
||||
if len(serial_nos) == 1:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Serial No {0} is already Delivered. You cannot use them again in Manufacture / Repack entry."
|
||||
"Serial No {0} is already Delivered. You cannot use it again in Manufacture / Repack entry."
|
||||
).format(bold(serial_nos[0]))
|
||||
)
|
||||
else:
|
||||
@@ -654,12 +654,12 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
def validate_negative_batch(self, batch_no, available_qty):
|
||||
if available_qty < 0 and not self.is_stock_reco_for_valuation_adjustment(available_qty):
|
||||
msg = f"""Batch No {bold(batch_no)} of an Item {bold(self.item_code)}
|
||||
has negative stock
|
||||
of quantity {bold(available_qty)} in the
|
||||
warehouse {self.warehouse}"""
|
||||
|
||||
frappe.throw(_(msg), BatchNegativeStockError)
|
||||
frappe.throw(
|
||||
_("Batch No {0} of Item {1} has negative stock of quantity {2} in the warehouse {3}").format(
|
||||
bold(batch_no), bold(self.item_code), bold(available_qty), self.warehouse
|
||||
),
|
||||
BatchNegativeStockError,
|
||||
)
|
||||
|
||||
def is_stock_reco_for_valuation_adjustment(self, available_qty):
|
||||
if (
|
||||
@@ -1153,8 +1153,7 @@ class SerialandBatchBundle(Document):
|
||||
|
||||
def validate_serial_and_batch_no(self):
|
||||
if self.item_code and not self.has_serial_no and not self.has_batch_no:
|
||||
msg = f"The Item {self.item_code} does not have Serial No or Batch No"
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(_("The Item {0} does not have Serial No or Batch No").format(self.item_code))
|
||||
|
||||
serial_nos = []
|
||||
batch_nos = []
|
||||
@@ -1589,12 +1588,11 @@ class SerialandBatchBundle(Document):
|
||||
date_msg = " " + _("as of {0}").format(format_datetime(posting_datetime))
|
||||
|
||||
msg = _(
|
||||
"""
|
||||
The Batch {0} of an item {1} has negative stock in the warehouse {2}{3}.
|
||||
Please add a stock quantity of {4} to proceed with this entry.
|
||||
If it is not possible to make an adjustment entry, please enable 'Allow Negative Stock for Batch' in the batch {0} or in the Stock Settings to proceed.
|
||||
However, enabling this setting may lead to negative stock in the system.
|
||||
So please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."""
|
||||
"The Batch {0} of item {1} has negative stock in the warehouse {2}{3}. "
|
||||
"Please add a stock quantity of {4} to proceed with this entry. "
|
||||
"If it is not possible to make an adjustment entry, please enable 'Allow Negative Stock for Batch' in the batch {0} or in the Stock Settings to proceed. "
|
||||
"However, enabling this setting may lead to negative stock in the system. "
|
||||
"So please ensure the stock levels are adjusted as soon as possible to maintain the correct valuation rate."
|
||||
).format(
|
||||
bold(batch_no),
|
||||
bold(self.item_code),
|
||||
@@ -1728,9 +1726,11 @@ class SerialandBatchBundle(Document):
|
||||
and self.voucher_detail_no
|
||||
and frappe.db.exists(child_doctype, self.voucher_detail_no)
|
||||
):
|
||||
msg = f"""The {self.voucher_type} {bold(self.voucher_no)}
|
||||
is in submitted state, please cancel it first"""
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(
|
||||
_("The {0} {1} is in submitted state, please cancel it first").format(
|
||||
self.voucher_type, bold(self.voucher_no)
|
||||
)
|
||||
)
|
||||
|
||||
def on_trash(self):
|
||||
self.validate_voucher_no_docstatus()
|
||||
@@ -3486,13 +3486,13 @@ def is_serial_batch_no_exists(
|
||||
):
|
||||
if serial_no and not frappe.db.exists("Serial No", serial_no):
|
||||
if type_of_transaction != "Inward":
|
||||
frappe.throw(_("Serial No {0} does not exists").format(serial_no))
|
||||
frappe.throw(_("Serial No {0} does not exist").format(serial_no))
|
||||
|
||||
make_serial_no(serial_no, item_code)
|
||||
|
||||
if batch_no and not frappe.db.exists("Batch", batch_no):
|
||||
if type_of_transaction != "Inward":
|
||||
frappe.throw(_("Batch No {0} does not exists").format(batch_no))
|
||||
frappe.throw(_("Batch No {0} does not exist").format(batch_no))
|
||||
|
||||
make_batch_no(batch_no, item_code)
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ class StockClosingEntry(Document):
|
||||
enqueue(prepare_closing_stock_balance, name=self.name, queue="long", timeout=1500)
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Stock Closing Entry {0} has been queued for processing, system will take sometime to complete it."
|
||||
"Stock Closing Entry {0} has been queued for processing, the system will take some time to complete it."
|
||||
).format(self.name)
|
||||
)
|
||||
|
||||
|
||||
@@ -124,7 +124,8 @@ class BaseManufactureStockEntry(BaseStockEntry):
|
||||
self.doc.process_loss_qty = flt(process_loss_qty, precision)
|
||||
|
||||
frappe.msgprint(
|
||||
_("The Process Loss Qty has reset as per job cards Process Loss Qty"), alert=True
|
||||
_("The Process Loss Qty has been reset as per the Job Card's Process Loss Qty"),
|
||||
alert=True,
|
||||
)
|
||||
|
||||
if not self.doc.process_loss_percentage and not self.doc.process_loss_qty:
|
||||
|
||||
@@ -91,7 +91,7 @@ class SendToSubcontractorStockEntry(BaseStockEntry):
|
||||
child_row.db_set(self.doc.subcontract_data.rm_detail_field, order_rm_detail)
|
||||
elif not child_row.allow_alternative_item:
|
||||
frappe.throw(
|
||||
_("Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}").format(
|
||||
_("Row #{0}: Item {1} not found in 'Raw Materials Supplied' table in {2} {3}").format(
|
||||
child_row.idx,
|
||||
item_code,
|
||||
self.doc.subcontract_data.order_doctype,
|
||||
|
||||
@@ -404,12 +404,11 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
if row.job_card_item or not row.s_warehouse:
|
||||
continue
|
||||
|
||||
msg = f"""Row #{row.idx}: The job card item reference
|
||||
is missing. Kindly create the stock entry
|
||||
from the job card. If you have added the row manually
|
||||
then you won't be able to add job card item reference."""
|
||||
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: The job card item reference is missing. Kindly create the stock entry from the job card. If you have added the row manually then you won't be able to add job card item reference."
|
||||
).format(row.idx)
|
||||
)
|
||||
|
||||
def validate_work_order_status(self):
|
||||
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
||||
@@ -885,7 +884,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
|
||||
if not finished_items:
|
||||
frappe.throw(
|
||||
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
|
||||
msg=_("There must be at least 1 Finished Good in this Stock Entry").format(self.name),
|
||||
title=_("Missing Finished Good"),
|
||||
exc=FinishedGoodError,
|
||||
)
|
||||
@@ -908,7 +907,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
# No work order could mean independent Manufacture entry, if so skip validation
|
||||
if self.work_order and self.fg_completed_qty > allowed_qty:
|
||||
frappe.throw(
|
||||
_("For quantity {0} should not be greater than allowed quantity {1}").format(
|
||||
_("Quantity {0} should not be greater than allowed quantity {1}").format(
|
||||
flt(self.fg_completed_qty), allowed_qty
|
||||
)
|
||||
)
|
||||
@@ -1373,7 +1372,8 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
self.process_loss_qty = flt(process_loss_qty, precision)
|
||||
|
||||
frappe.msgprint(
|
||||
_("The Process Loss Qty has reset as per job cards Process Loss Qty"), alert=True
|
||||
_("The Process Loss Qty has been reset as per the job card's Process Loss Qty"),
|
||||
alert=True,
|
||||
)
|
||||
|
||||
if not self.process_loss_percentage and not self.process_loss_qty:
|
||||
|
||||
@@ -40,19 +40,12 @@ from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
def get_sle(**args):
|
||||
condition, values = "", []
|
||||
for key, value in args.items():
|
||||
condition += " and " if condition else " where "
|
||||
condition += f"`{key}`=%s"
|
||||
values.append(value)
|
||||
|
||||
return frappe.db.sql(
|
||||
# posting_datetime is the precomputed date+time column; MySQL-only timestamp(date,time) errors on Postgres
|
||||
"""select * from `tabStock Ledger Entry` %s
|
||||
order by posting_datetime desc, creation desc limit 1"""
|
||||
% condition,
|
||||
values,
|
||||
as_dict=1,
|
||||
return frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters=args,
|
||||
fields=["*"],
|
||||
order_by="posting_datetime desc, creation desc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
|
||||
@@ -269,20 +262,10 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
mr.cancel()
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.sql(
|
||||
"""select * from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""",
|
||||
mr.name,
|
||||
)
|
||||
frappe.db.exists("Stock Ledger Entry", {"voucher_type": "Stock Entry", "voucher_no": mr.name})
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.sql(
|
||||
"""select * from `tabGL Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""",
|
||||
mr.name,
|
||||
)
|
||||
)
|
||||
self.assertTrue(frappe.db.exists("GL Entry", {"voucher_type": "Stock Entry", "voucher_no": mr.name}))
|
||||
|
||||
def test_material_issue_gl_entry(self):
|
||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||
@@ -361,12 +344,7 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
if source_warehouse_account == target_warehouse_account:
|
||||
# no gl entry as both source and target warehouse has linked to same account.
|
||||
self.assertFalse(
|
||||
frappe.db.sql(
|
||||
"""select * from `tabGL Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""",
|
||||
mtn.name,
|
||||
as_dict=1,
|
||||
)
|
||||
frappe.db.exists("GL Entry", {"voucher_type": "Stock Entry", "voucher_no": mtn.name})
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -460,14 +438,9 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
],
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Stock Entry' and voucher_no=%s
|
||||
order by account desc""",
|
||||
repack.name,
|
||||
as_dict=1,
|
||||
self.assertFalse(
|
||||
frappe.db.exists("GL Entry", {"voucher_type": "Stock Entry", "voucher_no": repack.name})
|
||||
)
|
||||
self.assertFalse(gl_entries)
|
||||
|
||||
def test_repack_with_additional_costs(self):
|
||||
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
|
||||
@@ -601,15 +574,15 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
expected_sle.sort(key=lambda x: x[1])
|
||||
|
||||
# check stock ledger entries
|
||||
sle = frappe.db.sql(
|
||||
"""select item_code, warehouse, actual_qty
|
||||
from `tabStock Ledger Entry` where voucher_type = %s
|
||||
and voucher_no = %s order by item_code, warehouse, actual_qty""",
|
||||
(voucher_type, voucher_no),
|
||||
as_list=1,
|
||||
sle = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
|
||||
fields=["item_code", "warehouse", "actual_qty"],
|
||||
order_by="item_code, warehouse, actual_qty",
|
||||
as_list=True,
|
||||
)
|
||||
self.assertTrue(sle)
|
||||
sle.sort(key=lambda x: x[1])
|
||||
sle = sorted(sle, key=lambda x: x[1])
|
||||
|
||||
for i, sle_value in enumerate(sle):
|
||||
self.assertEqual(expected_sle[i][0], sle_value[0])
|
||||
@@ -619,16 +592,16 @@ class TestStockEntry(ERPNextTestSuite):
|
||||
def check_gl_entries(self, voucher_type, voucher_no, expected_gl_entries):
|
||||
expected_gl_entries.sort(key=lambda x: x[0])
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
order by account asc, debit asc""",
|
||||
(voucher_type, voucher_no),
|
||||
as_list=1,
|
||||
gl_entries = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
|
||||
fields=["account", "debit", "credit"],
|
||||
order_by="account asc, debit asc",
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
gl_entries.sort(key=lambda x: x[0])
|
||||
gl_entries = sorted(gl_entries, key=lambda x: x[0])
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gl_entries[i][0], gle[0])
|
||||
self.assertEqual(expected_gl_entries[i][1], gle[1])
|
||||
|
||||
@@ -100,7 +100,7 @@ class StockEntryDetail(Document):
|
||||
def validate_and_update_item_details(self, item_details, company, purpose):
|
||||
if flt(self.qty) and flt(self.qty) < 0:
|
||||
frappe.throw(
|
||||
_("Row {0}: The item {1}, quantity must be positive number").format(
|
||||
_("Row {0}: The item {1}, quantity must be a positive number").format(
|
||||
self.idx, bold(self.item_code)
|
||||
)
|
||||
)
|
||||
@@ -153,7 +153,7 @@ class StockEntryDetail(Document):
|
||||
if is_opening == "Yes" and acc_details.report_type == "Profit and Loss":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Difference Account must be a Asset/Liability type account "
|
||||
"Difference Account must be an Asset/Liability type account "
|
||||
"(Temporary Opening), since this Stock Entry is an Opening Entry"
|
||||
),
|
||||
OpeningEntryAccountError,
|
||||
|
||||
@@ -62,7 +62,7 @@ class StockEntryType(Document):
|
||||
"Subcontracting Delivery",
|
||||
"Subcontracting Return",
|
||||
]:
|
||||
frappe.throw(f"Stock Entry Type {self.name} cannot be set as standard")
|
||||
frappe.throw(_("Stock Entry Type {0} cannot be set as standard").format(self.name))
|
||||
|
||||
|
||||
class ManufactureEntry:
|
||||
|
||||
@@ -342,7 +342,7 @@ class StockLedgerEntry(Document):
|
||||
"You are not authorized to make/edit Stock Transactions for Item {0} under warehouse {1} before this time."
|
||||
).format(frappe.bold(self.item_code), frappe.bold(self.warehouse))
|
||||
|
||||
msg += "<br><br>" + _("Please contact any of the following users to {} this transaction.")
|
||||
msg += "<br><br>" + _("Please contact any of the following users for this transaction.")
|
||||
msg += "<br>" + "<br>".join(authorized_users)
|
||||
frappe.throw(msg, BackDatedStockTransaction, title=_("Backdated Stock Entry"))
|
||||
|
||||
|
||||
@@ -982,7 +982,7 @@ class StockReconciliation(StockController):
|
||||
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"
|
||||
"Difference Account must be an Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"
|
||||
),
|
||||
OpeningEntryAccountError,
|
||||
)
|
||||
@@ -1246,7 +1246,7 @@ def get_stock_balance_for(
|
||||
|
||||
if not item_dict:
|
||||
# In cases of data upload to Items table
|
||||
msg = _("Item {} does not exist.").format(item_code)
|
||||
msg = _("Item {0} does not exist.").format(item_code)
|
||||
frappe.throw(msg, title=_("Missing"))
|
||||
|
||||
serial_nos = None
|
||||
|
||||
@@ -85,11 +85,10 @@ class TestStockReconciliation(ERPNextTestSuite, StockTestMixin):
|
||||
)
|
||||
|
||||
# check stock value
|
||||
sle = frappe.db.sql(
|
||||
"""select * from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Reconciliation' and voucher_no=%s""",
|
||||
stock_reco.name,
|
||||
as_dict=1,
|
||||
sle = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name},
|
||||
fields=["qty_after_transaction", "stock_value"],
|
||||
)
|
||||
|
||||
qty_after_transaction = flt(d[0]) if d[0] != "" else flt(last_sle.get("qty_after_transaction"))
|
||||
|
||||
@@ -138,7 +138,7 @@ class StockReservationEntry(Document):
|
||||
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot cancel Stock Reservation Entry {0}, as it has used in the work order {1}. Please cancel the work order first or unreserved the stock"
|
||||
"Cannot cancel Stock Reservation Entry {0}, as it has been used in the work order {1}. Please cancel the work order first or unreserve the stock"
|
||||
).format(
|
||||
", ".join([frappe.bold(entry.name) for entry in entries]),
|
||||
", ".join([frappe.bold(wo.name) for wo in work_orders]),
|
||||
@@ -261,7 +261,7 @@ class StockReservationEntry(Document):
|
||||
if cint(frappe.db.get_value("UOM", self.stock_uom, "must_be_whole_number", cache=True)):
|
||||
if cint(self.reserved_qty) != flt(self.reserved_qty, self.precision("reserved_qty")):
|
||||
msg = _(
|
||||
"Reserved Qty ({0}) cannot be a fraction. To allow this, disable '{1}' in UOM {3}."
|
||||
"Reserved Qty ({0}) cannot be a fraction. To allow this, disable '{1}' in UOM {2}."
|
||||
).format(
|
||||
flt(self.reserved_qty, self.precision("reserved_qty")),
|
||||
frappe.bold(_("Must be Whole Number")),
|
||||
@@ -427,7 +427,7 @@ class StockReservationEntry(Document):
|
||||
entry.db_update()
|
||||
else:
|
||||
msg = _(
|
||||
"Row #{0}: Qty should be less than or equal to Available Qty to Reserve (Actual Qty - Reserved Qty) {1} for Iem {2} against Batch {3} in Warehouse {4}."
|
||||
"Row #{0}: Qty should be less than or equal to Available Qty to Reserve (Actual Qty - Reserved Qty) {1} for Item {2} against Batch {3} in Warehouse {4}."
|
||||
).format(
|
||||
entry.idx,
|
||||
frappe.bold(available_qty_to_reserve),
|
||||
@@ -623,19 +623,19 @@ class StockReservationEntry(Document):
|
||||
|
||||
if qty_to_be_reserved > allowed_qty:
|
||||
actual_qty = get_stock_balance(self.item_code, self.warehouse)
|
||||
msg = """
|
||||
Cannot reserve more than Allowed Qty {} {} for Item {} against {} {}.<br /><br />
|
||||
The <b>Allowed Qty</b> is calculated as follows:<br />
|
||||
<ul>
|
||||
<li>Actual Qty [Available Qty at Warehouse] = {}</li>
|
||||
<li>Reserved Stock [Ignore current SRE] = {}</li>
|
||||
<li>Available Qty To Reserve [Actual Qty - Reserved Stock] = {}</li>
|
||||
<li>Voucher Qty [Voucher Item Qty] = {}</li>
|
||||
<li>Delivered Qty [Qty delivered against the Voucher Item] = {}</li>
|
||||
<li>Total Reserved Qty [Qty reserved against the Voucher Item] = {}</li>
|
||||
<li>Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {}</li>
|
||||
</ul>
|
||||
""".format(
|
||||
msg = _(
|
||||
"Cannot reserve more than Allowed Qty {0} {1} for Item {2} against {3} {4}.<br /><br />"
|
||||
"The <b>Allowed Qty</b> is calculated as follows:<br />"
|
||||
"<ul>"
|
||||
"<li>Actual Qty [Available Qty at Warehouse] = {5}</li>"
|
||||
"<li>Reserved Stock [Ignore current SRE] = {6}</li>"
|
||||
"<li>Available Qty To Reserve [Actual Qty - Reserved Stock] = {7}</li>"
|
||||
"<li>Voucher Qty [Voucher Item Qty] = {8}</li>"
|
||||
"<li>Delivered Qty [Qty delivered against the Voucher Item] = {9}</li>"
|
||||
"<li>Total Reserved Qty [Qty reserved against the Voucher Item] = {10}</li>"
|
||||
"<li>Allowed Qty [Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))] = {11}</li>"
|
||||
"</ul>"
|
||||
).format(
|
||||
frappe.bold(allowed_qty),
|
||||
self.stock_uom,
|
||||
frappe.bold(self.item_code),
|
||||
|
||||
@@ -189,7 +189,7 @@ class StockSettings(Document):
|
||||
if sle:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"
|
||||
"Can't change the valuation method, as there are transactions against some items which do not have their own valuation method"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -247,7 +247,7 @@ class StockSettings(Document):
|
||||
|
||||
if has_reserved_stock:
|
||||
frappe.throw(
|
||||
_("As there are reserved stock, you cannot disable {0}.").format(
|
||||
_("As there is reserved stock, you cannot disable {0}.").format(
|
||||
frappe.bold(_("Stock Reservation"))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -19,11 +19,10 @@ class TestWarehouse(ERPNextTestSuite):
|
||||
def test_warehouse_hierarchy(self):
|
||||
p_warehouse = frappe.get_doc("Warehouse", "_Test Warehouse Group - _TC")
|
||||
|
||||
child_warehouses = frappe.db.sql(
|
||||
"""select name, is_group, parent_warehouse from `tabWarehouse` wh
|
||||
where wh.lft > %s and wh.rgt < %s""",
|
||||
(p_warehouse.lft, p_warehouse.rgt),
|
||||
as_dict=1,
|
||||
child_warehouses = frappe.get_all(
|
||||
"Warehouse",
|
||||
filters={"lft": [">", p_warehouse.lft], "rgt": ["<", p_warehouse.rgt]},
|
||||
fields=["name", "is_group", "parent_warehouse"],
|
||||
)
|
||||
|
||||
for child_warehouse in child_warehouses:
|
||||
|
||||
@@ -355,9 +355,10 @@ def validate_item_details(ctx: ItemDetailsCtx, item):
|
||||
validate_end_of_life(item.name, item.end_of_life, item.disabled)
|
||||
|
||||
if cint(item.has_variants):
|
||||
msg = f"Item {item.name} is a template, please select one of its variants"
|
||||
|
||||
throw(_(msg), title=_("Template Item Selected"))
|
||||
throw(
|
||||
_("Item {0} is a template, please select one of its variants").format(item.name),
|
||||
title=_("Template Item Selected"),
|
||||
)
|
||||
|
||||
elif ctx.doctype != "Material Request":
|
||||
if ctx.is_subcontracted and item.is_stock_item:
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.batch_item_expiry_status.batch_item_expiry_status import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestBatchItemExpiryStatus(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"from_date": "2026-01-01",
|
||||
"to_date": "2026-12-31",
|
||||
"company": "_Test Company",
|
||||
}
|
||||
)
|
||||
filters.update(extra)
|
||||
return execute(filters)[1]
|
||||
|
||||
def test_batch_listed_with_balance(self):
|
||||
item = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BIE-.#####",
|
||||
"has_expiry_date": 1,
|
||||
"shelf_life_in_days": 30,
|
||||
}
|
||||
).name
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="_Test Warehouse - _TC",
|
||||
qty=10,
|
||||
rate=100,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
|
||||
batch_no = frappe.db.get_value("Batch", {"item": item}, "name")
|
||||
self.assertTrue(batch_no, "Stock entry did not auto-create a batch")
|
||||
|
||||
data = self.run_report(item=item)
|
||||
|
||||
# Columns: [item, item_name, batch, stock_uom, quantity, expires_on, expiry_in_days]
|
||||
row = next((r for r in data if r[2] == batch_no), None)
|
||||
self.assertIsNotNone(row, f"Batch {batch_no} not found in report for item {item}")
|
||||
|
||||
self.assertEqual(row[0], item)
|
||||
self.assertEqual(row[2], batch_no)
|
||||
self.assertEqual(row[4], 10)
|
||||
# expiry = batch manufacturing_date + 30 day shelf life; matches the Batch record
|
||||
batch_expiry = frappe.db.get_value("Batch", batch_no, "expiry_date")
|
||||
self.assertIsNotNone(row[5], "Expiry date should be set for a batch with shelf life")
|
||||
self.assertEqual(frappe.utils.getdate(row[5]), frappe.utils.getdate(batch_expiry))
|
||||
# Expiry (In Days) column = days until expiry
|
||||
expected_days = max((frappe.utils.getdate(batch_expiry) - frappe.utils.datetime.date.today()).days, 0)
|
||||
self.assertEqual(row[6], expected_days)
|
||||
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.batch_wise_balance_history.batch_wise_balance_history import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
WH = "Stores - _TC"
|
||||
# row indexes: 0 item, 1 name, 2 desc, 3 wh, 4 batch, 5 opening, 6 in, 7 out, 8 bal, 9 rate, 10 value, 11 uom
|
||||
|
||||
|
||||
class TestBatchWiseBalanceHistory(ERPNextTestSuite):
|
||||
def make_batch_item(self):
|
||||
return make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BWB-.#####",
|
||||
}
|
||||
).name
|
||||
|
||||
def run_report(self, item, from_date="2026-01-01", to_date="2026-12-31"):
|
||||
filters = frappe._dict(
|
||||
{"company": "_Test Company", "item_code": item, "from_date": from_date, "to_date": to_date}
|
||||
)
|
||||
return execute(filters)[1]
|
||||
|
||||
def test_in_out_balance_and_valuation(self):
|
||||
item = self.make_batch_item()
|
||||
make_stock_entry(item_code=item, to_warehouse=WH, qty=10, rate=100, posting_date="2026-06-01")
|
||||
make_stock_entry(item_code=item, from_warehouse=WH, qty=4, posting_date="2026-06-02")
|
||||
|
||||
(row,) = self.run_report(item)
|
||||
self.assertEqual(row[5], 0) # opening
|
||||
self.assertEqual(row[6], 10) # in
|
||||
self.assertEqual(row[7], 4) # out
|
||||
self.assertEqual(row[8], 6) # balance
|
||||
self.assertEqual(row[9], 100) # valuation rate
|
||||
self.assertEqual(row[10], 600) # balance value
|
||||
|
||||
def test_opening_qty_from_prior_period(self):
|
||||
item = self.make_batch_item()
|
||||
make_stock_entry(item_code=item, to_warehouse=WH, qty=8, rate=50, posting_date="2025-12-01")
|
||||
|
||||
(row,) = self.run_report(item)
|
||||
self.assertEqual(row[5], 8) # opening carried from 2025
|
||||
self.assertEqual(row[6], 0)
|
||||
self.assertEqual(row[8], 8) # balance
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.selling.doctype.sales_order.mapper import make_delivery_note
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.delayed_item_report.delayed_item_report import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestDelayedItemReport(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"based_on": "Delivery Note",
|
||||
"from_date": "2026-06-01",
|
||||
"to_date": "2026-06-30",
|
||||
}
|
||||
)
|
||||
filters.update(extra)
|
||||
return execute(filters)[1]
|
||||
|
||||
def test_late_delivery_shows_delay(self):
|
||||
item = "_Test Item"
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
qty=10,
|
||||
to_warehouse="Stores - _TC",
|
||||
rate=100,
|
||||
posting_date="2026-06-01",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
so = make_sales_order(
|
||||
item_code=item,
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse="Stores - _TC",
|
||||
transaction_date="2026-06-01",
|
||||
company="_Test Company",
|
||||
do_not_submit=True,
|
||||
)
|
||||
so.delivery_date = "2026-06-05"
|
||||
for row in so.items:
|
||||
row.delivery_date = "2026-06-05"
|
||||
so.submit()
|
||||
|
||||
dn = make_delivery_note(so.name)
|
||||
dn.posting_date = "2026-06-10"
|
||||
dn.set_posting_time = 1
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
rows = self.run_report(sales_order=so.name)
|
||||
|
||||
matching = [r for r in rows if r.get("name") == dn.name and r.get("item_code") == item]
|
||||
self.assertTrue(matching, f"No report row found for DN {dn.name} / item {item}")
|
||||
|
||||
row = matching[0]
|
||||
self.assertEqual(row.get("sales_order"), so.name)
|
||||
self.assertEqual(str(row.get("delivery_date")), "2026-06-05")
|
||||
self.assertEqual(str(row.get("posting_date")), "2026-06-10")
|
||||
# delayed_days = date_diff(actual posting_date, expected delivery_date)
|
||||
self.assertEqual(row.get("delayed_days"), 5)
|
||||
@@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.selling.doctype.sales_order.mapper import make_delivery_note
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.delayed_order_report.delayed_order_report import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestDelayedOrderReport(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"from_date": "2026-06-01",
|
||||
"to_date": "2026-06-30",
|
||||
"based_on": "Delivery Note",
|
||||
}
|
||||
)
|
||||
filters.update(extra)
|
||||
return execute(filters)[1]
|
||||
|
||||
def test_late_order_shows_delay(self):
|
||||
item_code = "_Test Item"
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item_code,
|
||||
target="Stores - _TC",
|
||||
qty=10,
|
||||
basic_rate=100,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
|
||||
sales_order = make_sales_order(
|
||||
item_code=item_code,
|
||||
qty=5,
|
||||
warehouse="Stores - _TC",
|
||||
transaction_date="2026-06-01",
|
||||
do_not_submit=True,
|
||||
)
|
||||
sales_order.delivery_date = "2026-06-05"
|
||||
for item in sales_order.items:
|
||||
item.delivery_date = "2026-06-05"
|
||||
sales_order.submit()
|
||||
|
||||
delivery_note = make_delivery_note(sales_order.name)
|
||||
delivery_note.set_posting_time = 1
|
||||
delivery_note.posting_date = "2026-06-10"
|
||||
delivery_note.insert()
|
||||
delivery_note.submit()
|
||||
|
||||
data = self.run_report(sales_order=sales_order.name)
|
||||
|
||||
matching = [row for row in data if row.get("sales_order") == sales_order.name]
|
||||
self.assertEqual(len(matching), 1)
|
||||
|
||||
row = matching[0]
|
||||
self.assertEqual(frappe.utils.getdate(row.get("delivery_date")), frappe.utils.getdate("2026-06-05"))
|
||||
self.assertEqual(frappe.utils.getdate(row.get("posting_date")), frappe.utils.getdate("2026-06-10"))
|
||||
self.assertEqual(row.get("delayed_days"), 5)
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.item_price_stock.item_price_stock import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestItemPriceStock(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
return execute(frappe._dict(extra))[1]
|
||||
|
||||
def test_price_and_stock_shown(self):
|
||||
item = "_Test Item"
|
||||
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"item_code": item,
|
||||
"price_list": "Standard Selling",
|
||||
"price_list_rate": 300,
|
||||
}
|
||||
).insert()
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=7,
|
||||
rate=100,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
|
||||
rows = self.run_report(item_code=item)
|
||||
warehouse_rows = [
|
||||
row
|
||||
for row in rows
|
||||
if row["warehouse"] == "Stores - _TC" and row["selling_price_list"] == "Standard Selling"
|
||||
]
|
||||
|
||||
self.assertEqual(len(warehouse_rows), 1)
|
||||
row = warehouse_rows[0]
|
||||
self.assertEqual(row["item_code"], item)
|
||||
self.assertEqual(row["selling_price_list"], "Standard Selling")
|
||||
self.assertEqual(row["selling_rate"], 300)
|
||||
self.assertEqual(row["stock_available"], 7)
|
||||
@@ -22,7 +22,7 @@ def get_data(item):
|
||||
)
|
||||
|
||||
if not variant_results:
|
||||
frappe.msgprint(_("There aren't any item variants for the selected item"))
|
||||
frappe.msgprint(_("There are no item variants for the selected item"))
|
||||
return []
|
||||
else:
|
||||
variant_list = [variant["name"] for variant in variant_results]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.controllers.item_variant import create_variant
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.item_variant_details.item_variant_details import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestItemVariantDetails(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
return execute(frappe._dict(extra))[1]
|
||||
|
||||
def test_variants_listed_for_template(self):
|
||||
template = "_Test Variant Item"
|
||||
|
||||
variant = create_variant(template, {"Test Size": "Small"})
|
||||
variant.insert(ignore_if_duplicate=True)
|
||||
|
||||
make_stock_entry(
|
||||
item_code=variant.name,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=5,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
rows = self.run_report(item=template)
|
||||
|
||||
variant_rows = [row for row in rows if row.get("variant_name") == variant.name]
|
||||
self.assertEqual(len(variant_rows), 1)
|
||||
|
||||
row = variant_rows[0]
|
||||
self.assertEqual(row.get("test_size"), "Small")
|
||||
self.assertEqual(row.get("current_stock"), 5)
|
||||
self.assertEqual(row.get("open_orders"), 0)
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestSerialAndBatchSummary(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
from erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary import execute
|
||||
|
||||
return execute(frappe._dict(extra))[1]
|
||||
|
||||
@staticmethod
|
||||
def _cancel_and_delete_stock_entry(name):
|
||||
if not frappe.db.exists("Stock Entry", name):
|
||||
return
|
||||
doc = frappe.get_doc("Stock Entry", name)
|
||||
if doc.docstatus == 1:
|
||||
doc.cancel()
|
||||
frappe.delete_doc("Stock Entry", name, force=1)
|
||||
|
||||
def test_serial_receipt_listed(self):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
item = "_Test Serialized Item With Series"
|
||||
se = make_stock_entry(item_code=item, to_warehouse="Stores - _TC", qty=3, basic_rate=100)
|
||||
self.addCleanup(self._cancel_and_delete_stock_entry, se.name)
|
||||
|
||||
data = self.run_report(voucher_no=[se.name], voucher_type="Stock Entry")
|
||||
|
||||
self.assertEqual(len(data), 3)
|
||||
self.assertEqual(len({row.serial_no for row in data}), 3)
|
||||
for row in data:
|
||||
self.assertTrue(row.serial_no)
|
||||
self.assertEqual(row.qty, 1)
|
||||
self.assertEqual(row.incoming_rate, 100)
|
||||
self.assertEqual(row.warehouse, "Stores - _TC")
|
||||
self.assertEqual(row.voucher_no, se.name)
|
||||
|
||||
def test_batch_receipt_listed(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
get_batch_from_bundle,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
item = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "SBB-.#####",
|
||||
}
|
||||
).name
|
||||
se = make_stock_entry(item_code=item, to_warehouse="_Test Warehouse - _TC", qty=10, basic_rate=50)
|
||||
self.addCleanup(self._cancel_and_delete_stock_entry, se.name)
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
data = self.run_report(voucher_no=[se.name], voucher_type="Stock Entry")
|
||||
|
||||
row = next((d for d in data if d.batch_no == batch_no), None)
|
||||
self.assertIsNotNone(row)
|
||||
self.assertEqual(row.qty, 10)
|
||||
self.assertEqual(row.incoming_rate, 50)
|
||||
self.assertEqual(row.warehouse, "_Test Warehouse - _TC")
|
||||
self.assertEqual(row.voucher_no, se.name)
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.serial_no_ledger.serial_no_ledger import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestSerialNoLedger(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
filters = {
|
||||
"company": "_Test Company",
|
||||
"warehouse": "Stores - _TC",
|
||||
"posting_date": "2026-06-30",
|
||||
}
|
||||
filters.update(extra)
|
||||
return execute(frappe._dict(filters))[1]
|
||||
|
||||
def make_serial_item(self) -> str:
|
||||
return "_Test Serialized Item With Series"
|
||||
|
||||
def test_receipt_appears_in_serial_ledger(self):
|
||||
item = self.make_serial_item()
|
||||
stock_entry = make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=2,
|
||||
rate=100,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
|
||||
serial_nos = frappe.get_all("Serial No", {"item_code": item}, pluck="name")
|
||||
self.assertEqual(len(serial_nos), 2)
|
||||
serial_no = serial_nos[0]
|
||||
|
||||
data = self.run_report(item_code=item, serial_no=serial_no)
|
||||
|
||||
self.assertEqual(len(data), 1)
|
||||
row = data[0]
|
||||
self.assertEqual(row["serial_no"], serial_no)
|
||||
self.assertEqual(row["voucher_type"], "Stock Entry")
|
||||
self.assertEqual(row["voucher_no"], stock_entry.name)
|
||||
self.assertEqual(row["warehouse"], "Stores - _TC")
|
||||
self.assertEqual(row["qty"], 1)
|
||||
self.assertEqual(row["valuation_rate"], 100)
|
||||
|
||||
def test_filter_by_item_lists_all_received_serials(self):
|
||||
item = self.make_serial_item()
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=2,
|
||||
rate=150,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
|
||||
serial_nos = frappe.get_all("Serial No", {"item_code": item}, pluck="name")
|
||||
|
||||
data = self.run_report(item_code=item)
|
||||
|
||||
ledger_serials = sorted(row["serial_no"] for row in data)
|
||||
self.assertEqual(ledger_serials, sorted(serial_nos))
|
||||
for row in data:
|
||||
self.assertEqual(row["qty"], 1)
|
||||
self.assertEqual(row["valuation_rate"], 150)
|
||||
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
from erpnext.stock.report.stock_ledger_invariant_check.stock_ledger_invariant_check import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
WAREHOUSE = "Stores - _TC"
|
||||
COMPANY = "_Test Company"
|
||||
ITEM = "_Test Item"
|
||||
|
||||
|
||||
class TestStockLedgerInvariantCheck(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
filters = frappe._dict({"company": COMPANY, "warehouse": WAREHOUSE})
|
||||
filters.update(extra)
|
||||
return execute(filters)[1]
|
||||
|
||||
def make_movements(self) -> str:
|
||||
frappe.db.set_value("Item", ITEM, "valuation_method", "FIFO")
|
||||
make_stock_entry(item_code=ITEM, to_warehouse=WAREHOUSE, qty=10, rate=100, posting_date="2026-06-01")
|
||||
make_stock_entry(item_code=ITEM, to_warehouse=WAREHOUSE, qty=5, rate=120, posting_date="2026-06-02")
|
||||
make_stock_entry(item_code=ITEM, from_warehouse=WAREHOUSE, qty=4, rate=0, posting_date="2026-06-03")
|
||||
return ITEM
|
||||
|
||||
def test_diagnostic_rows_have_no_discrepancy(self):
|
||||
item = self.make_movements()
|
||||
|
||||
data = self.run_report(item_code=item)
|
||||
|
||||
self.assertEqual(len(data), 3)
|
||||
for row in data:
|
||||
self.assertLess(abs(row.difference_in_qty), 0.01)
|
||||
self.assertLess(abs(row.fifo_qty_diff), 0.01)
|
||||
self.assertLess(abs(row.diff_value_diff), 0.01)
|
||||
|
||||
def test_running_balance_matches(self):
|
||||
item = self.make_movements()
|
||||
|
||||
data = self.run_report(item_code=item)
|
||||
|
||||
self.assertEqual(data[-1].qty_after_transaction, 11)
|
||||
@@ -1,83 +0,0 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestStockLedgerVariance(ERPNextTestSuite):
|
||||
def run_report(self, **extra):
|
||||
from erpnext.stock.report.stock_ledger_variance.stock_ledger_variance import execute
|
||||
|
||||
filters = {"company": "_Test Company"}
|
||||
filters.update(extra)
|
||||
|
||||
return execute(frappe._dict(filters))[1]
|
||||
|
||||
def test_healthy_stock_has_no_variance(self):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
item = "_Test Item"
|
||||
frappe.db.set_value("Item", item, "valuation_method", "Moving Average")
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=10,
|
||||
rate=100,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
from_warehouse="Stores - _TC",
|
||||
qty=4,
|
||||
posting_date="2026-06-02",
|
||||
)
|
||||
|
||||
# A clean receipt followed by a clean issue keeps the ledger consistent,
|
||||
# so the corruption detector must not flag any entry for this item.
|
||||
data = self.run_report(item_code=item)
|
||||
self.assertFalse([row for row in data if row.get("item_code") == item])
|
||||
|
||||
qty_data = self.run_report(item_code=item, difference_in="Qty")
|
||||
self.assertFalse([row for row in qty_data if row.get("item_code") == item])
|
||||
|
||||
def test_multiple_clean_movements_no_variance(self):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||
|
||||
item = "_Test Item"
|
||||
frappe.db.set_value("Item", item, "valuation_method", "Moving Average")
|
||||
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=10,
|
||||
rate=100,
|
||||
posting_date="2026-06-01",
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=5,
|
||||
rate=120,
|
||||
posting_date="2026-06-02",
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
to_warehouse="Stores - _TC",
|
||||
qty=8,
|
||||
rate=90,
|
||||
posting_date="2026-06-03",
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code=item,
|
||||
from_warehouse="Stores - _TC",
|
||||
qty=6,
|
||||
posting_date="2026-06-04",
|
||||
)
|
||||
|
||||
# Several receipts at different rates plus an issue still produce a
|
||||
# self-consistent ledger, so no variance rows are expected.
|
||||
data = self.run_report(item_code=item)
|
||||
self.assertFalse([row for row in data if row.get("item_code") == item])
|
||||
@@ -1231,7 +1231,9 @@ class SerialBatchCreation:
|
||||
required_qty = flt(abs(self.actual_qty), precision)
|
||||
|
||||
if required_qty - total_qty > 0:
|
||||
msg = f"For the item {bold(doc.item_code)}, the Available qty {bold(total_qty)} is less than the Required Qty {bold(required_qty)} in the warehouse {bold(doc.warehouse)}. Please add sufficient qty in the warehouse."
|
||||
msg = _(
|
||||
"For the item {0}, the Available qty {1} is less than the Required Qty {2} in the warehouse {3}. Please add sufficient qty in the warehouse."
|
||||
).format(bold(doc.item_code), bold(total_qty), bold(required_qty), bold(doc.warehouse))
|
||||
frappe.throw(msg, title=_("Insufficient Stock"))
|
||||
|
||||
def set_auto_serial_batch_entries_for_outward(self):
|
||||
|
||||
@@ -101,7 +101,7 @@ class StockInternalTransferService:
|
||||
|
||||
if recevied_qty > flt(transferred_qty, precision):
|
||||
frappe.throw(
|
||||
_("For Item {0} cannot be received more than {1} qty against the {2} {3}").format(
|
||||
_("Item {0} cannot be received in more than {1} qty against the {2} {3}").format(
|
||||
bold(key[1]),
|
||||
bold(flt(transferred_qty, precision)),
|
||||
bold(parent_doctype),
|
||||
|
||||
@@ -496,7 +496,7 @@ class SerialBatchBundleService:
|
||||
if throw_error:
|
||||
frappe.throw(
|
||||
_(
|
||||
"At row {0}: Serial and Batch Bundle {1} has already created. Please remove the values from the serial no or batch no fields."
|
||||
"At row {0}: Serial and Batch Bundle {1} has already been created. Please remove the values from the serial no or batch no fields."
|
||||
).format(row.idx, row.serial_and_batch_bundle)
|
||||
)
|
||||
|
||||
|
||||
@@ -366,8 +366,7 @@ def create_file(doc, compressed_content):
|
||||
def validate_item_warehouse(args):
|
||||
for field in ["item_code", "warehouse", "posting_date", "posting_time"]:
|
||||
if args.get(field) in [None, ""]:
|
||||
validation_msg = f"The field {frappe.unscrub(field)} is required for the reposting"
|
||||
frappe.throw(_(validation_msg))
|
||||
frappe.throw(_("The field {0} is required for reposting").format(frappe.unscrub(field)))
|
||||
|
||||
|
||||
def get_items_to_be_repost(voucher_type=None, voucher_no=None, doc=None, reposting_data=None):
|
||||
@@ -831,7 +830,7 @@ class update_entries_after:
|
||||
if previous_sle and previous_sle.get("qty_after_transaction") < 0 and sle.get("actual_qty") > 0:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'>documentation<a>."
|
||||
"The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'>documentation</a>."
|
||||
).format(
|
||||
bold(sle.item_code),
|
||||
bold(sle.warehouse),
|
||||
|
||||
@@ -224,7 +224,7 @@ class SubcontractingInwardOrder(SubcontractingController):
|
||||
if not any([rm.is_customer_provided_item for rm in raw_materials]):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Atleast one raw material for Finished Good Item {0} should be customer provided."
|
||||
"At least one raw material for Finished Good Item {0} should be customer provided."
|
||||
).format(frappe.bold(item.item_code))
|
||||
)
|
||||
|
||||
|
||||
@@ -139,12 +139,14 @@ class SubcontractingOrder(SubcontractingController):
|
||||
frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting."))
|
||||
|
||||
if po.docstatus != 1:
|
||||
msg = f"Please submit Purchase Order {po.name} before proceeding."
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(_("Please submit Purchase Order {0} before proceeding.").format(po.name))
|
||||
|
||||
if po.per_received == 100:
|
||||
msg = f"Cannot create more Subcontracting Orders against the Purchase Order {po.name}."
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(
|
||||
_("Cannot create more Subcontracting Orders against the Purchase Order {0}.").format(
|
||||
po.name
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.service_items = self.items = self.supplied_items = None
|
||||
frappe.throw(_("Please select a Subcontracting Purchase Order."))
|
||||
@@ -172,8 +174,11 @@ class SubcontractingOrder(SubcontractingController):
|
||||
if self.supplier_warehouse:
|
||||
for item in self.supplied_items:
|
||||
if self.supplier_warehouse == item.reserve_warehouse:
|
||||
msg = f"Reserve Warehouse must be different from Supplier Warehouse for Supplied Item {item.main_item_code}."
|
||||
frappe.throw(_(msg))
|
||||
frappe.throw(
|
||||
_(
|
||||
"Reserve Warehouse must be different from Supplier Warehouse for Supplied Item {0}."
|
||||
).format(item.main_item_code)
|
||||
)
|
||||
|
||||
def set_missing_values(self):
|
||||
self.calculate_additional_costs()
|
||||
|
||||
@@ -143,7 +143,7 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
self.validate_inspection()
|
||||
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
frappe.throw(_("Posting Date cannot be future date"))
|
||||
frappe.throw(_("Posting Date cannot be a future date"))
|
||||
|
||||
super().validate()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user