mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-29 05:48:36 +00:00
Compare commits
2 Commits
develop
...
chore/prof
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03ecd2fd3a | ||
|
|
55c6d16d69 |
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.1
|
||||
uses: pre-commit/action@v3.0.0
|
||||
|
||||
semgrep:
|
||||
name: semgrep
|
||||
|
||||
@@ -48,6 +48,7 @@ repos:
|
||||
cypress/.*|
|
||||
.*node_modules.*|
|
||||
.*boilerplate.*|
|
||||
erpnext/public/js/controllers/.*|
|
||||
erpnext/templates/pages/order.js|
|
||||
erpnext/templates/includes/.*
|
||||
)$
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate, nowdate
|
||||
|
||||
|
||||
class OverlapError(frappe.ValidationError):
|
||||
@@ -37,20 +36,8 @@ class AccountingPeriod(Document):
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_overlap()
|
||||
|
||||
def validate_dates(self):
|
||||
if getdate(self.start_date) > getdate(self.end_date):
|
||||
frappe.throw(_("Start Date cannot be after End Date"))
|
||||
|
||||
if getdate(self.end_date) > getdate(nowdate()):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Accounting Period cannot be created for a future date. End Date {0} is after today."
|
||||
).format(frappe.bold(frappe.format(self.end_date, "Date")))
|
||||
)
|
||||
|
||||
def before_insert(self):
|
||||
self.bootstrap_doctypes_for_closing()
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.utils import nowdate
|
||||
from frappe.utils import add_months, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||
ClosedAccountingPeriod,
|
||||
@@ -93,7 +93,7 @@ def create_accounting_period(**args):
|
||||
|
||||
accounting_period = frappe.new_doc("Accounting Period")
|
||||
accounting_period.start_date = args.start_date or nowdate()
|
||||
accounting_period.end_date = args.end_date or nowdate()
|
||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||
accounting_period.company = args.company or "_Test Company"
|
||||
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
|
||||
accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1})
|
||||
|
||||
@@ -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 get entries"))
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
def before_submit(self):
|
||||
self.remove_accounts_without_gain_loss()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# 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
|
||||
@@ -64,9 +63,13 @@ class TestLoyaltyPointEntry(ERPNextTestSuite):
|
||||
self.assertEqual(doc.loyalty_points, -7)
|
||||
|
||||
# Check balance
|
||||
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]
|
||||
balance = frappe.db.sql(
|
||||
"""
|
||||
SELECT SUM(loyalty_points)
|
||||
FROM `tabLoyalty Point Entry`
|
||||
WHERE customer = %s
|
||||
""",
|
||||
(self.customer_name,),
|
||||
)[0][0]
|
||||
|
||||
self.assertEqual(balance, 3) # 10 added, 7 redeemed
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
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 (
|
||||
@@ -263,12 +262,14 @@ class TestLoyaltyProgram(ERPNextTestSuite):
|
||||
|
||||
def get_points_earned(self):
|
||||
def get_returned_amount():
|
||||
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()
|
||||
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,
|
||||
)
|
||||
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.delete("POS Opening Entry")
|
||||
frappe.db.delete("POS Closing Entry")
|
||||
frappe.db.delete("POS Invoice")
|
||||
frappe.db.sql("delete from `tabPOS Opening Entry`;")
|
||||
frappe.db.sql("delete from `tabPOS Closing Entry`;")
|
||||
frappe.db.sql("delete from `tabPOS Invoice`;")
|
||||
|
||||
def setUp(self):
|
||||
self.clear_pos_data()
|
||||
|
||||
@@ -25,11 +25,15 @@ class TestPOSProfile(ERPNextTestSuite):
|
||||
items = get_items_list(doc, doc.company)
|
||||
customers = get_customers_list(doc)
|
||||
|
||||
products_count = frappe.db.count("Item", {"item_group": "_Test Item Group"})
|
||||
customers_count = frappe.db.count("Customer", {"customer_group": "_Test Customer Group"})
|
||||
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'"""
|
||||
)
|
||||
|
||||
self.assertEqual(len(items), products_count)
|
||||
self.assertEqual(len(customers), customers_count)
|
||||
self.assertEqual(len(items), products_count[0][0])
|
||||
self.assertEqual(len(customers), customers_count[0][0])
|
||||
|
||||
def test_disabled_pos_profile_creation(self):
|
||||
make_pos_profile(name="_Test POS Profile 001", disabled=1)
|
||||
@@ -79,6 +83,7 @@ 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
|
||||
@@ -86,16 +91,14 @@ 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"))]
|
||||
)
|
||||
|
||||
filters = {"disabled": 0}
|
||||
if customer_groups:
|
||||
filters["customer_group"] = ["in", customer_groups]
|
||||
cond = "customer_group in ({})".format(", ".join(["%s"] * len(customer_groups)))
|
||||
|
||||
return (
|
||||
frappe.get_all(
|
||||
"Customer",
|
||||
filters=filters,
|
||||
fields=["name", "customer_name", "customer_group", "territory"],
|
||||
frappe.db.sql(
|
||||
f""" select name, customer_name, customer_group, territory from tabCustomer where disabled = 0
|
||||
and {cond}""",
|
||||
tuple(customer_groups),
|
||||
as_dict=1,
|
||||
)
|
||||
or {}
|
||||
)
|
||||
@@ -132,8 +135,8 @@ def get_items_list(pos_profile, company):
|
||||
|
||||
|
||||
def make_pos_profile(**args):
|
||||
frappe.db.delete("POS Payment Method")
|
||||
frappe.db.delete("POS Profile")
|
||||
frappe.db.sql("delete from `tabPOS Payment Method`")
|
||||
frappe.db.sql("delete from `tabPOS Profile`")
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
|
||||
@@ -91,9 +91,7 @@ class TestPricingRule(ERPNextTestSuite):
|
||||
details = get_item_details(args)
|
||||
self.assertEqual(details.get("discount_percentage"), 5)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Pricing Rule", {"campaign": "_Test Campaign"}, "priority", None, update_modified=False
|
||||
)
|
||||
frappe.db.sql("update `tabPricing Rule` set priority=NULL where campaign='_Test Campaign'")
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import MultiplePricingRuleConflict
|
||||
|
||||
self.assertRaises(MultiplePricingRuleConflict, get_item_details, args)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form
|
||||
|
||||
import erpnext
|
||||
@@ -131,7 +130,6 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
|
||||
get_purchase_document_details,
|
||||
)
|
||||
from erpnext.stock.utils import get_valuation_method
|
||||
|
||||
doc = self.doc
|
||||
tax_service = TaxService(doc)
|
||||
@@ -331,33 +329,20 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
self.make_provisional_gl_entry(gl_entries, item)
|
||||
|
||||
if not doc.is_internal_transfer():
|
||||
handled = False
|
||||
if (
|
||||
item.item_code
|
||||
and item.item_code in stock_items
|
||||
and item.get("purchase_receipt")
|
||||
and not doc.is_return
|
||||
and get_valuation_method(item.item_code, doc.company) == "Standard Cost"
|
||||
):
|
||||
handled = self.make_standard_cost_srbnb_split(
|
||||
gl_entries, item, expense_account, account_currency, base_amount
|
||||
)
|
||||
|
||||
if not handled:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": doc.supplier,
|
||||
"debit": base_amount,
|
||||
"debit_in_transaction_currency": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or doc.project,
|
||||
},
|
||||
account_currency,
|
||||
item=item,
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": doc.supplier,
|
||||
"debit": base_amount,
|
||||
"debit_in_transaction_currency": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or doc.project,
|
||||
},
|
||||
account_currency,
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# check if the exchange rate has changed
|
||||
if (
|
||||
@@ -530,107 +515,6 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
},
|
||||
)
|
||||
|
||||
def make_standard_cost_srbnb_split(
|
||||
self, gl_entries, item, expense_account, account_currency, base_amount
|
||||
):
|
||||
"""For a Standard Cost item billed against a Purchase Receipt, clear SRBNB at the standard
|
||||
value the receipt actually booked and post the (Net Amount - standard) difference to the
|
||||
Purchase Price Variance account. Returns False (caller falls back) if the receipt value
|
||||
can't be resolved."""
|
||||
from erpnext.stock.doctype.item_standard_cost.item_standard_cost import (
|
||||
get_purchase_price_variance_account,
|
||||
)
|
||||
|
||||
doc = self.doc
|
||||
precision = item.precision("base_net_amount")
|
||||
standard_value = flt(self.get_pr_stock_value(item), precision)
|
||||
if not standard_value:
|
||||
return False
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": doc.supplier,
|
||||
"debit": standard_value,
|
||||
"debit_in_transaction_currency": flt(standard_value / doc.conversion_rate, precision),
|
||||
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or doc.project,
|
||||
},
|
||||
account_currency,
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
variance = flt(base_amount - standard_value, precision)
|
||||
if variance:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": get_purchase_price_variance_account(item.item_code, doc.company),
|
||||
"against": doc.supplier,
|
||||
"debit": variance,
|
||||
"debit_in_transaction_currency": flt(variance / doc.conversion_rate, precision),
|
||||
"remarks": doc.get("remarks") or _("Purchase Price Variance"),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or doc.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def get_pr_stock_value(self, item):
|
||||
"""Stock value (at standard) the linked Purchase Receipt booked for the quantity this invoice
|
||||
row is billing.
|
||||
|
||||
Accepted and rejected stock for the same receipt row share `voucher_detail_no`, so the
|
||||
warehouse filter is required: without it the accepted warehouse's SRBNB would be cleared at
|
||||
accepted + rejected value and post the wrong Purchase Price Variance amount. The accepted
|
||||
warehouse is read from the receipt row itself (not the invoice row, which may be unset on a
|
||||
non-stock invoice).
|
||||
|
||||
The receipt's full accepted value is pro-rated to the invoiced quantity, so a partial bill
|
||||
clears SRBNB (and posts PPV) for only the units it covers, not the whole receipt row."""
|
||||
pr_detail = frappe.db.get_value(
|
||||
"Purchase Receipt Item", item.pr_detail, ["warehouse", "stock_qty"], as_dict=True
|
||||
)
|
||||
if not pr_detail or not pr_detail.warehouse:
|
||||
return 0.0
|
||||
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
result = (
|
||||
frappe.qb.from_(sle)
|
||||
.select(Sum(sle.stock_value_difference))
|
||||
.where(
|
||||
(sle.voucher_type == "Purchase Receipt")
|
||||
& (sle.voucher_no == item.purchase_receipt)
|
||||
& (sle.voucher_detail_no == item.pr_detail)
|
||||
& (sle.warehouse == pr_detail.warehouse)
|
||||
& (sle.is_cancelled == 0)
|
||||
)
|
||||
).run()
|
||||
accepted_value = flt(result[0][0]) if result and result[0][0] else 0.0
|
||||
if not accepted_value or not flt(pr_detail.stock_qty):
|
||||
return accepted_value
|
||||
|
||||
# Pro-rate to the quantity being billed by this invoice row (handles partial billing).
|
||||
return accepted_value * flt(item.stock_qty) / flt(pr_detail.stock_qty)
|
||||
|
||||
def get_stock_variance_account(self, item):
|
||||
"""For Standard Cost items the purchase-price-vs-standard difference is a Purchase Price
|
||||
Variance; for all other items it keeps the existing behaviour (default expense account)."""
|
||||
from erpnext.stock.doctype.item_standard_cost.item_standard_cost import (
|
||||
get_purchase_price_variance_account,
|
||||
)
|
||||
from erpnext.stock.utils import get_valuation_method
|
||||
|
||||
if item.item_code and get_valuation_method(item.item_code, self.doc.company) == "Standard Cost":
|
||||
return get_purchase_price_variance_account(item.item_code, self.doc.company)
|
||||
return self.doc.get_company_default("default_expense_account")
|
||||
|
||||
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
|
||||
doc = self.doc
|
||||
net_amt_precision = item.precision("base_net_amount")
|
||||
@@ -652,7 +536,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
)
|
||||
|
||||
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
|
||||
cost_of_goods_sold_account = self.get_stock_variance_account(item)
|
||||
cost_of_goods_sold_account = doc.get_company_default("default_expense_account")
|
||||
stock_adjustment_amt = stock_amount - warehouse_debit_amount
|
||||
|
||||
gl_entries.append(
|
||||
@@ -677,7 +561,7 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
and warehouse_debit_amount
|
||||
!= flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
|
||||
):
|
||||
cost_of_goods_sold_account = self.get_stock_variance_account(item)
|
||||
cost_of_goods_sold_account = doc.get_company_default("default_expense_account")
|
||||
stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision)
|
||||
stock_adjustment_amt = warehouse_debit_amount - stock_amount
|
||||
|
||||
|
||||
@@ -135,9 +135,38 @@ class TestAccountsPayable(ERPNextTestSuite, AccountsTestMixin):
|
||||
def test_payment_terms_template_filters(self):
|
||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||
|
||||
template = frappe.get_doc("Payment Terms Template", "_Test Payment Term Template")
|
||||
first_term = frappe.get_doc("Payment Term", template.terms[0].payment_term)
|
||||
expected_payment_term = first_term.description or first_term.name
|
||||
payment_term1 = frappe.get_doc(
|
||||
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 15 Days"}
|
||||
).insert()
|
||||
payment_term2 = frappe.get_doc(
|
||||
{"doctype": "Payment Term", "payment_term_name": "_Test 50% on 30 Days"}
|
||||
).insert()
|
||||
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Payment Terms Template",
|
||||
"template_name": "_Test 50-50",
|
||||
"terms": [
|
||||
{
|
||||
"doctype": "Payment Terms Template Detail",
|
||||
"due_date_based_on": "Day(s) after invoice date",
|
||||
"payment_term": payment_term1.name,
|
||||
"description": "_Test 50-50",
|
||||
"invoice_portion": 50,
|
||||
"credit_days": 15,
|
||||
},
|
||||
{
|
||||
"doctype": "Payment Terms Template Detail",
|
||||
"due_date_based_on": "Day(s) after invoice date",
|
||||
"payment_term": payment_term2.name,
|
||||
"description": "_Test 50-50",
|
||||
"invoice_portion": 50,
|
||||
"credit_days": 30,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
template.insert()
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
@@ -164,10 +193,12 @@ class TestAccountsPayable(ERPNextTestSuite, AccountsTestMixin):
|
||||
row = report[1][0]
|
||||
|
||||
self.assertEqual(len(report[1]), 2)
|
||||
self.assertEqual([pi.name, expected_payment_term], [row.voucher_no, row.payment_term])
|
||||
self.assertEqual([pi.name, payment_term1.payment_term_name], [row.voucher_no, row.payment_term])
|
||||
|
||||
def test_project_filter(self):
|
||||
project = frappe.get_doc("Project", {"project_name": "_Test Project"})
|
||||
project = frappe.get_doc(
|
||||
{"doctype": "Project", "project_name": "_Test AP Project", "company": self.company}
|
||||
).insert()
|
||||
|
||||
pi = self.create_purchase_invoice(do_not_submit=True)
|
||||
pi.project = project.name
|
||||
@@ -196,7 +227,9 @@ class TestAccountsPayable(ERPNextTestSuite, AccountsTestMixin):
|
||||
"range": "30, 60, 90, 120",
|
||||
}
|
||||
|
||||
project = frappe.get_doc("Project", {"project_name": "_Test Project"})
|
||||
project = frappe.get_doc(
|
||||
{"doctype": "Project", "project_name": "_Test AP Project Output", "company": self.company}
|
||||
).insert()
|
||||
|
||||
pi = self.create_purchase_invoice(do_not_submit=True)
|
||||
pi.project = project.name
|
||||
|
||||
@@ -1422,10 +1422,10 @@ class TestAccountsReceivable(ERPNextTestSuite, AccountsTestMixin):
|
||||
# Party is a dynamic link on Payment Ledger Entry, so user permissions on Customer
|
||||
# must be applied explicitly. The report should only show permitted customers.
|
||||
original_customer = self.customer
|
||||
second_customer = "_Test Customer 1"
|
||||
second_customer = "_Test AR Perm Customer"
|
||||
|
||||
# create_customer overrides self.customer, so build the restricted invoice first
|
||||
self.customer = second_customer
|
||||
self.create_customer(customer_name=second_customer)
|
||||
self.create_sales_invoice(no_payment_schedule=True)
|
||||
|
||||
self.customer = original_customer
|
||||
|
||||
@@ -59,9 +59,16 @@ class TestCashFlow(ERPNextTestSuite):
|
||||
|
||||
def test_cash_purchase_of_asset_is_investing_outflow(self):
|
||||
"""Buying a fixed asset for cash is an investing outflow that reduces net change in cash."""
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
|
||||
asset_account = "Office Equipment - _TC"
|
||||
create_account(
|
||||
account_name="_Test Cash Flow Asset",
|
||||
company=self.company,
|
||||
parent_account="Fixed Assets - _TC",
|
||||
account_type="Fixed Asset",
|
||||
)
|
||||
asset_account = "_Test Cash Flow Asset - _TC"
|
||||
|
||||
before = self.net_change_in_cash()
|
||||
# debit the fixed asset, credit cash -> cash goes out
|
||||
|
||||
@@ -64,12 +64,16 @@ class TestGeneralLedger(ERPNextTestSuite):
|
||||
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||
|
||||
def test_opening_total_and_closing_balances(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
|
||||
self.clear_old_entries()
|
||||
# reuse bootstrap non-party accounts; clear_old_entries() leaves them clean of GL
|
||||
account = "_Test Account Cost for Goods Sold - _TC"
|
||||
offset = "_Test Bank - _TC"
|
||||
account = create_account(
|
||||
account_name="_Test GL Account", company=self.company, parent_account="Current Assets - _TC"
|
||||
)
|
||||
offset = create_account(
|
||||
account_name="_Test GL Offset", company=self.company, parent_account="Current Assets - _TC"
|
||||
)
|
||||
make_journal_entry(account, offset, 1000, posting_date=add_days(today(), -60), submit=True) # opening
|
||||
make_journal_entry(account, offset, 200, posting_date=today(), submit=True) # in period
|
||||
|
||||
@@ -83,13 +87,19 @@ class TestGeneralLedger(ERPNextTestSuite):
|
||||
self.assertEqual(labelled["'Closing (Opening + Total)'"]["debit"], 1200)
|
||||
|
||||
def test_categorize_by_account_subtotals(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
|
||||
self.clear_old_entries()
|
||||
# reuse bootstrap non-party accounts; clear_old_entries() leaves them clean of GL
|
||||
account_a = "_Test Account Cost for Goods Sold - _TC"
|
||||
account_b = "_Test Bank - _TC"
|
||||
offset = "_Test Cash - _TC"
|
||||
account_a = create_account(
|
||||
account_name="_Test GL Account A", company=self.company, parent_account="Current Assets - _TC"
|
||||
)
|
||||
account_b = create_account(
|
||||
account_name="_Test GL Account B", company=self.company, parent_account="Current Assets - _TC"
|
||||
)
|
||||
offset = create_account(
|
||||
account_name="_Test GL Offset", company=self.company, parent_account="Current Assets - _TC"
|
||||
)
|
||||
make_journal_entry(account_a, offset, 300, posting_date=today(), submit=True)
|
||||
make_journal_entry(account_b, offset, 400, posting_date=today(), submit=True)
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.profitability_analysis.profitability_analysis import execute
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
INCOME = "Sales - _TC"
|
||||
EXPENSE = "_Test Account Cost for Goods Sold - _TC"
|
||||
BANK = "_Test Bank - _TC"
|
||||
|
||||
|
||||
class TestProfitabilityAnalysis(ERPNextTestSuite):
|
||||
def run_report(self, fiscal_year="_Test Fiscal Year 2026", **extra):
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"based_on": "Cost Center",
|
||||
"fiscal_year": fiscal_year,
|
||||
"from_date": "2026-01-01",
|
||||
"to_date": "2026-12-31",
|
||||
**extra,
|
||||
}
|
||||
)
|
||||
return execute(filters)[1]
|
||||
|
||||
def make_cc(self, name, **args):
|
||||
create_cost_center(cost_center_name=name, **args)
|
||||
return name + " - _TC"
|
||||
|
||||
def row(self, data, account):
|
||||
return next(r for r in data if r.get("account") == account)
|
||||
|
||||
def book_income(self, cost_center, amount, posting_date="2026-06-01"):
|
||||
create_sales_invoice(
|
||||
cost_center=cost_center, income_account=INCOME, rate=amount, qty=1, posting_date=posting_date
|
||||
)
|
||||
|
||||
def book_expense(self, cost_center, amount, posting_date="2026-06-01"):
|
||||
make_journal_entry(
|
||||
EXPENSE, BANK, amount, cost_center=cost_center, posting_date=posting_date, submit=True
|
||||
)
|
||||
|
||||
def test_income_expense_and_gross_profit(self):
|
||||
# bootstrap leaf cost center; clean of committed GL so exact assertions hold
|
||||
cc = "_Test Cost Center - _TC"
|
||||
self.book_income(cc, 10000)
|
||||
self.book_expense(cc, 4000)
|
||||
|
||||
row = self.row(self.run_report(), cc)
|
||||
self.assertEqual(row["income"], 10000)
|
||||
self.assertEqual(row["expense"], 4000)
|
||||
self.assertEqual(row["gross_profit_loss"], 6000)
|
||||
|
||||
def test_parent_cost_center_accumulates_children(self):
|
||||
parent = self.make_cc("_Test PA Parent", is_group=1)
|
||||
child_1 = self.make_cc("_Test PA Child 1", parent_cost_center=parent)
|
||||
child_2 = self.make_cc("_Test PA Child 2", parent_cost_center=parent)
|
||||
|
||||
self.book_income(child_1, 10000)
|
||||
self.book_expense(child_2, 3000)
|
||||
|
||||
data = self.run_report()
|
||||
self.assertEqual(self.row(data, child_1)["income"], 10000)
|
||||
self.assertEqual(self.row(data, child_2)["expense"], 3000)
|
||||
|
||||
parent_row = self.row(data, parent)
|
||||
self.assertEqual(parent_row["income"], 10000)
|
||||
self.assertEqual(parent_row["expense"], 3000)
|
||||
self.assertEqual(parent_row["gross_profit_loss"], 7000)
|
||||
|
||||
def test_date_range_excludes_out_of_period_entries(self):
|
||||
cc = "_Test Cost Center 2 - _TC"
|
||||
self.book_income(cc, 10000, posting_date="2025-06-01")
|
||||
|
||||
# the 2025 income must not appear in a 2026 report (zero-value rows are dropped)
|
||||
accounts_2026 = {r.get("account") for r in self.run_report()}
|
||||
self.assertNotIn(cc, accounts_2026)
|
||||
|
||||
row_2025 = self.row(
|
||||
self.run_report(
|
||||
fiscal_year="_Test Fiscal Year 2025", from_date="2025-01-01", to_date="2025-12-31"
|
||||
),
|
||||
cc,
|
||||
)
|
||||
self.assertEqual(row_2025["income"], 10000)
|
||||
|
||||
def test_total_row_sums_income_and_expense(self):
|
||||
cc = "_Test Cost Center - _TC"
|
||||
self.book_income(cc, 10000)
|
||||
self.book_expense(cc, 4000)
|
||||
|
||||
data = self.run_report()
|
||||
# the report appends a blank separator row and a totals row at the end
|
||||
total_row = data[-1]
|
||||
self.assertEqual(total_row["account"], "'Total'")
|
||||
# total is built from direct (non-accumulated) values, so it stays internally consistent
|
||||
self.assertEqual(total_row["gross_profit_loss"], total_row["income"] - total_row["expense"])
|
||||
# and it includes this test's bookings
|
||||
self.assertGreaterEqual(total_row["income"], 10000)
|
||||
self.assertGreaterEqual(total_row["expense"], 4000)
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Sum
|
||||
@@ -128,32 +127,17 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
row.update({frappe.scrub(tax_acc): tax_amount})
|
||||
|
||||
# total tax, grand total, rounded total & outstanding amount
|
||||
|
||||
outstanding_precision = (
|
||||
get_field_precision(
|
||||
frappe.get_meta("Purchase Invoice").get_field("outstanding_amount"),
|
||||
currency=company_currency,
|
||||
)
|
||||
or 2
|
||||
)
|
||||
row.update(
|
||||
{
|
||||
"total_tax": total_tax,
|
||||
"grand_total": inv.base_grand_total,
|
||||
"rounded_total": inv.base_rounded_total,
|
||||
"outstanding_amount": inv.outstanding_amount,
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
row.update(
|
||||
{
|
||||
"debit": inv.base_grand_total,
|
||||
"credit": 0.0,
|
||||
"outstanding_amount": flt(
|
||||
(inv.outstanding_amount * (inv.conversion_rate or 1)), outstanding_precision
|
||||
),
|
||||
}
|
||||
)
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
@@ -426,7 +410,6 @@ def get_invoices(filters, additional_query_columns):
|
||||
pi.base_rounded_total,
|
||||
pi.outstanding_amount,
|
||||
pi.mode_of_payment,
|
||||
pi.conversion_rate,
|
||||
)
|
||||
.where(pi.docstatus == 1)
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_months, flt, today
|
||||
from frappe.utils import add_months, today
|
||||
|
||||
from erpnext.accounts.report.purchase_register.purchase_register import execute
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
@@ -90,35 +90,6 @@ class TestPurchaseRegister(ERPNextTestSuite):
|
||||
self.assertEqual(first_row.total_tax, 100)
|
||||
self.assertEqual(first_row.grand_total, 1100)
|
||||
|
||||
def test_purchase_currency_conversion(self):
|
||||
usd_creditors = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": "USD Creditors",
|
||||
"parent_account": "Accounts Payable - _TC",
|
||||
"company": "_Test Company",
|
||||
"account_type": "Payable",
|
||||
"root_type": "Liability",
|
||||
"report_type": "Balance Sheet",
|
||||
"account_currency": "USD",
|
||||
}
|
||||
).insert()
|
||||
foreign_invoice = make_purchase_invoice()
|
||||
foreign_invoice.db_set("currency", "USD")
|
||||
foreign_invoice.db_set("conversion_rate", 80)
|
||||
foreign_invoice.db_set("credit_to", usd_creditors.name)
|
||||
foreign_invoice.db_set("outstanding_amount", 100.236)
|
||||
local_invoice = make_purchase_invoice()
|
||||
local_invoice.db_set("currency", "INR")
|
||||
local_invoice.db_set("conversion_rate", 1)
|
||||
local_invoice.db_set("outstanding_amount", 200.456)
|
||||
columns, data, *_ = execute(frappe._dict({"company": foreign_invoice.company}))
|
||||
outstanding_precision = 2
|
||||
|
||||
data_by_name = {x.get("voucher_no"): x.get("outstanding_amount") for x in data}
|
||||
self.assertEqual(data_by_name.get(foreign_invoice.name), flt((100.236 * 80), outstanding_precision))
|
||||
self.assertEqual(data_by_name.get(local_invoice.name), flt(200.456, outstanding_precision))
|
||||
|
||||
def test_purchase_register_ledger_view(self):
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6",
|
||||
|
||||
@@ -141,31 +141,17 @@ def _execute(filters, additional_table_columns=None):
|
||||
|
||||
# total tax, grand total, outstanding amount & rounded total
|
||||
|
||||
outstanding_precision = (
|
||||
get_field_precision(
|
||||
frappe.get_meta("Sales Invoice").get_field("outstanding_amount"),
|
||||
currency=company_currency,
|
||||
)
|
||||
or 2
|
||||
)
|
||||
row.update(
|
||||
{
|
||||
"tax_total": total_tax,
|
||||
"grand_total": inv.base_grand_total,
|
||||
"rounded_total": inv.base_rounded_total,
|
||||
"outstanding_amount": inv.outstanding_amount,
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Sales Invoice":
|
||||
row.update(
|
||||
{
|
||||
"debit": inv.base_grand_total,
|
||||
"credit": 0.0,
|
||||
"outstanding_amount": flt(
|
||||
(inv.outstanding_amount * (inv.conversion_rate or 1)), outstanding_precision
|
||||
),
|
||||
}
|
||||
)
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
@@ -462,7 +448,6 @@ def get_invoices(filters, additional_query_columns):
|
||||
si.is_internal_customer,
|
||||
si.represents_company,
|
||||
si.company,
|
||||
si.conversion_rate,
|
||||
)
|
||||
.where(si.docstatus == 1)
|
||||
)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import frappe
|
||||
from frappe.utils import add_days, flt, getdate, today
|
||||
from frappe.utils import getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.sales_register.sales_register import execute
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.selling.doctype.customer.test_customer import make_customer
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
@@ -250,25 +249,3 @@ class TestItemWiseSalesRegister(ERPNextTestSuite, AccountsTestMixin):
|
||||
}
|
||||
result_output = {k: v for k, v in filtered_output[0].items() if k in expected_result}
|
||||
self.assertDictEqual(result_output, expected_result)
|
||||
|
||||
def test_outstanding_currency_conversion(self):
|
||||
foreign_invoice = create_sales_invoice(
|
||||
customer="_Test Customer",
|
||||
posting_date=add_days(today(), -1),
|
||||
qty=1,
|
||||
rate=100,
|
||||
)
|
||||
foreign_invoice.db_set("currency", "USD")
|
||||
foreign_invoice.db_set("conversion_rate", 80)
|
||||
foreign_invoice.db_set("outstanding_amount", 100.236)
|
||||
make_customer("_Test Customer2")
|
||||
local_invoice = create_sales_invoice(
|
||||
customer="_Test Customer2", currency="INR", conversion_rate=1, qty=1, rate=200
|
||||
)
|
||||
local_invoice.db_set("outstanding_amount", 200.456)
|
||||
columns, data, *_ = execute(frappe._dict({"company": foreign_invoice.company}))
|
||||
outstanding_precision = 2
|
||||
|
||||
data_by_name = {x.get("voucher_no"): x.get("outstanding_amount") for x in data}
|
||||
self.assertEqual(data_by_name.get(foreign_invoice.name), flt((100.236 * 80), outstanding_precision))
|
||||
self.assertEqual(data_by_name.get(local_invoice.name), flt(200.456, outstanding_precision))
|
||||
|
||||
@@ -597,21 +597,11 @@ 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.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",
|
||||
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,
|
||||
)
|
||||
if not accounts:
|
||||
return None
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
# 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,13 +238,22 @@ class TestAssetRepair(ERPNextTestSuite):
|
||||
submit=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)
|
||||
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,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
@@ -278,13 +287,22 @@ class TestAssetRepair(ERPNextTestSuite):
|
||||
submit=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)
|
||||
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,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
@@ -94,12 +94,11 @@ class TestAssetValueAdjustment(ERPNextTestSuite):
|
||||
("_Test Fixed Asset - _TC", 0.0, 4625.29),
|
||||
)
|
||||
|
||||
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,
|
||||
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,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
@@ -185,12 +184,11 @@ class TestAssetValueAdjustment(ERPNextTestSuite):
|
||||
("_Test Fixed Asset - _TC", 0.0, 5175.29),
|
||||
)
|
||||
|
||||
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,
|
||||
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,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
@@ -3,26 +3,11 @@
|
||||
|
||||
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."""
|
||||
@@ -33,170 +18,16 @@ class TestFixedAssetRegister(AssetSetup):
|
||||
location="Test Location",
|
||||
submit=1,
|
||||
)
|
||||
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
|
||||
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",
|
||||
}
|
||||
)
|
||||
|
||||
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")})
|
||||
data = execute(filters)[1]
|
||||
asset_ids = {row.get("asset_id") for row in data}
|
||||
self.assertIn(asset.name, asset_ids)
|
||||
|
||||
@@ -547,7 +547,6 @@
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"label": "Alias",
|
||||
"no_copy": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
@@ -562,7 +561,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2026-06-27 16:12:33.190257",
|
||||
"modified": "2026-06-22 12:23:09.241125",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -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 unlinked").format("\n".join(linked_po)))
|
||||
frappe.msgprint(_("Purchase Orders {0} are un-linked").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(
|
||||
_(
|
||||
"{0} has submitted assets linked to it. You need to cancel the assets to create purchase return."
|
||||
"{} 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' cannot be checked because items are not delivered via {0}").format(
|
||||
_("'Update Stock' can not 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 at least {4}.<br><br>Alternatively,
|
||||
Selling {3} should be atleast {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 {0} in {1} to allow same item in multiple rows").format(
|
||||
duplicate_items_msg += _("Please enable {} in {} 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 {0} will be treated as material transfer.").format(_(self.doctype))
|
||||
msg += " " + _("This {} 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 a positive number").format(d.item_code))
|
||||
frappe.throw(_("For an item {0}, quantity must be 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 a negative number").format(d.item_code))
|
||||
frappe.throw(_("For an item {0}, quantity must be 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`")),
|
||||
|
||||
@@ -820,8 +820,6 @@ def create_item_wise_repost_entries(
|
||||
):
|
||||
"""Using a voucher create repost item valuation records for all item-warehouse pairs."""
|
||||
|
||||
from erpnext.stock.utils import get_valuation_method
|
||||
|
||||
stock_ledger_entries = get_items_to_be_repost(voucher_type, voucher_no)
|
||||
|
||||
distinct_item_warehouses = set()
|
||||
@@ -833,11 +831,6 @@ def create_item_wise_repost_entries(
|
||||
continue
|
||||
distinct_item_warehouses.add(item_wh)
|
||||
|
||||
# Standard Cost items don't need a full repost: a backdated entry only shifts future balances
|
||||
# (qty and value at the standard rate), which is done in place by update_qty_in_future_sle.
|
||||
if get_valuation_method(sle.item_code) == "Standard Cost":
|
||||
continue
|
||||
|
||||
repost_entry = frappe.new_doc("Repost Item Valuation")
|
||||
repost_entry.based_on = "Item and Warehouse"
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ class SubcontractingController(StockController):
|
||||
)
|
||||
if bom_item != item.item_code:
|
||||
frappe.throw(
|
||||
_("Row {0}: Please select a valid BOM for Item {1}.").format(
|
||||
_("Row {0}: Please select an valid BOM for Item {1}.").format(
|
||||
item.idx, item.item_name
|
||||
)
|
||||
)
|
||||
@@ -1053,10 +1053,8 @@ class SubcontractingController(StockController):
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
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"))
|
||||
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"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
if row.get("serial_and_batch_bundle") and self.__transferred_items.get(key).get("serial_no"):
|
||||
@@ -1068,10 +1066,8 @@ class SubcontractingController(StockController):
|
||||
link = get_link_to_form(
|
||||
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
||||
)
|
||||
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"))
|
||||
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"))
|
||||
|
||||
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 the item code is not permitted, add another row instead."
|
||||
"Row #{0}: Item {1} mismatch. Changing of 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 the item code is not permitted.").format(
|
||||
_("Row #{0}: Item {1} mismatch. Changing of 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} are not a part of the linked Subcontracting Inward Order. Please select valid Batch No(s)."
|
||||
"Row #{0}: Batch No(s) {1} is 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 for {1} updated as per validity and rate applied"
|
||||
).format(item.idx, frappe.bold(item.item_code))
|
||||
_("Row {0}: Item Tax template 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 the same"))
|
||||
frappe.throw(_("'Based On' and 'Group By' can not be 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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields, delete_custom_fields
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class CRMSettings(Document):
|
||||
if self.enable_frappe_crm_data_synchronization and not self.allowed_users:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please add at least one user on Allowed Users to allow Data Synchronization from Frappe CRM site."
|
||||
"Please add atleast 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 {0} while linking with Plaid.").format(
|
||||
_("There was an error updating Bank Account {} while linking with Plaid.").format(
|
||||
existing_bank_account
|
||||
),
|
||||
title=_("Plaid Link Failed"),
|
||||
|
||||
3274
erpnext/locale/ar.po
3274
erpnext/locale/ar.po
File diff suppressed because it is too large
Load Diff
62914
erpnext/locale/bg.po
62914
erpnext/locale/bg.po
File diff suppressed because it is too large
Load Diff
3316
erpnext/locale/bs.po
3316
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
2996
erpnext/locale/cs.po
2996
erpnext/locale/cs.po
File diff suppressed because it is too large
Load Diff
2992
erpnext/locale/da.po
2992
erpnext/locale/da.po
File diff suppressed because it is too large
Load Diff
3307
erpnext/locale/de.po
3307
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
3294
erpnext/locale/eo.po
3294
erpnext/locale/eo.po
File diff suppressed because it is too large
Load Diff
3238
erpnext/locale/es.po
3238
erpnext/locale/es.po
File diff suppressed because it is too large
Load Diff
3242
erpnext/locale/fa.po
3242
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
3114
erpnext/locale/fr.po
3114
erpnext/locale/fr.po
File diff suppressed because it is too large
Load Diff
3008
erpnext/locale/hi.po
3008
erpnext/locale/hi.po
File diff suppressed because it is too large
Load Diff
3385
erpnext/locale/hr.po
3385
erpnext/locale/hr.po
File diff suppressed because it is too large
Load Diff
3002
erpnext/locale/hu.po
3002
erpnext/locale/hu.po
File diff suppressed because it is too large
Load Diff
3108
erpnext/locale/id.po
3108
erpnext/locale/id.po
File diff suppressed because it is too large
Load Diff
2994
erpnext/locale/it.po
2994
erpnext/locale/it.po
File diff suppressed because it is too large
Load Diff
3052
erpnext/locale/ko.po
3052
erpnext/locale/ko.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2984
erpnext/locale/my.po
2984
erpnext/locale/my.po
File diff suppressed because it is too large
Load Diff
3008
erpnext/locale/nb.po
3008
erpnext/locale/nb.po
File diff suppressed because it is too large
Load Diff
3293
erpnext/locale/nl.po
3293
erpnext/locale/nl.po
File diff suppressed because it is too large
Load Diff
3018
erpnext/locale/pl.po
3018
erpnext/locale/pl.po
File diff suppressed because it is too large
Load Diff
2996
erpnext/locale/pt.po
2996
erpnext/locale/pt.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3293
erpnext/locale/ru.po
3293
erpnext/locale/ru.po
File diff suppressed because it is too large
Load Diff
3012
erpnext/locale/sl.po
3012
erpnext/locale/sl.po
File diff suppressed because it is too large
Load Diff
3293
erpnext/locale/sr.po
3293
erpnext/locale/sr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3320
erpnext/locale/sv.po
3320
erpnext/locale/sv.po
File diff suppressed because it is too large
Load Diff
3293
erpnext/locale/th.po
3293
erpnext/locale/th.po
File diff suppressed because it is too large
Load Diff
3246
erpnext/locale/tr.po
3246
erpnext/locale/tr.po
File diff suppressed because it is too large
Load Diff
62954
erpnext/locale/uz.po
62954
erpnext/locale/uz.po
File diff suppressed because it is too large
Load Diff
3293
erpnext/locale/vi.po
3293
erpnext/locale/vi.po
File diff suppressed because it is too large
Load Diff
3270
erpnext/locale/zh.po
3270
erpnext/locale/zh.po
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.get_all(
|
||||
"BOM Item",
|
||||
filters={"item_code": "_Test Item 2", "docstatus": 1, "parenttype": "BOM"},
|
||||
fields=["base_rate"],
|
||||
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,
|
||||
):
|
||||
self.assertEqual(d.base_rate, rm_base_rate + 10)
|
||||
|
||||
@@ -881,8 +881,12 @@ 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.get_all(
|
||||
"Bin", filters={"item_code": item_code, "actual_qty": [">", 0]}, pluck="warehouse"
|
||||
warehouse_list = frappe.db.sql_list(
|
||||
"""
|
||||
select warehouse from `tabBin`
|
||||
where item_code=%s and actual_qty > 0
|
||||
""",
|
||||
item_code,
|
||||
)
|
||||
|
||||
if not warehouse_list:
|
||||
|
||||
@@ -5249,8 +5249,11 @@ def update_job_card(job_card, jc_qty=None, days=None):
|
||||
|
||||
def get_secondary_item_details(bom_no):
|
||||
secondary_items = {}
|
||||
for item in frappe.get_all(
|
||||
"BOM Secondary Item", filters={"parent": bom_no}, fields=["item_code", "stock_qty"]
|
||||
for item in frappe.db.sql(
|
||||
"""select item_code, stock_qty from `tabBOM Secondary Item`
|
||||
where parent = %s""",
|
||||
bom_no,
|
||||
as_dict=1,
|
||||
):
|
||||
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.delete("Task", {"project": project_name})
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, 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.delete("Task", {"project": project_name})
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, 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.delete("Task", {"project": project_name})
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, 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.delete("Task", {"project": project_name})
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, 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 dependent task {1} is not completed / cancelled."
|
||||
"Cannot complete task {0} as its dependant task {1} are 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 cannot delete this Task."))
|
||||
throw(_("Child Task exists for this Task. You can not 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 Time"))
|
||||
frappe.throw(_("To Time cannot be before from date"))
|
||||
|
||||
def validate_parent_project(self, parent_project: str):
|
||||
"""Validate that project is same as Timesheet's parent project."""
|
||||
|
||||
@@ -23,12 +23,15 @@ erpnext.accounts.taxes = {
|
||||
onload: function (frm) {
|
||||
if (frm.get_field("taxes")) {
|
||||
frm.set_query("account_head", "taxes", function (doc) {
|
||||
let account_type = ["Tax", "Chargeable"];
|
||||
|
||||
if (frm.cscript.tax_table == "Sales Taxes and Charges") {
|
||||
account_type.push("Expense Account");
|
||||
var account_type = ["Tax", "Chargeable", "Expense Account"];
|
||||
} else {
|
||||
account_type.push("Income Account", "Expenses Included In Valuation");
|
||||
var account_type = [
|
||||
"Tax",
|
||||
"Chargeable",
|
||||
"Income Account",
|
||||
"Expenses Included In Valuation",
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -952,15 +952,14 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
if (["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
|
||||
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
|
||||
let total_amount_to_pay;
|
||||
|
||||
if (this.frm.doc.party_account_currency == this.frm.doc.currency) {
|
||||
total_amount_to_pay = flt(
|
||||
var total_amount_to_pay = flt(
|
||||
grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount,
|
||||
precision("grand_total")
|
||||
);
|
||||
} else {
|
||||
total_amount_to_pay = flt(
|
||||
var total_amount_to_pay = flt(
|
||||
flt(base_grand_total, precision("base_grand_total")) -
|
||||
this.frm.doc.total_advance -
|
||||
this.frm.doc.base_write_off_amount,
|
||||
@@ -1005,15 +1004,14 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
async set_total_amount_to_default_mop() {
|
||||
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
|
||||
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
|
||||
let total_amount_to_pay;
|
||||
|
||||
if (this.frm.doc.party_account_currency == this.frm.doc.currency) {
|
||||
total_amount_to_pay = flt(
|
||||
var total_amount_to_pay = flt(
|
||||
grand_total - this.frm.doc.total_advance - this.frm.doc.write_off_amount,
|
||||
precision("grand_total")
|
||||
);
|
||||
} else {
|
||||
total_amount_to_pay = flt(
|
||||
var total_amount_to_pay = flt(
|
||||
flt(base_grand_total, precision("base_grand_total")) -
|
||||
this.frm.doc.total_advance -
|
||||
this.frm.doc.base_write_off_amount,
|
||||
|
||||
@@ -1291,8 +1291,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
var set_party_account = function (set_pricing) {
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
||||
let party_type = me.frm.doc.doctype == "Sales Invoice" ? "Customer" : "Supplier";
|
||||
let party_account_field = me.frm.doc.doctype == "Sales Invoice" ? "debit_to" : "credit_to";
|
||||
if (me.frm.doc.doctype == "Sales Invoice") {
|
||||
var party_type = "Customer";
|
||||
var party_account_field = "debit_to";
|
||||
} else {
|
||||
var party_type = "Supplier";
|
||||
var party_account_field = "credit_to";
|
||||
}
|
||||
|
||||
var party = me.frm.doc[frappe.model.scrub(party_type)];
|
||||
if (
|
||||
@@ -2066,7 +2071,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||
let item_grid = this.frm.fields_dict["operations"].grid;
|
||||
var item_grid = this.frm.fields_dict["operations"].grid;
|
||||
$.each(["base_operating_cost", "base_hour_rate"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
|
||||
@@ -2074,7 +2079,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if (this.frm.doc.secondary_items && this.frm.doc.secondary_items.length > 0) {
|
||||
let item_grid = this.frm.fields_dict["secondary_items"].grid;
|
||||
var item_grid = this.frm.fields_dict["secondary_items"].grid;
|
||||
$.each(["base_rate", "base_amount"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
item_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
|
||||
@@ -2465,7 +2470,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
row_to_modify[key] = pr_row[key];
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.frm.doc, "is_pos") && this.frm.doc.is_pos) {
|
||||
if (this.frm.doc.hasOwnProperty("is_pos") && this.frm.doc.is_pos) {
|
||||
let r = await frappe.db.get_value("POS Profile", this.frm.doc.pos_profile, "cost_center");
|
||||
if (r.message.cost_center) {
|
||||
row_to_modify["cost_center"] = r.message.cost_center;
|
||||
@@ -2730,7 +2735,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
$.each(me.frm.doc.items || [], function (i, item) {
|
||||
if (
|
||||
item.name &&
|
||||
Object.prototype.hasOwnProperty.call(r.message, item.name) &&
|
||||
r.message.hasOwnProperty(item.name) &&
|
||||
r.message[item.name].item_tax_template
|
||||
) {
|
||||
item.item_tax_template = r.message[item.name].item_tax_template;
|
||||
|
||||
@@ -681,7 +681,6 @@
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"label": "Alias",
|
||||
"no_copy": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
@@ -696,7 +695,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2026-06-27 16:12:10.457900",
|
||||
"modified": "2026-06-22 12:23:19.196991",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -158,7 +158,7 @@ class Customer(TransactionBase):
|
||||
new_customer_name = f"{self.customer_name} - {cstr(count)}"
|
||||
|
||||
msgprint(
|
||||
_("Changed customer name to '{0}' as '{1}' already exists.").format(
|
||||
_("Changed customer name to '{}' as '{}' 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 the same name. Please change the Customer name or rename the Customer Group"
|
||||
"A Customer Group exists with 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 at least {0}"""
|
||||
"""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {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 {0}. Please select manually.").format(
|
||||
_("Multiple Loyalty Programs found for Customer {}. 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(
|
||||
msg + " <br><br> <ul>{}</ul>".format("\n".join(reqd_fields)),
|
||||
"{} <br><br> <ul>{}</ul>".format(msg, "\n".join(reqd_fields)),
|
||||
title=_("Missing Values Required"),
|
||||
)
|
||||
|
||||
|
||||
@@ -32,6 +32,4 @@ 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 {0}. You will have to cancel these documents in order to delete this Product Bundle"
|
||||
).format(", ".join(invoice_links)),
|
||||
"This Product Bundle is linked with {}. You will have to cancel these documents in order to delete this Product Bundle".format(
|
||||
", ".join(invoice_links)
|
||||
),
|
||||
title=_("Not Allowed"),
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ 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
|
||||
|
||||
@@ -908,12 +907,10 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
item_doc.save()
|
||||
else:
|
||||
# update valid from
|
||||
frappe.db.set_value(
|
||||
"Item Tax",
|
||||
{"parent": item, "item_tax_template": tax_template},
|
||||
"valid_from",
|
||||
today(),
|
||||
update_modified=False,
|
||||
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},
|
||||
)
|
||||
|
||||
so = make_sales_order(item_code=item, qty=1, do_not_save=1)
|
||||
@@ -963,12 +960,10 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
self.assertEqual(so.taxes[1].total, 480)
|
||||
|
||||
# teardown
|
||||
frappe.db.set_value(
|
||||
"Item Tax",
|
||||
{"parent": item, "item_tax_template": tax_template},
|
||||
"valid_from",
|
||||
None,
|
||||
update_modified=False,
|
||||
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},
|
||||
)
|
||||
so.cancel()
|
||||
so.delete()
|
||||
@@ -1564,12 +1559,10 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
|
||||
# Check if Work Orders were raised
|
||||
for item in so_item_name:
|
||||
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()
|
||||
wo_qty = frappe.db.sql(
|
||||
"select sum(qty) from `tabWork Order` where sales_order=%s and sales_order_item=%s",
|
||||
(so.name, item),
|
||||
)
|
||||
self.assertEqual(wo_qty[0][0], so_item_name.get(item))
|
||||
|
||||
def test_advance_payment_entry_unlink_against_sales_order(self):
|
||||
@@ -1749,7 +1742,9 @@ 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.get_all("Material Request", fields=["name"], order_by="creation desc", limit=1)[0]
|
||||
mr = frappe.db.sql(
|
||||
"""select name from `tabMaterial Request` ORDER BY creation DESC LIMIT 1""", as_dict=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 {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)))
|
||||
frappe.throw(_("DocType can be one of them {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 {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)),
|
||||
_("DocType can be one of them {0}").format(comma_or(SALES_TRANSACTION_DOCTYPES)),
|
||||
):
|
||||
run(self.report_name, self.filters)
|
||||
|
||||
|
||||
@@ -129,7 +129,6 @@
|
||||
"valuation_method",
|
||||
"column_break_32",
|
||||
"stock_adjustment_account",
|
||||
"default_purchase_price_variance_account",
|
||||
"stock_received_but_not_billed",
|
||||
"stock_delivered_but_not_billed",
|
||||
"disable_sdbnb_in_sr",
|
||||
@@ -492,15 +491,6 @@
|
||||
"no_copy": 1,
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"description": "Used for items valued at Standard Cost: the difference between the purchase price and the standard rate is booked here.",
|
||||
"fieldname": "default_purchase_price_variance_account",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Default Purchase Price Variance Account",
|
||||
"no_copy": 1,
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_32",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -1014,7 +1004,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2026-06-26 10:05:00.000000",
|
||||
"modified": "2026-05-14 16:50:34.132345",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# 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,
|
||||
@@ -21,8 +20,7 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
|
||||
def test_basic_tree(self, records=None):
|
||||
min_lft = 1
|
||||
ig = frappe.qb.DocType("Item Group")
|
||||
max_rgt = frappe.qb.from_(ig).select(Max(ig.rgt)).run()[0][0]
|
||||
max_rgt = frappe.db.sql("select max(rgt) from `tabItem Group`")[0][0]
|
||||
|
||||
if not records:
|
||||
records = self.globalTestRecords["Item Group"][2:]
|
||||
@@ -133,7 +131,12 @@ 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.get_all("Item Group", filters={"name": ["in", ancestors]}, fields=["name", "rgt"])
|
||||
ancestors = frappe.db.sql(
|
||||
"""select name, rgt from `tabItem Group`
|
||||
where name in ({})""".format(", ".join(["%s"] * len(ancestors))),
|
||||
tuple(ancestors),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
frappe.delete_doc("Item Group", "_Test Item Group B - 3")
|
||||
records_to_test = self.globalTestRecords["Item Group"][2:]
|
||||
@@ -165,8 +168,9 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
self.test_basic_tree()
|
||||
|
||||
# move its children back
|
||||
for name in frappe.get_all(
|
||||
"Item Group", filters={"parent_item_group": "_Test Item Group C"}, pluck="name"
|
||||
for name in frappe.db.sql_list(
|
||||
"""select name from `tabItem Group`
|
||||
where parent_item_group='_Test Item Group C'"""
|
||||
):
|
||||
doc = frappe.get_doc("Item Group", name)
|
||||
doc.parent_item_group = "_Test Item Group B"
|
||||
@@ -214,7 +218,11 @@ class TestItemGroup(ERPNextTestSuite):
|
||||
def get_no_of_children(item_groups, no_of_children):
|
||||
children = []
|
||||
for ig in item_groups:
|
||||
children += frappe.get_all("Item Group", filters={"parent_item_group": ig}, pluck="name")
|
||||
children += frappe.db.sql_list(
|
||||
"""select name from `tabItem Group`
|
||||
where ifnull(parent_item_group, '')=%s""",
|
||||
ig or "",
|
||||
)
|
||||
|
||||
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))
|
||||
|
||||
|
||||
|
||||
@@ -277,27 +277,19 @@ def update_qty(bin_name, args):
|
||||
- flt(bin_details.reserved_qty_for_production_plan)
|
||||
)
|
||||
|
||||
bin_values = {
|
||||
"actual_qty": actual_qty,
|
||||
"ordered_qty": ordered_qty,
|
||||
"reserved_qty": reserved_qty,
|
||||
"indented_qty": indented_qty,
|
||||
"planned_qty": planned_qty,
|
||||
"projected_qty": projected_qty,
|
||||
}
|
||||
|
||||
# Standard Cost items are not reposted on backdated entries, so the Bin's stock value is not
|
||||
# refreshed by a repost. Keep it in step with the balance at the standard rate.
|
||||
from erpnext.stock.utils import get_valuation_method
|
||||
|
||||
if get_valuation_method(args.get("item_code")) == "Standard Cost":
|
||||
from erpnext.stock.doctype.item_standard_cost.item_standard_cost import get_item_standard_rate
|
||||
|
||||
bin_values["stock_value"] = flt(actual_qty) * flt(
|
||||
get_item_standard_rate(args.get("item_code"), args.get("company"))
|
||||
)
|
||||
|
||||
frappe.db.set_value("Bin", bin_name, bin_values, update_modified=True)
|
||||
frappe.db.set_value(
|
||||
"Bin",
|
||||
bin_name,
|
||||
{
|
||||
"actual_qty": actual_qty,
|
||||
"ordered_qty": ordered_qty,
|
||||
"reserved_qty": reserved_qty,
|
||||
"indented_qty": indented_qty,
|
||||
"planned_qty": planned_qty,
|
||||
"projected_qty": projected_qty,
|
||||
},
|
||||
update_modified=True,
|
||||
)
|
||||
|
||||
|
||||
def get_actual_qty(item_code, warehouse):
|
||||
|
||||
@@ -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 the driver address is missing."))
|
||||
frappe.throw(_("Cannot Calculate Arrival Time as Driver Address is Missing."))
|
||||
|
||||
home_address = get_address_display(frappe.get_doc("Address", self.driver_address).as_dict())
|
||||
|
||||
|
||||
@@ -384,7 +384,7 @@
|
||||
"fieldname": "valuation_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Valuation Method",
|
||||
"options": "\nFIFO\nMoving Average\nLIFO\nStandard Cost"
|
||||
"options": "\nFIFO\nMoving Average\nLIFO"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_stock_item",
|
||||
@@ -1090,7 +1090,7 @@
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2026-06-26 10:05:00.000000",
|
||||
"modified": "2026-05-27 10:18:46.862670",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -143,7 +143,7 @@ class Item(Document):
|
||||
taxes: DF.Table[ItemTax]
|
||||
total_projected_qty: DF.Float
|
||||
uoms: DF.Table[UOMConversionDetail]
|
||||
valuation_method: DF.Literal["", "FIFO", "Moving Average", "LIFO", "Standard Cost"]
|
||||
valuation_method: DF.Literal["", "FIFO", "Moving Average", "LIFO"]
|
||||
valuation_rate: DF.Currency
|
||||
variant_based_on: DF.Literal["Item Attribute", "Manufacturer"]
|
||||
variant_of: DF.Link | None
|
||||
@@ -239,7 +239,6 @@ class Item(Document):
|
||||
self.validate_item_defaults()
|
||||
self.validate_auto_reorder_enabled_in_stock_settings()
|
||||
self.cant_change()
|
||||
self.validate_standard_cost_change()
|
||||
self.validate_item_tax_net_rate_range()
|
||||
|
||||
if not self.is_new():
|
||||
@@ -464,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' cannot be 'Yes' for non-stock item"))
|
||||
frappe.throw(_("'Has Serial No' can not be 'Yes' for non-stock item"))
|
||||
|
||||
if self.has_serial_no == 0 and self.serial_no_series:
|
||||
self.serial_no_series = None
|
||||
@@ -1061,30 +1060,6 @@ class Item(Document):
|
||||
for d in self.attributes:
|
||||
d.variant_of = self.variant_of
|
||||
|
||||
def validate_standard_cost_change(self):
|
||||
"""Once stock exists, an item's valuation method cannot be switched to or from Standard
|
||||
Cost — either change would leave existing stock valued on a basis the ledger never
|
||||
recorded."""
|
||||
if not self.is_standard_cost_valuation_change():
|
||||
return
|
||||
|
||||
if self.stock_ledger_created():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Valuation Method cannot be changed to or from 'Standard Cost' for {0} because stock transactions already exist for it."
|
||||
).format(frappe.bold(self.name))
|
||||
)
|
||||
|
||||
def is_standard_cost_valuation_change(self):
|
||||
"""True if this save switches the valuation method into or out of Standard Cost."""
|
||||
if self.is_new() or not self.has_value_changed("valuation_method"):
|
||||
return False
|
||||
|
||||
previous = self.get_doc_before_save()
|
||||
was_standard = previous and previous.valuation_method == "Standard Cost"
|
||||
is_standard = self.valuation_method == "Standard Cost"
|
||||
return bool(was_standard or is_standard)
|
||||
|
||||
def cant_change(self):
|
||||
if self.is_new():
|
||||
return
|
||||
@@ -1533,9 +1508,7 @@ 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 #{0}: {1} {2} does not belong to Company {3}. Please select valid {4}."
|
||||
).format(
|
||||
_("Row #{}: {} {} doesn't belong to Company {}. Please select valid {}.").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(_("Cannot set alternative item for the item {0}").format(self.item_code))
|
||||
frappe.throw(_("Not allow to 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 {0}")
|
||||
alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {}")
|
||||
|
||||
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(_("Record already exists for the item {0}").format(self.item_code))
|
||||
frappe.throw(_("Already record exists for the item {0}").format(self.item_code))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"default_provisional_account",
|
||||
"purchase_expense_account",
|
||||
"purchase_expense_contra_account",
|
||||
"purchase_price_variance_account",
|
||||
"selling_defaults",
|
||||
"column_break_sales",
|
||||
"vf_selling_cost_center",
|
||||
@@ -190,14 +189,6 @@
|
||||
"options": "Account",
|
||||
"show_description_on_click": 1
|
||||
},
|
||||
{
|
||||
"description": "For Standard Cost items: the purchase price vs standard rate difference is booked here. Falls back to the Company's Default Purchase Price Variance Account.",
|
||||
"fieldname": "purchase_price_variance_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Price Variance Account",
|
||||
"options": "Account",
|
||||
"show_description_on_click": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_purchase",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -365,7 +356,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-06-26 10:05:00.000000",
|
||||
"modified": "2026-06-03 17:25:35.982082",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Default",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user