mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-26 22:18:18 +00:00
Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
955f98cfe9 | ||
|
|
bd16af46b6 | ||
|
|
dd0013e844 | ||
|
|
573a402ee5 | ||
|
|
0089f7a1d5 | ||
|
|
4d197b1b7c | ||
|
|
535543d406 | ||
|
|
49ac166f9e | ||
|
|
9ccafd30be | ||
|
|
5515346e24 | ||
|
|
2df491bf22 | ||
|
|
3e75d4ea60 | ||
|
|
748467f27f | ||
|
|
a8bf6ada89 | ||
|
|
75c9512e38 | ||
|
|
4e5703c223 | ||
|
|
46d492df65 | ||
|
|
f7a8e64818 | ||
|
|
c48a8868c2 | ||
|
|
10cb6f8306 | ||
|
|
0c9ce9306f | ||
|
|
1786c04298 | ||
|
|
236f1bbfdb | ||
|
|
d352322688 | ||
|
|
e2a34ab3fe | ||
|
|
ccecc2bd70 | ||
|
|
8f31b0dccd | ||
|
|
427d5cf502 | ||
|
|
a8e7d82746 | ||
|
|
b2d14bd947 | ||
|
|
a695a11130 | ||
|
|
afe8e3a023 | ||
|
|
c8bb55f35b | ||
|
|
c9efcb7822 | ||
|
|
8753ed9992 | ||
|
|
e72f398b7c | ||
|
|
7af4acab29 | ||
|
|
60163f3df6 | ||
|
|
b7400b9b35 | ||
|
|
cecdcffce8 | ||
|
|
1ecc04c7a2 | ||
|
|
31ab75787e | ||
|
|
e1d9598075 | ||
|
|
36030483e5 | ||
|
|
9e40f87339 | ||
|
|
a5e92785b7 | ||
|
|
99a81db4d9 | ||
|
|
253e7a9398 | ||
|
|
3b2ae7ba4f | ||
|
|
ab8fe146ab | ||
|
|
6ca5f355bf | ||
|
|
c73a05356b | ||
|
|
5fac73c920 | ||
|
|
60a9c08f0d | ||
|
|
966eaae3a0 | ||
|
|
c8afc04957 | ||
|
|
41c59ac165 | ||
|
|
9ac52f0b03 | ||
|
|
8dcd89b455 | ||
|
|
89b7e11381 | ||
|
|
f508f4149f | ||
|
|
efce145dad | ||
|
|
bb98acb0c0 | ||
|
|
abc463bfb8 | ||
|
|
41bcf96601 | ||
|
|
0685c0b9ff | ||
|
|
82eb200c73 | ||
|
|
4af54a7ac7 | ||
|
|
42cfbae782 | ||
|
|
6660e643bb | ||
|
|
9a92fc117a | ||
|
|
0a5fe99d4a | ||
|
|
d1d1efdcd1 | ||
|
|
24910cf2d2 | ||
|
|
ac9ec7e34a | ||
|
|
270a294e81 | ||
|
|
4a7fdd1f91 | ||
|
|
09279f08e3 | ||
|
|
82b97abbad | ||
|
|
541bfb664b | ||
|
|
a4d2c34dde | ||
|
|
a857f0b5c2 | ||
|
|
875114d33c | ||
|
|
4f8b948b91 | ||
|
|
bdc3b0666d | ||
|
|
b7b7ec1fba | ||
|
|
b9b551f248 | ||
|
|
d43c04df88 | ||
|
|
c791d2853a | ||
|
|
95ef5b513e | ||
|
|
46d8028fcc | ||
|
|
c7da21812a | ||
|
|
f9a38696b6 | ||
|
|
8f4a6d08d4 | ||
|
|
edf2f53082 | ||
|
|
cc2be4be39 | ||
|
|
c6e01aa1eb | ||
|
|
0fde990c9e | ||
|
|
4f646a2f2d | ||
|
|
0116d4c789 | ||
|
|
77ec67869c | ||
|
|
6fdb5d486a | ||
|
|
839f5f6a29 | ||
|
|
005ff0d6a8 | ||
|
|
c7da8e2471 | ||
|
|
1a46a8fec0 | ||
|
|
6b42a7e454 | ||
|
|
a7eb24488e | ||
|
|
0b30967556 | ||
|
|
7149630eff | ||
|
|
da617fdc11 | ||
|
|
1a62d324fd | ||
|
|
db841dde4c | ||
|
|
e3d7a94307 | ||
|
|
29d565b9de | ||
|
|
733191eae2 | ||
|
|
572c3cdc74 | ||
|
|
b55bf7d660 | ||
|
|
76a32686a5 | ||
|
|
73354356e1 | ||
|
|
c6000c0522 | ||
|
|
b0fc21b8ed | ||
|
|
8f02184c80 | ||
|
|
ddb441359e | ||
|
|
38e5d295c7 | ||
|
|
f853ef9fec | ||
|
|
1131313d20 | ||
|
|
1a52fa2a03 | ||
|
|
42b4fe2f68 | ||
|
|
aaa29a3ce8 | ||
|
|
10538d58ad | ||
|
|
716645ea1d | ||
|
|
3aeee125a4 | ||
|
|
12ccc3881b | ||
|
|
c39ee76946 | ||
|
|
94a02b05fa | ||
|
|
9e185b7ea9 | ||
|
|
b510f78510 | ||
|
|
a02aa9eb05 | ||
|
|
699a683b8e | ||
|
|
0685a9241e | ||
|
|
93a597410e | ||
|
|
8e8a7b2686 | ||
|
|
83f2fadbcf | ||
|
|
fff9750f48 | ||
|
|
fdf0d6611d | ||
|
|
e89aaca870 | ||
|
|
fb17a00fb6 | ||
|
|
96f7eb1194 | ||
|
|
1938a56164 | ||
|
|
f97e0f8c9c | ||
|
|
f410bfcea6 | ||
|
|
6793c1455c | ||
|
|
4386af1df7 | ||
|
|
92003df7f9 | ||
|
|
b2f695310c | ||
|
|
3b7410f2d3 | ||
|
|
80c20531aa | ||
|
|
8f26168a55 | ||
|
|
1470a79a1b | ||
|
|
f7dd730bc3 | ||
|
|
f428663f0d | ||
|
|
2ccf8d050f | ||
|
|
c8560fa0d3 | ||
|
|
c3d8918ee4 | ||
|
|
78fa2c5477 | ||
|
|
0aa63e0ae9 | ||
|
|
b984f5f1b0 | ||
|
|
57aab56baa | ||
|
|
12cfd8e052 | ||
|
|
0361ee0444 | ||
|
|
d251c94ea3 | ||
|
|
76a6ff9026 | ||
|
|
bf774527fb | ||
|
|
69b217065c | ||
|
|
e5a9bc8193 | ||
|
|
8463cf88df | ||
|
|
aac26aa19c | ||
|
|
e2800e343e | ||
|
|
7b44e412d9 | ||
|
|
7f6b8a3e40 | ||
|
|
b619f6d906 | ||
|
|
194b158e18 | ||
|
|
f9b6336dd5 | ||
|
|
3f11d0b8ad | ||
|
|
27c3c1dee0 | ||
|
|
78074c0cdd | ||
|
|
4d0e28a608 | ||
|
|
87a7bc6f49 | ||
|
|
6c23d5b682 |
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "16.8.1"
|
||||
__version__ = "16.10.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -6,64 +6,83 @@
|
||||
"Current Assets": {
|
||||
"Accounts Receivable": {
|
||||
"Debtors": {
|
||||
"account_type": "Receivable"
|
||||
"account_type": "Receivable",
|
||||
"account_category": "Trade Receivables"
|
||||
}
|
||||
},
|
||||
"Bank Accounts": {
|
||||
"account_type": "Bank",
|
||||
"is_group": 1
|
||||
"is_group": 1,
|
||||
"account_category": "Cash and Cash Equivalents"
|
||||
},
|
||||
"Cash In Hand": {
|
||||
"Cash": {
|
||||
"account_type": "Cash"
|
||||
"account_type": "Cash",
|
||||
"account_category": "Cash and Cash Equivalents"
|
||||
},
|
||||
"account_type": "Cash"
|
||||
"account_type": "Cash",
|
||||
"account_category": "Cash and Cash Equivalents"
|
||||
},
|
||||
"Loans and Advances (Assets)": {
|
||||
"is_group": 1
|
||||
"is_group": 1,
|
||||
"account_category": "Other Receivables"
|
||||
},
|
||||
"Securities and Deposits": {
|
||||
"Earnest Money": {}
|
||||
"Earnest Money": {
|
||||
"account_category": "Other Current Assets"
|
||||
}
|
||||
},
|
||||
"Stock Assets": {
|
||||
"Stock In Hand": {
|
||||
"account_type": "Stock"
|
||||
"account_type": "Stock",
|
||||
"account_category": "Stock Assets"
|
||||
},
|
||||
"account_type": "Stock"
|
||||
"account_type": "Stock",
|
||||
"account_category": "Stock Assets"
|
||||
},
|
||||
"Tax Assets": {
|
||||
"is_group": 1
|
||||
"is_group": 1,
|
||||
"account_category": "Other Current Assets"
|
||||
}
|
||||
},
|
||||
"Fixed Assets": {
|
||||
"Capital Equipment": {
|
||||
"account_type": "Fixed Asset"
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
},
|
||||
"Electronic Equipment": {
|
||||
"account_type": "Fixed Asset"
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
},
|
||||
"Furniture and Fixtures": {
|
||||
"account_type": "Fixed Asset"
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
},
|
||||
"Office Equipment": {
|
||||
"account_type": "Fixed Asset"
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
},
|
||||
"Plants and Machineries": {
|
||||
"account_type": "Fixed Asset"
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
},
|
||||
"Buildings": {
|
||||
"account_type": "Fixed Asset"
|
||||
"account_type": "Fixed Asset",
|
||||
"account_category": "Tangible Assets"
|
||||
},
|
||||
"Accumulated Depreciations": {
|
||||
"account_type": "Accumulated Depreciation"
|
||||
"account_type": "Accumulated Depreciation",
|
||||
"account_category": "Tangible Assets"
|
||||
}
|
||||
},
|
||||
"Investments": {
|
||||
"is_group": 1
|
||||
"is_group": 1,
|
||||
"account_category": "Long-term Investments"
|
||||
},
|
||||
"Temporary Accounts": {
|
||||
"Temporary Opening": {
|
||||
"account_type": "Temporary"
|
||||
"account_type": "Temporary",
|
||||
"account_category": "Other Non-current Assets"
|
||||
}
|
||||
},
|
||||
"root_type": "Asset"
|
||||
@@ -72,55 +91,103 @@
|
||||
"Direct Expenses": {
|
||||
"Stock Expenses": {
|
||||
"Cost of Goods Sold": {
|
||||
"account_type": "Cost of Goods Sold"
|
||||
"account_type": "Cost of Goods Sold",
|
||||
"account_category": "Cost of Goods Sold"
|
||||
},
|
||||
"Expenses Included In Valuation": {
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
"account_type": "Expenses Included In Valuation",
|
||||
"account_category": "Other Direct Costs"
|
||||
},
|
||||
"Stock Adjustment": {
|
||||
"account_type": "Stock Adjustment"
|
||||
"account_type": "Stock Adjustment",
|
||||
"account_category": "Other Direct Costs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Indirect Expenses": {
|
||||
"Administrative Expenses": {},
|
||||
"Commission on Sales": {},
|
||||
"Administrative Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Commission on Sales": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Depreciation": {
|
||||
"account_type": "Depreciation"
|
||||
"account_type": "Depreciation",
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Entertainment Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Entertainment Expenses": {},
|
||||
"Freight and Forwarding Charges": {
|
||||
"account_type": "Chargeable"
|
||||
"account_type": "Chargeable",
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Legal Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Marketing Expenses": {
|
||||
"account_type": "Chargeable",
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Miscellaneous Expenses": {
|
||||
"account_type": "Chargeable",
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Office Maintenance Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Office Rent": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Postal Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Print and Stationery": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Legal Expenses": {},
|
||||
"Marketing Expenses": {},
|
||||
"Miscellaneous Expenses": {},
|
||||
"Office Maintenance Expenses": {},
|
||||
"Office Rent": {},
|
||||
"Postal Expenses": {},
|
||||
"Print and Stationery": {},
|
||||
"Rounded Off": {
|
||||
"account_type": "Round Off"
|
||||
"account_type": "Round Off",
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Salary": {},
|
||||
"Sales Expenses": {},
|
||||
"Telephone Expenses": {},
|
||||
"Travel Expenses": {},
|
||||
"Utility Expenses": {},
|
||||
"Write Off": {},
|
||||
"Exchange Gain/Loss": {},
|
||||
"Gain/Loss on Asset Disposal": {},
|
||||
"Impairment": {}
|
||||
"Salary": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Sales Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Telephone Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Travel Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Utility Expenses": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Write Off": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Exchange Gain/Loss": {
|
||||
"account_category": "Operating Expenses"
|
||||
},
|
||||
"Gain/Loss on Asset Disposal": {
|
||||
"account_category": "Other Operating Income"
|
||||
},
|
||||
"Impairment": {
|
||||
"account_category": "Operating Expenses"
|
||||
}
|
||||
},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Income": {
|
||||
"Direct Income": {
|
||||
"Sales": {
|
||||
"account_type": "Income Account"
|
||||
"account_type": "Income Account",
|
||||
"account_category": "Revenue from Operations"
|
||||
},
|
||||
"Service": {
|
||||
"account_type": "Income Account"
|
||||
"account_type": "Income Account",
|
||||
"account_category": "Revenue from Operations"
|
||||
},
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
@@ -132,31 +199,51 @@
|
||||
},
|
||||
"Source of Funds (Liabilities)": {
|
||||
"Capital Account": {
|
||||
"Reserves and Surplus": {},
|
||||
"Shareholders Funds": {},
|
||||
"Revaluation Surplus": {}
|
||||
"Reserves and Surplus": {
|
||||
"account_category": "Reserves and Surplus"
|
||||
},
|
||||
"Shareholders Funds": {
|
||||
"account_category": "Share Capital"
|
||||
},
|
||||
"Revaluation Surplus": {
|
||||
"account_category": "Reserves and Surplus"
|
||||
}
|
||||
},
|
||||
"Current Liabilities": {
|
||||
"Accounts Payable": {
|
||||
"Creditors": {
|
||||
"account_type": "Payable"
|
||||
"account_type": "Payable",
|
||||
"account_category": "Trade Payables"
|
||||
},
|
||||
"Payroll Payable": {}
|
||||
"Payroll Payable": {
|
||||
"account_category": "Other Payables"
|
||||
}
|
||||
},
|
||||
"Stock Liabilities": {
|
||||
"Stock Received But Not Billed": {
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
"account_type": "Stock Received But Not Billed",
|
||||
"account_category": "Trade Payables"
|
||||
}
|
||||
},
|
||||
"Duties and Taxes": {
|
||||
"TDS": {
|
||||
"account_type": "Tax"
|
||||
}
|
||||
"account_type": "Tax",
|
||||
"account_category": "Current Tax Liabilities"
|
||||
},
|
||||
"account_type": "Tax",
|
||||
"is_group": 1,
|
||||
"account_category": "Current Tax Liabilities"
|
||||
},
|
||||
"Loans (Liabilities)": {
|
||||
"Secured Loans": {},
|
||||
"Unsecured Loans": {},
|
||||
"Bank Overdraft Account": {}
|
||||
"Secured Loans": {
|
||||
"account_category": "Long-term Borrowings"
|
||||
},
|
||||
"Unsecured Loans": {
|
||||
"account_category": "Long-term Borrowings"
|
||||
},
|
||||
"Bank Overdraft Account": {
|
||||
"account_category": "Short-term Borrowings"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root_type": "Liability"
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
{
|
||||
"fieldname": "period_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Period Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
@@ -79,7 +78,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2025-12-01 16:53:44.631299",
|
||||
"modified": "2026-03-09 17:15:33.577217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Period",
|
||||
|
||||
@@ -97,7 +97,7 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
if doc.doctype == "Bank Clearance":
|
||||
return
|
||||
elif doc.doctype == "Asset":
|
||||
if doc.is_existing_asset:
|
||||
if doc.asset_type == "Existing Asset":
|
||||
return
|
||||
else:
|
||||
date = doc.available_for_use_date
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
||||
"fieldname": "automatically_fetch_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Fetch Payment Terms from Order"
|
||||
"label": "Automatically Fetch Payment Terms from Order/Quotation"
|
||||
},
|
||||
{
|
||||
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||
@@ -697,7 +697,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-04 17:15:38.609327",
|
||||
"modified": "2026-02-27 01:04:09.415288",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import cint, flt, fmt_money, get_link_to_form, getdate
|
||||
from frappe.query_builder.functions import Coalesce, Sum
|
||||
from frappe.utils import cint, flt, fmt_money, getdate
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
@@ -182,65 +184,162 @@ def get_payment_entries_for_bank_clearance(
|
||||
):
|
||||
entries = []
|
||||
|
||||
condition = ""
|
||||
pe_condition = ""
|
||||
journal_entry = frappe.qb.DocType("Journal Entry")
|
||||
journal_entry_account = frappe.qb.DocType("Journal Entry Account")
|
||||
|
||||
journal_entry_query = (
|
||||
frappe.qb.from_(journal_entry_account)
|
||||
.inner_join(journal_entry)
|
||||
.on(journal_entry_account.parent == journal_entry.name)
|
||||
.select(
|
||||
ConstantColumn("Journal Entry").as_("payment_document"),
|
||||
journal_entry.name.as_("payment_entry"),
|
||||
journal_entry.cheque_no.as_("cheque_number"),
|
||||
journal_entry.cheque_date,
|
||||
Sum(journal_entry_account.debit_in_account_currency).as_("debit"),
|
||||
Sum(journal_entry_account.credit_in_account_currency).as_("credit"),
|
||||
journal_entry.posting_date,
|
||||
journal_entry_account.against_account,
|
||||
journal_entry.clearance_date,
|
||||
journal_entry_account.account_currency,
|
||||
)
|
||||
.where(
|
||||
(journal_entry_account.account == account)
|
||||
& (journal_entry.docstatus == 1)
|
||||
& (journal_entry.posting_date >= from_date)
|
||||
& (journal_entry.posting_date <= to_date)
|
||||
& (journal_entry.is_opening == "No")
|
||||
)
|
||||
)
|
||||
|
||||
if not include_reconciled_entries:
|
||||
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
|
||||
pe_condition = "and (pe.clearance_date IS NULL or pe.clearance_date='0000-00-00')"
|
||||
journal_entry_query = journal_entry_query.where(
|
||||
(journal_entry.clearance_date.isnull()) | (journal_entry.clearance_date == "0000-00-00")
|
||||
)
|
||||
|
||||
journal_entries = frappe.db.sql(
|
||||
f"""
|
||||
select
|
||||
"Journal Entry" as payment_document, t1.name as payment_entry,
|
||||
t1.cheque_no as cheque_number, t1.cheque_date,
|
||||
sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit,
|
||||
t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
|
||||
from
|
||||
`tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where
|
||||
t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1
|
||||
and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s
|
||||
and ifnull(t1.is_opening, 'No') = 'No' {condition}
|
||||
group by t2.account, t1.name
|
||||
order by t1.posting_date ASC, t1.name DESC
|
||||
""",
|
||||
{"account": account, "from": from_date, "to": to_date},
|
||||
as_dict=1,
|
||||
journal_entries = (
|
||||
journal_entry_query.groupby(journal_entry_account.account, journal_entry.name)
|
||||
.orderby(journal_entry.posting_date)
|
||||
.orderby(journal_entry.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
company = frappe.qb.DocType("Company")
|
||||
payment_entry_query = (
|
||||
frappe.qb.from_(pe)
|
||||
.join(company)
|
||||
.on(pe.company == company.name)
|
||||
.select(
|
||||
ConstantColumn("Payment Entry").as_("payment_document"),
|
||||
pe.name.as_("payment_entry"),
|
||||
pe.reference_no.as_("cheque_number"),
|
||||
pe.reference_date.as_("cheque_date"),
|
||||
(
|
||||
Case()
|
||||
.when(
|
||||
pe.paid_from == account,
|
||||
(
|
||||
pe.paid_amount
|
||||
+ (
|
||||
Case()
|
||||
.when(
|
||||
(pe.payment_type == "Pay")
|
||||
& (company.default_currency == pe.paid_from_account_currency),
|
||||
pe.base_total_taxes_and_charges,
|
||||
)
|
||||
.else_(pe.total_taxes_and_charges)
|
||||
)
|
||||
),
|
||||
)
|
||||
.else_(0)
|
||||
).as_("credit"),
|
||||
(
|
||||
Case()
|
||||
.when(pe.paid_from == account, 0)
|
||||
.else_(
|
||||
pe.received_amount
|
||||
+ (
|
||||
Case()
|
||||
.when(
|
||||
company.default_currency == pe.paid_to_account_currency,
|
||||
pe.base_total_taxes_and_charges,
|
||||
)
|
||||
.else_(pe.total_taxes_and_charges)
|
||||
)
|
||||
)
|
||||
).as_("debit"),
|
||||
pe.posting_date,
|
||||
Coalesce(pe.party, Case().when(pe.paid_from == account, pe.paid_to).else_(pe.paid_from)).as_(
|
||||
"against_account"
|
||||
),
|
||||
pe.clearance_date,
|
||||
(
|
||||
Case()
|
||||
.when(pe.paid_to == account, pe.paid_to_account_currency)
|
||||
.else_(pe.paid_from_account_currency)
|
||||
).as_("account_currency"),
|
||||
)
|
||||
.where(
|
||||
((pe.paid_from == account) | (pe.paid_to == account))
|
||||
& (pe.docstatus == 1)
|
||||
& (pe.posting_date >= from_date)
|
||||
& (pe.posting_date <= to_date)
|
||||
)
|
||||
)
|
||||
|
||||
payment_entries = frappe.db.sql(
|
||||
f"""
|
||||
select
|
||||
"Payment Entry" as payment_document, pe.name as payment_entry,
|
||||
pe.reference_no as cheque_number, pe.reference_date as cheque_date,
|
||||
if(pe.paid_from=%(account)s, pe.paid_amount + if(pe.payment_type = 'Pay' and c.default_currency = pe.paid_from_account_currency, pe.base_total_taxes_and_charges, pe.total_taxes_and_charges) , 0) as credit,
|
||||
if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit,
|
||||
pe.posting_date, ifnull(pe.party,if(pe.paid_from=%(account)s,pe.paid_to,pe.paid_from)) as against_account, pe.clearance_date,
|
||||
if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry` as pe
|
||||
join `tabCompany` c on c.name = pe.company
|
||||
where
|
||||
(pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1
|
||||
and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s
|
||||
{pe_condition}
|
||||
order by
|
||||
pe.posting_date ASC, pe.name DESC
|
||||
""",
|
||||
{
|
||||
"account": account,
|
||||
"from": from_date,
|
||||
"to": to_date,
|
||||
},
|
||||
as_dict=1,
|
||||
if not include_reconciled_entries:
|
||||
payment_entry_query = payment_entry_query.where(
|
||||
(pe.clearance_date.isnull()) | (pe.clearance_date == "0000-00-00")
|
||||
)
|
||||
|
||||
payment_entries = (payment_entry_query.orderby(pe.posting_date).orderby(pe.name, order=Order.desc)).run(
|
||||
as_dict=True
|
||||
)
|
||||
|
||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||
acc = frappe.qb.DocType("Account")
|
||||
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
|
||||
paid_purchase_invoices_query = (
|
||||
frappe.qb.from_(pi)
|
||||
.inner_join(acc)
|
||||
.on(pi.cash_bank_account == acc.name)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("payment_document"),
|
||||
pi.name.as_("payment_entry"),
|
||||
pi.paid_amount.as_("credit"),
|
||||
pi.posting_date,
|
||||
pi.supplier.as_("against_account"),
|
||||
pi.bill_no.as_("cheque_number"),
|
||||
pi.clearance_date,
|
||||
acc.account_currency,
|
||||
ConstantColumn(0).as_("debit"),
|
||||
)
|
||||
.where(
|
||||
(pi.docstatus == 1)
|
||||
& (pi.is_paid == 1)
|
||||
& (pi.cash_bank_account == account)
|
||||
& (pi.posting_date >= from_date)
|
||||
& (pi.posting_date <= to_date)
|
||||
)
|
||||
)
|
||||
|
||||
if not include_reconciled_entries:
|
||||
paid_purchase_invoices_query = paid_purchase_invoices_query.where(
|
||||
(pi.clearance_date.isnull()) | (pi.clearance_date == "0000-00-00")
|
||||
)
|
||||
|
||||
paid_purchase_invoices = (
|
||||
paid_purchase_invoices_query.orderby(pi.posting_date).orderby(pi.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
pos_sales_invoices = []
|
||||
|
||||
if include_pos_transactions:
|
||||
si_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
acc = frappe.qb.DocType("Account")
|
||||
|
||||
pos_sales_invoices = (
|
||||
pos_sales_invoices_query = (
|
||||
frappe.qb.from_(si_payment)
|
||||
.inner_join(si)
|
||||
.on(si_payment.parent == si.name)
|
||||
@@ -263,38 +362,22 @@ def get_payment_entries_for_bank_clearance(
|
||||
& (si.posting_date >= from_date)
|
||||
& (si.posting_date <= to_date)
|
||||
)
|
||||
.orderby(si.posting_date)
|
||||
.orderby(si.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
)
|
||||
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
if not include_reconciled_entries:
|
||||
pos_sales_invoices_query = pos_sales_invoices_query.where(
|
||||
(si_payment.clearance_date.isnull()) | (si_payment.clearance_date == "0000-00-00")
|
||||
)
|
||||
|
||||
pos_purchase_invoices = (
|
||||
frappe.qb.from_(pi)
|
||||
.inner_join(acc)
|
||||
.on(pi.cash_bank_account == acc.name)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("payment_document"),
|
||||
pi.name.as_("payment_entry"),
|
||||
pi.paid_amount.as_("credit"),
|
||||
pi.posting_date,
|
||||
pi.supplier.as_("against_account"),
|
||||
pi.clearance_date,
|
||||
acc.account_currency,
|
||||
ConstantColumn(0).as_("debit"),
|
||||
)
|
||||
.where(
|
||||
(pi.docstatus == 1)
|
||||
& (pi.cash_bank_account == account)
|
||||
& (pi.posting_date >= from_date)
|
||||
& (pi.posting_date <= to_date)
|
||||
)
|
||||
.orderby(pi.posting_date)
|
||||
.orderby(pi.name, order=Order.desc)
|
||||
pos_sales_invoices = (
|
||||
pos_sales_invoices_query.orderby(si.posting_date).orderby(si.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
entries = (
|
||||
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
|
||||
list(payment_entries)
|
||||
+ list(journal_entries)
|
||||
+ list(pos_sales_invoices)
|
||||
+ list(paid_purchase_invoices)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
@@ -101,11 +101,11 @@
|
||||
"label": "Use HTTP Protocol"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:19:02.873815",
|
||||
"modified": "2026-03-16 13:28:21.075743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
||||
@@ -140,6 +140,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
@@ -181,7 +182,6 @@
|
||||
"fieldname": "cheque_no",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Number",
|
||||
"mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"",
|
||||
"no_copy": 1,
|
||||
@@ -665,7 +665,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-16 16:06:10.468482",
|
||||
"modified": "2026-03-09 17:15:26.569327",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -293,6 +293,8 @@ class JournalEntry(AccountsController):
|
||||
|
||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||
super().on_cancel()
|
||||
|
||||
from_doc_events = getattr(self, "ignore_linked_doctypes", ())
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
@@ -306,6 +308,10 @@ class JournalEntry(AccountsController):
|
||||
"Advance Payment Ledger Entry",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
|
||||
if from_doc_events and from_doc_events != self.ignore_linked_doctypes:
|
||||
self.ignore_linked_doctypes = self.ignore_linked_doctypes + from_doc_events
|
||||
|
||||
self.make_gl_entries(1)
|
||||
JournalTaxWithholding(self).on_cancel()
|
||||
self.unlink_advance_entry_reference()
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -160,7 +161,6 @@
|
||||
{
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
@@ -228,6 +228,7 @@
|
||||
"fieldname": "paid_from",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Account Paid From",
|
||||
"options": "Account",
|
||||
"print_hide": 1,
|
||||
@@ -252,6 +253,7 @@
|
||||
"fieldname": "paid_to",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Account Paid To",
|
||||
"options": "Account",
|
||||
"print_hide": 1,
|
||||
@@ -414,6 +416,7 @@
|
||||
"depends_on": "eval:(doc.paid_from && doc.paid_to)",
|
||||
"fieldname": "reference_no",
|
||||
"fieldtype": "Data",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Cheque/Reference No",
|
||||
"mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')"
|
||||
},
|
||||
@@ -792,7 +795,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-03 16:08:49.800381",
|
||||
"modified": "2026-03-09 17:15:30.453920",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -2556,14 +2556,9 @@ def get_orders_to_be_billed(
|
||||
if not voucher_type:
|
||||
return []
|
||||
|
||||
# Add cost center condition
|
||||
doc = frappe.get_doc({"doctype": voucher_type})
|
||||
condition = ""
|
||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||
condition = " and cost_center='%s'" % cost_center
|
||||
|
||||
# dynamic dimension filters
|
||||
active_dimensions = get_dimensions()[0]
|
||||
condition = ""
|
||||
active_dimensions = get_dimensions(True)[0]
|
||||
for dim in active_dimensions:
|
||||
if filters.get(dim.fieldname):
|
||||
condition += f" and {dim.fieldname}='{filters.get(dim.fieldname)}'"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@@ -59,7 +60,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-13 06:52:46.130142",
|
||||
"modified": "2026-03-11 14:26:11.312950",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Deduction",
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
"options": "1"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-09 17:30:41.476806",
|
||||
"modified": "2026-03-16 13:28:19.677217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
|
||||
@@ -534,7 +534,7 @@ cur_frm.fields_dict["select_print_heading"].get_query = function (doc, cdt, cdn)
|
||||
|
||||
cur_frm.set_query("wip_composite_asset", "items", function () {
|
||||
return {
|
||||
filters: { is_composite_asset: 1, docstatus: 0 },
|
||||
filters: { asset_type: "Composite Asset", docstatus: 0 },
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -266,6 +266,7 @@
|
||||
{
|
||||
"fieldname": "due_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date",
|
||||
"oldfieldname": "due_date",
|
||||
"oldfieldtype": "Date"
|
||||
@@ -319,7 +320,8 @@
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
"print_hide": 1,
|
||||
@@ -389,7 +391,7 @@
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "bill_no",
|
||||
"collapsible_depends_on": "posting_date",
|
||||
"fieldname": "supplier_invoice_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Supplier Invoice"
|
||||
@@ -397,6 +399,8 @@
|
||||
{
|
||||
"fieldname": "bill_no",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Supplier Invoice No",
|
||||
"oldfieldname": "bill_no",
|
||||
"oldfieldtype": "Data",
|
||||
@@ -1689,7 +1693,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-23 14:23:57.269770",
|
||||
"modified": "2026-03-17 20:44:00.221219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -733,9 +733,10 @@ class PurchaseInvoice(BuyingController):
|
||||
for item in self.get("items"):
|
||||
if item.purchase_receipt:
|
||||
frappe.throw(
|
||||
_("Stock cannot be updated against Purchase Receipt {0}").format(
|
||||
item.purchase_receipt
|
||||
)
|
||||
_(
|
||||
"Stock cannot be updated for Purchase Invoice {0} because a Purchase Receipt {1} has already been created for this transaction. Please disable the 'Update Stock' checkbox in the Purchase Invoice and save the invoice."
|
||||
).format(self.name, item.purchase_receipt),
|
||||
title=_("Stock Update Not Allowed"),
|
||||
)
|
||||
|
||||
def validate_for_repost(self):
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:19:08.888368",
|
||||
"modified": "2026-03-16 13:28:21.312607",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger Settings",
|
||||
|
||||
@@ -381,7 +381,9 @@
|
||||
"fieldtype": "Date",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
@@ -415,6 +417,7 @@
|
||||
"fieldtype": "Date",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Due Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "due_date",
|
||||
@@ -1639,6 +1642,7 @@
|
||||
"fieldtype": "Select",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"length": 30,
|
||||
@@ -2330,7 +2334,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-28 17:58:56.453076",
|
||||
"modified": "2026-03-09 17:15:30.931929",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -919,11 +919,9 @@ class SalesInvoice(SellingController):
|
||||
if self.pos_profile:
|
||||
pos = frappe.get_doc("POS Profile", self.pos_profile)
|
||||
|
||||
if not self.get("payments") and not for_validate:
|
||||
update_multi_mode_option(self, pos)
|
||||
|
||||
if pos:
|
||||
if not for_validate:
|
||||
update_multi_mode_option(self, pos)
|
||||
self.tax_category = pos.get("tax_category")
|
||||
|
||||
if not for_validate and not self.customer:
|
||||
@@ -1451,6 +1449,9 @@ class SalesInvoice(SellingController):
|
||||
return asset_qty_map
|
||||
|
||||
def process_asset_depreciation(self):
|
||||
if self.is_internal_transfer():
|
||||
return
|
||||
|
||||
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
|
||||
self.depreciate_asset_on_sale()
|
||||
else:
|
||||
@@ -3005,6 +3006,8 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
payment.account = payment_mode.default_account
|
||||
payment.type = payment_mode.type
|
||||
|
||||
mop_refetched = bool(doc.payments) and not doc.is_created_using_pos
|
||||
|
||||
doc.set("payments", [])
|
||||
invalid_modes = []
|
||||
mode_of_payments = [d.mode_of_payment for d in pos_profile.get("payments")]
|
||||
@@ -3026,6 +3029,12 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
msg = _("Please set default Cash or Bank account in Mode of Payments {}")
|
||||
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
|
||||
|
||||
if mop_refetched:
|
||||
frappe.toast(
|
||||
_("Payment methods refreshed. Please review before proceeding."),
|
||||
indicator="orange",
|
||||
)
|
||||
|
||||
|
||||
def get_all_mode_of_payments(doc):
|
||||
return frappe.db.sql(
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:18:34.671062",
|
||||
"modified": "2026-03-16 13:28:20.485964",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription Settings",
|
||||
|
||||
@@ -8,6 +8,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.address.address import get_default_address
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import IfNull
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
@@ -83,6 +85,8 @@ class TaxRule(Document):
|
||||
frappe.throw(_("Tax Template is mandatory."))
|
||||
|
||||
def validate_filters(self):
|
||||
TaxRule = DocType("Tax Rule")
|
||||
|
||||
filters = {
|
||||
"tax_type": self.tax_type,
|
||||
"customer": self.customer,
|
||||
@@ -105,33 +109,34 @@ class TaxRule(Document):
|
||||
"company": self.company,
|
||||
}
|
||||
|
||||
conds = ""
|
||||
for d in filters:
|
||||
if conds:
|
||||
conds += " and "
|
||||
conds += f"""ifnull({d}, '') = {frappe.db.escape(cstr(filters[d]))}"""
|
||||
|
||||
if self.from_date and self.to_date:
|
||||
conds += f""" and ((from_date > '{self.from_date}' and from_date < '{self.to_date}') or
|
||||
(to_date > '{self.from_date}' and to_date < '{self.to_date}') or
|
||||
('{self.from_date}' > from_date and '{self.from_date}' < to_date) or
|
||||
('{self.from_date}' = from_date and '{self.to_date}' = to_date))"""
|
||||
|
||||
elif self.from_date and not self.to_date:
|
||||
conds += f""" and to_date > '{self.from_date}'"""
|
||||
|
||||
elif self.to_date and not self.from_date:
|
||||
conds += f""" and from_date < '{self.to_date}'"""
|
||||
|
||||
tax_rule = frappe.db.sql(
|
||||
f"select name, priority \
|
||||
from `tabTax Rule` where {conds} and name != '{self.name}'",
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(TaxRule).select(TaxRule.name, TaxRule.priority).where(TaxRule.name != self.name)
|
||||
)
|
||||
|
||||
if tax_rule:
|
||||
if tax_rule[0].priority == self.priority:
|
||||
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
|
||||
for field, value in filters.items():
|
||||
query = query.where(IfNull(TaxRule[field], "") == cstr(value))
|
||||
|
||||
if self.from_date and self.to_date:
|
||||
query = query.where(
|
||||
((TaxRule.from_date > self.from_date) & (TaxRule.from_date < self.to_date))
|
||||
| ((TaxRule.to_date > self.from_date) & (TaxRule.to_date < self.to_date))
|
||||
| ((self.from_date > TaxRule.from_date) & (self.from_date < TaxRule.to_date))
|
||||
| ((TaxRule.from_date == self.from_date) & (TaxRule.to_date == self.to_date))
|
||||
)
|
||||
|
||||
elif self.from_date:
|
||||
query = query.where(TaxRule.to_date > self.from_date)
|
||||
|
||||
elif self.to_date:
|
||||
query = query.where(TaxRule.from_date < self.to_date)
|
||||
|
||||
tax_rule = query.run(as_dict=True)
|
||||
|
||||
if tax_rule and tax_rule[0].priority == self.priority:
|
||||
frappe.throw(
|
||||
_("Tax Rule Conflicts with {0}").format(tax_rule[0].name),
|
||||
ConflictingTaxRule,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"align_labels_right": 0,
|
||||
"creation": "2016-05-05 17:16:18.564460",
|
||||
"custom_format": 1,
|
||||
"disabled": 0,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "Default",
|
||||
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Qty Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ qty_total }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
|
||||
"idx": 0,
|
||||
"line_breaks": 0,
|
||||
"modified": "2019-09-05 17:20:30.726659",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Point of Sale",
|
||||
"owner": "Administrator",
|
||||
"print_format_builder": 0,
|
||||
"print_format_type": "JS",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 0,
|
||||
"standard": "Yes"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,10 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import getdate, nowdate
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import getdate
|
||||
from pypika import Order
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -48,17 +51,6 @@ def get_columns():
|
||||
return columns
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date>=%(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date<=%(to_date)s"
|
||||
|
||||
return conditions
|
||||
|
||||
|
||||
def get_entries(filters):
|
||||
entries = []
|
||||
|
||||
@@ -73,41 +65,90 @@ def get_entries(filters):
|
||||
|
||||
return sorted(
|
||||
entries,
|
||||
key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()),
|
||||
key=lambda k: getdate(k[2]),
|
||||
)
|
||||
|
||||
|
||||
def get_entries_for_bank_clearance_summary(filters):
|
||||
entries = []
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
jea = frappe.qb.DocType("Journal Entry Account")
|
||||
|
||||
journal_entries = frappe.db.sql(
|
||||
f"""SELECT
|
||||
"Journal Entry", jv.name, jv.posting_date, jv.cheque_no,
|
||||
jv.clearance_date, jvd.against_account, jvd.debit - jvd.credit
|
||||
FROM
|
||||
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
|
||||
WHERE
|
||||
jvd.parent = jv.name and jv.docstatus=1 and jvd.account = %(account)s {conditions}
|
||||
order by posting_date DESC, jv.name DESC""",
|
||||
filters,
|
||||
as_list=1,
|
||||
)
|
||||
journal_entries = (
|
||||
frappe.qb.from_(jea)
|
||||
.inner_join(je)
|
||||
.on(jea.parent == je.name)
|
||||
.select(
|
||||
ConstantColumn("Journal Entry").as_("payment_document"),
|
||||
je.name.as_("payment_entry"),
|
||||
je.posting_date,
|
||||
je.cheque_no,
|
||||
je.clearance_date,
|
||||
jea.against_account,
|
||||
jea.debit_in_account_currency - jea.credit_in_account_currency,
|
||||
)
|
||||
.where(
|
||||
(jea.account == filters.account)
|
||||
& (je.docstatus == 1)
|
||||
& (je.posting_date >= filters.from_date)
|
||||
& (je.posting_date <= filters.to_date)
|
||||
& ((je.is_opening == "No") | (je.is_opening.isnull()))
|
||||
)
|
||||
.orderby(je.posting_date, order=Order.desc)
|
||||
.orderby(je.name, order=Order.desc)
|
||||
).run(as_list=True)
|
||||
|
||||
payment_entries = frappe.db.sql(
|
||||
f"""SELECT
|
||||
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
|
||||
if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount)
|
||||
FROM
|
||||
`tabPayment Entry`
|
||||
WHERE
|
||||
docstatus=1 and (paid_from = %(account)s or paid_to = %(account)s) {conditions}
|
||||
order by posting_date DESC, name DESC""",
|
||||
filters,
|
||||
as_list=1,
|
||||
)
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
payment_entries = (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
ConstantColumn("Payment Entry").as_("payment_document"),
|
||||
pe.name.as_("payment_entry"),
|
||||
pe.posting_date,
|
||||
pe.reference_no.as_("cheque_no"),
|
||||
pe.clearance_date,
|
||||
pe.party.as_("against_account"),
|
||||
Case()
|
||||
.when(
|
||||
(pe.paid_from == filters.account),
|
||||
((pe.paid_amount * -1) - pe.total_taxes_and_charges),
|
||||
)
|
||||
.else_(pe.received_amount),
|
||||
)
|
||||
.where((pe.paid_from == filters.account) | (pe.paid_to == filters.account))
|
||||
.where(
|
||||
(pe.docstatus == 1)
|
||||
& (pe.posting_date >= filters.from_date)
|
||||
& (pe.posting_date <= filters.to_date)
|
||||
)
|
||||
.orderby(pe.posting_date, order=Order.desc)
|
||||
.orderby(pe.name, order=Order.desc)
|
||||
).run(as_list=True)
|
||||
|
||||
entries = journal_entries + payment_entries
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
purchase_invoices = (
|
||||
frappe.qb.from_(pi)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("payment_document"),
|
||||
pi.name.as_("payment_entry"),
|
||||
pi.posting_date,
|
||||
pi.bill_no.as_("cheque_no"),
|
||||
pi.clearance_date,
|
||||
pi.supplier.as_("against_account"),
|
||||
(pi.paid_amount * -1).as_("amount"),
|
||||
)
|
||||
.where(
|
||||
(pi.docstatus == 1)
|
||||
& (pi.is_paid == 1)
|
||||
& (pi.cash_bank_account == filters.account)
|
||||
& (pi.posting_date >= filters.from_date)
|
||||
& (pi.posting_date <= filters.to_date)
|
||||
)
|
||||
.orderby(pi.posting_date, order=Order.desc)
|
||||
.orderby(pi.name, order=Order.desc)
|
||||
).run(as_list=True)
|
||||
|
||||
entries = journal_entries + payment_entries + purchase_invoices
|
||||
|
||||
return entries
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Coalesce, Sum
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
|
||||
@@ -123,73 +127,143 @@ def get_entries_for_bank_reconciliation_statement(filters):
|
||||
|
||||
payment_entries = get_payment_entries(filters)
|
||||
|
||||
purchase_invoices = get_purchase_invoices(filters)
|
||||
|
||||
pos_entries = []
|
||||
if filters.include_pos_transactions:
|
||||
pos_entries = get_pos_entries(filters)
|
||||
|
||||
return list(journal_entries) + list(payment_entries) + list(pos_entries)
|
||||
return list(journal_entries) + list(payment_entries) + list(pos_entries) + list(purchase_invoices)
|
||||
|
||||
|
||||
def get_journal_entries(filters):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select "Journal Entry" as payment_document, jv.posting_date,
|
||||
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
|
||||
jvd.credit_in_account_currency as credit, jvd.against_account,
|
||||
jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency
|
||||
from
|
||||
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
|
||||
where jvd.parent = jv.name and jv.docstatus=1
|
||||
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
|
||||
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
|
||||
and ifnull(jv.is_opening, 'No') = 'No'
|
||||
and jv.company = %(company)s """,
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
jea = frappe.qb.DocType("Journal Entry Account")
|
||||
return (
|
||||
frappe.qb.from_(jea)
|
||||
.join(je)
|
||||
.on(jea.parent == je.name)
|
||||
.select(
|
||||
ConstantColumn("Journal Entry").as_("payment_document"),
|
||||
je.name.as_("payment_entry"),
|
||||
je.posting_date,
|
||||
jea.debit_in_account_currency.as_("debit"),
|
||||
jea.credit_in_account_currency.as_("credit"),
|
||||
jea.against_account,
|
||||
je.cheque_no.as_("reference_no"),
|
||||
je.cheque_date.as_("ref_date"),
|
||||
je.clearance_date,
|
||||
jea.account_currency,
|
||||
)
|
||||
.where(
|
||||
(je.docstatus == 1)
|
||||
& (jea.account == filters.account)
|
||||
& (je.posting_date <= filters.report_date)
|
||||
& (je.clearance_date.isnull() | (je.clearance_date > filters.report_date))
|
||||
& (je.company == filters.company)
|
||||
& ((je.is_opening.isnull()) | (je.is_opening == "No"))
|
||||
)
|
||||
.orderby(je.posting_date)
|
||||
.orderby(je.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def get_payment_entries(filters):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no, reference_date as ref_date,
|
||||
if(paid_to=%(account)s, received_amount_after_tax, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit,
|
||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry`
|
||||
where
|
||||
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
|
||||
and posting_date <= %(report_date)s
|
||||
and ifnull(clearance_date, '4000-01-01') > %(report_date)s
|
||||
and company = %(company)s
|
||||
""",
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
return (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
ConstantColumn("Payment Entry").as_("payment_document"),
|
||||
pe.name.as_("payment_entry"),
|
||||
pe.reference_no.as_("reference_no"),
|
||||
pe.reference_date.as_("ref_date"),
|
||||
Case().when(pe.paid_to == filters.account, pe.received_amount_after_tax).else_(0).as_("debit"),
|
||||
Case().when(pe.paid_from == filters.account, pe.paid_amount_after_tax).else_(0).as_("credit"),
|
||||
pe.posting_date,
|
||||
Coalesce(
|
||||
pe.party, Case().when(pe.paid_from == filters.account, pe.paid_to).else_(pe.paid_from)
|
||||
).as_("against_account"),
|
||||
pe.clearance_date,
|
||||
(
|
||||
Case()
|
||||
.when(pe.paid_to == filters.account, pe.paid_to_account_currency)
|
||||
.else_(pe.paid_from_account_currency)
|
||||
).as_("account_currency"),
|
||||
)
|
||||
.where(
|
||||
(pe.docstatus == 1)
|
||||
& ((pe.paid_from == filters.account) | (pe.paid_to == filters.account))
|
||||
& (pe.posting_date <= filters.report_date)
|
||||
& (pe.clearance_date.isnull() | (pe.clearance_date > filters.report_date))
|
||||
& (pe.company == filters.company)
|
||||
)
|
||||
.orderby(pe.posting_date)
|
||||
.orderby(pe.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def get_purchase_invoices(filters):
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
acc = frappe.qb.DocType("Account")
|
||||
return (
|
||||
frappe.qb.from_(pi)
|
||||
.inner_join(acc)
|
||||
.on(pi.cash_bank_account == acc.name)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("payment_document"),
|
||||
pi.name.as_("payment_entry"),
|
||||
pi.bill_no.as_("reference_no"),
|
||||
pi.posting_date.as_("ref_date"),
|
||||
Case().when(pi.paid_amount < 0, pi.paid_amount * -1).else_(0).as_("debit"),
|
||||
Case().when(pi.paid_amount > 0, pi.paid_amount).else_(0).as_("credit"),
|
||||
pi.posting_date,
|
||||
pi.supplier.as_("against_account"),
|
||||
pi.clearance_date,
|
||||
acc.account_currency,
|
||||
)
|
||||
.where(
|
||||
(pi.docstatus == 1)
|
||||
& (pi.is_paid == 1)
|
||||
& (pi.cash_bank_account == filters.account)
|
||||
& (pi.posting_date <= filters.report_date)
|
||||
& (pi.clearance_date.isnull() | (pi.clearance_date > filters.report_date))
|
||||
& (pi.company == filters.company)
|
||||
)
|
||||
.orderby(pi.posting_date)
|
||||
.orderby(pi.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def get_pos_entries(filters):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
|
||||
si.posting_date, si.debit_to as against_account, sip.clearance_date,
|
||||
account.account_currency, 0 as credit
|
||||
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
|
||||
where
|
||||
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
|
||||
and account.name = sip.account and si.posting_date <= %(report_date)s and
|
||||
ifnull(sip.clearance_date, '4000-01-01') > %(report_date)s
|
||||
and si.company = %(company)s
|
||||
order by
|
||||
si.posting_date ASC, si.name DESC
|
||||
""",
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
si_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||
acc = frappe.qb.DocType("Account")
|
||||
return (
|
||||
frappe.qb.from_(si_payment)
|
||||
.join(si)
|
||||
.on(si_payment.parent == si.name)
|
||||
.join(acc)
|
||||
.on(si_payment.account == acc.name)
|
||||
.select(
|
||||
ConstantColumn("Sales Invoice").as_("payment_document"),
|
||||
si.name.as_("payment_entry"),
|
||||
si_payment.amount.as_("debit"),
|
||||
si.posting_date,
|
||||
si.debit_to.as_("against_account"),
|
||||
si_payment.clearance_date,
|
||||
acc.account_currency,
|
||||
ConstantColumn(0).as_("credit"),
|
||||
)
|
||||
.where(
|
||||
(si_payment.account == filters.account)
|
||||
& (si.docstatus == 1)
|
||||
& (si.posting_date <= filters.report_date)
|
||||
& (si_payment.clearance_date.isnull() | (si_payment.clearance_date > filters.report_date))
|
||||
& (si.company == filters.company)
|
||||
)
|
||||
.orderby(si.posting_date)
|
||||
.orderby(si_payment.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def get_amounts_not_reflected_in_system(filters):
|
||||
@@ -205,30 +279,66 @@ def get_amounts_not_reflected_in_system(filters):
|
||||
|
||||
|
||||
def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters):
|
||||
je_amount = frappe.db.sql(
|
||||
"""
|
||||
select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency)
|
||||
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
|
||||
where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s
|
||||
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
|
||||
and ifnull(jv.is_opening, 'No') = 'No' """,
|
||||
filters,
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
jea = frappe.qb.DocType("Journal Entry Account")
|
||||
|
||||
je_amount = (
|
||||
frappe.qb.from_(jea)
|
||||
.inner_join(je)
|
||||
.on(jea.parent == je.name)
|
||||
.select(
|
||||
Sum(jea.debit_in_account_currency - jea.credit_in_account_currency).as_("amount"),
|
||||
)
|
||||
.where(
|
||||
(je.docstatus == 1)
|
||||
& (jea.account == filters.account)
|
||||
& (je.posting_date > filters.report_date)
|
||||
& (je.clearance_date <= filters.report_date)
|
||||
& (je.company == filters.company)
|
||||
& ((je.is_opening.isnull()) | (je.is_opening == "No"))
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
je_amount = flt(je_amount[0].amount) if je_amount else 0.0
|
||||
|
||||
je_amount = flt(je_amount[0][0]) if je_amount else 0.0
|
||||
|
||||
pe_amount = frappe.db.sql(
|
||||
"""
|
||||
select sum(if(paid_from=%(account)s, paid_amount, received_amount))
|
||||
from `tabPayment Entry`
|
||||
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
|
||||
and posting_date > %(report_date)s and clearance_date <= %(report_date)s""",
|
||||
filters,
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
pe_amount = (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
Sum(Case().when(pe.paid_from == filters.account, pe.paid_amount).else_(pe.received_amount)).as_(
|
||||
"amount"
|
||||
),
|
||||
)
|
||||
.where(
|
||||
((pe.paid_from == filters.account) | (pe.paid_to == filters.account))
|
||||
& (pe.docstatus == 1)
|
||||
& (pe.posting_date > filters.report_date)
|
||||
& (pe.clearance_date <= filters.report_date)
|
||||
& (pe.company == filters.company)
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
pe_amount = flt(pe_amount[0].amount) if pe_amount else 0.0
|
||||
|
||||
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
pi_amount = (
|
||||
frappe.qb.from_(pi)
|
||||
.select(
|
||||
Sum(pi.paid_amount).as_("amount"),
|
||||
)
|
||||
.where(
|
||||
(pi.docstatus == 1)
|
||||
& (pi.is_paid == 1)
|
||||
& (pi.cash_bank_account == filters.account)
|
||||
& (pi.posting_date > filters.report_date)
|
||||
& (pi.clearance_date <= filters.report_date)
|
||||
& (pi.company == filters.company)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return je_amount + pe_amount
|
||||
pi_amount = flt(pi_amount[0].amount) if pi_amount else 0.0
|
||||
|
||||
return je_amount + pe_amount + pi_amount
|
||||
|
||||
|
||||
def get_balance_row(label, amount, account_currency):
|
||||
|
||||
@@ -37,6 +37,20 @@ function get_filters() {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "party_type",
|
||||
label: __("Party Type"),
|
||||
fieldtype: "Link",
|
||||
options: "Party Type",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
fieldname: "party",
|
||||
label: __("Party"),
|
||||
fieldtype: "Dynamic Link",
|
||||
options: "party_type",
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
fieldname: "voucher_no",
|
||||
label: __("Voucher No"),
|
||||
|
||||
@@ -68,6 +68,12 @@ class General_Payment_Ledger_Comparison:
|
||||
if self.filters.period_end_date:
|
||||
filter_criterion.append(gle.posting_date.lte(self.filters.period_end_date))
|
||||
|
||||
if self.filters.party_type:
|
||||
filter_criterion.append(gle.party_type.eq(self.filters.party_type))
|
||||
|
||||
if self.filters.party:
|
||||
filter_criterion.append(gle.party.eq(self.filters.party))
|
||||
|
||||
if acc_type == "receivable":
|
||||
outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding")
|
||||
else:
|
||||
@@ -111,6 +117,12 @@ class General_Payment_Ledger_Comparison:
|
||||
if self.filters.period_end_date:
|
||||
filter_criterion.append(ple.posting_date.lte(self.filters.period_end_date))
|
||||
|
||||
if self.filters.party_type:
|
||||
filter_criterion.append(ple.party_type.eq(self.filters.party_type))
|
||||
|
||||
if self.filters.party:
|
||||
filter_criterion.append(ple.party.eq(self.filters.party))
|
||||
|
||||
self.account_types[acc_type].ple = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
|
||||
@@ -649,7 +649,7 @@ class GrossProfitGenerator:
|
||||
new_row = row
|
||||
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion)
|
||||
else:
|
||||
new_row.qty += flt(row.qty)
|
||||
new_row.qty = flt((new_row.qty + row.qty), self.float_precision)
|
||||
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion, True)
|
||||
|
||||
new_row = self.set_average_rate(new_row)
|
||||
@@ -659,11 +659,17 @@ class GrossProfitGenerator:
|
||||
if i == 0:
|
||||
new_row = row
|
||||
else:
|
||||
new_row.qty += flt(row.qty)
|
||||
new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
|
||||
new_row.base_amount += flt(row.base_amount, self.currency_precision)
|
||||
new_row.qty = flt((new_row.qty + row.qty), self.float_precision)
|
||||
new_row.buying_amount = flt(
|
||||
(new_row.buying_amount + row.buying_amount), self.currency_precision
|
||||
)
|
||||
new_row.base_amount = flt(
|
||||
(new_row.base_amount + row.base_amount), self.currency_precision
|
||||
)
|
||||
if self.filters.get("group_by") == "Sales Person":
|
||||
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
|
||||
new_row.allocated_amount = flt(
|
||||
(new_row.allocated_amount + row.allocated_amount), self.currency_precision
|
||||
)
|
||||
new_row = self.set_average_rate(new_row)
|
||||
self.grouped_data.append(new_row)
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
|
||||
item_list = get_items(filters, additional_table_columns)
|
||||
aii_account_map = get_aii_accounts()
|
||||
default_taxes = {}
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(
|
||||
item_list,
|
||||
@@ -40,6 +41,9 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
doctype="Purchase Invoice",
|
||||
tax_doctype="Purchase Taxes and Charges",
|
||||
)
|
||||
for tax in tax_columns:
|
||||
default_taxes[f"{tax}_rate"] = 0
|
||||
default_taxes[f"{tax}_amount"] = 0
|
||||
|
||||
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
|
||||
|
||||
@@ -87,6 +91,7 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
|
||||
total_tax = 0
|
||||
total_other_charges = 0
|
||||
row.update(default_taxes.copy())
|
||||
for tax, details in itemised_tax.get(d.name, {}).items():
|
||||
row.update(
|
||||
{
|
||||
|
||||
@@ -33,6 +33,10 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
|
||||
return columns, [], None, None, None, 0
|
||||
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||
default_taxes = {}
|
||||
for tax in tax_columns:
|
||||
default_taxes[f"{tax}_rate"] = 0
|
||||
default_taxes[f"{tax}_amount"] = 0
|
||||
|
||||
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
|
||||
so_dn_map = get_delivery_notes_against_sales_order(item_list)
|
||||
@@ -90,6 +94,9 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
|
||||
|
||||
total_tax = 0
|
||||
total_other_charges = 0
|
||||
|
||||
row.update(default_taxes.copy())
|
||||
|
||||
for tax, details in itemised_tax.get(d.name, {}).items():
|
||||
row.update(
|
||||
{
|
||||
|
||||
@@ -16,9 +16,11 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def create_sales_invoice(self, do_not_submit=False):
|
||||
def create_sales_invoice(self, item=None, taxes=None, do_not_submit=False):
|
||||
si = create_sales_invoice(
|
||||
item=self.item,
|
||||
item=item or self.item,
|
||||
item_name=item or self.item,
|
||||
description=item or self.item,
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
debit_to=self.debit_to,
|
||||
@@ -29,6 +31,19 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase):
|
||||
price_list_rate=100,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
for tax in taxes or []:
|
||||
si.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": tax["account_head"],
|
||||
"cost_center": self.cost_center,
|
||||
"description": tax["description"],
|
||||
"rate": tax["rate"],
|
||||
},
|
||||
)
|
||||
|
||||
si = si.save()
|
||||
if not do_not_submit:
|
||||
si = si.submit()
|
||||
@@ -62,3 +77,50 @@ class TestItemWiseSalesRegister(AccountsTestMixin, IntegrationTestCase):
|
||||
|
||||
report_output = {k: v for k, v in report[1][0].items() if k in expected_result}
|
||||
self.assertDictEqual(report_output, expected_result)
|
||||
|
||||
def test_grouped_report_handles_different_tax_descriptions(self):
|
||||
self.create_item(item_name="_Test Item Tax Description A")
|
||||
first_item = self.item
|
||||
self.create_item(item_name="_Test Item Tax Description B")
|
||||
second_item = self.item
|
||||
|
||||
first_tax_description = "Tax Description A"
|
||||
second_tax_description = "Tax Description B"
|
||||
first_tax_amount_field = f"{frappe.scrub(first_tax_description)}_amount"
|
||||
second_tax_amount_field = f"{frappe.scrub(second_tax_description)}_amount"
|
||||
|
||||
self.create_sales_invoice(
|
||||
item=first_item,
|
||||
taxes=[
|
||||
{
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"description": first_tax_description,
|
||||
"rate": 5,
|
||||
}
|
||||
],
|
||||
)
|
||||
self.create_sales_invoice(
|
||||
item=second_item,
|
||||
taxes=[
|
||||
{
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"description": second_tax_description,
|
||||
"rate": 2,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"from_date": today(),
|
||||
"to_date": today(),
|
||||
"company": self.company,
|
||||
"group_by": "Customer",
|
||||
}
|
||||
)
|
||||
_, data, _, _, _, _ = execute(filters)
|
||||
|
||||
grand_total_row = next(row for row in data if row.get("bold") and row.get("item_code") == "Total")
|
||||
|
||||
self.assertEqual(grand_total_row[first_tax_amount_field], 5.0)
|
||||
self.assertEqual(grand_total_row[second_tax_amount_field], 2.0)
|
||||
|
||||
@@ -39,7 +39,7 @@ frappe.query_reports[PL_REPORT_NAME]["filters"].push(
|
||||
fieldname: "accumulated_values",
|
||||
label: __("Accumulated Values"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
default: 0,
|
||||
},
|
||||
{
|
||||
fieldname: "include_default_book_entries",
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 3,
|
||||
"idx": 4,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2025-11-05 11:55:49.950442",
|
||||
"letter_head": null,
|
||||
"modified": "2026-03-13 17:35:39.703838",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Trends",
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"filters": [],
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2025-11-05 11:55:50.070651",
|
||||
"letter_head": null,
|
||||
"modified": "2026-03-13 17:36:13.725601",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Trends",
|
||||
|
||||
@@ -1536,7 +1536,7 @@ def parse_naming_series_variable(doc, variable):
|
||||
getdate(doc.get("posting_date") or doc.get("transaction_date") or doc.get("posting_datetime"))
|
||||
or now_datetime()
|
||||
)
|
||||
if frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents")
|
||||
if doc and frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents")
|
||||
else now_datetime()
|
||||
)
|
||||
return date.strftime(data[variable]) if variable in data else determine_consecutive_week_number(date)
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"asset_type\":[\"!=\",\"Existing Asset\"]}",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-10-28 23:16:16.939070",
|
||||
"modified": "2026-02-03 15:48:13.407835",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Category-wise Asset Value",
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}",
|
||||
"filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"asset_type\":[\"!=\",\"Existing Asset\"]}",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-10-28 23:16:07.883312",
|
||||
"modified": "2026-02-03 15:48:13.407835",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Location-wise Asset Value",
|
||||
|
||||
@@ -100,7 +100,7 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
|
||||
"company": company,
|
||||
"status": "In Location",
|
||||
"group_by": "Asset Category",
|
||||
"is_existing_asset": 0,
|
||||
"asset_type": ["!=", "Existing Asset"],
|
||||
}
|
||||
),
|
||||
"type": "Donut",
|
||||
@@ -126,7 +126,12 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
|
||||
"x_field": "location",
|
||||
"timeseries": 0,
|
||||
"filters_json": json.dumps(
|
||||
{"company": company, "status": "In Location", "group_by": "Location", "is_existing_asset": 0}
|
||||
{
|
||||
"company": company,
|
||||
"status": "In Location",
|
||||
"group_by": "Location",
|
||||
"asset_type": ["!=", "Existing Asset"],
|
||||
}
|
||||
),
|
||||
"type": "Donut",
|
||||
"doctype": "Dashboard Chart",
|
||||
|
||||
@@ -36,6 +36,7 @@ frappe.ui.form.on("Asset", {
|
||||
},
|
||||
|
||||
company: function (frm) {
|
||||
frm.trigger("set_dynamic_labels");
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
|
||||
@@ -81,23 +82,81 @@ frappe.ui.form.on("Asset", {
|
||||
},
|
||||
|
||||
before_submit: function (frm) {
|
||||
if (frm.doc.is_composite_asset && !frm.has_active_capitalization) {
|
||||
if (frm.doc.asset_type == "Composite Asset" && !frm.has_active_capitalization) {
|
||||
frappe.throw(__("Please capitalize this asset before submitting."));
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frappe.ui.form.trigger("Asset", "is_existing_asset");
|
||||
refresh: async function (frm) {
|
||||
frm.trigger("set_dynamic_labels");
|
||||
|
||||
frappe.ui.form.trigger("Asset", "asset_type");
|
||||
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
|
||||
|
||||
let has_create_buttons = false;
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (["Submitted", "Partially Depreciated"].includes(frm.doc.status)) {
|
||||
frm.add_custom_button(
|
||||
__("Asset Value Adjustment"),
|
||||
function () {
|
||||
frm.trigger("create_asset_value_adjustment");
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Asset Repair"),
|
||||
function () {
|
||||
frm.trigger("create_asset_repair");
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
has_create_buttons = true;
|
||||
}
|
||||
|
||||
if (
|
||||
!frm.doc.calculate_depreciation &&
|
||||
["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Depreciation Entry"),
|
||||
function () {
|
||||
frm.trigger("make_journal_entry");
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
has_create_buttons = true;
|
||||
}
|
||||
|
||||
if (has_create_buttons) {
|
||||
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
|
||||
if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) {
|
||||
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
|
||||
frm.add_custom_button(
|
||||
__("Maintain Asset"),
|
||||
function () {
|
||||
frm.trigger("create_asset_maintenance");
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
}
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Split Asset"),
|
||||
function () {
|
||||
frm.trigger("split_asset");
|
||||
},
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Transfer Asset"),
|
||||
function () {
|
||||
erpnext.asset.transfer_asset(frm);
|
||||
},
|
||||
__("Manage")
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
@@ -105,7 +164,7 @@ frappe.ui.form.on("Asset", {
|
||||
function () {
|
||||
erpnext.asset.scrap_asset(frm);
|
||||
},
|
||||
__("Manage")
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
@@ -113,15 +172,7 @@ frappe.ui.form.on("Asset", {
|
||||
function () {
|
||||
frm.trigger("sell_asset");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Split Asset"),
|
||||
function () {
|
||||
frm.trigger("split_asset");
|
||||
},
|
||||
__("Manage")
|
||||
__("Actions")
|
||||
);
|
||||
} else if (frm.doc.status == "Scrapped") {
|
||||
frm.add_custom_button(__("Restore Asset"), function () {
|
||||
@@ -129,47 +180,9 @@ frappe.ui.form.on("Asset", {
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
|
||||
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
|
||||
if (await frm.events.should_show_accounting_ledger(frm)) {
|
||||
frm.add_custom_button(
|
||||
__("Maintain Asset"),
|
||||
function () {
|
||||
frm.trigger("create_asset_maintenance");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
}
|
||||
|
||||
if (["Submitted", "Partially Depreciated"].includes(frm.doc.status)) {
|
||||
frm.add_custom_button(
|
||||
__("Adjust Asset Value"),
|
||||
function () {
|
||||
frm.trigger("create_asset_value_adjustment");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Repair Asset"),
|
||||
function () {
|
||||
frm.trigger("create_asset_repair");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
}
|
||||
|
||||
if (!frm.doc.calculate_depreciation) {
|
||||
frm.add_custom_button(
|
||||
__("Create Depreciation Entry"),
|
||||
function () {
|
||||
frm.trigger("make_journal_entry");
|
||||
},
|
||||
__("Manage")
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
|
||||
frm.add_custom_button(
|
||||
__("View General Ledger"),
|
||||
__("Accounting Ledger"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
voucher_no: frm.doc.name,
|
||||
@@ -179,7 +192,7 @@ frappe.ui.form.on("Asset", {
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
},
|
||||
__("Manage")
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,7 +208,7 @@ frappe.ui.form.on("Asset", {
|
||||
if (frm.doc.docstatus == 0) {
|
||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||
|
||||
if (frm.doc.is_composite_asset) {
|
||||
if (frm.doc.asset_type == "Composite Asset") {
|
||||
frappe.call({
|
||||
method: "erpnext.assets.doctype.asset.asset.has_active_capitalization",
|
||||
args: {
|
||||
@@ -217,6 +230,32 @@ frappe.ui.form.on("Asset", {
|
||||
}
|
||||
},
|
||||
|
||||
set_dynamic_labels: function (frm) {
|
||||
frm.set_currency_labels(["net_purchase_amount"], erpnext.get_currency(frm.doc.company));
|
||||
},
|
||||
|
||||
should_show_accounting_ledger: async function (frm) {
|
||||
if (["Capitalized"].includes(frm.doc.status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!frm.doc.purchase_receipt &&
|
||||
!frm.doc.purchase_invoice &&
|
||||
["Existing Asset", "Composite Component"].includes(frm.doc.asset_type)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const asset_category = await frappe.db.get_value(
|
||||
"Asset Category",
|
||||
frm.doc.asset_category,
|
||||
"enable_cwip_accounting"
|
||||
);
|
||||
|
||||
return !!asset_category.message?.enable_cwip_accounting;
|
||||
},
|
||||
|
||||
set_depr_posting_failure_alert: function (frm) {
|
||||
const alert = `
|
||||
<div class="row">
|
||||
@@ -232,7 +271,8 @@ frappe.ui.form.on("Asset", {
|
||||
|
||||
toggle_reference_doc: function (frm) {
|
||||
const is_submitted = frm.doc.docstatus === 1;
|
||||
const is_special_asset = frm.doc.is_existing_asset || frm.doc.is_composite_asset;
|
||||
const is_special_asset =
|
||||
frm.doc.asset_type == "Existing Asset" || frm.doc.asset_type == "Composite Asset";
|
||||
|
||||
const clear_field = (field) => {
|
||||
if (frm.doc[field]) {
|
||||
@@ -508,18 +548,13 @@ frappe.ui.form.on("Asset", {
|
||||
});
|
||||
},
|
||||
|
||||
is_existing_asset: function (frm) {
|
||||
frm.trigger("toggle_reference_doc");
|
||||
},
|
||||
|
||||
is_composite_asset: function (frm) {
|
||||
asset_type: function (frm) {
|
||||
if (frm.doc.docstatus == 0) {
|
||||
if (frm.doc.is_composite_asset) {
|
||||
if (frm.doc.asset_type == "Composite Asset") {
|
||||
frm.set_value("net_purchase_amount", 0);
|
||||
} else {
|
||||
frm.set_df_property("net_purchase_amount", "read_only", 0);
|
||||
}
|
||||
frm.trigger("toggle_reference_doc");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -12,17 +12,14 @@
|
||||
"item_code",
|
||||
"item_name",
|
||||
"asset_name",
|
||||
"asset_category",
|
||||
"location",
|
||||
"image",
|
||||
"column_break_3",
|
||||
"status",
|
||||
"company",
|
||||
"asset_owner",
|
||||
"asset_owner_company",
|
||||
"is_existing_asset",
|
||||
"is_composite_asset",
|
||||
"is_composite_component",
|
||||
"asset_category",
|
||||
"asset_type",
|
||||
"maintenance_required",
|
||||
"calculate_depreciation",
|
||||
"purchase_details_section",
|
||||
"purchase_receipt",
|
||||
"purchase_receipt_item",
|
||||
@@ -30,31 +27,44 @@
|
||||
"purchase_invoice_item",
|
||||
"purchase_date",
|
||||
"available_for_use_date",
|
||||
"disposal_date",
|
||||
"column_break_23",
|
||||
"net_purchase_amount",
|
||||
"purchase_amount",
|
||||
"asset_quantity",
|
||||
"additional_asset_cost",
|
||||
"section_break_uiyd",
|
||||
"column_break_bbwr",
|
||||
"column_break_bfkm",
|
||||
"total_asset_cost",
|
||||
"disposal_date",
|
||||
"depreciation_tab",
|
||||
"calculate_depreciation",
|
||||
"column_break_33",
|
||||
"column_break_wqzi",
|
||||
"opening_accumulated_depreciation",
|
||||
"opening_number_of_booked_depreciations",
|
||||
"is_fully_depreciated",
|
||||
"column_break_33",
|
||||
"opening_number_of_booked_depreciations",
|
||||
"section_break_36",
|
||||
"finance_books",
|
||||
"section_break_33",
|
||||
"depreciation_method",
|
||||
"value_after_depreciation",
|
||||
"total_number_of_depreciations",
|
||||
"column_break_24",
|
||||
"frequency_of_depreciation",
|
||||
"column_break_24",
|
||||
"next_depreciation_date",
|
||||
"total_number_of_depreciations",
|
||||
"depreciation_schedule_sb",
|
||||
"depreciation_schedule_view",
|
||||
"insurance_details_tab",
|
||||
"other_info_tab",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"column_break_rjyw",
|
||||
"asset_owner_section",
|
||||
"asset_owner",
|
||||
"column_break_yeds",
|
||||
"asset_owner_company",
|
||||
"customer",
|
||||
"supplier",
|
||||
"insurance_section",
|
||||
"policy_number",
|
||||
"insurer",
|
||||
"insured_value",
|
||||
@@ -62,22 +72,17 @@
|
||||
"insurance_start_date",
|
||||
"insurance_end_date",
|
||||
"comprehensive_insurance",
|
||||
"other_info_tab",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"section_break_jtou",
|
||||
"status",
|
||||
"custodian",
|
||||
"department",
|
||||
"default_finance_book",
|
||||
"depr_entry_posting_status",
|
||||
"booked_fixed_asset",
|
||||
"customer",
|
||||
"supplier",
|
||||
"column_break_51",
|
||||
"department",
|
||||
"split_from",
|
||||
"journal_entry_for_scrap",
|
||||
"split_from",
|
||||
"amended_from",
|
||||
"maintenance_required",
|
||||
"booked_fixed_asset",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
@@ -106,13 +111,6 @@
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "item_code",
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Item Name"
|
||||
},
|
||||
{
|
||||
"depends_on": "item_code",
|
||||
"fetch_from": "item_code.asset_category",
|
||||
@@ -171,6 +169,8 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
@@ -207,7 +207,7 @@
|
||||
"fieldname": "purchase_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Purchase Date",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
|
||||
"read_only_depends_on": "eval:doc.asset_type != \"Existing Asset\" && doc.asset_type != \"Composite Asset\"",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -229,25 +229,18 @@
|
||||
{
|
||||
"fieldname": "available_for_use_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Available-for-use Date",
|
||||
"mandatory_depends_on": "eval:(!(doc.is_composite_component || doc.is_composite_asset) || doc.docstatus==1)"
|
||||
"label": "Available for Use Date",
|
||||
"mandatory_depends_on": "eval:(!(doc.asset_type == \"Composite Component\" || doc.asset_type == \"Composite Asset\") || doc.docstatus==1)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "calculate_depreciation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate Depreciation",
|
||||
"read_only_depends_on": "eval:(doc.is_composite_asset && !doc.net_purchase_amount) || doc.is_composite_component"
|
||||
"read_only_depends_on": "eval:(doc.asset_type == \"Composite Asset\" && !doc.net_purchase_amount) || doc.asset_type == \"Composite Component\""
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(!doc.is_composite_asset && !doc.is_composite_component)",
|
||||
"fieldname": "is_existing_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Existing Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.is_existing_asset)",
|
||||
"depends_on": "eval:(doc.asset_type == \"Existing Asset\")",
|
||||
"fieldname": "opening_accumulated_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Opening Accumulated Depreciation",
|
||||
@@ -257,18 +250,20 @@
|
||||
"columns": 10,
|
||||
"fieldname": "finance_books",
|
||||
"fieldtype": "Table",
|
||||
"label": "Finance Books",
|
||||
"options": "Asset Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_33",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1
|
||||
"hidden": 1,
|
||||
"label": "Depreciation Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Depreciation Method",
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nManual"
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual"
|
||||
},
|
||||
{
|
||||
"fieldname": "value_after_depreciation",
|
||||
@@ -295,6 +290,7 @@
|
||||
{
|
||||
"fieldname": "next_depreciation_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"label": "Next Depreciation Date",
|
||||
"no_copy": 1
|
||||
},
|
||||
@@ -364,7 +360,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"depends_on": "eval:doc.asset_type != \"Composite Asset\" && doc.asset_type != \"Existing Asset\"",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Receipt",
|
||||
@@ -373,7 +369,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
|
||||
"depends_on": "eval:doc.asset_type != \"Composite Asset\" && doc.asset_type != \"Existing Asset\"",
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
@@ -399,7 +395,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible_depends_on": "is_existing_asset",
|
||||
"collapsible_depends_on": "eval:doc.asset_type == \"Existing Asset\"",
|
||||
"fieldname": "purchase_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Purchase Details"
|
||||
@@ -413,10 +409,9 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "calculate_depreciation",
|
||||
"depends_on": "eval: doc.calculate_depreciation",
|
||||
"fieldname": "section_break_36",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Finance Books"
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "split_from",
|
||||
@@ -455,18 +450,11 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.is_existing_asset)",
|
||||
"fieldname": "is_fully_depreciated",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Is Fully Depreciated"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(!doc.is_existing_asset && !doc.is_composite_component)",
|
||||
"fieldname": "is_composite_asset",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Composite Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus > 0",
|
||||
"fieldname": "total_asset_cost",
|
||||
@@ -496,7 +484,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.is_existing_asset)",
|
||||
"depends_on": "eval:(doc.asset_type == \"Existing Asset\")",
|
||||
"fieldname": "opening_number_of_booked_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Opening Number of Booked Depreciations"
|
||||
@@ -513,15 +501,10 @@
|
||||
"hidden": 1,
|
||||
"label": "Purchase Invoice Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "insurance_details_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Insurance"
|
||||
},
|
||||
{
|
||||
"fieldname": "other_info_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Other Info"
|
||||
"label": "More Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "connections_tab",
|
||||
@@ -530,6 +513,7 @@
|
||||
"show_dashboard": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.calculate_depreciation || doc.asset_type == \"Existing Asset\"",
|
||||
"fieldname": "depreciation_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Depreciation"
|
||||
@@ -544,20 +528,61 @@
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Additional Info"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(!doc.is_existing_asset && !doc.is_composite_asset)",
|
||||
"fieldname": "is_composite_component",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Composite Component"
|
||||
},
|
||||
{
|
||||
"fieldname": "net_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Net Purchase Amount",
|
||||
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only_depends_on": "eval: doc.is_composite_asset"
|
||||
"mandatory_depends_on": "eval:(doc.asset_type != \"Composite Asset\" || doc.docstatus==1)",
|
||||
"options": "currency",
|
||||
"read_only_depends_on": "eval: doc.asset_type == \"Composite Asset\""
|
||||
},
|
||||
{
|
||||
"fieldname": "asset_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Asset Type",
|
||||
"options": "\nExisting Asset\nComposite Asset\nComposite Component"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_wqzi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_rjyw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "insurance_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Insurance"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_uiyd",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bbwr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bfkm",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 1,
|
||||
"label": "Item Name"
|
||||
},
|
||||
{
|
||||
"fieldname": "asset_owner_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Ownership"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_yeds",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@@ -601,7 +626,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2025-12-18 16:36:40.904246",
|
||||
"modified": "2026-03-12 16:07:39.543227",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -56,6 +56,7 @@ class Asset(AccountsController):
|
||||
asset_owner: DF.Literal["", "Company", "Supplier", "Customer"]
|
||||
asset_owner_company: DF.Link | None
|
||||
asset_quantity: DF.Int
|
||||
asset_type: DF.Literal["", "Existing Asset", "Composite Asset", "Composite Component"]
|
||||
available_for_use_date: DF.Date | None
|
||||
booked_fixed_asset: DF.Check
|
||||
calculate_depreciation: DF.Check
|
||||
@@ -67,7 +68,9 @@ class Asset(AccountsController):
|
||||
default_finance_book: DF.Link | None
|
||||
department: DF.Link | None
|
||||
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"]
|
||||
depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"]
|
||||
depreciation_method: DF.Literal[
|
||||
"", "Straight Line", "Double Declining Balance", "Written Down Value", "Manual"
|
||||
]
|
||||
disposal_date: DF.Date | None
|
||||
finance_books: DF.Table[AssetFinanceBook]
|
||||
frequency_of_depreciation: DF.Int
|
||||
@@ -76,9 +79,6 @@ class Asset(AccountsController):
|
||||
insurance_start_date: DF.Date | None
|
||||
insured_value: DF.Data | None
|
||||
insurer: DF.Data | None
|
||||
is_composite_asset: DF.Check
|
||||
is_composite_component: DF.Check
|
||||
is_existing_asset: DF.Check
|
||||
is_fully_depreciated: DF.Check
|
||||
item_code: DF.Link
|
||||
item_name: DF.ReadOnly | None
|
||||
@@ -243,7 +243,7 @@ class Asset(AccountsController):
|
||||
self.set_total_booked_depreciations()
|
||||
|
||||
def before_submit(self):
|
||||
if self.is_composite_asset and not has_active_capitalization(self.name):
|
||||
if self.asset_type == "Composite Asset" and not has_active_capitalization(self.name):
|
||||
if self.split_from and has_active_capitalization(self.split_from):
|
||||
return
|
||||
frappe.throw(_("Please capitalize this asset before submitting."))
|
||||
@@ -252,7 +252,11 @@ class Asset(AccountsController):
|
||||
self.validate_in_use_date()
|
||||
self.make_asset_movement()
|
||||
self.reload()
|
||||
if not self.booked_fixed_asset and not self.is_composite_component and self.validate_make_gl_entry():
|
||||
if (
|
||||
not self.booked_fixed_asset
|
||||
and self.asset_type != "Composite Component"
|
||||
and self.validate_make_gl_entry()
|
||||
):
|
||||
self.make_gl_entries()
|
||||
if self.calculate_depreciation and not self.split_from:
|
||||
convert_draft_asset_depr_schedules_into_active(self)
|
||||
@@ -267,7 +271,7 @@ class Asset(AccountsController):
|
||||
cancel_asset_depr_schedules(self)
|
||||
self.set_status()
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||
if not self.is_composite_component:
|
||||
if self.asset_type != "Composite Component":
|
||||
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
|
||||
self.db_set("booked_fixed_asset", 0)
|
||||
add_asset_activity(self.name, _("Asset cancelled"))
|
||||
@@ -285,7 +289,7 @@ class Asset(AccountsController):
|
||||
add_asset_activity(self.name, _("Asset deleted"))
|
||||
|
||||
def set_purchase_doc_row_item(self):
|
||||
if self.is_existing_asset or self.is_composite_asset:
|
||||
if self.asset_type == "Existing Asset" or self.asset_type == "Composite Asset":
|
||||
return
|
||||
|
||||
self.purchase_amount = self.net_purchase_amount
|
||||
@@ -328,7 +332,7 @@ class Asset(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
if self.is_existing_asset and self.purchase_invoice:
|
||||
if self.asset_type == "Existing Asset" and self.purchase_invoice:
|
||||
frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
|
||||
|
||||
def validate_item(self):
|
||||
@@ -374,7 +378,7 @@ class Asset(AccountsController):
|
||||
)
|
||||
|
||||
def validate_in_use_date(self):
|
||||
if not self.available_for_use_date and not self.is_composite_component:
|
||||
if not self.available_for_use_date and self.asset_type != "Composite Component":
|
||||
frappe.throw(_("Available for use date is required"))
|
||||
|
||||
for d in self.finance_books:
|
||||
@@ -442,13 +446,13 @@ class Asset(AccountsController):
|
||||
if not self.asset_category:
|
||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||
|
||||
if not flt(self.net_purchase_amount) and not self.is_composite_asset:
|
||||
if not flt(self.net_purchase_amount) and self.asset_type != "Composite Asset":
|
||||
frappe.throw(_("Net Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||
|
||||
if is_cwip_accounting_enabled(self.asset_category):
|
||||
if (
|
||||
not self.is_existing_asset
|
||||
and not self.is_composite_asset
|
||||
not self.asset_type == "Existing Asset"
|
||||
and not self.asset_type == "Composite Asset"
|
||||
and not self.purchase_receipt
|
||||
and not self.purchase_invoice
|
||||
):
|
||||
@@ -477,7 +481,7 @@ class Asset(AccountsController):
|
||||
if self.is_fully_depreciated:
|
||||
frappe.throw(_("Depreciation cannot be calculated for fully depreciated assets"))
|
||||
|
||||
if self.is_existing_asset:
|
||||
if self.asset_type == "Existing Asset":
|
||||
return
|
||||
|
||||
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
|
||||
@@ -549,7 +553,7 @@ class Asset(AccountsController):
|
||||
)
|
||||
|
||||
def validate_gross_and_purchase_amount(self):
|
||||
if self.is_existing_asset:
|
||||
if self.asset_type == "Existing Asset":
|
||||
return
|
||||
|
||||
if self.net_purchase_amount and self.net_purchase_amount != self.purchase_amount:
|
||||
@@ -617,7 +621,7 @@ class Asset(AccountsController):
|
||||
self.validate_depreciation_start_date(row)
|
||||
self.validate_total_number_of_depreciations_and_frequency(row)
|
||||
|
||||
if not self.is_existing_asset:
|
||||
if self.asset_type != "Existing Asset":
|
||||
self.opening_accumulated_depreciation = 0
|
||||
self.opening_number_of_booked_depreciations = 0
|
||||
else:
|
||||
@@ -770,7 +774,7 @@ class Asset(AccountsController):
|
||||
def get_status(self):
|
||||
"""Returns status based on whether it is draft, submitted, scrapped or depreciated"""
|
||||
if self.docstatus == 0:
|
||||
if self.is_composite_asset:
|
||||
if self.asset_type == "Composite Asset":
|
||||
status = "Work In Progress"
|
||||
else:
|
||||
status = "Draft"
|
||||
@@ -843,7 +847,7 @@ class Asset(AccountsController):
|
||||
return records
|
||||
|
||||
def validate_make_gl_entry(self):
|
||||
if self.is_composite_asset:
|
||||
if self.asset_type == "Composite Asset":
|
||||
return True
|
||||
|
||||
purchase_document = self.get_purchase_document()
|
||||
@@ -924,7 +928,7 @@ class Asset(AccountsController):
|
||||
purchase_document = self.get_purchase_document()
|
||||
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
||||
|
||||
if (self.is_composite_asset or (purchase_document and self.purchase_amount)) and getdate(
|
||||
if (self.asset_type == "Composite Asset" or (purchase_document and self.purchase_amount)) and getdate(
|
||||
self.available_for_use_date
|
||||
) <= getdate():
|
||||
gl_entries.append(
|
||||
@@ -964,7 +968,7 @@ class Asset(AccountsController):
|
||||
self.db_set("booked_fixed_asset", 1)
|
||||
|
||||
def check_asset_capitalization_gl_entries(self):
|
||||
if self.is_composite_asset:
|
||||
if self.asset_type == "Composite Asset":
|
||||
result = frappe.db.get_value(
|
||||
"Asset Capitalization",
|
||||
{"target_asset": self.name, "docstatus": 1},
|
||||
@@ -1395,7 +1399,7 @@ def process_asset_split(existing_asset, split_qty, splitted_asset=None, is_new_a
|
||||
|
||||
def set_split_asset_values(asset_doc, scaling_factor, split_qty, existing_asset, is_new_asset):
|
||||
asset_doc.net_purchase_amount = existing_asset.net_purchase_amount * scaling_factor
|
||||
asset_doc.purchase_amount = existing_asset.net_purchase_amount
|
||||
asset_doc.purchase_amount = existing_asset.net_purchase_amount * scaling_factor
|
||||
asset_doc.additional_asset_cost = existing_asset.additional_asset_cost * scaling_factor
|
||||
asset_doc.total_asset_cost = asset_doc.net_purchase_amount + asset_doc.additional_asset_cost
|
||||
asset_doc.opening_accumulated_depreciation = (
|
||||
|
||||
@@ -786,10 +786,14 @@ def get_disposal_account_and_cost_center(company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
|
||||
def get_value_after_depreciation_on_disposal_date(
|
||||
asset: str,
|
||||
disposal_date: str,
|
||||
finance_book: str | None = None,
|
||||
) -> float:
|
||||
asset_doc = frappe.get_doc("Asset", asset)
|
||||
|
||||
if asset_doc.is_composite_component:
|
||||
if asset_doc.asset_type == "Composite Component":
|
||||
validate_disposal_date(asset_doc.purchase_date, getdate(disposal_date), "purchase")
|
||||
return flt(asset_doc.value_after_depreciation)
|
||||
|
||||
|
||||
@@ -71,16 +71,16 @@ class TestAsset(AssetSetup):
|
||||
self.assertRaises(frappe.MandatoryError, asset.save)
|
||||
|
||||
def test_pr_or_pi_mandatory_if_not_existing_asset(self):
|
||||
"""Tests if either PI or PR is present if CWIP is enabled and is_existing_asset=0."""
|
||||
"""Tests if either PI or PR is present if CWIP is enabled and asset_type == Existing Asset."""
|
||||
|
||||
asset = create_asset(item_code="Macbook Pro", do_not_save=1)
|
||||
asset.is_existing_asset = 0
|
||||
asset.asset_type = ""
|
||||
|
||||
self.assertRaises(frappe.ValidationError, asset.save)
|
||||
|
||||
def test_available_for_use_date_is_after_purchase_date(self):
|
||||
asset = create_asset(item_code="Macbook Pro", calculate_depreciation=1, do_not_save=1)
|
||||
asset.is_existing_asset = 0
|
||||
asset.asset_type = ""
|
||||
asset.purchase_date = getdate("2021-10-10")
|
||||
asset.available_for_use_date = getdate("2021-10-1")
|
||||
|
||||
@@ -183,7 +183,7 @@ class TestAsset(AssetSetup):
|
||||
asset.submit()
|
||||
|
||||
def test_is_fixed_asset_set(self):
|
||||
asset = create_asset(is_existing_asset=1)
|
||||
asset = create_asset(asset_type="Existing Asset")
|
||||
doc = frappe.new_doc("Purchase Invoice")
|
||||
doc.company = "_Test Company"
|
||||
doc.supplier = "_Test Supplier"
|
||||
@@ -710,7 +710,7 @@ class TestAsset(AssetSetup):
|
||||
# create an asset
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date=purchase_date,
|
||||
purchase_date=purchase_date,
|
||||
@@ -890,7 +890,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2030-06-06",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
opening_number_of_booked_depreciations=2,
|
||||
opening_accumulated_depreciation=47178.08,
|
||||
expected_value_after_useful_life=10000,
|
||||
@@ -939,7 +939,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2030-01-01",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
depreciation_method="Double Declining Balance",
|
||||
opening_number_of_booked_depreciations=1,
|
||||
opening_accumulated_depreciation=50000,
|
||||
@@ -1680,7 +1680,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0)
|
||||
|
||||
def test_asset_cost_center(self):
|
||||
asset = create_asset(is_existing_asset=1, do_not_save=1)
|
||||
asset = create_asset(asset_type="Existing Asset", do_not_save=1)
|
||||
asset.cost_center = "Main - WP"
|
||||
|
||||
self.assertRaises(frappe.ValidationError, asset.submit)
|
||||
@@ -1717,7 +1717,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
def test_manual_depreciation_for_existing_asset(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
purchase_date="2020-01-30",
|
||||
available_for_use_date="2020-01-30",
|
||||
submit=1,
|
||||
@@ -1843,7 +1843,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
# Create composite asset
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset for Split",
|
||||
is_composite_asset=1,
|
||||
asset_type="Composite Asset",
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
asset_quantity=2, # Set quantity > 1 to allow splitting
|
||||
@@ -1937,9 +1937,7 @@ def create_asset(**args):
|
||||
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
||||
"location": args.location or "Test Location",
|
||||
"asset_owner": args.asset_owner or "Company",
|
||||
"is_existing_asset": args.is_existing_asset or 1,
|
||||
"is_composite_asset": args.is_composite_asset or 0,
|
||||
"is_composite_component": args.is_composite_component or 0,
|
||||
"asset_type": args.asset_type or "Existing Asset",
|
||||
"asset_quantity": args.get("asset_quantity") or 1,
|
||||
"depr_entry_posting_status": args.depr_entry_posting_status or "",
|
||||
}
|
||||
@@ -1961,7 +1959,7 @@ def create_asset(**args):
|
||||
},
|
||||
)
|
||||
|
||||
if asset.is_composite_asset:
|
||||
if asset.asset_type == "Composite Asset":
|
||||
asset.net_purchase_amount = 0
|
||||
asset.purchase_amount = 0
|
||||
|
||||
|
||||
@@ -16,11 +16,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
|
||||
refresh() {
|
||||
this.show_general_ledger();
|
||||
erpnext.toggle_serial_batch_fields(this.frm);
|
||||
|
||||
if (
|
||||
(this.frm.doc.stock_items && this.frm.doc.stock_items.length) ||
|
||||
!this.frm.doc.target_is_fixed_asset
|
||||
) {
|
||||
if (this.frm.doc.stock_items && this.frm.doc.stock_items.length) {
|
||||
this.show_stock_ledger();
|
||||
}
|
||||
|
||||
@@ -41,7 +39,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
|
||||
me.frm.set_query("target_asset", function () {
|
||||
return {
|
||||
filters: { is_composite_asset: 1, docstatus: 0 },
|
||||
filters: { asset_type: "Composite Asset", docstatus: 0 },
|
||||
};
|
||||
});
|
||||
|
||||
@@ -240,10 +238,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
this.calculate_totals();
|
||||
}
|
||||
|
||||
target_qty() {
|
||||
this.calculate_totals();
|
||||
}
|
||||
|
||||
rate() {
|
||||
this.calculate_totals();
|
||||
}
|
||||
@@ -485,10 +479,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
me.frm.doc.stock_items_total + me.frm.doc.asset_items_total + me.frm.doc.service_items_total;
|
||||
me.frm.doc.total_value = flt(me.frm.doc.total_value, precision("total_value"));
|
||||
|
||||
me.frm.doc.target_qty = flt(me.frm.doc.target_qty, precision("target_qty"));
|
||||
me.frm.doc.target_incoming_rate = me.frm.doc.target_qty
|
||||
? me.frm.doc.total_value / flt(me.frm.doc.target_qty)
|
||||
: me.frm.doc.total_value;
|
||||
me.frm.doc.target_incoming_rate = me.frm.doc.total_value;
|
||||
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
|
||||
@@ -9,30 +9,33 @@
|
||||
"field_order": [
|
||||
"title",
|
||||
"naming_series",
|
||||
"company",
|
||||
"target_asset",
|
||||
"target_asset_name",
|
||||
"target_item_code",
|
||||
"finance_book",
|
||||
"target_qty",
|
||||
"column_break_9",
|
||||
"company",
|
||||
"finance_book",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"set_posting_time",
|
||||
"target_batch_no",
|
||||
"target_serial_no",
|
||||
"target_item_code",
|
||||
"amended_from",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_batch_no",
|
||||
"target_has_serial_no",
|
||||
"section_break_16",
|
||||
"stock_items",
|
||||
"section_break_urtz",
|
||||
"column_break_gqep",
|
||||
"column_break_yvlx",
|
||||
"stock_items_total",
|
||||
"section_break_26",
|
||||
"asset_items",
|
||||
"section_break_arbh",
|
||||
"column_break_boeu",
|
||||
"column_break_qecy",
|
||||
"asset_items_total",
|
||||
"service_expenses_section",
|
||||
"service_items",
|
||||
"section_break_ptna",
|
||||
"column_break_szvh",
|
||||
"column_break_katv",
|
||||
"service_items_total",
|
||||
"totals_section",
|
||||
"total_value",
|
||||
@@ -55,20 +58,12 @@
|
||||
"depends_on": "eval:(doc.target_item_code && !doc.__islocal)",
|
||||
"fieldname": "target_item_code",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Item Code",
|
||||
"options": "Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "target_item_code.is_fixed_asset",
|
||||
"fieldname": "target_is_fixed_asset",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Target Is Fixed Asset",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "target_asset",
|
||||
"fieldtype": "Link",
|
||||
@@ -143,6 +138,7 @@
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"label": "Consumed Stock Items"
|
||||
},
|
||||
{
|
||||
@@ -151,49 +147,11 @@
|
||||
"label": "Stock Items",
|
||||
"options": "Asset Capitalization Stock Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "target_has_batch_no",
|
||||
"fieldname": "target_batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Batch No",
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "target_qty",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Target Qty",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "target_item_code.has_batch_no",
|
||||
"fieldname": "target_has_batch_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Target Has Batch No",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "target_item_code.has_serial_no",
|
||||
"fieldname": "target_has_serial_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Target Has Serial No",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "target_has_serial_no",
|
||||
"fieldname": "target_serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Target Serial No"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
|
||||
"fieldname": "section_break_26",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"label": "Consumed Assets"
|
||||
},
|
||||
{
|
||||
@@ -203,6 +161,7 @@
|
||||
"options": "Asset Capitalization Asset Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.stock_items_total",
|
||||
"fieldname": "stock_items_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumed Stock Total Value",
|
||||
@@ -210,6 +169,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.asset_items_total",
|
||||
"fieldname": "asset_items_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumed Asset Total Value",
|
||||
@@ -226,6 +186,7 @@
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
|
||||
"fieldname": "service_expenses_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"label": "Service Expenses"
|
||||
},
|
||||
{
|
||||
@@ -235,6 +196,7 @@
|
||||
"options": "Asset Capitalization Service Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.service_items_total",
|
||||
"fieldname": "service_items_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Service Expense Total Amount",
|
||||
@@ -277,10 +239,10 @@
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
@@ -292,12 +254,48 @@
|
||||
"label": "Target Fixed Asset Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_urtz",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gqep",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_yvlx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_arbh",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_boeu",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qecy",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ptna",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_szvh",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_katv",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-20 15:15:12.110035",
|
||||
"modified": "2026-02-06 01:52:41.890713",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization",
|
||||
|
||||
@@ -39,9 +39,6 @@ force_fields = [
|
||||
"target_asset_name",
|
||||
"item_name",
|
||||
"asset_name",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_serial_no",
|
||||
"target_has_batch_no",
|
||||
"stock_uom",
|
||||
"fixed_asset_account",
|
||||
"valuation_rate",
|
||||
@@ -76,6 +73,7 @@ class AssetCapitalization(StockController):
|
||||
naming_series: DF.Literal["ACC-ASC-.YYYY.-"]
|
||||
posting_date: DF.Date
|
||||
posting_time: DF.Time
|
||||
project: DF.Link | None
|
||||
service_items: DF.Table[AssetCapitalizationServiceItem]
|
||||
service_items_total: DF.Currency
|
||||
set_posting_time: DF.Check
|
||||
@@ -83,15 +81,9 @@ class AssetCapitalization(StockController):
|
||||
stock_items_total: DF.Currency
|
||||
target_asset: DF.Link | None
|
||||
target_asset_name: DF.Data | None
|
||||
target_batch_no: DF.Link | None
|
||||
target_fixed_asset_account: DF.Link | None
|
||||
target_has_batch_no: DF.Check
|
||||
target_has_serial_no: DF.Check
|
||||
target_incoming_rate: DF.Currency
|
||||
target_is_fixed_asset: DF.Check
|
||||
target_item_code: DF.Link | None
|
||||
target_qty: DF.Float
|
||||
target_serial_no: DF.SmallText | None
|
||||
title: DF.Data | None
|
||||
total_value: DF.Currency
|
||||
# end: auto-generated types
|
||||
@@ -190,22 +182,13 @@ class AssetCapitalization(StockController):
|
||||
if not target_item.is_fixed_asset:
|
||||
frappe.throw(_("Target Item {0} must be a Fixed Asset item").format(target_item.name))
|
||||
|
||||
if target_item.is_fixed_asset:
|
||||
self.target_qty = 1
|
||||
if flt(self.target_qty) <= 0:
|
||||
frappe.throw(_("Target Qty must be a positive number"))
|
||||
if not target_item.has_batch_no:
|
||||
self.target_batch_no = None
|
||||
if not target_item.has_serial_no:
|
||||
self.target_serial_no = ""
|
||||
|
||||
self.validate_item(target_item)
|
||||
|
||||
def validate_target_asset(self):
|
||||
if self.target_asset:
|
||||
target_asset = self.get_asset_for_validation(self.target_asset)
|
||||
|
||||
if not target_asset.is_composite_asset:
|
||||
if not target_asset.asset_type == "Composite Asset":
|
||||
frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name))
|
||||
|
||||
if target_asset.item_code != self.target_item_code:
|
||||
@@ -314,7 +297,7 @@ class AssetCapitalization(StockController):
|
||||
return frappe.db.get_value(
|
||||
"Asset",
|
||||
asset,
|
||||
["name", "item_code", "company", "status", "docstatus", "is_composite_asset"],
|
||||
["name", "item_code", "company", "status", "docstatus", "asset_type"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -380,8 +363,7 @@ class AssetCapitalization(StockController):
|
||||
self.total_value = self.stock_items_total + self.asset_items_total + self.service_items_total
|
||||
self.total_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
self.target_qty = flt(self.target_qty, self.precision("target_qty"))
|
||||
self.target_incoming_rate = self.total_value / self.target_qty
|
||||
self.target_incoming_rate = self.total_value
|
||||
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
@@ -489,7 +471,7 @@ class AssetCapitalization(StockController):
|
||||
for item in self.asset_items:
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
|
||||
if not asset.is_composite_component:
|
||||
if asset.asset_type != "Composite Component":
|
||||
if asset.calculate_depreciation:
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
|
||||
@@ -542,30 +524,29 @@ class AssetCapitalization(StockController):
|
||||
def get_composite_component_value(self):
|
||||
composite_component_value = 0
|
||||
for item in self.asset_items:
|
||||
asset = frappe.db.get_value("Asset", item.asset, ["is_composite_component"], as_dict=True)
|
||||
if asset and asset.is_composite_component:
|
||||
asset = frappe.db.get_value("Asset", item.asset, ["asset_type"], as_dict=True)
|
||||
if asset and asset.asset_type == "Composite Component":
|
||||
composite_component_value += flt(item.asset_value, item.precision("asset_value"))
|
||||
return composite_component_value
|
||||
|
||||
def get_gl_entries_for_target_item(
|
||||
self, gl_entries, target_account, target_against, precision, composite_component_value
|
||||
):
|
||||
if self.target_is_fixed_asset:
|
||||
total_value = flt(self.total_value - composite_component_value, precision)
|
||||
if total_value:
|
||||
# Capitalization
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": target_account,
|
||||
"against": ", ".join(target_against),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": total_value,
|
||||
"cost_center": self.get("cost_center"),
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
total_value = flt(self.total_value - composite_component_value, precision)
|
||||
if total_value:
|
||||
# Capitalization
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": target_account,
|
||||
"against": ", ".join(target_against),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": total_value,
|
||||
"cost_center": self.get("cost_center"),
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def update_target_asset(self):
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
@@ -611,14 +592,13 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def set_consumed_asset_status(self, asset):
|
||||
if self.docstatus == 1:
|
||||
if self.target_is_fixed_asset:
|
||||
asset.set_status("Capitalized")
|
||||
add_asset_activity(
|
||||
asset.name,
|
||||
_("Asset capitalized after Asset Capitalization {0} was submitted").format(
|
||||
get_link_to_form("Asset Capitalization", self.name)
|
||||
),
|
||||
)
|
||||
asset.set_status("Capitalized")
|
||||
add_asset_activity(
|
||||
asset.name,
|
||||
_("Asset capitalized after Asset Capitalization {0} was submitted").format(
|
||||
get_link_to_form("Asset Capitalization", self.name)
|
||||
),
|
||||
)
|
||||
else:
|
||||
asset.set_status()
|
||||
add_asset_activity(
|
||||
@@ -630,7 +610,7 @@ class AssetCapitalization(StockController):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_item_details(item_code=None, company=None):
|
||||
def get_target_item_details(item_code: str | None = None, company: str | None = None) -> frappe._dict:
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Item Details
|
||||
@@ -640,17 +620,6 @@ def get_target_item_details(item_code=None, company=None):
|
||||
|
||||
# Set Item Details
|
||||
out.target_item_name = item.item_name
|
||||
out.target_is_fixed_asset = cint(item.is_fixed_asset)
|
||||
out.target_has_batch_no = cint(item.has_batch_no)
|
||||
out.target_has_serial_no = cint(item.has_serial_no)
|
||||
|
||||
if out.target_is_fixed_asset:
|
||||
out.target_qty = 1
|
||||
|
||||
if not out.target_has_batch_no:
|
||||
out.target_batch_no = None
|
||||
if not out.target_has_serial_no:
|
||||
out.target_serial_no = ""
|
||||
|
||||
# Cost Center
|
||||
item_defaults = get_item_defaults(item.name, company)
|
||||
@@ -667,7 +636,7 @@ def get_target_item_details(item_code=None, company=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_asset_details(asset=None, company=None):
|
||||
def get_target_asset_details(asset: str | None = None, company: str | None = None) -> frappe._dict:
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Asset Details
|
||||
|
||||
@@ -10,12 +10,14 @@ from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
|
||||
from erpnext.assets.doctype.asset.test_asset import (
|
||||
create_asset,
|
||||
create_asset_data,
|
||||
create_fixed_asset_item,
|
||||
set_depreciation_settings_in_company,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_asset_depr_schedule_doc,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
|
||||
make_serial_batch_bundle,
|
||||
)
|
||||
@@ -61,7 +63,7 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset",
|
||||
is_composite_asset=1,
|
||||
asset_type="Composite Asset",
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
@@ -81,7 +83,6 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
|
||||
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||
@@ -156,7 +157,7 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset",
|
||||
is_composite_asset=1,
|
||||
asset_type="Composite Asset",
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
@@ -176,8 +177,6 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
|
||||
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
|
||||
@@ -245,7 +244,7 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset",
|
||||
is_composite_asset=1,
|
||||
asset_type="Composite Asset",
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
@@ -262,8 +261,6 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
|
||||
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
|
||||
@@ -313,7 +310,7 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset",
|
||||
is_composite_asset=1,
|
||||
asset_type="Composite Asset",
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
@@ -361,33 +358,45 @@ class TestAssetCapitalization(IntegrationTestCase):
|
||||
|
||||
wip_composite_asset = create_asset(
|
||||
asset_name="Asset Capitalization WIP Composite Asset",
|
||||
is_composite_asset=1,
|
||||
asset_type="Composite Asset",
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
|
||||
consumed_asset_value = 100000
|
||||
|
||||
consumed_asset = create_asset(
|
||||
asset_name="Asset Capitalization Consumable Asset",
|
||||
asset_value=consumed_asset_value,
|
||||
submit=1,
|
||||
warehouse="Stores - _TC",
|
||||
is_composite_component=1,
|
||||
item = create_fixed_asset_item("Asset Capitalization Consumable Asset")
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item.item_code,
|
||||
qty=1,
|
||||
rate=consumed_asset_value,
|
||||
company=company,
|
||||
warehouse="Stores - TCP1",
|
||||
)
|
||||
consumed_asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
|
||||
consumed_asset_doc = frappe.get_doc("Asset", consumed_asset_name)
|
||||
|
||||
consumed_asset_doc.update(
|
||||
{
|
||||
"asset_type": "Composite Component",
|
||||
"purchase_date": pr.posting_date,
|
||||
"available_for_use_date": pr.posting_date,
|
||||
}
|
||||
)
|
||||
consumed_asset_doc.save()
|
||||
consumed_asset_doc.submit()
|
||||
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
target_asset=wip_composite_asset.name,
|
||||
target_asset_location="Test Location",
|
||||
consumed_asset=consumed_asset.name,
|
||||
consumed_asset=consumed_asset_doc.name,
|
||||
company=company,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value)
|
||||
|
||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||
@@ -421,9 +430,6 @@ def create_asset_capitalization(**args):
|
||||
"target_item_code": target_item_code,
|
||||
"target_asset": target_asset.name,
|
||||
"target_asset_location": "Test Location",
|
||||
"target_qty": flt(args.target_qty) or 1,
|
||||
"target_batch_no": args.target_batch_no,
|
||||
"target_serial_no": args.target_serial_no,
|
||||
"finance_book": args.finance_book,
|
||||
}
|
||||
)
|
||||
@@ -516,7 +522,7 @@ def create_depreciation_asset(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
asset = frappe.new_doc("Asset")
|
||||
asset.is_existing_asset = 1
|
||||
asset.asset_type = args.asset_type or "Existing Asset"
|
||||
asset.calculate_depreciation = 1
|
||||
asset.asset_owner = "Company"
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
calculate_depreciation=1,
|
||||
depreciation_method="Straight Line",
|
||||
available_for_use_date="2023-10-10",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
opening_number_of_booked_depreciations=9,
|
||||
opening_accumulated_depreciation=265,
|
||||
depreciation_start_date="2024-07-31",
|
||||
@@ -127,7 +127,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
calculate_depreciation=1,
|
||||
depreciation_method="Straight Line",
|
||||
available_for_use_date="2023-10-10",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
opening_number_of_booked_depreciations=9,
|
||||
opening_accumulated_depreciation=265.30,
|
||||
depreciation_start_date="2024-07-31",
|
||||
@@ -165,7 +165,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
calculate_depreciation=1,
|
||||
depreciation_method="Straight Line",
|
||||
available_for_use_date="2023-11-01",
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
opening_number_of_booked_depreciations=4,
|
||||
opening_accumulated_depreciation=223.15,
|
||||
depreciation_start_date="2024-12-31",
|
||||
@@ -529,7 +529,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
depreciation_start_date="2023-03-31",
|
||||
frequency_of_depreciation=1,
|
||||
total_number_of_depreciations=12,
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
opening_accumulated_depreciation=64.52,
|
||||
opening_number_of_booked_depreciations=2,
|
||||
submit=1,
|
||||
@@ -851,7 +851,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
depreciation_start_date="2023-03-31",
|
||||
frequency_of_depreciation=1,
|
||||
total_number_of_depreciations=12,
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
opening_accumulated_depreciation=64.52,
|
||||
opening_number_of_booked_depreciations=2,
|
||||
submit=1,
|
||||
@@ -925,7 +925,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
depreciation_start_date="2021-12-31",
|
||||
frequency_of_depreciation=12,
|
||||
total_number_of_depreciations=3,
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
submit=1,
|
||||
)
|
||||
post_depreciation_entries(date="2021-12-31")
|
||||
@@ -1014,7 +1014,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
depreciation_start_date="2021-12-31",
|
||||
frequency_of_depreciation=12,
|
||||
total_number_of_depreciations=3,
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
submit=1,
|
||||
)
|
||||
post_depreciation_entries(date="2021-12-31")
|
||||
@@ -1093,7 +1093,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
||||
rate_of_depreciation=50,
|
||||
frequency_of_depreciation=12,
|
||||
total_number_of_depreciations=3,
|
||||
is_existing_asset=1,
|
||||
asset_type="Existing Asset",
|
||||
submit=1,
|
||||
)
|
||||
post_depreciation_entries(date="2021-12-31")
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
{
|
||||
"fieldname": "purpose",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Purpose",
|
||||
"options": "\nIssue\nReceipt\nTransfer\nTransfer and Issue",
|
||||
"reqd": 1
|
||||
@@ -97,7 +98,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-30 17:01:55.864353",
|
||||
"modified": "2026-03-09 17:19:02.087333",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Movement",
|
||||
|
||||
@@ -86,6 +86,26 @@ frappe.ui.form.on("Asset Repair", {
|
||||
}
|
||||
},
|
||||
|
||||
show_general_ledger: function (frm) {
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(
|
||||
__("Accounting Ledger"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
voucher_no: frm.doc.name,
|
||||
from_date: frm.doc.completion_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
categorize_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
},
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
repair_status: (frm) => {
|
||||
if (frm.doc.completion_date && frm.doc.repair_status == "Completed") {
|
||||
frappe.call({
|
||||
@@ -164,26 +184,6 @@ frappe.ui.form.on("Asset Repair Purchase Invoice", {
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
show_general_ledger: (frm) => {
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(
|
||||
__("Accounting Ledger"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
voucher_no: frm.doc.name,
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
categorize_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
},
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Asset Repair Consumed Item", {
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"company",
|
||||
"asset",
|
||||
"asset_name",
|
||||
"company",
|
||||
"column_break_2",
|
||||
"repair_status",
|
||||
"failure_date",
|
||||
@@ -28,10 +28,6 @@
|
||||
"column_break_ajbh",
|
||||
"column_break_hkem",
|
||||
"repair_cost",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"column_break_14",
|
||||
"project",
|
||||
"stock_consumption_details_section",
|
||||
"stock_items",
|
||||
"section_break_ltbb",
|
||||
@@ -43,7 +39,12 @@
|
||||
"capitalize_repair_cost",
|
||||
"increase_in_asset_life",
|
||||
"column_break_xebe",
|
||||
"total_repair_cost"
|
||||
"total_repair_cost",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"column_break_14",
|
||||
"project",
|
||||
"connection_tab"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -149,8 +150,7 @@
|
||||
{
|
||||
"fieldname": "accounting_details",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"label": "Repair Purchase Invoices"
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_items",
|
||||
@@ -206,6 +206,7 @@
|
||||
{
|
||||
"fieldname": "invoices",
|
||||
"fieldtype": "Table",
|
||||
"label": "Repair Purchase Invoices",
|
||||
"mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0;",
|
||||
"no_copy": 1,
|
||||
"options": "Asset Repair Purchase Invoice"
|
||||
@@ -244,6 +245,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.consumed_items_cost",
|
||||
"fieldname": "consumed_items_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumed Items Cost"
|
||||
@@ -256,7 +258,13 @@
|
||||
"depends_on": "capitalize_repair_cost",
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
"label": "Accounting Dimension"
|
||||
},
|
||||
{
|
||||
"fieldname": "connection_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connection",
|
||||
"show_dashboard": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -267,7 +275,7 @@
|
||||
"link_fieldname": "asset_repair"
|
||||
}
|
||||
],
|
||||
"modified": "2026-01-06 15:48:13.862505",
|
||||
"modified": "2026-02-06 14:57:54.257572",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
|
||||
@@ -360,7 +360,7 @@ class TestAssetRepair(IntegrationTestCase):
|
||||
self.assertEqual(stock_entry.asset_repair, asset_repair.name)
|
||||
|
||||
def test_gl_entries_with_capitalized_asset_repair(self):
|
||||
asset = create_asset(is_existing_asset=1, calculate_depreciation=1, submit=1)
|
||||
asset = create_asset(asset_type="Existing Asset", calculate_depreciation=1, submit=1)
|
||||
asset_repair = create_asset_repair(
|
||||
asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1
|
||||
)
|
||||
@@ -400,7 +400,7 @@ def create_asset_repair(**args):
|
||||
if args.asset:
|
||||
asset = args.asset
|
||||
else:
|
||||
asset = create_asset(is_existing_asset=1, submit=1, company=args.company)
|
||||
asset = create_asset(asset_type=args.asset_type or "Existing Asset", submit=1, company=args.company)
|
||||
asset_repair = frappe.new_doc("Asset Repair")
|
||||
asset_repair.update(
|
||||
{
|
||||
|
||||
@@ -144,7 +144,7 @@ def get_conditions(filters):
|
||||
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
|
||||
|
||||
if filters.get("only_existing_assets"):
|
||||
conditions["is_existing_asset"] = filters.get("only_existing_assets")
|
||||
conditions["asset_type"] = "Existing Asset"
|
||||
if filters.get("asset_category"):
|
||||
conditions["asset_category"] = filters.get("asset_category")
|
||||
if filters.get("cost_center"):
|
||||
@@ -274,7 +274,7 @@ def get_asset_depreciation_amount_map(filters, finance_book):
|
||||
)
|
||||
|
||||
if filters.only_existing_assets:
|
||||
query = query.where(asset.is_existing_asset == 1)
|
||||
query = query.where(asset.asset_type == "Existing Asset")
|
||||
if filters.asset_category:
|
||||
query = query.where(asset.asset_category == filters.asset_category)
|
||||
if filters.cost_center:
|
||||
@@ -325,7 +325,7 @@ def get_asset_value_adjustment_map(filters, finance_book):
|
||||
)
|
||||
|
||||
if filters.only_existing_assets:
|
||||
query = query.where(asset.is_existing_asset == 1)
|
||||
query = query.where(asset.asset_type == "Existing Asset")
|
||||
if filters.asset_category:
|
||||
query = query.where(asset.asset_category == filters.asset_category)
|
||||
if filters.cost_center:
|
||||
|
||||
@@ -282,13 +282,13 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:16:35.885540",
|
||||
"modified": "2026-03-16 13:28:19.432589",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -67,7 +67,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
},
|
||||
|
||||
transaction_date(frm) {
|
||||
prevent_past_schedule_dates(frm);
|
||||
erpnext.buying.prevent_past_schedule_dates(frm);
|
||||
frm.set_value("schedule_date", "");
|
||||
},
|
||||
|
||||
@@ -87,7 +87,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
if (frm.doc.docstatus == 0) {
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
}
|
||||
prevent_past_schedule_dates(frm);
|
||||
erpnext.buying.prevent_past_schedule_dates(frm);
|
||||
},
|
||||
|
||||
get_materials_from_supplier: function (frm) {
|
||||
@@ -779,11 +779,3 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
});
|
||||
|
||||
function prevent_past_schedule_dates(frm) {
|
||||
if (frm.doc.transaction_date) {
|
||||
frm.fields_dict["schedule_date"].datepicker?.update({
|
||||
minDate: new Date(frm.doc.transaction_date),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,6 +252,7 @@
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Required By"
|
||||
},
|
||||
{
|
||||
@@ -1327,7 +1328,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-02 00:40:47.119584",
|
||||
"modified": "2026-03-09 17:15:29.184682",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -165,14 +165,10 @@ frappe.ui.form.on("Request for Quotation", {
|
||||
},
|
||||
|
||||
show_supplier_quotation_comparison(frm) {
|
||||
const today = new Date();
|
||||
const oneMonthAgo = new Date(today);
|
||||
oneMonthAgo.setMonth(today.getMonth() - 1);
|
||||
|
||||
frappe.route_options = {
|
||||
company: frm.doc.company,
|
||||
from_date: moment(oneMonthAgo).format("YYYY-MM-DD"),
|
||||
to_date: moment(today).format("YYYY-MM-DD"),
|
||||
from_date: moment(frm.doc.transaction_date).format("YYYY-MM-DD"),
|
||||
to_date: moment(new Date()).format("YYYY-MM-DD"),
|
||||
request_for_quotation: frm.doc.name,
|
||||
};
|
||||
frappe.set_route("query-report", "Supplier Quotation Comparison");
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
@@ -77,6 +76,7 @@
|
||||
"fieldname": "vendor",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Supplier",
|
||||
"no_copy": 1,
|
||||
@@ -95,6 +95,7 @@
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"oldfieldname": "transaction_date",
|
||||
"oldfieldtype": "Date",
|
||||
@@ -147,7 +148,6 @@
|
||||
"depends_on": "eval:doc.use_html == 0",
|
||||
"fieldname": "message_for_supplier",
|
||||
"fieldtype": "Text Editor",
|
||||
"in_list_view": 1,
|
||||
"label": "Message for Supplier",
|
||||
"mandatory_depends_on": "eval:doc.use_html == 0",
|
||||
"print_hide": 1
|
||||
@@ -225,6 +225,8 @@
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
@@ -263,6 +265,7 @@
|
||||
{
|
||||
"fieldname": "schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Required Date"
|
||||
},
|
||||
{
|
||||
@@ -350,7 +353,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-01 23:38:48.079274",
|
||||
"modified": "2026-03-09 17:15:29.774614",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
"default": "Company",
|
||||
"fieldname": "supplier_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Supplier Type",
|
||||
"options": "Company\nIndividual\nPartnership",
|
||||
"reqd": 1
|
||||
@@ -238,6 +239,7 @@
|
||||
"fieldname": "default_currency",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Currency",
|
||||
"no_copy": 1,
|
||||
"options": "Currency"
|
||||
@@ -515,7 +517,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-10 21:28:01.101808",
|
||||
"modified": "2026-03-09 17:15:25.465759",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier",
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestPurchaseOrder(IntegrationTestCase):
|
||||
self.assertEqual(sq.get("items")[1].rate, 300)
|
||||
self.assertEqual(sq.get("items")[1].description, "test")
|
||||
|
||||
def test_update_supplier_quotation_child_rate_disallow(self):
|
||||
def test_update_supplier_quotation_child_rate(self):
|
||||
sq = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0])
|
||||
sq.submit()
|
||||
trans_item = json.dumps(
|
||||
@@ -47,6 +47,22 @@ class TestPurchaseOrder(IntegrationTestCase):
|
||||
},
|
||||
]
|
||||
)
|
||||
update_child_qty_rate("Supplier Quotation", trans_item, sq.name)
|
||||
sq.reload()
|
||||
self.assertEqual(sq.get("items")[0].rate, 300)
|
||||
po = make_purchase_order(sq.name)
|
||||
po.schedule_date = add_days(today(), 1)
|
||||
po.submit()
|
||||
trans_item = json.dumps(
|
||||
[
|
||||
{
|
||||
"item_code": sq.items[0].item_code,
|
||||
"rate": 20,
|
||||
"qty": sq.items[0].qty,
|
||||
"docname": sq.items[0].name,
|
||||
},
|
||||
]
|
||||
)
|
||||
self.assertRaises(
|
||||
frappe.ValidationError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name
|
||||
)
|
||||
|
||||
@@ -65,6 +65,11 @@ frappe.query_reports["Purchase Analytics"] = {
|
||||
default: "Monthly",
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "show_aggregate_value_from_subsidiary_companies",
|
||||
label: __("Show Aggregate Value from Subsidiary Companies"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
],
|
||||
get_datatable_options(options) {
|
||||
return Object.assign(options, {
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"filters": [],
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2025-11-05 11:55:50.058154",
|
||||
"letter_head": null,
|
||||
"modified": "2026-03-13 17:36:05.561765",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Trends",
|
||||
|
||||
@@ -2525,13 +2525,14 @@ class AccountsController(TransactionBase):
|
||||
grand_total = flt(self.get("rounded_total") or self.grand_total)
|
||||
automatically_fetch_payment_terms = 0
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice", "Sales Order"):
|
||||
po_or_so, doctype, fieldname = self.get_order_details()
|
||||
automatically_fetch_payment_terms = cint(
|
||||
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
||||
)
|
||||
if self.doctype != "Sales Order":
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
|
||||
if self.get("total_advance"):
|
||||
if party_account_currency == self.company_currency:
|
||||
@@ -2547,7 +2548,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if not self.get("payment_schedule"):
|
||||
if (
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice"]
|
||||
self.doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order"]
|
||||
and automatically_fetch_payment_terms
|
||||
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
|
||||
):
|
||||
@@ -2605,16 +2606,18 @@ class AccountsController(TransactionBase):
|
||||
if not self.get("items"):
|
||||
return None, None, None
|
||||
if self.doctype == "Sales Invoice":
|
||||
po_or_so = self.get("items")[0].get("sales_order")
|
||||
po_or_so_doctype = "Sales Order"
|
||||
po_or_so_doctype_name = "sales_order"
|
||||
|
||||
prev_doc = self.get("items")[0].get("sales_order")
|
||||
prev_doctype = "Sales Order"
|
||||
prev_doctype_name = "sales_order"
|
||||
elif self.doctype == "Purchase Invoice":
|
||||
prev_doc = self.get("items")[0].get("purchase_order")
|
||||
prev_doctype = "Purchase Order"
|
||||
prev_doctype_name = "purchase_order"
|
||||
else:
|
||||
po_or_so = self.get("items")[0].get("purchase_order")
|
||||
po_or_so_doctype = "Purchase Order"
|
||||
po_or_so_doctype_name = "purchase_order"
|
||||
|
||||
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
|
||||
prev_doc = self.get("items")[0].get("prevdoc_docname")
|
||||
prev_doctype = "Quotation"
|
||||
prev_doctype_name = "prevdoc_docname"
|
||||
return prev_doc, prev_doctype, prev_doctype_name
|
||||
|
||||
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
|
||||
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
|
||||
@@ -3872,20 +3875,28 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
return frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
|
||||
return False
|
||||
|
||||
def validate_quantity(child_item, new_data):
|
||||
def validate_quantity_and_rate(child_item, new_data):
|
||||
if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
|
||||
frappe.throw(
|
||||
_("Row #{0}: Quantity for Item {1} cannot be zero.").format(
|
||||
_("Row #{0}:Quantity for Item {1} cannot be zero.").format(
|
||||
new_data.get("idx"), frappe.bold(new_data.get("item_code"))
|
||||
),
|
||||
title=_("Invalid Qty"),
|
||||
)
|
||||
|
||||
if parent_doctype == "Sales Order" and flt(new_data.get("qty")) < flt(child_item.delivered_qty):
|
||||
frappe.throw(_("Cannot set quantity less than delivered quantity"))
|
||||
qty_limits = {
|
||||
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity")),
|
||||
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity")),
|
||||
}
|
||||
|
||||
if parent_doctype == "Purchase Order" and flt(new_data.get("qty")) < flt(child_item.received_qty):
|
||||
frappe.throw(_("Cannot set quantity less than received quantity"))
|
||||
if parent_doctype in qty_limits:
|
||||
qty_field, error_message = qty_limits[parent_doctype]
|
||||
if flt(new_data.get("qty")) < flt(child_item.get(qty_field)):
|
||||
frappe.throw(
|
||||
_("Row #{0}:").format(new_data.get("idx"))
|
||||
+ error_message.format(frappe.bold(new_data.get("item_code"))),
|
||||
title=_("Invalid Qty"),
|
||||
)
|
||||
|
||||
if parent_doctype in ["Quotation", "Supplier Quotation"]:
|
||||
if (parent_doctype == "Quotation" and not ordered_items) or (
|
||||
@@ -3898,7 +3909,15 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
if parent_doctype == "Quotation"
|
||||
else purchased_items.get(child_item.name)
|
||||
)
|
||||
|
||||
if qty_to_check:
|
||||
if not rate_unchanged:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot update rate as item {0} is already ordered or purchased against this quotation"
|
||||
).format(frappe.bold(new_data.get("item_code")))
|
||||
)
|
||||
|
||||
if flt(new_data.get("qty")) < qty_to_check:
|
||||
frappe.throw(_("Cannot reduce quantity than ordered or purchased quantity"))
|
||||
|
||||
@@ -4017,10 +4036,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
):
|
||||
continue
|
||||
|
||||
validate_quantity(child_item, d)
|
||||
if parent_doctype in ["Quotation", "Supplier Quotation"]:
|
||||
if not rate_unchanged:
|
||||
frappe.throw(_("Rates cannot be modified for quoted items"))
|
||||
validate_quantity_and_rate(child_item, d)
|
||||
|
||||
if flt(child_item.get("qty")) != flt(d.get("qty")):
|
||||
any_qty_changed = True
|
||||
|
||||
@@ -1092,12 +1092,9 @@ class BuyingController(SubcontractingController):
|
||||
}
|
||||
)
|
||||
for dimension in accounting_dimensions[0]:
|
||||
asset.update(
|
||||
{
|
||||
dimension["fieldname"]: self.get(dimension["fieldname"])
|
||||
or dimension.get("default_dimension")
|
||||
}
|
||||
)
|
||||
fieldname = dimension["fieldname"]
|
||||
default_dimension = accounting_dimensions[1].get(self.company, {}).get(fieldname)
|
||||
asset.update({fieldname: row.get(fieldname) or self.get(fieldname) or default_dimension})
|
||||
|
||||
asset.flags.ignore_validate = True
|
||||
asset.flags.ignore_mandatory = True
|
||||
|
||||
@@ -360,13 +360,13 @@ def copy_attributes_to_variant(item, variant):
|
||||
else:
|
||||
if item.variant_based_on == "Item Attribute":
|
||||
if variant.attributes:
|
||||
attributes_description = item.description + " "
|
||||
attributes_description = item.description or ""
|
||||
for d in variant.attributes:
|
||||
attributes_description += (
|
||||
"<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
|
||||
)
|
||||
|
||||
if attributes_description not in variant.description:
|
||||
if attributes_description not in (variant.description or ""):
|
||||
variant.description = attributes_description
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from frappe.utils import cint, nowdate, today, unique
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import build_qb_match_conditions
|
||||
from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template
|
||||
|
||||
|
||||
@@ -616,34 +617,37 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
|
||||
# income account can be any Credit account,
|
||||
# but can also be a Asset account with account_type='Income Account' in special circumstances.
|
||||
# Hence the first condition is an "OR"
|
||||
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
dt = "Account"
|
||||
|
||||
acc = qb.DocType(dt)
|
||||
condition = [
|
||||
(acc.report_type.eq("Profit and Loss") | acc.account_type.isin(["Income Account", "Temporary"])),
|
||||
acc.is_group.eq(0),
|
||||
acc.disabled.eq(0),
|
||||
]
|
||||
if txt:
|
||||
condition.append(acc.name.like(f"%{txt}%"))
|
||||
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
condition.append(acc.company.eq(filters.get("company")))
|
||||
|
||||
condition += " and tabAccount.disabled = %(disabled)s"
|
||||
user_perms = build_qb_match_conditions(dt)
|
||||
condition.extend(user_perms)
|
||||
|
||||
return frappe.db.sql(
|
||||
f"""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Income Account", "Temporary"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.`{searchfield}` LIKE %(txt)s
|
||||
{condition} {get_match_cond(doctype)}
|
||||
order by idx desc, name""",
|
||||
{
|
||||
"txt": "%" + txt + "%",
|
||||
"company": filters.get("company", ""),
|
||||
"disabled": cint(filters.get("disabled", 0)),
|
||||
},
|
||||
return (
|
||||
qb.from_(acc)
|
||||
.select(acc.name)
|
||||
.where(Criterion.all(condition))
|
||||
.orderby(acc.idx, order=Order.desc)
|
||||
.orderby(acc.name)
|
||||
.run()
|
||||
)
|
||||
|
||||
|
||||
@@ -704,26 +708,38 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters,
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
dt = "Account"
|
||||
|
||||
return frappe.db.sql(
|
||||
f"""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.disabled = 0
|
||||
and tabAccount.{searchfield} LIKE %(txt)s
|
||||
{condition} {get_match_cond(doctype)}""",
|
||||
{"company": filters.get("company", ""), "txt": "%" + txt + "%"},
|
||||
)
|
||||
acc = qb.DocType(dt)
|
||||
condition = [
|
||||
(
|
||||
acc.report_type.eq("Profit and Loss")
|
||||
| acc.account_type.isin(
|
||||
[
|
||||
"Expense Account",
|
||||
"Fixed Asset",
|
||||
"Temporary",
|
||||
"Asset Received But Not Billed",
|
||||
"Capital Work in Progress",
|
||||
]
|
||||
)
|
||||
),
|
||||
acc.is_group.eq(0),
|
||||
acc.disabled.eq(0),
|
||||
]
|
||||
if txt:
|
||||
condition.append(acc.name.like(f"%{txt}%"))
|
||||
|
||||
if filters.get("company"):
|
||||
condition.append(acc.company.eq(filters.get("company")))
|
||||
|
||||
user_perms = build_qb_match_conditions(dt)
|
||||
condition.extend(user_perms)
|
||||
|
||||
return qb.from_(acc).select(acc.name).where(Criterion.all(condition)).run()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -596,7 +596,6 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
||||
target_doc.against_sales_order = source_doc.against_sales_order
|
||||
target_doc.against_sales_invoice = source_doc.against_sales_invoice
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.si_detail = source_doc.si_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
target_doc.dn_detail = source_doc.name
|
||||
if default_warehouse_for_sales_return:
|
||||
|
||||
@@ -526,6 +526,9 @@ class SellingController(StockController):
|
||||
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
||||
return
|
||||
|
||||
if self.doctype == "Sales Invoice" and not self.update_stock and not self.is_internal_transfer():
|
||||
return
|
||||
|
||||
from erpnext.stock.serial_batch_bundle import get_batch_nos, get_serial_nos
|
||||
|
||||
allow_at_arms_length_price = frappe.get_cached_value(
|
||||
|
||||
@@ -444,7 +444,10 @@ class StatusUpdater(Document):
|
||||
):
|
||||
return
|
||||
|
||||
if args["source_dt"] != "Pick List Item" and args["target_dt"] != "Quotation Item":
|
||||
if args["source_dt"] != "Pick List Item" and args["target_dt"] not in [
|
||||
"Quotation Item",
|
||||
"Packed Item",
|
||||
]:
|
||||
if qty_or_amount == "qty":
|
||||
action_msg = _(
|
||||
'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.'
|
||||
|
||||
@@ -1439,6 +1439,16 @@ class StockController(AccountsController):
|
||||
continue
|
||||
|
||||
if qi_required: # validate row only if inspection is required on item level
|
||||
if self.doctype in [
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Sales Invoice",
|
||||
"Delivery Note",
|
||||
] and frappe.get_single_value(
|
||||
"Stock Settings", "allow_to_make_quality_inspection_after_purchase_or_delivery"
|
||||
):
|
||||
return
|
||||
|
||||
self.validate_qi_presence(row)
|
||||
if self.docstatus == 1:
|
||||
self.validate_qi_submission(row)
|
||||
@@ -1446,16 +1456,6 @@ class StockController(AccountsController):
|
||||
|
||||
def validate_qi_presence(self, row):
|
||||
"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
|
||||
if self.doctype in [
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Sales Invoice",
|
||||
"Delivery Note",
|
||||
] and frappe.get_single_value(
|
||||
"Stock Settings", "allow_to_make_quality_inspection_after_purchase_or_delivery"
|
||||
):
|
||||
return
|
||||
|
||||
if not row.quality_inspection:
|
||||
msg = _("Row #{0}: Quality Inspection is required for Item {1}").format(
|
||||
row.idx, frappe.bold(row.item_code)
|
||||
|
||||
@@ -102,10 +102,10 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:18:46.617101",
|
||||
"modified": "2026-03-16 13:28:21.198138",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Appointment Booking Settings",
|
||||
|
||||
@@ -101,12 +101,12 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-cog",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:18:52.204988",
|
||||
"modified": "2026-03-16 13:28:19.573964",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "CRM Settings",
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"label": "Banking",
|
||||
"link_to": "Banking",
|
||||
"link_type": "Workspace Sidebar",
|
||||
"modified": "2026-01-12 12:29:48.687545",
|
||||
"modified": "2026-02-12 12:29:48.687545",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Banking",
|
||||
"owner": "Administrator",
|
||||
|
||||
21
erpnext/desktop_icon/organization.json
Normal file
21
erpnext/desktop_icon/organization.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"app": "erpnext",
|
||||
"bg_color": "blue",
|
||||
"creation": "2026-02-24 17:43:08.379896",
|
||||
"docstatus": 0,
|
||||
"doctype": "Desktop Icon",
|
||||
"hidden": 0,
|
||||
"icon_type": "Link",
|
||||
"idx": 0,
|
||||
"label": "Organization",
|
||||
"link_to": "Organization",
|
||||
"link_type": "Workspace Sidebar",
|
||||
"modified": "2026-02-24 17:59:39.885360",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Organization",
|
||||
"owner": "Administrator",
|
||||
"parent_icon": "",
|
||||
"restrict_removal": 0,
|
||||
"roles": [],
|
||||
"standard": 1
|
||||
}
|
||||
@@ -219,6 +219,17 @@ website_route_rules = [
|
||||
{"from_route": "/tasks", "to_route": "Task"},
|
||||
]
|
||||
|
||||
standard_navbar_items = [
|
||||
{
|
||||
"item_label": "Delete Demo Data",
|
||||
"item_type": "Action",
|
||||
"action": "erpnext.demo.clear_demo();",
|
||||
"is_standard": 1,
|
||||
"condition": "eval: frappe.boot.sysdefaults.demo_company && frappe.boot.sysdefaults.demo_company.length > 0",
|
||||
"icon": "trash",
|
||||
},
|
||||
]
|
||||
|
||||
standard_portal_menu_items = [
|
||||
{"title": "Projects", "route": "/project", "reference_doctype": "Project", "role": "Customer"},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -637,11 +637,18 @@ erpnext.bom.BomController = class BomController extends erpnext.TransactionContr
|
||||
}
|
||||
|
||||
buying_price_list(doc) {
|
||||
this.apply_price_list();
|
||||
if (doc.rm_cost_as_per !== "Price List" && doc.buying_price_list) {
|
||||
this.frm.set_value("buying_price_list", "");
|
||||
return;
|
||||
}
|
||||
|
||||
if (doc.buying_price_list) {
|
||||
this.apply_price_list();
|
||||
}
|
||||
}
|
||||
|
||||
plc_conversion_rate(doc) {
|
||||
if (!this.in_apply_price_list) {
|
||||
if (!this.in_apply_price_list && doc.rm_cost_as_per === "Price List") {
|
||||
this.apply_price_list(null, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,18 @@ frappe.treeview_settings["BOM"] = {
|
||||
get_tree_root: false,
|
||||
show_expand_all: false,
|
||||
get_label: function (node) {
|
||||
if (node.data.qty) {
|
||||
const escape = frappe.utils.escape_html;
|
||||
let label = escape(node.data.item_code);
|
||||
if (node.data.item_name && node.data.item_code !== node.data.item_name) {
|
||||
label += `: ${escape(node.data.item_name)}`;
|
||||
}
|
||||
return `${label} <span class="badge badge-pill badge-light">${node.data.qty} ${escape(
|
||||
__(node.data.stock_uom)
|
||||
)}</span>`;
|
||||
} else {
|
||||
return node.data.item_code || node.data.value;
|
||||
if (node.is_root && node.data.value != "BOM") {
|
||||
frappe.model.with_doc("BOM", node.data.value, function () {
|
||||
var bom = frappe.model.get_doc("BOM", node.data.value);
|
||||
node.data.item_name = bom.item_name || "";
|
||||
node.data.item_code = bom.item || "";
|
||||
node.data.qty = bom.quantity || "";
|
||||
node.data.stock_uom = bom.uom || "";
|
||||
return get_bom_node(node);
|
||||
});
|
||||
}
|
||||
|
||||
return get_bom_node(node);
|
||||
},
|
||||
onload: function (me) {
|
||||
var label = frappe.get_route()[0] + "/" + frappe.get_route()[1];
|
||||
@@ -78,3 +78,22 @@ frappe.treeview_settings["BOM"] = {
|
||||
},
|
||||
view_template: "bom_item_preview",
|
||||
};
|
||||
|
||||
function get_bom_node(node) {
|
||||
if (node.data.qty) {
|
||||
const escape = frappe.utils.escape_html;
|
||||
let label = escape(node.data.item_code);
|
||||
if (node.is_root && node.data.value != "BOM") {
|
||||
label = escape(node.data.value);
|
||||
}
|
||||
|
||||
if (node.data.item_name && node.data.item_code !== node.data.item_name) {
|
||||
label += `: ${escape(node.data.item_name)}`;
|
||||
}
|
||||
return `${label} <span class="badge badge-pill badge-light">${node.data.qty} ${escape(
|
||||
__(node.data.stock_uom)
|
||||
)}</span>`;
|
||||
} else {
|
||||
return node.data.item_code || node.data.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,7 +561,10 @@ def delete_node(**kwargs):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def edit_bom_creator(doctype, docname, data, parent):
|
||||
def edit_bom_creator(doctype: str, docname: str, data: str | dict, parent: str):
|
||||
if not frappe.has_permission(doctype=doctype, ptype="write", parent_doctype="BOM Creator"):
|
||||
frappe.throw(_("You do not have permission to edit this document"), frappe.PermissionError)
|
||||
|
||||
if isinstance(data, str):
|
||||
data = frappe.parse_json(data)
|
||||
|
||||
|
||||
@@ -383,9 +383,8 @@ class JobCard(Document):
|
||||
# if key number reaches/crosses to production_capacity means capacity is full and overlap error generated
|
||||
# this will store last to_time of sequential job cards
|
||||
alloted_capacity = {1: time_logs[0]["to_time"]}
|
||||
# flag for sequential Job card found
|
||||
sequential_job_card_found = False
|
||||
for i in range(1, len(time_logs)):
|
||||
sequential_job_card_found = False
|
||||
# scanning for all Existing keys
|
||||
for key in alloted_capacity.keys():
|
||||
# if current Job Card from time is greater than last to_time in that key means these job card are sequential
|
||||
@@ -1321,9 +1320,9 @@ class JobCard(Document):
|
||||
|
||||
def is_work_order_closed(self):
|
||||
if self.work_order:
|
||||
status = frappe.get_value("Work Order", self.work_order)
|
||||
status = frappe.get_value("Work Order", self.work_order, "status")
|
||||
|
||||
if status == "Closed":
|
||||
if status in ["Closed", "Stopped"]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -239,12 +239,12 @@
|
||||
"label": "Allow Editing of Items and Quantities in Work Order"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-wrench",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:18:05.759229",
|
||||
"modified": "2026-03-16 13:28:20.714576",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
||||
@@ -731,6 +731,7 @@ class ProductionPlan(Document):
|
||||
"description": d.description,
|
||||
"stock_uom": d.stock_uom,
|
||||
"company": self.company,
|
||||
"source_warehouse": frappe.get_value("BOM", d.bom_no, "default_source_warehouse"),
|
||||
"fg_warehouse": d.warehouse,
|
||||
"production_plan": self.name,
|
||||
"production_plan_item": d.name,
|
||||
@@ -807,6 +808,7 @@ class ProductionPlan(Document):
|
||||
continue
|
||||
|
||||
work_order_data = {
|
||||
"source_warehouse": frappe.get_value("BOM", row.bom_no, "default_source_warehouse"),
|
||||
"wip_warehouse": default_warehouses.get("wip_warehouse"),
|
||||
"fg_warehouse": default_warehouses.get("fg_warehouse"),
|
||||
"scrap_warehouse": default_warehouses.get("scrap_warehouse"),
|
||||
@@ -1896,7 +1898,7 @@ def get_item_data(item_code):
|
||||
return {
|
||||
"bom_no": item_details.get("bom_no"),
|
||||
"stock_uom": item_details.get("stock_uom"),
|
||||
# "description": item_details.get("description")
|
||||
"description": item_details.get("description"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1912,6 +1914,7 @@ def get_sub_assembly_items(
|
||||
skip_available_sub_assembly_item=False,
|
||||
):
|
||||
data = get_bom_children(parent=bom_no)
|
||||
precision = frappe.get_precision("Production Plan Sub Assembly Item", "qty")
|
||||
for d in data:
|
||||
if d.expandable:
|
||||
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
|
||||
@@ -1951,8 +1954,8 @@ def get_sub_assembly_items(
|
||||
"is_sub_contracted_item": d.is_sub_contracted_item,
|
||||
"bom_level": indent,
|
||||
"indent": indent,
|
||||
"stock_qty": stock_qty,
|
||||
"required_qty": required_qty,
|
||||
"stock_qty": flt(stock_qty, precision),
|
||||
"required_qty": flt(required_qty, precision),
|
||||
"projected_qty": bin_details[d.item_code][0].get("projected_qty", 0)
|
||||
if bin_details.get(d.item_code)
|
||||
else 0,
|
||||
@@ -2098,16 +2101,16 @@ def get_raw_materials_of_sub_assembly_items(
|
||||
|
||||
for item in query.run(as_dict=True):
|
||||
key = (item.item_code, item.bom_no)
|
||||
if item.is_phantom_item:
|
||||
sub_assembly_items[key] += item.get("qty")
|
||||
existing_key = (item.item_code, item.bom_no or item.main_bom)
|
||||
|
||||
if (item.bom_no and key not in sub_assembly_items) or (
|
||||
(item.item_code, item.bom_no or item.main_bom) in existing_sub_assembly_items
|
||||
):
|
||||
if item.bom_no and not item.is_phantom_item and key not in sub_assembly_items:
|
||||
continue
|
||||
|
||||
if not item.is_phantom_item and existing_key in existing_sub_assembly_items:
|
||||
continue
|
||||
|
||||
if item.bom_no:
|
||||
planned_qty = flt(sub_assembly_items[key])
|
||||
recursion_qty = flt(item.get("qty")) if item.is_phantom_item else flt(sub_assembly_items[key])
|
||||
get_raw_materials_of_sub_assembly_items(
|
||||
existing_sub_assembly_items,
|
||||
item_details,
|
||||
@@ -2115,9 +2118,10 @@ def get_raw_materials_of_sub_assembly_items(
|
||||
item.bom_no,
|
||||
include_non_stock_items,
|
||||
sub_assembly_items,
|
||||
planned_qty=planned_qty,
|
||||
planned_qty=recursion_qty,
|
||||
)
|
||||
existing_sub_assembly_items.add((item.item_code, item.bom_no or item.main_bom))
|
||||
if not item.is_phantom_item:
|
||||
existing_sub_assembly_items.add(existing_key)
|
||||
else:
|
||||
if not item.conversion_factor and item.purchase_uom:
|
||||
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
|
||||
|
||||
@@ -2713,6 +2713,92 @@ class TestProductionPlan(IntegrationTestCase):
|
||||
[item.item_code for item in plan.mr_items], ["Item Level 1-3", "Item Level 2-3", "Item Level 3-1"]
|
||||
)
|
||||
|
||||
def test_phantom_bom_explosion_across_multiple_po_items(self):
|
||||
"""
|
||||
Regression: when the same phantom item (BOM) is referenced inside sub-assemblies
|
||||
of two different production plan items, its raw materials must be fully exploded
|
||||
for *both* plan items.
|
||||
"""
|
||||
# Setup items
|
||||
fg_a = make_item("FG for Cross-PO Phantom Test A")
|
||||
fg_b = make_item("FG for Cross-PO Phantom Test B")
|
||||
sa_a = make_item("SA for Cross-PO Phantom Test A")
|
||||
sa_b = make_item("SA for Cross-PO Phantom Test B")
|
||||
phantom = make_item("Phantom for Cross-PO Test")
|
||||
rm = make_item("RM for Cross-PO Phantom Test")
|
||||
|
||||
# Create the shared phantom BOM
|
||||
phantom_bom = make_bom(item=phantom.name, raw_materials=[rm.name], do_not_save=True)
|
||||
phantom_bom.is_phantom_bom = 1
|
||||
phantom_bom.save()
|
||||
phantom_bom.submit()
|
||||
|
||||
# Create SA-A BOM with phantom
|
||||
sa_a_bom = make_bom(item=sa_a.name, raw_materials=[phantom.name], do_not_save=True)
|
||||
sa_a_bom.items[0].bom_no = phantom_bom.name
|
||||
sa_a_bom.save()
|
||||
sa_a_bom.submit()
|
||||
|
||||
# Create SA-B BOM with the SAME phantom
|
||||
sa_b_bom = make_bom(item=sa_b.name, raw_materials=[phantom.name], do_not_save=True)
|
||||
sa_b_bom.items[0].bom_no = phantom_bom.name
|
||||
sa_b_bom.save()
|
||||
sa_b_bom.submit()
|
||||
|
||||
# Create FG-A BOM with SA-A
|
||||
fg_a_bom = make_bom(item=fg_a.name, raw_materials=[sa_a.name], do_not_save=True)
|
||||
fg_a_bom.items[0].bom_no = sa_a_bom.name
|
||||
fg_a_bom.save()
|
||||
fg_a_bom.submit()
|
||||
|
||||
# Create FG-B BOM with SA-B
|
||||
fg_b_bom = make_bom(item=fg_b.name, raw_materials=[sa_b.name], do_not_save=True)
|
||||
fg_b_bom.items[0].bom_no = sa_b_bom.name
|
||||
fg_b_bom.save()
|
||||
fg_b_bom.submit()
|
||||
|
||||
# Build Production Plan with both FGs
|
||||
plan = frappe.new_doc("Production Plan")
|
||||
plan.company = "_Test Company"
|
||||
plan.posting_date = nowdate()
|
||||
plan.ignore_existing_ordered_qty = 1
|
||||
plan.skip_available_sub_assembly_item = 1
|
||||
plan.sub_assembly_warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
for fg_item, bom in [(fg_a.name, fg_a_bom.name), (fg_b.name, fg_b_bom.name)]:
|
||||
plan.append(
|
||||
"po_items",
|
||||
{
|
||||
"use_multi_level_bom": 1,
|
||||
"item_code": fg_item,
|
||||
"bom_no": bom,
|
||||
"planned_qty": 1,
|
||||
"planned_start_date": now_datetime(),
|
||||
"stock_uom": "Nos",
|
||||
},
|
||||
)
|
||||
|
||||
plan.insert()
|
||||
plan.get_sub_assembly_items()
|
||||
|
||||
# Verify both sub-assemblies are present
|
||||
sa_items = {row.production_item for row in plan.sub_assembly_items}
|
||||
self.assertIn(sa_a.name, sa_items)
|
||||
self.assertIn(sa_b.name, sa_items)
|
||||
|
||||
plan.submit()
|
||||
|
||||
mr_items = get_items_for_material_requests(plan.as_dict())
|
||||
|
||||
# Phantom raw material should be counted twice (once per FG → SA → shared phantom)
|
||||
rm_total_qty = sum(flt(d["quantity"]) for d in mr_items if d["item_code"] == rm.name)
|
||||
self.assertEqual(
|
||||
rm_total_qty,
|
||||
2.0,
|
||||
f"Expected RM qty=2 (1 per FG via shared phantom BOM), got {rm_total_qty}. "
|
||||
"The phantom BOM was not re-exploded for the second po_item.",
|
||||
)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -268,7 +268,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-11 13:00:09.092676",
|
||||
"modified": "2026-03-16 10:28:41.879801",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan Sub Assembly Item",
|
||||
|
||||
@@ -461,10 +461,11 @@ frappe.ui.form.on("Work Order", {
|
||||
var added_min = false;
|
||||
|
||||
// produced qty
|
||||
var title = __("{0} items produced", [frm.doc.produced_qty]);
|
||||
let produced_qty = frm.doc.produced_qty - frm.doc.disassembled_qty;
|
||||
var title = __("{0} items produced", [produced_qty]);
|
||||
bars.push({
|
||||
title: title,
|
||||
width: (frm.doc.produced_qty / frm.doc.qty) * 100 + "%",
|
||||
width: (flt(produced_qty) / frm.doc.qty) * 100 + "%",
|
||||
progress_class: "progress-bar-success",
|
||||
});
|
||||
if (bars[0].width == "0%") {
|
||||
@@ -481,14 +482,27 @@ frappe.ui.form.on("Work Order", {
|
||||
if (pending_complete > 0) {
|
||||
var width = (pending_complete / frm.doc.qty) * 100 - added_min;
|
||||
title = __("{0} items in progress", [pending_complete]);
|
||||
let progress_class = "progress-bar-warning";
|
||||
if (frm.doc.status == "Closed") {
|
||||
if (frm.doc.required_items.find((d) => d.returned_qty > 0)) {
|
||||
title = __("{0} items returned", [pending_complete]);
|
||||
progress_class = "progress-bar-warning";
|
||||
} else {
|
||||
title = __("{0} items to return", [pending_complete]);
|
||||
progress_class = "progress-bar-info";
|
||||
}
|
||||
}
|
||||
|
||||
bars.push({
|
||||
title: title,
|
||||
width: (width > 100 ? "99.5" : width) + "%",
|
||||
progress_class: "progress-bar-warning",
|
||||
progress_class: progress_class,
|
||||
});
|
||||
message = message + ". " + title;
|
||||
}
|
||||
}
|
||||
|
||||
//process loss qty
|
||||
if (frm.doc.process_loss_qty) {
|
||||
var process_loss_width = (frm.doc.process_loss_qty / frm.doc.qty) * 100;
|
||||
title = __("{0} items lost during process.", [frm.doc.process_loss_qty]);
|
||||
@@ -499,6 +513,19 @@ frappe.ui.form.on("Work Order", {
|
||||
});
|
||||
message = message + ". " + title;
|
||||
}
|
||||
|
||||
// disassembled qty
|
||||
if (frm.doc.disassembled_qty) {
|
||||
var disassembled_width = (frm.doc.disassembled_qty / frm.doc.qty) * 100;
|
||||
title = __("{0} items disassembled", [frm.doc.disassembled_qty]);
|
||||
bars.push({
|
||||
title: title,
|
||||
width: disassembled_width + "%",
|
||||
progress_class: "progress-bar-secondary",
|
||||
});
|
||||
message = message + ". " + title;
|
||||
}
|
||||
|
||||
frm.dashboard.add_progress(__("Status"), bars, message);
|
||||
},
|
||||
|
||||
|
||||
@@ -570,7 +570,8 @@
|
||||
{
|
||||
"fieldname": "production_plan_sub_assembly_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Production Plan Sub-assembly Item",
|
||||
"hidden": 1,
|
||||
"label": "Production Plan Sub Assembly Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
@@ -704,7 +705,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-06 17:53:11.295600",
|
||||
"modified": "2026-03-16 10:15:28.708688",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Work Order",
|
||||
|
||||
@@ -713,19 +713,25 @@ class WorkOrder(Document):
|
||||
self.db_set("disassembled_qty", self.disassembled_qty)
|
||||
|
||||
def get_transferred_or_manufactured_qty(self, purpose, fieldname):
|
||||
table = frappe.qb.DocType("Stock Entry")
|
||||
query = frappe.qb.from_(table).where(
|
||||
(table.work_order == self.name) & (table.docstatus == 1) & (table.purpose == purpose)
|
||||
parent = frappe.qb.DocType("Stock Entry")
|
||||
|
||||
query = frappe.qb.from_(parent).where(
|
||||
(parent.work_order == self.name)
|
||||
& (parent.docstatus == 1)
|
||||
& (parent.purpose == purpose)
|
||||
& (parent.is_additional_transfer_entry == cint(fieldname == "additional_transferred_qty"))
|
||||
)
|
||||
|
||||
if purpose == "Manufacture":
|
||||
query = query.select(Sum(table.fg_completed_qty) - Sum(table.process_loss_qty))
|
||||
child = frappe.qb.DocType("Stock Entry Detail")
|
||||
query = (
|
||||
query.join(child)
|
||||
.on(parent.name == child.parent)
|
||||
.select(Sum(child.transfer_qty))
|
||||
.where(child.is_finished_item == 1)
|
||||
)
|
||||
else:
|
||||
query = query.select(Sum(table.fg_completed_qty))
|
||||
|
||||
query = query.where(
|
||||
table.is_additional_transfer_entry == cint(fieldname == "additional_transferred_qty")
|
||||
)
|
||||
query = query.select(Sum(parent.fg_completed_qty))
|
||||
|
||||
return flt(query.run()[0][0])
|
||||
|
||||
|
||||
@@ -101,9 +101,6 @@ class Workstation(Document):
|
||||
self.total_working_hours += row.hours
|
||||
|
||||
def validate_working_hours(self, row):
|
||||
if not (row.start_time and row.end_time):
|
||||
frappe.throw(_("Row #{0}: Start Time and End Time are required").format(row.idx))
|
||||
|
||||
if get_time(row.start_time) >= get_time(row.end_time):
|
||||
frappe.throw(_("Row #{0}: Start Time must be before End Time").format(row.idx))
|
||||
|
||||
|
||||
@@ -74,7 +74,6 @@ erpnext.patches.v12_0.make_item_manufacturer
|
||||
erpnext.patches.v12_0.move_item_tax_to_item_tax_template
|
||||
erpnext.patches.v11_1.set_variant_based_on
|
||||
erpnext.patches.v11_1.woocommerce_set_creation_user
|
||||
erpnext.patches.v11_1.rename_depends_on_lwp
|
||||
execute:frappe.delete_doc("Report", "Inactive Items")
|
||||
erpnext.patches.v11_1.delete_scheduling_tool
|
||||
erpnext.patches.v12_0.rename_tolerance_fields
|
||||
@@ -468,3 +467,8 @@ erpnext.patches.v15_0.replace_http_with_https_in_sales_partner
|
||||
erpnext.patches.v15_0.delete_quotation_lost_record_detail
|
||||
erpnext.patches.v16_0.add_portal_redirects
|
||||
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2
|
||||
erpnext.patches.v16_0.migrate_asset_type_checkboxes_to_select
|
||||
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
||||
erpnext.patches.v16_0.enable_serial_batch_setting
|
||||
erpnext.patches.v16_0.update_requested_qty_packed_item
|
||||
erpnext.patches.v16_0.remove_payables_receivables_workspace
|
||||
|
||||
@@ -13,6 +13,9 @@ def execute():
|
||||
return
|
||||
|
||||
company_creation = frappe.get_all("Company", fields=["creation"], order_by="creation asc", limit=1)
|
||||
if not company_creation:
|
||||
return
|
||||
|
||||
days_diff = date_diff(getdate(today()), getdate(company_creation[0].creation))
|
||||
|
||||
if days_diff > 15:
|
||||
|
||||
9
erpnext/patches/v16_0/enable_serial_batch_setting.py
Normal file
9
erpnext/patches/v16_0/enable_serial_batch_setting.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.get_all("Serial No", limit=1) and not frappe.get_all("Batch", limit=1):
|
||||
return
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_serial_and_batch_no_for_item", 1)
|
||||
frappe.db.set_default("enable_serial_and_batch_no_for_item", 1)
|
||||
@@ -0,0 +1,25 @@
|
||||
import frappe
|
||||
from frappe.query_builder import Case
|
||||
|
||||
|
||||
def execute():
|
||||
required_columns = [
|
||||
"is_existing_asset",
|
||||
"is_composite_asset",
|
||||
"is_composite_component",
|
||||
]
|
||||
|
||||
# Skip patch if any required column is missing
|
||||
if not all(frappe.db.has_column("Asset", col) for col in required_columns):
|
||||
return
|
||||
|
||||
Asset = frappe.qb.DocType("Asset")
|
||||
|
||||
frappe.qb.update(Asset).set(
|
||||
Asset.asset_type,
|
||||
Case()
|
||||
.when(Asset.is_existing_asset == 1, "Existing Asset")
|
||||
.when(Asset.is_composite_asset == 1, "Composite Asset")
|
||||
.when(Asset.is_composite_component == 1, "Composite Component")
|
||||
.else_(""),
|
||||
).run()
|
||||
@@ -0,0 +1,7 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
for ws in ["Receivables", "Payables"]:
|
||||
frappe.delete_doc_if_exists("Workspace Sidebar", ws)
|
||||
frappe.delete_doc_if_exists("Workspace", ws)
|
||||
@@ -0,0 +1,33 @@
|
||||
import frappe
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
|
||||
def execute():
|
||||
PurchaseOrderItem = DocType("Purchase Order Item")
|
||||
MaterialRequestItem = DocType("Material Request Item")
|
||||
|
||||
poi_query = (
|
||||
frappe.qb.from_(PurchaseOrderItem)
|
||||
.select(PurchaseOrderItem.sales_order_item, Sum(PurchaseOrderItem.stock_qty))
|
||||
.where(PurchaseOrderItem.sales_order_item.isnotnull() & PurchaseOrderItem.docstatus == 1)
|
||||
.groupby(PurchaseOrderItem.sales_order_item)
|
||||
)
|
||||
|
||||
mri_query = (
|
||||
frappe.qb.from_(MaterialRequestItem)
|
||||
.select(MaterialRequestItem.sales_order_item, Sum(MaterialRequestItem.stock_qty))
|
||||
.where(MaterialRequestItem.sales_order_item.isnotnull() & MaterialRequestItem.docstatus == 1)
|
||||
.groupby(MaterialRequestItem.sales_order_item)
|
||||
)
|
||||
|
||||
poi_data = poi_query.run()
|
||||
mri_data = mri_query.run()
|
||||
|
||||
updates_against_poi = {data[0]: {"ordered_qty": data[1]} for data in poi_data}
|
||||
updates_against_mri = {data[0]: {"requested_qty": data[1], "ordered_qty": 0} for data in mri_data}
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
frappe.db.bulk_update("Sales Order Item", updates_against_mri)
|
||||
frappe.db.bulk_update("Sales Order Item", updates_against_poi)
|
||||
frappe.db.auto_commit_on_many_writes = 0
|
||||
24
erpnext/patches/v16_0/update_requested_qty_packed_item.py
Normal file
24
erpnext/patches/v16_0/update_requested_qty_packed_item.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
|
||||
def execute():
|
||||
MaterialRequestItem = frappe.qb.DocType("Material Request Item")
|
||||
|
||||
mri_query = (
|
||||
frappe.qb.from_(MaterialRequestItem)
|
||||
.select(MaterialRequestItem.packed_item, Sum(MaterialRequestItem.qty))
|
||||
.where((MaterialRequestItem.packed_item.isnotnull()) & (MaterialRequestItem.docstatus == 1))
|
||||
.groupby(MaterialRequestItem.packed_item)
|
||||
)
|
||||
|
||||
mri_data = mri_query.run()
|
||||
|
||||
if not mri_data:
|
||||
return
|
||||
|
||||
updates_against_mr = {data[0]: {"requested_qty": data[1]} for data in mri_data}
|
||||
|
||||
frappe.db.auto_commit_on_many_writes = True
|
||||
frappe.db.bulk_update("Packed Item", updates_against_mr)
|
||||
frappe.db.auto_commit_on_many_writes = False
|
||||
@@ -205,7 +205,7 @@ frappe.ui.form.on("Project", {
|
||||
|
||||
collect_progress: function (frm) {
|
||||
if (frm.doc.collect_progress && !frm.doc.subject) {
|
||||
frm.set_value("subject", __("For project {0}, update your status", [frm.doc.name]));
|
||||
frm.set_value("subject", __("For project - {0}, update your status", [frm.doc.project_name]));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,29 +12,21 @@
|
||||
"project_name",
|
||||
"status",
|
||||
"project_type",
|
||||
"is_active",
|
||||
"percent_complete_method",
|
||||
"percent_complete",
|
||||
"column_break_5",
|
||||
"project_template",
|
||||
"expected_start_date",
|
||||
"expected_end_date",
|
||||
"priority",
|
||||
"department",
|
||||
"customer_details",
|
||||
"customer",
|
||||
"column_break_14",
|
||||
"sales_order",
|
||||
"users_section",
|
||||
"users",
|
||||
"copied_from",
|
||||
"section_break0",
|
||||
"notes",
|
||||
"is_active",
|
||||
"percent_complete",
|
||||
"section_break_18",
|
||||
"expected_start_date",
|
||||
"actual_start_date",
|
||||
"actual_time",
|
||||
"column_break_20",
|
||||
"expected_end_date",
|
||||
"actual_end_date",
|
||||
"costing_tab",
|
||||
"project_details",
|
||||
"estimated_costing",
|
||||
"total_costing_amount",
|
||||
@@ -50,7 +42,7 @@
|
||||
"gross_margin",
|
||||
"column_break_37",
|
||||
"per_gross_margin",
|
||||
"monitor_progress",
|
||||
"monitor_progress_tab",
|
||||
"collect_progress",
|
||||
"holiday_list",
|
||||
"frequency",
|
||||
@@ -63,7 +55,18 @@
|
||||
"weekly_time_to_send",
|
||||
"column_break_45",
|
||||
"subject",
|
||||
"message"
|
||||
"message",
|
||||
"more_info_tab",
|
||||
"customer_details",
|
||||
"customer",
|
||||
"column_break_14",
|
||||
"sales_order",
|
||||
"users_section",
|
||||
"users",
|
||||
"copied_from",
|
||||
"section_break0",
|
||||
"notes",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -115,6 +118,7 @@
|
||||
"bold": 1,
|
||||
"fieldname": "percent_complete",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "% Completed",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
@@ -135,6 +139,7 @@
|
||||
"bold": 1,
|
||||
"fieldname": "expected_start_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Expected Start Date",
|
||||
"oldfieldname": "project_start_date",
|
||||
"oldfieldtype": "Date"
|
||||
@@ -231,7 +236,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Start and End Dates"
|
||||
"label": "Timeline"
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_start_date",
|
||||
@@ -258,7 +263,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "project_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Costing and Billing",
|
||||
@@ -329,7 +333,6 @@
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "margin",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Margin",
|
||||
@@ -357,12 +360,6 @@
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "monitor_progress",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Monitor Progress"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "collect_progress",
|
||||
@@ -455,6 +452,27 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Subject",
|
||||
"mandatory_depends_on": "collect_progress"
|
||||
},
|
||||
{
|
||||
"fieldname": "costing_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Costing"
|
||||
},
|
||||
{
|
||||
"fieldname": "monitor_progress_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Progress"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "More Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "connections_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-puzzle-piece",
|
||||
@@ -462,7 +480,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"max_attachments": 4,
|
||||
"modified": "2025-08-21 17:57:58.314809",
|
||||
"modified": "2026-03-09 17:15:24.426294",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Project",
|
||||
|
||||
@@ -19,6 +19,13 @@ frappe.ui.form.on("Project Template", {
|
||||
frappe.ui.form.on("Project Template Task", {
|
||||
task: function (frm, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
|
||||
if (!row.task) {
|
||||
row.subject = null;
|
||||
refresh_field("tasks");
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.db.get_value("Task", row.task, "subject", (value) => {
|
||||
row.subject = value.subject;
|
||||
refresh_field("tasks");
|
||||
|
||||
@@ -43,10 +43,10 @@
|
||||
"label": "Fetch Timesheet in Sales Invoice"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-02 18:18:28.414222",
|
||||
"modified": "2026-03-16 13:28:20.265634",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Projects Settings",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user