mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-13 03:45:08 +00:00
Merge pull request #53292 from frappe/version-16-hotfix
chore: release v16
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,6 +320,7 @@
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
@@ -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-09 17:15:27.014131",
|
||||
"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):
|
||||
|
||||
@@ -381,6 +381,8 @@
|
||||
"fieldtype": "Date",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "posting_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",
|
||||
|
||||
@@ -1451,6 +1451,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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -81,23 +81,79 @@ 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) {
|
||||
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 +161,7 @@ frappe.ui.form.on("Asset", {
|
||||
function () {
|
||||
erpnext.asset.scrap_asset(frm);
|
||||
},
|
||||
__("Manage")
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
@@ -113,15 +169,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 +177,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 +189,7 @@ frappe.ui.form.on("Asset", {
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
},
|
||||
__("Manage")
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,7 +205,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 +227,28 @@ frappe.ui.form.on("Asset", {
|
||||
}
|
||||
},
|
||||
|
||||
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 +264,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 +541,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");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -9,20 +9,17 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"company",
|
||||
"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",
|
||||
"location",
|
||||
"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)",
|
||||
"mandatory_depends_on": "eval:(doc.asset_type != \"Composite Asset\" || doc.docstatus==1)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only_depends_on": "eval: doc.is_composite_asset"
|
||||
"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-09 17:15:32.819896",
|
||||
"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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
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,16 @@ website_route_rules = [
|
||||
{"from_route": "/tasks", "to_route": "Task"},
|
||||
]
|
||||
|
||||
standard_navbar_items = [
|
||||
{
|
||||
"item_label": "Clear Demo Data",
|
||||
"item_type": "Action",
|
||||
"action": "erpnext.demo.clear_demo();",
|
||||
"is_standard": 1,
|
||||
"condition": "eval: frappe.boot.sysdefaults.demo_company",
|
||||
},
|
||||
]
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1321,9 +1321,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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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,6 @@ 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
|
||||
|
||||
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,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
|
||||
@@ -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");
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"type",
|
||||
"color",
|
||||
"is_group",
|
||||
"is_template",
|
||||
"column_break0",
|
||||
"status",
|
||||
"priority",
|
||||
@@ -21,17 +20,21 @@
|
||||
"parent_task",
|
||||
"completed_by",
|
||||
"completed_on",
|
||||
"section_break_dafi",
|
||||
"is_template",
|
||||
"column_break_vvfp",
|
||||
"start",
|
||||
"duration",
|
||||
"sb_timeline",
|
||||
"exp_start_date",
|
||||
"expected_time",
|
||||
"start",
|
||||
"column_break_11",
|
||||
"exp_end_date",
|
||||
"progress",
|
||||
"duration",
|
||||
"is_milestone",
|
||||
"sb_details",
|
||||
"description",
|
||||
"dependencies_tab",
|
||||
"sb_depends_on",
|
||||
"depends_on",
|
||||
"depends_on_tasks",
|
||||
@@ -44,12 +47,13 @@
|
||||
"total_costing_amount",
|
||||
"column_break_20",
|
||||
"total_billing_amount",
|
||||
"more_info_tab",
|
||||
"sb_more_info",
|
||||
"company",
|
||||
"review_date",
|
||||
"closing_date",
|
||||
"column_break_22",
|
||||
"department",
|
||||
"company",
|
||||
"lft",
|
||||
"rgt",
|
||||
"old_parent",
|
||||
@@ -78,7 +82,6 @@
|
||||
"oldfieldname": "project",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Project",
|
||||
"remember_last_selected_value": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -218,7 +221,6 @@
|
||||
{
|
||||
"fieldname": "sb_depends_on",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dependencies",
|
||||
"oldfieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
@@ -298,10 +300,9 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "sb_more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Info"
|
||||
"label": "Additional Info"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"",
|
||||
@@ -334,8 +335,7 @@
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "lft",
|
||||
@@ -368,6 +368,7 @@
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_template",
|
||||
"fieldtype": "Check",
|
||||
@@ -397,6 +398,24 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Template Task"
|
||||
},
|
||||
{
|
||||
"fieldname": "dependencies_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Dependencies"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "More Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_dafi",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vvfp",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-check",
|
||||
@@ -404,11 +423,11 @@
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2025-10-16 08:39:12.214577",
|
||||
"modified": "2026-03-04 11:47:10.454548",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Task",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"naming_rule": "Expression",
|
||||
"nsm_parent_field": "parent_task",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
@@ -425,6 +444,7 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "subject",
|
||||
"show_name_in_global_search": 1,
|
||||
"show_preview_popup": 1,
|
||||
@@ -434,4 +454,4 @@
|
||||
"timeline_field": "project",
|
||||
"title_field": "subject",
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,8 @@ class Task(NestedSet):
|
||||
def validate_status(self):
|
||||
if self.is_template and self.status != "Template":
|
||||
self.status = "Template"
|
||||
if self.status == "Template" and not self.is_template:
|
||||
self.status = "Open"
|
||||
if self.status != self.get_db_value("status") and self.status == "Completed":
|
||||
for d in self.depends_on:
|
||||
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):
|
||||
|
||||
@@ -260,6 +260,33 @@ frappe.ui.form.on("Timesheet", {
|
||||
parent_project: function (frm) {
|
||||
set_project_in_timelog(frm);
|
||||
},
|
||||
|
||||
employee: function (frm) {
|
||||
if (frm.doc.employee && frm.doc.time_logs) {
|
||||
const selected_employee = frm.doc.employee;
|
||||
frm.doc.time_logs.forEach((row) => {
|
||||
if (row.activity_type) {
|
||||
frappe.call({
|
||||
method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost",
|
||||
args: {
|
||||
employee: frm.doc.employee,
|
||||
activity_type: row.activity_type,
|
||||
currency: frm.doc.currency,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (selected_employee !== frm.doc.employee) return;
|
||||
row.billing_rate = r.message["billing_rate"];
|
||||
row.costing_rate = r.message["costing_rate"];
|
||||
frm.refresh_fields("time_logs");
|
||||
calculate_billing_costing_amount(frm, row.doctype, row.name);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Timesheet Detail", {
|
||||
|
||||
@@ -18,28 +18,29 @@
|
||||
"column_break_3",
|
||||
"status",
|
||||
"parent_project",
|
||||
"employee_detail",
|
||||
"employee",
|
||||
"employee_name",
|
||||
"department",
|
||||
"column_break_9",
|
||||
"user",
|
||||
"start_date",
|
||||
"end_date",
|
||||
"employee_detail",
|
||||
"employee",
|
||||
"department",
|
||||
"column_break_9",
|
||||
"employee_name",
|
||||
"section_break_5",
|
||||
"time_logs",
|
||||
"working_hours",
|
||||
"total_hours",
|
||||
"billing_tab",
|
||||
"billing_details",
|
||||
"total_billable_hours",
|
||||
"total_billable_amount",
|
||||
"total_costing_amount",
|
||||
"base_total_billable_amount",
|
||||
"base_total_billed_amount",
|
||||
"base_total_costing_amount",
|
||||
"column_break_10",
|
||||
"total_billed_hours",
|
||||
"total_billable_amount",
|
||||
"total_billed_amount",
|
||||
"total_costing_amount",
|
||||
"base_total_billed_amount",
|
||||
"per_billed",
|
||||
"section_break_18",
|
||||
"note",
|
||||
@@ -176,7 +177,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "billing_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Billing Details",
|
||||
@@ -304,13 +304,18 @@
|
||||
"fieldname": "exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Exchange Rate"
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Billing"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-clock-o",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-19 13:48:23.453636",
|
||||
"modified": "2026-03-04 11:56:51.438298",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Timesheet",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.5714 0H15.4286C6.90761 0 0 6.90761 0 15.4286V38.5714C0 47.0924 6.90761 54 15.4286 54H38.5714C47.0924 54 54 47.0924 54 38.5714V15.4286C54 6.90761 47.0924 0 38.5714 0Z" fill="#0289F7"/>
|
||||
<path d="M19.2857 15.4286H22.1786C23.7763 15.4286 25.0714 16.7237 25.0714 18.3214V24.1071C25.0714 25.7048 23.7763 27 22.1786 27H19.2857V38.5714H15.4286V27H11.5714V23.1428H21.2143V19.2857H11.5714V15.4286H15.4286V11.5714H19.2857V15.4286ZM38.5714 38.5714H34.7143V34.7143H31.8214C30.2238 34.7143 28.9286 33.4191 28.9286 31.8214V26.0357C28.9286 24.438 30.2238 23.1428 31.8214 23.1428H34.7143V11.5714H38.5714V23.1428H42.4286V27H32.7857V30.8571H42.4286V34.7143H38.5714V38.5714Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 787 B |
@@ -0,0 +1,5 @@
|
||||
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 0H8C3.58172 0 0 3.58172 0 8V20C0 24.4183 3.58172 28 8 28H20C24.4183 28 28 24.4183 28 20V8C28 3.58172 24.4183 0 20 0Z" fill="#0289F7"/>
|
||||
<path d="M20.5 13.25C20.5 13.0926 20.5 13 20.5 13L18.5 11.499V19.5H20C20.2761 19.5 20 19.5 20.5 19.5V13.25ZM14.5 14V16H10.5V14H14.5ZM22.5 19C22.5 20.3807 21.3807 21.5 20 21.5H16.5V19.5V7.5C16.5 7.5 16.2761 7.5 16 7.5H9C8.72386 7.5 9 7.5 8.5 7.5V19.5C9 19.5 8.72386 19.5 9 19.5H12.5V21.5H9C7.61929 21.5 6.5 20.3807 6.5 19V8C6.5 6.61929 7.61929 5.5 9 5.5H16C17.3807 5.5 18.5 6.61929 18.5 8V9L21.5 11.25C22.1295 11.7221 22.5 12.4631 22.5 13.25V19Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="54" height="54" viewBox="0 0 54 54" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M38.5714 0H15.4286C6.90761 0 0 6.90761 0 15.4286V38.5714C0 47.0924 6.90761 54 15.4286 54H38.5714C47.0924 54 54 47.0924 54 38.5714V15.4286C54 6.90761 47.0924 0 38.5714 0Z" fill="#0289F7" fill-opacity="0.1"/>
|
||||
<path d="M19.2857 15.4286H22.1786C23.7762 15.4286 25.0714 16.7238 25.0714 18.3215V24.1072C25.0714 25.7049 23.7762 27 22.1786 27H19.2857V38.5715H15.4286V27H11.5714V23.1429H21.2143V19.2858H11.5714V15.4286H15.4286V11.5715H19.2857V15.4286ZM38.5714 38.5715H34.7143V34.7143H31.8214C30.2237 34.7143 28.9286 33.4192 28.9286 31.8215V26.0358C28.9286 24.4381 30.2237 23.1429 31.8214 23.1429H34.7143V11.5715H38.5714V23.1429H42.4286V27H32.7857V30.8572H42.4286V34.7143H38.5714V38.5715Z" fill="#0981E3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 809 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 0H8C3.58172 0 0 3.58172 0 8V20C0 24.4183 3.58172 28 8 28H20C24.4183 28 28 24.4183 28 20V8C28 3.58172 24.4183 0 20 0Z" fill="#0289F7" fill-opacity="0.1"/>
|
||||
<path d="M20.5 13.25C20.5 13.0926 20.5 13 20.5 13L18.5 11.499V19.5H20C20.2761 19.5 20 19.5 20.5 19.5V13.25ZM14.5 14V16H10.5V14H14.5ZM22.5 19C22.5 20.3807 21.3807 21.5 20 21.5H16.5V19.5V7.5C16.5 7.5 16.2761 7.5 16 7.5H9C8.72386 7.5 9 7.5 8.5 7.5V19.5C9 19.5 8.72386 19.5 9 19.5H12.5V21.5H9C7.61929 21.5 6.5 20.3807 6.5 19V8C6.5 6.61929 7.61929 5.5 9 5.5H16C17.3807 5.5 18.5 6.61929 18.5 8V9L21.5 11.25C22.1295 11.7221 22.5 12.4631 22.5 13.25V19Z" fill="#0981E3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 733 B |
@@ -543,7 +543,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
schedules: selected,
|
||||
},
|
||||
});
|
||||
|
||||
frappe.model.sync(pr_name);
|
||||
frappe.set_route("Form", "Payment Request", pr_name.name);
|
||||
},
|
||||
});
|
||||
@@ -580,6 +580,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.validate_has_items();
|
||||
erpnext.utils.view_serial_batch_nos(this.frm);
|
||||
this.set_route_options_for_new_doc();
|
||||
erpnext.toggle_serial_batch_fields(this.frm);
|
||||
}
|
||||
|
||||
set_route_options_for_new_doc() {
|
||||
@@ -1307,6 +1308,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
if (this.frm.doc.transaction_date) {
|
||||
this.frm.transaction_date = this.frm.doc.transaction_date;
|
||||
frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
|
||||
this.recalculate_terms();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,77 @@ $.extend(erpnext, {
|
||||
return currency_list;
|
||||
},
|
||||
|
||||
toggle_serial_batch_fields(frm) {
|
||||
let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0;
|
||||
let fields = ["serial_and_batch_bundle", "use_serial_batch_fields", "serial_no", "batch_no"];
|
||||
|
||||
if (
|
||||
[
|
||||
"Stock Entry",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
"Stock Reconciliation",
|
||||
"Subcontracting Receipt",
|
||||
].includes(frm.doc.doctype)
|
||||
) {
|
||||
fields.push("add_serial_batch_bundle");
|
||||
}
|
||||
|
||||
if (["Stock Reconciliation"].includes(frm.doc.doctype)) {
|
||||
fields.push("reconcile_all_serial_batch");
|
||||
}
|
||||
|
||||
if (["Sales Invoice", "Delivery Note", "Pick List"].includes(frm.doc.doctype)) {
|
||||
fields.push("pick_serial_and_batch");
|
||||
}
|
||||
|
||||
if (["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(frm.doc.doctype)) {
|
||||
fields.push("add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle");
|
||||
}
|
||||
|
||||
let child_name = "items";
|
||||
if (frm.doc.doctype === "Pick List") {
|
||||
child_name = "locations";
|
||||
}
|
||||
|
||||
if (frm.doc.doctype === "Asset Capitalization") {
|
||||
child_name = "stock_items";
|
||||
}
|
||||
|
||||
fields.forEach((field) => {
|
||||
if (frm.fields_dict[child_name].get_field(field)) {
|
||||
frm.fields_dict[child_name].grid.update_docfield_property(field, "hidden", hide_fields);
|
||||
|
||||
frm.fields_dict[child_name].grid.update_docfield_property(
|
||||
field,
|
||||
"in_list_view",
|
||||
hide_fields ? 0 : 1
|
||||
);
|
||||
|
||||
if (
|
||||
frm.doc.doctype === "Subcontracting Receipt" &&
|
||||
!["add_serial_batch_for_rejected_qty", "rejected_serial_and_batch_bundle"].includes(field)
|
||||
) {
|
||||
frm.fields_dict["supplied_items"].grid.update_docfield_property(
|
||||
field,
|
||||
"hidden",
|
||||
hide_fields
|
||||
);
|
||||
|
||||
frm.fields_dict["supplied_items"].grid.update_docfield_property(
|
||||
field,
|
||||
"in_list_view",
|
||||
hide_fields ? 0 : 1
|
||||
);
|
||||
|
||||
frm.fields_dict["supplied_items"].grid.reset_grid();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.fields_dict[child_name].grid.reset_grid();
|
||||
},
|
||||
|
||||
toggle_naming_series: function () {
|
||||
if (
|
||||
cur_frm &&
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
"fieldname": "default_currency",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Currency",
|
||||
"no_copy": 1,
|
||||
"options": "Currency"
|
||||
@@ -639,7 +640,7 @@
|
||||
"link_fieldname": "party"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-02 15:39:55.920831",
|
||||
"modified": "2026-03-09 17:15:26.040050",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -213,6 +213,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
@@ -1127,7 +1128,7 @@
|
||||
"idx": 82,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-06 17:34:22.170032",
|
||||
"modified": "2026-03-09 17:15:31.941114",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
@@ -7,7 +7,7 @@ import json
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.utils import flt, getdate, nowdate
|
||||
from frappe.utils import cint, flt, getdate, nowdate
|
||||
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
|
||||
@@ -446,6 +446,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
automatically_fetch_payment_terms = cint(
|
||||
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
|
||||
)
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Quotation",
|
||||
source_name,
|
||||
@@ -453,6 +457,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
||||
"Quotation": {
|
||||
"doctype": "Sales Order",
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
"field_no_map": ["payment_terms_template"],
|
||||
},
|
||||
"Quotation Item": {
|
||||
"doctype": "Sales Order Item",
|
||||
@@ -462,13 +467,15 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
||||
"Payment Schedule": {"doctype": "Payment Schedule", "add_if_empty": True},
|
||||
},
|
||||
target_doc,
|
||||
set_missing_values,
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
|
||||
if automatically_fetch_payment_terms:
|
||||
doclist.set_payment_schedule()
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -59,8 +59,22 @@ class TestQuotation(IntegrationTestCase):
|
||||
qo.payment_schedule[0].due_date = add_days(qo.transaction_date, -2)
|
||||
self.assertRaises(frappe.ValidationError, qo.save)
|
||||
|
||||
def test_update_child_disallow_rate_change(self):
|
||||
qo = make_quotation(qty=4)
|
||||
def test_update_child_rate_change(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item_1 = make_item("_Test Item")
|
||||
item_2 = make_item("_Test Item 1")
|
||||
|
||||
item_list = [
|
||||
{"item_code": item_1.item_code, "warehouse": "_Test Warehouse - _TC", "qty": 10, "rate": 300},
|
||||
{"item_code": item_2.item_code, "warehouse": "_Test Warehouse - _TC", "qty": 5, "rate": 400},
|
||||
]
|
||||
|
||||
qo = make_quotation(item_list=item_list)
|
||||
so = make_sales_order(qo.name, args={"filtered_children": [qo.items[0].name]})
|
||||
so.delivery_date = nowdate()
|
||||
so.submit()
|
||||
qo.reload()
|
||||
trans_item = json.dumps(
|
||||
[
|
||||
{
|
||||
@@ -68,10 +82,35 @@ class TestQuotation(IntegrationTestCase):
|
||||
"rate": 5000,
|
||||
"qty": qo.items[0].qty,
|
||||
"docname": qo.items[0].name,
|
||||
}
|
||||
},
|
||||
{
|
||||
"item_code": qo.items[1].item_code,
|
||||
"rate": qo.items[1].rate,
|
||||
"qty": qo.items[1].qty,
|
||||
"docname": qo.items[1].name,
|
||||
},
|
||||
]
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, update_child_qty_rate, "Quotation", trans_item, qo.name)
|
||||
trans_item = json.dumps(
|
||||
[
|
||||
{
|
||||
"item_code": qo.items[0].item_code,
|
||||
"rate": qo.items[0].rate,
|
||||
"qty": qo.items[0].qty,
|
||||
"docname": qo.items[0].name,
|
||||
},
|
||||
{
|
||||
"item_code": qo.items[1].item_code,
|
||||
"rate": 50,
|
||||
"qty": qo.items[1].qty,
|
||||
"docname": qo.items[1].name,
|
||||
},
|
||||
]
|
||||
)
|
||||
update_child_qty_rate("Quotation", trans_item, qo.name)
|
||||
qo.reload()
|
||||
self.assertEqual(qo.items[1].rate, 50)
|
||||
|
||||
def test_update_child_removing_item(self):
|
||||
qo = make_quotation(qty=10)
|
||||
@@ -143,6 +182,10 @@ class TestQuotation(IntegrationTestCase):
|
||||
|
||||
self.assertTrue(quotation.payment_schedule)
|
||||
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"automatically_fetch_payment_terms": 1},
|
||||
)
|
||||
def test_make_sales_order_terms_copied(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
|
||||
@@ -285,7 +328,11 @@ class TestQuotation(IntegrationTestCase):
|
||||
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"add_taxes_from_item_tax_template": 0, "add_taxes_from_taxes_and_charges_template": 0},
|
||||
{
|
||||
"add_taxes_from_item_tax_template": 0,
|
||||
"add_taxes_from_taxes_and_charges_template": 0,
|
||||
"automatically_fetch_payment_terms": 1,
|
||||
},
|
||||
)
|
||||
def test_make_sales_order_with_terms(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
@@ -323,10 +370,13 @@ class TestQuotation(IntegrationTestCase):
|
||||
sales_order.save()
|
||||
|
||||
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00)
|
||||
self.assertEqual(sales_order.payment_schedule[0].due_date, getdate(quotation.transaction_date))
|
||||
self.assertEqual(
|
||||
getdate(sales_order.payment_schedule[0].due_date), getdate(quotation.transaction_date)
|
||||
)
|
||||
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 8906.00)
|
||||
self.assertEqual(
|
||||
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
|
||||
getdate(sales_order.payment_schedule[1].due_date),
|
||||
getdate(add_days(quotation.transaction_date, 30)),
|
||||
)
|
||||
|
||||
def test_valid_till_before_transaction_date(self):
|
||||
@@ -1026,6 +1076,56 @@ class TestQuotation(IntegrationTestCase):
|
||||
quotation.reload()
|
||||
self.assertEqual(quotation.status, "Open")
|
||||
|
||||
@IntegrationTestCase.change_settings(
|
||||
"Accounts Settings",
|
||||
{"automatically_fetch_payment_terms": 1},
|
||||
)
|
||||
def test_make_sales_order_with_payment_terms(self):
|
||||
from erpnext.selling.doctype.quotation.quotation import make_sales_order
|
||||
|
||||
template = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Payment Terms Template",
|
||||
"template_name": "_Test Payment Terms Template for Quotation",
|
||||
"terms": [
|
||||
{
|
||||
"doctype": "Payment Terms Template Detail",
|
||||
"invoice_portion": 50.00,
|
||||
"credit_days_based_on": "Day(s) after invoice date",
|
||||
"credit_days": 0,
|
||||
},
|
||||
{
|
||||
"doctype": "Payment Terms Template Detail",
|
||||
"invoice_portion": 50.00,
|
||||
"credit_days_based_on": "Day(s) after invoice date",
|
||||
"credit_days": 10,
|
||||
},
|
||||
],
|
||||
}
|
||||
).save()
|
||||
|
||||
quotation = make_quotation(qty=10, rate=1000, do_not_submit=1)
|
||||
quotation.transaction_date = add_days(nowdate(), -2)
|
||||
quotation.valid_till = add_days(nowdate(), 10)
|
||||
quotation.update({"payment_terms_template": template.name, "payment_schedule": []})
|
||||
quotation.save()
|
||||
quotation.submit()
|
||||
|
||||
self.assertEqual(quotation.payment_schedule[0].payment_amount, 5000)
|
||||
self.assertEqual(quotation.payment_schedule[1].payment_amount, 5000)
|
||||
self.assertEqual(quotation.payment_schedule[0].due_date, quotation.transaction_date)
|
||||
self.assertEqual(quotation.payment_schedule[1].due_date, add_days(quotation.transaction_date, 10))
|
||||
|
||||
sales_order = make_sales_order(quotation.name)
|
||||
sales_order.transaction_date = nowdate()
|
||||
sales_order.delivery_date = nowdate()
|
||||
sales_order.save()
|
||||
|
||||
self.assertEqual(sales_order.payment_schedule[0].due_date, sales_order.transaction_date)
|
||||
self.assertEqual(sales_order.payment_schedule[1].due_date, add_days(sales_order.transaction_date, 10))
|
||||
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 5000)
|
||||
self.assertEqual(sales_order.payment_schedule[1].payment_amount, 5000)
|
||||
|
||||
|
||||
def enable_calculate_bundle_price(enable=1):
|
||||
selling_settings = frappe.get_doc("Selling Settings")
|
||||
|
||||
@@ -56,6 +56,13 @@ frappe.ui.form.on("Sales Order", {
|
||||
frm.set_df_property("packed_items", "cannot_add_rows", true);
|
||||
frm.set_df_property("packed_items", "cannot_delete_rows", true);
|
||||
},
|
||||
delivery_date(frm) {
|
||||
if (frm.doc.delivery_date) {
|
||||
frm.doc.items.forEach((d) => {
|
||||
frappe.model.set_value(d.doctype, d.name, "delivery_date", frm.doc.delivery_date);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.fields_dict["items"].grid.update_docfield_property(
|
||||
@@ -158,7 +165,7 @@ frappe.ui.form.on("Sales Order", {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
prevent_past_delivery_dates(frm);
|
||||
// Hide `Reserve Stock` field description in submitted or cancelled Sales Order.
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.set_df_property("reserve_stock", "description", null);
|
||||
@@ -238,13 +245,6 @@ frappe.ui.form.on("Sales Order", {
|
||||
];
|
||||
},
|
||||
|
||||
delivery_date: function (frm) {
|
||||
$.each(frm.doc.items || [], function (i, d) {
|
||||
if (!d.delivery_date) d.delivery_date = frm.doc.delivery_date;
|
||||
});
|
||||
refresh_field("items");
|
||||
},
|
||||
|
||||
create_stock_reservation_entries(frm) {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Stock Reservation"),
|
||||
@@ -1816,3 +1816,11 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
};
|
||||
|
||||
extend_cscript(cur_frm.cscript, new erpnext.selling.SalesOrderController({ frm: cur_frm }));
|
||||
|
||||
function prevent_past_delivery_dates(frm) {
|
||||
if (frm.doc.transaction_date) {
|
||||
frm.fields_dict["delivery_date"].datepicker?.update({
|
||||
minDate: new Date(frm.doc.transaction_date),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"company_contact_person",
|
||||
"payment_schedule_section",
|
||||
"payment_terms_section",
|
||||
"ignore_default_payment_terms_template",
|
||||
"payment_terms_template",
|
||||
"payment_schedule",
|
||||
"terms_section_break",
|
||||
@@ -1733,6 +1734,14 @@
|
||||
"fieldtype": "Time",
|
||||
"label": "Time",
|
||||
"mandatory_depends_on": "is_internal_customer"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_default_payment_terms_template",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Ignore Default Payment Terms Template",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1740,7 +1749,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-02 00:42:18.834823",
|
||||
"modified": "2026-03-04 18:04:05.873483",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -116,6 +116,7 @@ class SalesOrder(SellingController):
|
||||
grand_total: DF.Currency
|
||||
group_same_items: DF.Check
|
||||
has_unit_price_items: DF.Check
|
||||
ignore_default_payment_terms_template: DF.Check
|
||||
ignore_pricing_rule: DF.Check
|
||||
in_words: DF.Data | None
|
||||
incoterm: DF.Link | None
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
"ordered_qty",
|
||||
"planned_qty",
|
||||
"production_plan_qty",
|
||||
"requested_qty",
|
||||
"column_break_69",
|
||||
"work_order_qty",
|
||||
"delivered_qty",
|
||||
@@ -1010,13 +1011,21 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Finished Good Qty",
|
||||
"mandatory_depends_on": "eval:parent.is_subcontracted"
|
||||
},
|
||||
{
|
||||
"fieldname": "requested_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Requested Qty",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-20 16:39:00.200328",
|
||||
"modified": "2026-02-21 16:39:00.200328",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
||||
@@ -80,6 +80,7 @@ class SalesOrderItem(Document):
|
||||
quotation_item: DF.Data | None
|
||||
rate: DF.Currency
|
||||
rate_with_margin: DF.Currency
|
||||
requested_qty: DF.Float
|
||||
reserve_stock: DF.Check
|
||||
returned_qty: DF.Float
|
||||
stock_qty: DF.Float
|
||||
|
||||
@@ -329,7 +329,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-12 10:38:34.605126",
|
||||
"modified": "2026-02-27 00:47:46.003305",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
@@ -166,6 +166,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "is_group",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Group"
|
||||
},
|
||||
{
|
||||
@@ -187,7 +188,6 @@
|
||||
"fieldname": "parent_company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Parent Company",
|
||||
"options": "Company"
|
||||
},
|
||||
@@ -245,6 +245,7 @@
|
||||
"fieldname": "default_currency",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Default Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
@@ -468,6 +469,7 @@
|
||||
"default": "1",
|
||||
"fieldname": "enable_perpetual_inventory",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Enable Perpetual Inventory"
|
||||
},
|
||||
{
|
||||
@@ -940,7 +942,6 @@
|
||||
"description": "Accounting entries are frozen up to this date. Only users with the specified role can create or modify entries before this date.",
|
||||
"fieldname": "accounts_frozen_till_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Accounts Frozen Till Date"
|
||||
},
|
||||
{
|
||||
@@ -955,7 +956,6 @@
|
||||
{
|
||||
"fieldname": "role_allowed_for_frozen_entries",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Roles Allowed to Set and Edit Frozen Account Entries",
|
||||
"options": "Role"
|
||||
},
|
||||
@@ -970,7 +970,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-16 16:51:27.624096",
|
||||
"modified": "2026-03-09 17:15:33.819426",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -183,13 +183,11 @@ class Employee(NestedSet):
|
||||
throw(_("Please enter relieving date."))
|
||||
|
||||
def validate_for_enabled_user_id(self, enabled):
|
||||
if self.status != "Active":
|
||||
return
|
||||
|
||||
if enabled is None:
|
||||
frappe.throw(_("User {0} does not exist").format(self.user_id))
|
||||
if enabled == 0:
|
||||
frappe.throw(_("User {0} is disabled").format(self.user_id), EmployeeUserDisabledError)
|
||||
|
||||
if self.status != "Active" and enabled or self.status == "Active" and enabled == 0:
|
||||
frappe.set_value("User", self.user_id, "enabled", not enabled)
|
||||
|
||||
def validate_duplicate_user_id(self):
|
||||
Employee = frappe.qb.DocType("Employee")
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Sales Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Manufacturing Manager"
|
||||
},
|
||||
{
|
||||
"role": "Stock Manager"
|
||||
}
|
||||
],
|
||||
"creation": "2026-02-24 18:03:53.158438",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"modified": "2026-02-24 18:07:36.808560",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Organization Onboarding",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Setup Company"
|
||||
},
|
||||
{
|
||||
"step": "Invite Users"
|
||||
},
|
||||
{
|
||||
"step": "Setup Email Account"
|
||||
},
|
||||
{
|
||||
"step": "Setup Role Permissions"
|
||||
},
|
||||
{
|
||||
"step": "Review System Settings"
|
||||
}
|
||||
],
|
||||
"title": "Setup Organization"
|
||||
}
|
||||
20
erpnext/setup/onboarding_step/invite_users/invite_users.json
Normal file
20
erpnext/setup/onboarding_step/invite_users/invite_users.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Invite Users",
|
||||
"creation": "2026-02-24 18:04:21.585575",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-24 18:04:21.585575",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Invite Users",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "User",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Invite Users",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Update Settings",
|
||||
"action_label": "Review System Settings",
|
||||
"creation": "2026-02-24 18:06:56.781335",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 1,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-24 18:06:56.781335",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Review System Settings",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "System Settings",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Review System Settings",
|
||||
"validate_action": 0,
|
||||
"value_to_validate": ""
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "Setup Company",
|
||||
"creation": "2026-02-20 11:12:50.373049",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 1,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 21:10:17.680053",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Setup Company",
|
||||
"owner": "Administrator",
|
||||
"path": "company",
|
||||
"reference_document": "Company",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Setup Company",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Setup Email Account",
|
||||
"creation": "2026-02-24 18:04:39.983155",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-24 18:04:39.983155",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Setup Email Account",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Email Account",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Setup Email Account",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "Setup Role Permissions",
|
||||
"creation": "2026-02-24 18:05:10.485778",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-24 18:05:10.485778",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Setup Role Permissions",
|
||||
"owner": "Administrator",
|
||||
"path": "permission-manager",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Setup Role Permissions",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -221,6 +221,8 @@ def set_defaults_for_tests():
|
||||
frappe.db.set_default(key, value)
|
||||
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "enable_serial_and_batch_no_for_item", 1)
|
||||
|
||||
|
||||
def insert_record(records):
|
||||
from frappe.desk.page.setup_wizard.setup_wizard import make_records
|
||||
|
||||
@@ -246,6 +246,7 @@
|
||||
"default": "Today",
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "posting_date",
|
||||
@@ -971,6 +972,7 @@
|
||||
{
|
||||
"fieldname": "per_billed",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "% Amount Billed",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
@@ -1061,6 +1063,7 @@
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
@@ -1078,7 +1081,6 @@
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "per_installed",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "% Installed",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "per_installed",
|
||||
@@ -1212,6 +1214,7 @@
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "per_returned",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "% Returned",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
@@ -1449,7 +1452,7 @@
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-10 14:35:08.523130",
|
||||
"modified": "2026-03-09 17:15:27.932956",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -84,7 +84,25 @@ frappe.ui.form.on("Item", {
|
||||
}
|
||||
},
|
||||
|
||||
toggle_has_serial_batch_fields(frm) {
|
||||
let hide_fields = cint(frappe.user_defaults?.enable_serial_and_batch_no_for_item) === 0 ? 1 : 0;
|
||||
|
||||
frm.toggle_display(["serial_no_series", "batch_number_series", "create_new_batch"], !hide_fields);
|
||||
frm.toggle_enable(["has_serial_no", "has_batch_no"], !hide_fields);
|
||||
|
||||
if (hide_fields) {
|
||||
let description = __(
|
||||
"To enable the Serial No and Batch No feature, please check the 'Enable Serial / Batch No for Item' checkbox in Stock Settings."
|
||||
);
|
||||
|
||||
frm.set_df_property("has_serial_no", "description", description);
|
||||
frm.set_df_property("has_batch_no", "description", description);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.trigger("toggle_has_serial_batch_fields");
|
||||
|
||||
if (frm.doc.is_stock_item) {
|
||||
frm.add_custom_button(
|
||||
__("Stock Balance"),
|
||||
|
||||
@@ -452,6 +452,7 @@
|
||||
"fieldname": "batch_number_series",
|
||||
"fieldtype": "Data",
|
||||
"label": "Batch Number Series",
|
||||
"show_description_on_click": 1,
|
||||
"translatable": 1
|
||||
},
|
||||
{
|
||||
@@ -493,7 +494,8 @@
|
||||
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
|
||||
"fieldname": "serial_no_series",
|
||||
"fieldtype": "Data",
|
||||
"label": "Serial Number Series"
|
||||
"label": "Serial Number Series",
|
||||
"show_description_on_click": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@@ -985,7 +987,7 @@
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2026-02-05 17:20:35.605734",
|
||||
"modified": "2026-03-05 16:29:31.653447",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -159,6 +159,7 @@
|
||||
"default": "Today",
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Transaction Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "transaction_date",
|
||||
@@ -282,7 +283,6 @@
|
||||
{
|
||||
"fieldname": "set_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -377,7 +377,7 @@
|
||||
"idx": 70,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-21 12:48:40.792323",
|
||||
"modified": "2026-03-09 17:15:30.124509",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request",
|
||||
|
||||
@@ -90,7 +90,7 @@ class MaterialRequest(BuyingController):
|
||||
{
|
||||
"source_dt": "Material Request Item",
|
||||
"target_dt": "Sales Order Item",
|
||||
"target_field": "ordered_qty",
|
||||
"target_field": "requested_qty",
|
||||
"target_parent_dt": "Sales Order",
|
||||
"target_parent_field": "",
|
||||
"join_field": "sales_order_item",
|
||||
@@ -280,6 +280,8 @@ class MaterialRequest(BuyingController):
|
||||
def on_cancel(self):
|
||||
self.update_requested_qty_in_production_plan(cancel=True)
|
||||
self.update_requested_qty()
|
||||
if self.material_request_type == "Purchase":
|
||||
self.update_prevdoc_status()
|
||||
|
||||
def get_mr_items_ordered_qty(self, mr_items):
|
||||
mr_items_ordered_qty = {}
|
||||
|
||||
@@ -119,6 +119,8 @@ frappe.ui.form.on("Pick List", {
|
||||
refresh: (frm) => {
|
||||
frm.trigger("add_get_items_button");
|
||||
frm.trigger("update_warehouse_property");
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
|
||||
if (frm.doc.docstatus === 1) {
|
||||
const status_completed = frm.doc.status === "Completed";
|
||||
|
||||
|
||||
@@ -938,6 +938,7 @@
|
||||
{
|
||||
"fieldname": "per_billed",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "% Amount Billed",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
@@ -1302,7 +1303,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-23 16:56:41.075091",
|
||||
"modified": "2026-03-09 17:15:28.602690",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -107,6 +107,7 @@ class SerialandBatchBundle(Document):
|
||||
self.autoname()
|
||||
|
||||
def validate(self):
|
||||
self.validate_allow_to_set_serial_batch()
|
||||
if self.docstatus == 1 and self.voucher_detail_no:
|
||||
self.validate_voucher_detail_no()
|
||||
|
||||
@@ -143,6 +144,15 @@ class SerialandBatchBundle(Document):
|
||||
self.calculate_qty_and_amount()
|
||||
self.set_child_details()
|
||||
|
||||
def validate_allow_to_set_serial_batch(self):
|
||||
if not frappe.db.get_single_value("Stock Settings", "enable_serial_and_batch_no_for_item"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please check the 'Enable Serial and Batch No for Item' checkbox in the {0} to make Serial and Batch Bundle for the item."
|
||||
).format(get_link_to_form("Stock Settings", "Stock Settings")),
|
||||
title=_("Serial and Batch No for Item Disabled"),
|
||||
)
|
||||
|
||||
def validate_serial_no_status(self):
|
||||
serial_nos = [d.serial_no for d in self.entries if d.serial_no]
|
||||
invalid_serial_nos = frappe.get_all(
|
||||
|
||||
@@ -245,6 +245,7 @@ frappe.ui.form.on("Stock Entry", {
|
||||
refresh: function (frm) {
|
||||
frm.trigger("get_items_from_transit_entry");
|
||||
frm.trigger("toggle_warehouse_fields");
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
|
||||
if (!frm.doc.docstatus && !frm.doc.subcontracting_inward_order) {
|
||||
frm.trigger("validate_purpose_consumption");
|
||||
@@ -930,10 +931,6 @@ frappe.ui.form.on("Stock Entry Detail", {
|
||||
);
|
||||
},
|
||||
|
||||
qty(frm, cdt, cdn) {
|
||||
frm.events.set_rate_and_fg_qty(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
conversion_factor(frm, cdt, cdn) {
|
||||
frm.events.set_rate_and_fg_qty(frm, cdt, cdn);
|
||||
},
|
||||
|
||||
@@ -76,6 +76,8 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
|
||||
if (frm.doc.docstatus < 1) {
|
||||
frm.add_custom_button(__("Fetch Items from Warehouse"), function () {
|
||||
frm.events.get_items(frm);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"allow_internal_transfer_at_arms_length_price",
|
||||
"validate_material_transfer_warehouses",
|
||||
"serial_and_batch_item_settings_tab",
|
||||
"enable_serial_and_batch_no_for_item",
|
||||
"section_break_7",
|
||||
"allow_existing_serial_no",
|
||||
"do_not_use_batchwise_valuation",
|
||||
@@ -48,9 +49,8 @@
|
||||
"use_serial_batch_fields",
|
||||
"do_not_update_serial_batch_on_creation_of_auto_bundle",
|
||||
"allow_negative_stock_for_batch",
|
||||
"serial_and_batch_bundle_section",
|
||||
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
"section_break_gnhq",
|
||||
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
"use_naming_series",
|
||||
"column_break_wslv",
|
||||
"naming_series_prefix",
|
||||
@@ -158,6 +158,7 @@
|
||||
"label": "Convert Item Description to Clean HTML in Transactions"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_serial_and_batch_no_for_item",
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serial & Batch Item Settings"
|
||||
@@ -487,11 +488,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Reserve Stock"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_and_batch_bundle_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Serial and Batch Bundle"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
@@ -499,6 +495,7 @@
|
||||
"label": "Set Serial and Batch Bundle Naming Based on Naming Series"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_serial_and_batch_no_for_item",
|
||||
"fieldname": "section_break_gnhq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
@@ -554,6 +551,11 @@
|
||||
"fieldname": "allow_negative_stock_for_batch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative Stock for Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "enable_serial_and_batch_no_for_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Serial / Batch No for Item"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -562,7 +564,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-25 09:56:34.105949",
|
||||
"modified": "2026-02-25 10:56:34.105949",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
||||
@@ -47,6 +47,7 @@ class StockSettings(Document):
|
||||
disable_serial_no_and_batch_selector: DF.Check
|
||||
do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check
|
||||
do_not_use_batchwise_valuation: DF.Check
|
||||
enable_serial_and_batch_no_for_item: DF.Check
|
||||
enable_stock_reservation: DF.Check
|
||||
item_group: DF.Link | None
|
||||
item_naming_by: DF.Literal["Item Code", "Naming Series"]
|
||||
@@ -82,6 +83,7 @@ class StockSettings(Document):
|
||||
"default_warehouse",
|
||||
"set_qty_in_transactions_based_on_serial_no_input",
|
||||
"use_serial_batch_fields",
|
||||
"enable_serial_and_batch_no_for_item",
|
||||
"set_serial_and_batch_bundle_naming_based_on_naming_series",
|
||||
]:
|
||||
frappe.db.set_default(key, self.get(key, ""))
|
||||
@@ -104,6 +106,7 @@ class StockSettings(Document):
|
||||
)
|
||||
|
||||
self.validate_warehouses()
|
||||
self.validate_serial_and_batch_no_settings()
|
||||
self.cant_change_valuation_method()
|
||||
self.validate_clean_description_html()
|
||||
self.validate_pending_reposts()
|
||||
@@ -112,6 +115,25 @@ class StockSettings(Document):
|
||||
self.change_precision_for_for_sales()
|
||||
self.change_precision_for_purchase()
|
||||
|
||||
def validate_serial_and_batch_no_settings(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
if not doc_before_save:
|
||||
return
|
||||
|
||||
if doc_before_save.enable_serial_and_batch_no_for_item == self.enable_serial_and_batch_no_for_item:
|
||||
return
|
||||
|
||||
if (
|
||||
doc_before_save.enable_serial_and_batch_no_for_item
|
||||
and not self.enable_serial_and_batch_no_for_item
|
||||
):
|
||||
if frappe.get_all("Serial and Batch Bundle", filters={"docstatus": 1}, limit=1, pluck="name"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot disable Serial and Batch No for Item, as there are existing records for serial / batch."
|
||||
)
|
||||
)
|
||||
|
||||
def validate_warehouses(self):
|
||||
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
|
||||
for field in warehouse_fields:
|
||||
|
||||
@@ -352,7 +352,7 @@ class SerialBatchBundle:
|
||||
"Serial and Batch Entry", {"parent": self.sle.serial_and_batch_bundle, "docstatus": 0}
|
||||
)
|
||||
> 0
|
||||
):
|
||||
) and not self.sle.is_cancelled:
|
||||
frappe.throw(
|
||||
_("Serial and Batch Bundle {0} is not submitted").format(
|
||||
bold(self.sle.serial_and_batch_bundle)
|
||||
|
||||
@@ -30,6 +30,7 @@ frappe.ui.form.on("Subcontracting Receipt", {
|
||||
refresh: (frm) => {
|
||||
frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" };
|
||||
|
||||
erpnext.toggle_serial_batch_fields(frm);
|
||||
if (frm.doc.docstatus === 1) {
|
||||
frm.add_custom_button(
|
||||
__("Stock Ledger"),
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<form action="/search_help" style="display: flex;">
|
||||
<input name='q' class='form-control' type='text'
|
||||
style='max-width: 400px; display: inline-block; margin-right: 10px;'
|
||||
value='{{ frappe.form_dict.q or ''}}'
|
||||
value='{{ (frappe.form_dict.q or '') | e }}'
|
||||
{% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
|
||||
<input type='submit'
|
||||
class='btn btn-sm btn-light btn-search' value="{{ _("Search") }}">
|
||||
|
||||
102
erpnext/workspace_sidebar/organization.json
Normal file
102
erpnext/workspace_sidebar/organization.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"app": "erpnext",
|
||||
"creation": "2026-02-24 17:39:43.793115",
|
||||
"docstatus": 0,
|
||||
"doctype": "Workspace Sidebar",
|
||||
"header_icon": "organization",
|
||||
"idx": 1,
|
||||
"items": [
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "organization",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Company",
|
||||
"link_to": "Company",
|
||||
"link_type": "DocType",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "book-text",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Letter Head",
|
||||
"link_to": "Letter Head",
|
||||
"link_type": "DocType",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "file-user",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Department",
|
||||
"link_to": "Department",
|
||||
"link_type": "DocType",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "book-user",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Branch",
|
||||
"link_to": "Branch",
|
||||
"link_type": "DocType",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "users",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "User",
|
||||
"link_to": "User",
|
||||
"link_type": "DocType",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "user-round-check",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Role Permissions",
|
||||
"link_to": "permission-manager",
|
||||
"link_type": "Page",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 0,
|
||||
"collapsible": 1,
|
||||
"icon": "mail",
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Email Account",
|
||||
"link_to": "Email Account",
|
||||
"link_type": "DocType",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-24 18:08:00.796746",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"module_onboarding": "Organization Onboarding",
|
||||
"name": "Organization",
|
||||
"owner": "Administrator",
|
||||
"standard": 1,
|
||||
"title": "Organization"
|
||||
}
|
||||
Reference in New Issue
Block a user