Merge pull request #53292 from frappe/version-16-hotfix

chore: release v16
This commit is contained in:
ruthra kumar
2026-03-10 20:19:47 +05:30
committed by GitHub
99 changed files with 3292 additions and 1991 deletions

View File

@@ -6,64 +6,83 @@
"Current Assets": { "Current Assets": {
"Accounts Receivable": { "Accounts Receivable": {
"Debtors": { "Debtors": {
"account_type": "Receivable" "account_type": "Receivable",
"account_category": "Trade Receivables"
} }
}, },
"Bank Accounts": { "Bank Accounts": {
"account_type": "Bank", "account_type": "Bank",
"is_group": 1 "is_group": 1,
"account_category": "Cash and Cash Equivalents"
}, },
"Cash In Hand": { "Cash In Hand": {
"Cash": { "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)": { "Loans and Advances (Assets)": {
"is_group": 1 "is_group": 1,
"account_category": "Other Receivables"
}, },
"Securities and Deposits": { "Securities and Deposits": {
"Earnest Money": {} "Earnest Money": {
"account_category": "Other Current Assets"
}
}, },
"Stock Assets": { "Stock Assets": {
"Stock In Hand": { "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": { "Tax Assets": {
"is_group": 1 "is_group": 1,
"account_category": "Other Current Assets"
} }
}, },
"Fixed Assets": { "Fixed Assets": {
"Capital Equipment": { "Capital Equipment": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset",
"account_category": "Tangible Assets"
}, },
"Electronic Equipment": { "Electronic Equipment": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset",
"account_category": "Tangible Assets"
}, },
"Furniture and Fixtures": { "Furniture and Fixtures": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset",
"account_category": "Tangible Assets"
}, },
"Office Equipment": { "Office Equipment": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset",
"account_category": "Tangible Assets"
}, },
"Plants and Machineries": { "Plants and Machineries": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset",
"account_category": "Tangible Assets"
}, },
"Buildings": { "Buildings": {
"account_type": "Fixed Asset" "account_type": "Fixed Asset",
"account_category": "Tangible Assets"
}, },
"Accumulated Depreciations": { "Accumulated Depreciations": {
"account_type": "Accumulated Depreciation" "account_type": "Accumulated Depreciation",
"account_category": "Tangible Assets"
} }
}, },
"Investments": { "Investments": {
"is_group": 1 "is_group": 1,
"account_category": "Long-term Investments"
}, },
"Temporary Accounts": { "Temporary Accounts": {
"Temporary Opening": { "Temporary Opening": {
"account_type": "Temporary" "account_type": "Temporary",
"account_category": "Other Non-current Assets"
} }
}, },
"root_type": "Asset" "root_type": "Asset"
@@ -72,55 +91,103 @@
"Direct Expenses": { "Direct Expenses": {
"Stock Expenses": { "Stock Expenses": {
"Cost of Goods Sold": { "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": { "Expenses Included In Valuation": {
"account_type": "Expenses Included In Valuation" "account_type": "Expenses Included In Valuation",
"account_category": "Other Direct Costs"
}, },
"Stock Adjustment": { "Stock Adjustment": {
"account_type": "Stock Adjustment" "account_type": "Stock Adjustment",
"account_category": "Other Direct Costs"
} }
} }
}, },
"Indirect Expenses": { "Indirect Expenses": {
"Administrative Expenses": {}, "Administrative Expenses": {
"Commission on Sales": {}, "account_category": "Operating Expenses"
},
"Commission on Sales": {
"account_category": "Operating Expenses"
},
"Depreciation": { "Depreciation": {
"account_type": "Depreciation" "account_type": "Depreciation",
"account_category": "Operating Expenses"
},
"Entertainment Expenses": {
"account_category": "Operating Expenses"
}, },
"Entertainment Expenses": {},
"Freight and Forwarding Charges": { "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": { "Rounded Off": {
"account_type": "Round Off" "account_type": "Round Off",
"account_category": "Operating Expenses"
}, },
"Salary": {}, "Salary": {
"Sales Expenses": {}, "account_category": "Operating Expenses"
"Telephone Expenses": {}, },
"Travel Expenses": {}, "Sales Expenses": {
"Utility Expenses": {}, "account_category": "Operating Expenses"
"Write Off": {}, },
"Exchange Gain/Loss": {}, "Telephone Expenses": {
"Gain/Loss on Asset Disposal": {}, "account_category": "Operating Expenses"
"Impairment": {} },
"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" "root_type": "Expense"
}, },
"Income": { "Income": {
"Direct Income": { "Direct Income": {
"Sales": { "Sales": {
"account_type": "Income Account" "account_type": "Income Account",
"account_category": "Revenue from Operations"
}, },
"Service": { "Service": {
"account_type": "Income Account" "account_type": "Income Account",
"account_category": "Revenue from Operations"
}, },
"account_type": "Income Account" "account_type": "Income Account"
}, },
@@ -132,31 +199,51 @@
}, },
"Source of Funds (Liabilities)": { "Source of Funds (Liabilities)": {
"Capital Account": { "Capital Account": {
"Reserves and Surplus": {}, "Reserves and Surplus": {
"Shareholders Funds": {}, "account_category": "Reserves and Surplus"
"Revaluation Surplus": {} },
"Shareholders Funds": {
"account_category": "Share Capital"
},
"Revaluation Surplus": {
"account_category": "Reserves and Surplus"
}
}, },
"Current Liabilities": { "Current Liabilities": {
"Accounts Payable": { "Accounts Payable": {
"Creditors": { "Creditors": {
"account_type": "Payable" "account_type": "Payable",
"account_category": "Trade Payables"
}, },
"Payroll Payable": {} "Payroll Payable": {
"account_category": "Other Payables"
}
}, },
"Stock Liabilities": { "Stock Liabilities": {
"Stock Received But Not Billed": { "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": { "Duties and Taxes": {
"TDS": { "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)": { "Loans (Liabilities)": {
"Secured Loans": {}, "Secured Loans": {
"Unsecured Loans": {}, "account_category": "Long-term Borrowings"
"Bank Overdraft Account": {} },
"Unsecured Loans": {
"account_category": "Long-term Borrowings"
},
"Bank Overdraft Account": {
"account_category": "Short-term Borrowings"
}
} }
}, },
"root_type": "Liability" "root_type": "Liability"

View File

@@ -20,7 +20,6 @@
{ {
"fieldname": "period_name", "fieldname": "period_name",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1,
"label": "Period Name", "label": "Period Name",
"reqd": 1, "reqd": 1,
"unique": 1 "unique": 1
@@ -79,7 +78,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2025-12-01 16:53:44.631299", "modified": "2026-03-09 17:15:33.577217",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Period", "name": "Accounting Period",

View File

@@ -97,7 +97,7 @@ def validate_accounting_period_on_doc_save(doc, method=None):
if doc.doctype == "Bank Clearance": if doc.doctype == "Bank Clearance":
return return
elif doc.doctype == "Asset": elif doc.doctype == "Asset":
if doc.is_existing_asset: if doc.asset_type == "Existing Asset":
return return
else: else:
date = doc.available_for_use_date date = doc.available_for_use_date

View File

@@ -205,7 +205,7 @@
"description": "Payment Terms from orders will be fetched into the invoices as is", "description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms", "fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check", "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 ", "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, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2026-02-04 17:15:38.609327", "modified": "2026-02-27 01:04:09.415288",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -140,6 +140,7 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Company", "label": "Company",
"oldfieldname": "company", "oldfieldname": "company",
@@ -181,7 +182,6 @@
"fieldname": "cheque_no", "fieldname": "cheque_no",
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1,
"label": "Reference Number", "label": "Reference Number",
"mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"", "mandatory_depends_on": "eval:doc.voucher_type == \"Bank Entry\"",
"no_copy": 1, "no_copy": 1,
@@ -665,7 +665,7 @@
"table_fieldname": "payment_entries" "table_fieldname": "payment_entries"
} }
], ],
"modified": "2026-02-16 16:06:10.468482", "modified": "2026-03-09 17:15:26.569327",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry", "name": "Journal Entry",

View File

@@ -138,6 +138,7 @@
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1,
"label": "Posting Date", "label": "Posting Date",
"reqd": 1 "reqd": 1
}, },
@@ -160,7 +161,6 @@
{ {
"fieldname": "mode_of_payment", "fieldname": "mode_of_payment",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Mode of Payment", "label": "Mode of Payment",
"options": "Mode of Payment" "options": "Mode of Payment"
}, },
@@ -228,6 +228,7 @@
"fieldname": "paid_from", "fieldname": "paid_from",
"fieldtype": "Link", "fieldtype": "Link",
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1,
"label": "Account Paid From", "label": "Account Paid From",
"options": "Account", "options": "Account",
"print_hide": 1, "print_hide": 1,
@@ -252,6 +253,7 @@
"fieldname": "paid_to", "fieldname": "paid_to",
"fieldtype": "Link", "fieldtype": "Link",
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1,
"label": "Account Paid To", "label": "Account Paid To",
"options": "Account", "options": "Account",
"print_hide": 1, "print_hide": 1,
@@ -414,6 +416,7 @@
"depends_on": "eval:(doc.paid_from && doc.paid_to)", "depends_on": "eval:(doc.paid_from && doc.paid_to)",
"fieldname": "reference_no", "fieldname": "reference_no",
"fieldtype": "Data", "fieldtype": "Data",
"in_standard_filter": 1,
"label": "Cheque/Reference No", "label": "Cheque/Reference No",
"mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')" "mandatory_depends_on": "eval:(doc.paid_from_account_type == 'Bank' || doc.paid_to_account_type == 'Bank')"
}, },
@@ -792,7 +795,7 @@
"table_fieldname": "payment_entries" "table_fieldname": "payment_entries"
} }
], ],
"modified": "2026-02-03 16:08:49.800381", "modified": "2026-03-09 17:15:30.453920",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@@ -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 () { cur_frm.set_query("wip_composite_asset", "items", function () {
return { return {
filters: { is_composite_asset: 1, docstatus: 0 }, filters: { asset_type: "Composite Asset", docstatus: 0 },
}; };
}); });

View File

@@ -266,6 +266,7 @@
{ {
"fieldname": "due_date", "fieldname": "due_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Due Date", "label": "Due Date",
"oldfieldname": "due_date", "oldfieldname": "due_date",
"oldfieldtype": "Date" "oldfieldtype": "Date"
@@ -319,6 +320,7 @@
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1,
"label": "Date", "label": "Date",
"oldfieldname": "posting_date", "oldfieldname": "posting_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
@@ -397,6 +399,8 @@
{ {
"fieldname": "bill_no", "fieldname": "bill_no",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Supplier Invoice No", "label": "Supplier Invoice No",
"oldfieldname": "bill_no", "oldfieldname": "bill_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
@@ -1689,7 +1693,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-02-23 14:23:57.269770", "modified": "2026-03-09 17:15:27.014131",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -733,9 +733,10 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"): for item in self.get("items"):
if item.purchase_receipt: if item.purchase_receipt:
frappe.throw( 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): def validate_for_repost(self):

View File

@@ -381,6 +381,8 @@
"fieldtype": "Date", "fieldtype": "Date",
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Date", "label": "Date",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "posting_date", "oldfieldname": "posting_date",
@@ -415,6 +417,7 @@
"fieldtype": "Date", "fieldtype": "Date",
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"in_list_view": 1,
"label": "Payment Due Date", "label": "Payment Due Date",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "due_date", "oldfieldname": "due_date",
@@ -1639,6 +1642,7 @@
"fieldtype": "Select", "fieldtype": "Select",
"hide_days": 1, "hide_days": 1,
"hide_seconds": 1, "hide_seconds": 1,
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"length": 30, "length": 30,
@@ -2330,7 +2334,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2026-02-28 17:58:56.453076", "modified": "2026-03-09 17:15:30.931929",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -1451,6 +1451,9 @@ class SalesInvoice(SellingController):
return asset_qty_map return asset_qty_map
def process_asset_depreciation(self): 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): if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
self.depreciate_asset_on_sale() self.depreciate_asset_on_sale()
else: else:

View File

@@ -8,6 +8,8 @@ import frappe
from frappe import _ from frappe import _
from frappe.contacts.doctype.address.address import get_default_address from frappe.contacts.doctype.address.address import get_default_address
from frappe.model.document import Document 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 import cstr
from frappe.utils.nestedset import get_root_of from frappe.utils.nestedset import get_root_of
@@ -83,6 +85,8 @@ class TaxRule(Document):
frappe.throw(_("Tax Template is mandatory.")) frappe.throw(_("Tax Template is mandatory."))
def validate_filters(self): def validate_filters(self):
TaxRule = DocType("Tax Rule")
filters = { filters = {
"tax_type": self.tax_type, "tax_type": self.tax_type,
"customer": self.customer, "customer": self.customer,
@@ -105,33 +109,34 @@ class TaxRule(Document):
"company": self.company, "company": self.company,
} }
conds = "" query = (
for d in filters: frappe.qb.from_(TaxRule).select(TaxRule.name, TaxRule.priority).where(TaxRule.name != self.name)
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,
) )
if tax_rule: for field, value in filters.items():
if tax_rule[0].priority == self.priority: query = query.where(IfNull(TaxRule[field], "") == cstr(value))
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
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() @frappe.whitelist()

View File

@@ -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", fieldname: "voucher_no",
label: __("Voucher No"), label: __("Voucher No"),

View File

@@ -68,6 +68,12 @@ class General_Payment_Ledger_Comparison:
if self.filters.period_end_date: if self.filters.period_end_date:
filter_criterion.append(gle.posting_date.lte(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": if acc_type == "receivable":
outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding") outstanding = (Sum(gle.debit) - Sum(gle.credit)).as_("outstanding")
else: else:
@@ -111,6 +117,12 @@ class General_Payment_Ledger_Comparison:
if self.filters.period_end_date: if self.filters.period_end_date:
filter_criterion.append(ple.posting_date.lte(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 = ( self.account_types[acc_type].ple = (
qb.from_(ple) qb.from_(ple)
.select( .select(

View File

@@ -649,7 +649,7 @@ class GrossProfitGenerator:
new_row = row new_row = row
self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion) self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion)
else: 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) self.set_average_based_on_payment_term_portion(new_row, row, invoice_portion, True)
new_row = self.set_average_rate(new_row) new_row = self.set_average_rate(new_row)
@@ -659,11 +659,17 @@ class GrossProfitGenerator:
if i == 0: if i == 0:
new_row = row new_row = row
else: else:
new_row.qty += flt(row.qty) new_row.qty = flt((new_row.qty + row.qty), self.float_precision)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision) new_row.buying_amount = flt(
new_row.base_amount += flt(row.base_amount, self.currency_precision) (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": 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) new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row) self.grouped_data.append(new_row)

View File

@@ -6,11 +6,11 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "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()\"}", "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, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2020-10-28 23:16:16.939070", "modified": "2026-02-03 15:48:13.407835",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Category-wise Asset Value", "name": "Category-wise Asset Value",

View File

@@ -6,11 +6,11 @@
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "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()\"}", "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, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2020-10-28 23:16:07.883312", "modified": "2026-02-03 15:48:13.407835",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Location-wise Asset Value", "name": "Location-wise Asset Value",

View File

@@ -100,7 +100,7 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"company": company, "company": company,
"status": "In Location", "status": "In Location",
"group_by": "Asset Category", "group_by": "Asset Category",
"is_existing_asset": 0, "asset_type": ["!=", "Existing Asset"],
} }
), ),
"type": "Donut", "type": "Donut",
@@ -126,7 +126,12 @@ def get_charts(fiscal_year, year_start_date, year_end_date):
"x_field": "location", "x_field": "location",
"timeseries": 0, "timeseries": 0,
"filters_json": json.dumps( "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", "type": "Donut",
"doctype": "Dashboard Chart", "doctype": "Dashboard Chart",

View File

@@ -81,23 +81,79 @@ frappe.ui.form.on("Asset", {
}, },
before_submit: function (frm) { 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.")); frappe.throw(__("Please capitalize this asset before submitting."));
} }
}, },
refresh: function (frm) { refresh: async function (frm) {
frappe.ui.form.trigger("Asset", "is_existing_asset"); frappe.ui.form.trigger("Asset", "asset_type");
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1); frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
let has_create_buttons = false;
if (frm.doc.docstatus == 1) { 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 (["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( frm.add_custom_button(
__("Transfer Asset"), __("Transfer Asset"),
function () { function () {
erpnext.asset.transfer_asset(frm); erpnext.asset.transfer_asset(frm);
}, },
__("Manage") __("Actions")
); );
frm.add_custom_button( frm.add_custom_button(
@@ -105,7 +161,7 @@ frappe.ui.form.on("Asset", {
function () { function () {
erpnext.asset.scrap_asset(frm); erpnext.asset.scrap_asset(frm);
}, },
__("Manage") __("Actions")
); );
frm.add_custom_button( frm.add_custom_button(
@@ -113,15 +169,7 @@ frappe.ui.form.on("Asset", {
function () { function () {
frm.trigger("sell_asset"); frm.trigger("sell_asset");
}, },
__("Manage") __("Actions")
);
frm.add_custom_button(
__("Split Asset"),
function () {
frm.trigger("split_asset");
},
__("Manage")
); );
} else if (frm.doc.status == "Scrapped") { } else if (frm.doc.status == "Scrapped") {
frm.add_custom_button(__("Restore Asset"), function () { frm.add_custom_button(__("Restore Asset"), function () {
@@ -129,47 +177,9 @@ frappe.ui.form.on("Asset", {
}).addClass("btn-primary"); }).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( frm.add_custom_button(
__("Maintain Asset"), __("Accounting Ledger"),
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"),
function () { function () {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
@@ -179,7 +189,7 @@ frappe.ui.form.on("Asset", {
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, },
__("Manage") __("View")
); );
} }
@@ -195,7 +205,7 @@ frappe.ui.form.on("Asset", {
if (frm.doc.docstatus == 0) { if (frm.doc.docstatus == 0) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.is_composite_asset) { if (frm.doc.asset_type == "Composite Asset") {
frappe.call({ frappe.call({
method: "erpnext.assets.doctype.asset.asset.has_active_capitalization", method: "erpnext.assets.doctype.asset.asset.has_active_capitalization",
args: { 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) { set_depr_posting_failure_alert: function (frm) {
const alert = ` const alert = `
<div class="row"> <div class="row">
@@ -232,7 +264,8 @@ frappe.ui.form.on("Asset", {
toggle_reference_doc: function (frm) { toggle_reference_doc: function (frm) {
const is_submitted = frm.doc.docstatus === 1; 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) => { const clear_field = (field) => {
if (frm.doc[field]) { if (frm.doc[field]) {
@@ -508,18 +541,13 @@ frappe.ui.form.on("Asset", {
}); });
}, },
is_existing_asset: function (frm) { asset_type: function (frm) {
frm.trigger("toggle_reference_doc");
},
is_composite_asset: function (frm) {
if (frm.doc.docstatus == 0) { 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); frm.set_value("net_purchase_amount", 0);
} else { } else {
frm.set_df_property("net_purchase_amount", "read_only", 0); frm.set_df_property("net_purchase_amount", "read_only", 0);
} }
frm.trigger("toggle_reference_doc");
} }
}, },

View File

@@ -9,20 +9,17 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"naming_series", "naming_series",
"company",
"item_code", "item_code",
"item_name", "item_name",
"asset_name", "asset_name",
"asset_category",
"location",
"image", "image",
"column_break_3", "column_break_3",
"status", "location",
"company", "asset_category",
"asset_owner", "asset_type",
"asset_owner_company", "maintenance_required",
"is_existing_asset", "calculate_depreciation",
"is_composite_asset",
"is_composite_component",
"purchase_details_section", "purchase_details_section",
"purchase_receipt", "purchase_receipt",
"purchase_receipt_item", "purchase_receipt_item",
@@ -30,31 +27,44 @@
"purchase_invoice_item", "purchase_invoice_item",
"purchase_date", "purchase_date",
"available_for_use_date", "available_for_use_date",
"disposal_date",
"column_break_23", "column_break_23",
"net_purchase_amount", "net_purchase_amount",
"purchase_amount", "purchase_amount",
"asset_quantity", "asset_quantity",
"additional_asset_cost", "additional_asset_cost",
"section_break_uiyd",
"column_break_bbwr",
"column_break_bfkm",
"total_asset_cost", "total_asset_cost",
"disposal_date",
"depreciation_tab", "depreciation_tab",
"calculate_depreciation", "column_break_wqzi",
"column_break_33",
"opening_accumulated_depreciation", "opening_accumulated_depreciation",
"opening_number_of_booked_depreciations",
"is_fully_depreciated", "is_fully_depreciated",
"column_break_33",
"opening_number_of_booked_depreciations",
"section_break_36", "section_break_36",
"finance_books", "finance_books",
"section_break_33", "section_break_33",
"depreciation_method", "depreciation_method",
"value_after_depreciation", "value_after_depreciation",
"total_number_of_depreciations",
"column_break_24",
"frequency_of_depreciation", "frequency_of_depreciation",
"column_break_24",
"next_depreciation_date", "next_depreciation_date",
"total_number_of_depreciations",
"depreciation_schedule_sb", "depreciation_schedule_sb",
"depreciation_schedule_view", "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", "policy_number",
"insurer", "insurer",
"insured_value", "insured_value",
@@ -62,22 +72,17 @@
"insurance_start_date", "insurance_start_date",
"insurance_end_date", "insurance_end_date",
"comprehensive_insurance", "comprehensive_insurance",
"other_info_tab",
"accounting_dimensions_section",
"cost_center",
"section_break_jtou", "section_break_jtou",
"status",
"custodian", "custodian",
"department",
"default_finance_book", "default_finance_book",
"depr_entry_posting_status", "depr_entry_posting_status",
"booked_fixed_asset",
"customer",
"supplier",
"column_break_51", "column_break_51",
"department",
"split_from",
"journal_entry_for_scrap", "journal_entry_for_scrap",
"split_from",
"amended_from", "amended_from",
"maintenance_required", "booked_fixed_asset",
"connections_tab" "connections_tab"
], ],
"fields": [ "fields": [
@@ -106,13 +111,6 @@
"options": "Item", "options": "Item",
"reqd": 1 "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", "depends_on": "item_code",
"fetch_from": "item_code.asset_category", "fetch_from": "item_code.asset_category",
@@ -171,6 +169,8 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
@@ -207,7 +207,7 @@
"fieldname": "purchase_date", "fieldname": "purchase_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Purchase 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 "reqd": 1
}, },
{ {
@@ -229,25 +229,18 @@
{ {
"fieldname": "available_for_use_date", "fieldname": "available_for_use_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Available-for-use Date", "label": "Available for Use Date",
"mandatory_depends_on": "eval:(!(doc.is_composite_component || doc.is_composite_asset) || doc.docstatus==1)" "mandatory_depends_on": "eval:(!(doc.asset_type == \"Composite Component\" || doc.asset_type == \"Composite Asset\") || doc.docstatus==1)"
}, },
{ {
"default": "0", "default": "0",
"fieldname": "calculate_depreciation", "fieldname": "calculate_depreciation",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Calculate Depreciation", "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.asset_type == \"Existing Asset\")",
"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)",
"fieldname": "opening_accumulated_depreciation", "fieldname": "opening_accumulated_depreciation",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Opening Accumulated Depreciation", "label": "Opening Accumulated Depreciation",
@@ -257,18 +250,20 @@
"columns": 10, "columns": 10,
"fieldname": "finance_books", "fieldname": "finance_books",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Finance Books",
"options": "Asset Finance Book" "options": "Asset Finance Book"
}, },
{ {
"fieldname": "section_break_33", "fieldname": "section_break_33",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 1 "hidden": 1,
"label": "Depreciation Details"
}, },
{ {
"fieldname": "depreciation_method", "fieldname": "depreciation_method",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Depreciation Method", "label": "Depreciation Method",
"options": "\nStraight Line\nDouble Declining Balance\nManual" "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual"
}, },
{ {
"fieldname": "value_after_depreciation", "fieldname": "value_after_depreciation",
@@ -295,6 +290,7 @@
{ {
"fieldname": "next_depreciation_date", "fieldname": "next_depreciation_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 1,
"label": "Next Depreciation Date", "label": "Next Depreciation Date",
"no_copy": 1 "no_copy": 1
}, },
@@ -364,7 +360,7 @@
"fieldtype": "Column Break" "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", "fieldname": "purchase_receipt",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Purchase Receipt", "label": "Purchase Receipt",
@@ -373,7 +369,7 @@
"print_hide": 1 "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", "fieldname": "purchase_invoice",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Purchase Invoice", "label": "Purchase Invoice",
@@ -399,7 +395,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible_depends_on": "is_existing_asset", "collapsible_depends_on": "eval:doc.asset_type == \"Existing Asset\"",
"fieldname": "purchase_details_section", "fieldname": "purchase_details_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Purchase Details" "label": "Purchase Details"
@@ -413,10 +409,9 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "calculate_depreciation", "depends_on": "eval: doc.calculate_depreciation",
"fieldname": "section_break_36", "fieldname": "section_break_36",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"label": "Finance Books"
}, },
{ {
"fieldname": "split_from", "fieldname": "split_from",
@@ -455,18 +450,11 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:(doc.is_existing_asset)",
"fieldname": "is_fully_depreciated", "fieldname": "is_fully_depreciated",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Is Fully Depreciated" "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", "depends_on": "eval:doc.docstatus > 0",
"fieldname": "total_asset_cost", "fieldname": "total_asset_cost",
@@ -496,7 +484,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:(doc.is_existing_asset)", "depends_on": "eval:(doc.asset_type == \"Existing Asset\")",
"fieldname": "opening_number_of_booked_depreciations", "fieldname": "opening_number_of_booked_depreciations",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Opening Number of Booked Depreciations" "label": "Opening Number of Booked Depreciations"
@@ -513,15 +501,10 @@
"hidden": 1, "hidden": 1,
"label": "Purchase Invoice Item" "label": "Purchase Invoice Item"
}, },
{
"fieldname": "insurance_details_tab",
"fieldtype": "Tab Break",
"label": "Insurance"
},
{ {
"fieldname": "other_info_tab", "fieldname": "other_info_tab",
"fieldtype": "Tab Break", "fieldtype": "Tab Break",
"label": "Other Info" "label": "More Info"
}, },
{ {
"fieldname": "connections_tab", "fieldname": "connections_tab",
@@ -530,6 +513,7 @@
"show_dashboard": 1 "show_dashboard": 1
}, },
{ {
"depends_on": "eval: doc.calculate_depreciation || doc.asset_type == \"Existing Asset\"",
"fieldname": "depreciation_tab", "fieldname": "depreciation_tab",
"fieldtype": "Tab Break", "fieldtype": "Tab Break",
"label": "Depreciation" "label": "Depreciation"
@@ -544,20 +528,61 @@
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Additional Info" "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", "fieldname": "net_purchase_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Net Purchase Amount", "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", "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, "idx": 72,
@@ -601,7 +626,7 @@
"link_fieldname": "target_asset" "link_fieldname": "target_asset"
} }
], ],
"modified": "2025-12-18 16:36:40.904246", "modified": "2026-03-09 17:15:32.819896",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@@ -56,6 +56,7 @@ class Asset(AccountsController):
asset_owner: DF.Literal["", "Company", "Supplier", "Customer"] asset_owner: DF.Literal["", "Company", "Supplier", "Customer"]
asset_owner_company: DF.Link | None asset_owner_company: DF.Link | None
asset_quantity: DF.Int asset_quantity: DF.Int
asset_type: DF.Literal["", "Existing Asset", "Composite Asset", "Composite Component"]
available_for_use_date: DF.Date | None available_for_use_date: DF.Date | None
booked_fixed_asset: DF.Check booked_fixed_asset: DF.Check
calculate_depreciation: DF.Check calculate_depreciation: DF.Check
@@ -67,7 +68,9 @@ class Asset(AccountsController):
default_finance_book: DF.Link | None default_finance_book: DF.Link | None
department: DF.Link | None department: DF.Link | None
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"] 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 disposal_date: DF.Date | None
finance_books: DF.Table[AssetFinanceBook] finance_books: DF.Table[AssetFinanceBook]
frequency_of_depreciation: DF.Int frequency_of_depreciation: DF.Int
@@ -76,9 +79,6 @@ class Asset(AccountsController):
insurance_start_date: DF.Date | None insurance_start_date: DF.Date | None
insured_value: DF.Data | None insured_value: DF.Data | None
insurer: 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 is_fully_depreciated: DF.Check
item_code: DF.Link item_code: DF.Link
item_name: DF.ReadOnly | None item_name: DF.ReadOnly | None
@@ -243,7 +243,7 @@ class Asset(AccountsController):
self.set_total_booked_depreciations() self.set_total_booked_depreciations()
def before_submit(self): 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): if self.split_from and has_active_capitalization(self.split_from):
return return
frappe.throw(_("Please capitalize this asset before submitting.")) frappe.throw(_("Please capitalize this asset before submitting."))
@@ -252,7 +252,11 @@ class Asset(AccountsController):
self.validate_in_use_date() self.validate_in_use_date()
self.make_asset_movement() self.make_asset_movement()
self.reload() 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() self.make_gl_entries()
if self.calculate_depreciation and not self.split_from: if self.calculate_depreciation and not self.split_from:
convert_draft_asset_depr_schedules_into_active(self) convert_draft_asset_depr_schedules_into_active(self)
@@ -267,7 +271,7 @@ class Asset(AccountsController):
cancel_asset_depr_schedules(self) cancel_asset_depr_schedules(self)
self.set_status() self.set_status()
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry") 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) make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
self.db_set("booked_fixed_asset", 0) self.db_set("booked_fixed_asset", 0)
add_asset_activity(self.name, _("Asset cancelled")) add_asset_activity(self.name, _("Asset cancelled"))
@@ -285,7 +289,7 @@ class Asset(AccountsController):
add_asset_activity(self.name, _("Asset deleted")) add_asset_activity(self.name, _("Asset deleted"))
def set_purchase_doc_row_item(self): 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 return
self.purchase_amount = self.net_purchase_amount 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)) frappe.throw(_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name))
def validate_item(self): def validate_item(self):
@@ -374,7 +378,7 @@ class Asset(AccountsController):
) )
def validate_in_use_date(self): 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")) frappe.throw(_("Available for use date is required"))
for d in self.finance_books: for d in self.finance_books:
@@ -442,13 +446,13 @@ class Asset(AccountsController):
if not self.asset_category: if not self.asset_category:
self.asset_category = frappe.get_cached_value("Item", self.item_code, "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) frappe.throw(_("Net Purchase Amount is mandatory"), frappe.MandatoryError)
if is_cwip_accounting_enabled(self.asset_category): if is_cwip_accounting_enabled(self.asset_category):
if ( if (
not self.is_existing_asset not self.asset_type == "Existing Asset"
and not self.is_composite_asset and not self.asset_type == "Composite Asset"
and not self.purchase_receipt and not self.purchase_receipt
and not self.purchase_invoice and not self.purchase_invoice
): ):
@@ -477,7 +481,7 @@ class Asset(AccountsController):
if self.is_fully_depreciated: if self.is_fully_depreciated:
frappe.throw(_("Depreciation cannot be calculated for fully depreciated assets")) frappe.throw(_("Depreciation cannot be calculated for fully depreciated assets"))
if self.is_existing_asset: if self.asset_type == "Existing Asset":
return return
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date): 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): def validate_gross_and_purchase_amount(self):
if self.is_existing_asset: if self.asset_type == "Existing Asset":
return return
if self.net_purchase_amount and self.net_purchase_amount != self.purchase_amount: 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_depreciation_start_date(row)
self.validate_total_number_of_depreciations_and_frequency(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_accumulated_depreciation = 0
self.opening_number_of_booked_depreciations = 0 self.opening_number_of_booked_depreciations = 0
else: else:
@@ -770,7 +774,7 @@ class Asset(AccountsController):
def get_status(self): def get_status(self):
"""Returns status based on whether it is draft, submitted, scrapped or depreciated""" """Returns status based on whether it is draft, submitted, scrapped or depreciated"""
if self.docstatus == 0: if self.docstatus == 0:
if self.is_composite_asset: if self.asset_type == "Composite Asset":
status = "Work In Progress" status = "Work In Progress"
else: else:
status = "Draft" status = "Draft"
@@ -843,7 +847,7 @@ class Asset(AccountsController):
return records return records
def validate_make_gl_entry(self): def validate_make_gl_entry(self):
if self.is_composite_asset: if self.asset_type == "Composite Asset":
return True return True
purchase_document = self.get_purchase_document() purchase_document = self.get_purchase_document()
@@ -924,7 +928,7 @@ class Asset(AccountsController):
purchase_document = self.get_purchase_document() purchase_document = self.get_purchase_document()
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account() 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 self.available_for_use_date
) <= getdate(): ) <= getdate():
gl_entries.append( gl_entries.append(
@@ -964,7 +968,7 @@ class Asset(AccountsController):
self.db_set("booked_fixed_asset", 1) self.db_set("booked_fixed_asset", 1)
def check_asset_capitalization_gl_entries(self): def check_asset_capitalization_gl_entries(self):
if self.is_composite_asset: if self.asset_type == "Composite Asset":
result = frappe.db.get_value( result = frappe.db.get_value(
"Asset Capitalization", "Asset Capitalization",
{"target_asset": self.name, "docstatus": 1}, {"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): 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.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.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.total_asset_cost = asset_doc.net_purchase_amount + asset_doc.additional_asset_cost
asset_doc.opening_accumulated_depreciation = ( asset_doc.opening_accumulated_depreciation = (

View File

@@ -786,10 +786,14 @@ def get_disposal_account_and_cost_center(company):
@frappe.whitelist() @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) 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") validate_disposal_date(asset_doc.purchase_date, getdate(disposal_date), "purchase")
return flt(asset_doc.value_after_depreciation) return flt(asset_doc.value_after_depreciation)

View File

@@ -71,16 +71,16 @@ class TestAsset(AssetSetup):
self.assertRaises(frappe.MandatoryError, asset.save) self.assertRaises(frappe.MandatoryError, asset.save)
def test_pr_or_pi_mandatory_if_not_existing_asset(self): 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 = create_asset(item_code="Macbook Pro", do_not_save=1)
asset.is_existing_asset = 0 asset.asset_type = ""
self.assertRaises(frappe.ValidationError, asset.save) self.assertRaises(frappe.ValidationError, asset.save)
def test_available_for_use_date_is_after_purchase_date(self): 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 = 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.purchase_date = getdate("2021-10-10")
asset.available_for_use_date = getdate("2021-10-1") asset.available_for_use_date = getdate("2021-10-1")
@@ -183,7 +183,7 @@ class TestAsset(AssetSetup):
asset.submit() asset.submit()
def test_is_fixed_asset_set(self): 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 = frappe.new_doc("Purchase Invoice")
doc.company = "_Test Company" doc.company = "_Test Company"
doc.supplier = "_Test Supplier" doc.supplier = "_Test Supplier"
@@ -710,7 +710,7 @@ class TestAsset(AssetSetup):
# create an asset # create an asset
asset = create_asset( asset = create_asset(
item_code="Macbook Pro", item_code="Macbook Pro",
is_existing_asset=1, asset_type="Existing Asset",
calculate_depreciation=1, calculate_depreciation=1,
available_for_use_date=purchase_date, available_for_use_date=purchase_date,
purchase_date=purchase_date, purchase_date=purchase_date,
@@ -890,7 +890,7 @@ class TestDepreciationMethods(AssetSetup):
asset = create_asset( asset = create_asset(
calculate_depreciation=1, calculate_depreciation=1,
available_for_use_date="2030-06-06", available_for_use_date="2030-06-06",
is_existing_asset=1, asset_type="Existing Asset",
opening_number_of_booked_depreciations=2, opening_number_of_booked_depreciations=2,
opening_accumulated_depreciation=47178.08, opening_accumulated_depreciation=47178.08,
expected_value_after_useful_life=10000, expected_value_after_useful_life=10000,
@@ -939,7 +939,7 @@ class TestDepreciationMethods(AssetSetup):
asset = create_asset( asset = create_asset(
calculate_depreciation=1, calculate_depreciation=1,
available_for_use_date="2030-01-01", available_for_use_date="2030-01-01",
is_existing_asset=1, asset_type="Existing Asset",
depreciation_method="Double Declining Balance", depreciation_method="Double Declining Balance",
opening_number_of_booked_depreciations=1, opening_number_of_booked_depreciations=1,
opening_accumulated_depreciation=50000, opening_accumulated_depreciation=50000,
@@ -1680,7 +1680,7 @@ class TestDepreciationBasics(AssetSetup):
self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0) self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0)
def test_asset_cost_center(self): 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" asset.cost_center = "Main - WP"
self.assertRaises(frappe.ValidationError, asset.submit) self.assertRaises(frappe.ValidationError, asset.submit)
@@ -1717,7 +1717,7 @@ class TestDepreciationBasics(AssetSetup):
def test_manual_depreciation_for_existing_asset(self): def test_manual_depreciation_for_existing_asset(self):
asset = create_asset( asset = create_asset(
item_code="Macbook Pro", item_code="Macbook Pro",
is_existing_asset=1, asset_type="Existing Asset",
purchase_date="2020-01-30", purchase_date="2020-01-30",
available_for_use_date="2020-01-30", available_for_use_date="2020-01-30",
submit=1, submit=1,
@@ -1843,7 +1843,7 @@ class TestDepreciationBasics(AssetSetup):
# Create composite asset # Create composite asset
wip_composite_asset = create_asset( wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset for Split", asset_name="Asset Capitalization WIP Composite Asset for Split",
is_composite_asset=1, asset_type="Composite Asset",
warehouse="Stores - TCP1", warehouse="Stores - TCP1",
company=company, company=company,
asset_quantity=2, # Set quantity > 1 to allow splitting 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", "available_for_use_date": args.available_for_use_date or "2020-06-06",
"location": args.location or "Test Location", "location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company", "asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1, "asset_type": args.asset_type or "Existing Asset",
"is_composite_asset": args.is_composite_asset or 0,
"is_composite_component": args.is_composite_component or 0,
"asset_quantity": args.get("asset_quantity") or 1, "asset_quantity": args.get("asset_quantity") or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "", "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.net_purchase_amount = 0
asset.purchase_amount = 0 asset.purchase_amount = 0

View File

@@ -16,11 +16,9 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
refresh() { refresh() {
this.show_general_ledger(); this.show_general_ledger();
erpnext.toggle_serial_batch_fields(this.frm);
if ( if (this.frm.doc.stock_items && this.frm.doc.stock_items.length) {
(this.frm.doc.stock_items && this.frm.doc.stock_items.length) ||
!this.frm.doc.target_is_fixed_asset
) {
this.show_stock_ledger(); this.show_stock_ledger();
} }
@@ -41,7 +39,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
me.frm.set_query("target_asset", function () { me.frm.set_query("target_asset", function () {
return { 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(); this.calculate_totals();
} }
target_qty() {
this.calculate_totals();
}
rate() { rate() {
this.calculate_totals(); 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.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.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.total_value;
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.refresh_fields(); me.frm.refresh_fields();
} }

View File

@@ -9,30 +9,33 @@
"field_order": [ "field_order": [
"title", "title",
"naming_series", "naming_series",
"company",
"target_asset", "target_asset",
"target_asset_name", "target_asset_name",
"target_item_code",
"finance_book",
"target_qty",
"column_break_9", "column_break_9",
"company", "finance_book",
"posting_date", "posting_date",
"posting_time", "posting_time",
"set_posting_time", "set_posting_time",
"target_batch_no", "target_item_code",
"target_serial_no",
"amended_from", "amended_from",
"target_is_fixed_asset",
"target_has_batch_no",
"target_has_serial_no",
"section_break_16", "section_break_16",
"stock_items", "stock_items",
"section_break_urtz",
"column_break_gqep",
"column_break_yvlx",
"stock_items_total", "stock_items_total",
"section_break_26", "section_break_26",
"asset_items", "asset_items",
"section_break_arbh",
"column_break_boeu",
"column_break_qecy",
"asset_items_total", "asset_items_total",
"service_expenses_section", "service_expenses_section",
"service_items", "service_items",
"section_break_ptna",
"column_break_szvh",
"column_break_katv",
"service_items_total", "service_items_total",
"totals_section", "totals_section",
"total_value", "total_value",
@@ -55,20 +58,12 @@
"depends_on": "eval:(doc.target_item_code && !doc.__islocal)", "depends_on": "eval:(doc.target_item_code && !doc.__islocal)",
"fieldname": "target_item_code", "fieldname": "target_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Target Item Code", "label": "Target Item Code",
"options": "Item", "options": "Item",
"read_only": 1 "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", "fieldname": "target_asset",
"fieldtype": "Link", "fieldtype": "Link",
@@ -143,6 +138,7 @@
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)", "depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
"fieldname": "section_break_16", "fieldname": "section_break_16",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1,
"label": "Consumed Stock Items" "label": "Consumed Stock Items"
}, },
{ {
@@ -151,49 +147,11 @@
"label": "Stock Items", "label": "Stock Items",
"options": "Asset Capitalization Stock Item" "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)", "depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
"fieldname": "section_break_26", "fieldname": "section_break_26",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1,
"label": "Consumed Assets" "label": "Consumed Assets"
}, },
{ {
@@ -203,6 +161,7 @@
"options": "Asset Capitalization Asset Item" "options": "Asset Capitalization Asset Item"
}, },
{ {
"depends_on": "eval: doc.stock_items_total",
"fieldname": "stock_items_total", "fieldname": "stock_items_total",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Consumed Stock Total Value", "label": "Consumed Stock Total Value",
@@ -210,6 +169,7 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval: doc.asset_items_total",
"fieldname": "asset_items_total", "fieldname": "asset_items_total",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Consumed Asset Total Value", "label": "Consumed Asset Total Value",
@@ -226,6 +186,7 @@
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)", "depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
"fieldname": "service_expenses_section", "fieldname": "service_expenses_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1,
"label": "Service Expenses" "label": "Service Expenses"
}, },
{ {
@@ -235,6 +196,7 @@
"options": "Asset Capitalization Service Item" "options": "Asset Capitalization Service Item"
}, },
{ {
"depends_on": "eval: doc.service_items_total",
"fieldname": "service_items_total", "fieldname": "service_items_total",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Service Expense Total Amount", "label": "Service Expense Total Amount",
@@ -277,10 +239,10 @@
"options": "Cost Center" "options": "Cost Center"
}, },
{ {
"fieldname": "project", "fieldname": "project",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Project", "label": "Project",
"options": "Project" "options": "Project"
}, },
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
@@ -292,12 +254,48 @@
"label": "Target Fixed Asset Account", "label": "Target Fixed Asset Account",
"options": "Account", "options": "Account",
"read_only": 1 "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, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-05-20 15:15:12.110035", "modified": "2026-02-06 01:52:41.890713",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Capitalization", "name": "Asset Capitalization",

View File

@@ -39,9 +39,6 @@ force_fields = [
"target_asset_name", "target_asset_name",
"item_name", "item_name",
"asset_name", "asset_name",
"target_is_fixed_asset",
"target_has_serial_no",
"target_has_batch_no",
"stock_uom", "stock_uom",
"fixed_asset_account", "fixed_asset_account",
"valuation_rate", "valuation_rate",
@@ -76,6 +73,7 @@ class AssetCapitalization(StockController):
naming_series: DF.Literal["ACC-ASC-.YYYY.-"] naming_series: DF.Literal["ACC-ASC-.YYYY.-"]
posting_date: DF.Date posting_date: DF.Date
posting_time: DF.Time posting_time: DF.Time
project: DF.Link | None
service_items: DF.Table[AssetCapitalizationServiceItem] service_items: DF.Table[AssetCapitalizationServiceItem]
service_items_total: DF.Currency service_items_total: DF.Currency
set_posting_time: DF.Check set_posting_time: DF.Check
@@ -83,15 +81,9 @@ class AssetCapitalization(StockController):
stock_items_total: DF.Currency stock_items_total: DF.Currency
target_asset: DF.Link | None target_asset: DF.Link | None
target_asset_name: DF.Data | None target_asset_name: DF.Data | None
target_batch_no: DF.Link | None
target_fixed_asset_account: 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_incoming_rate: DF.Currency
target_is_fixed_asset: DF.Check
target_item_code: DF.Link | None target_item_code: DF.Link | None
target_qty: DF.Float
target_serial_no: DF.SmallText | None
title: DF.Data | None title: DF.Data | None
total_value: DF.Currency total_value: DF.Currency
# end: auto-generated types # end: auto-generated types
@@ -190,22 +182,13 @@ class AssetCapitalization(StockController):
if not target_item.is_fixed_asset: if not target_item.is_fixed_asset:
frappe.throw(_("Target Item {0} must be a Fixed Asset item").format(target_item.name)) 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) self.validate_item(target_item)
def validate_target_asset(self): def validate_target_asset(self):
if self.target_asset: if self.target_asset:
target_asset = self.get_asset_for_validation(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)) frappe.throw(_("Target Asset {0} needs to be composite asset").format(target_asset.name))
if target_asset.item_code != self.target_item_code: if target_asset.item_code != self.target_item_code:
@@ -314,7 +297,7 @@ class AssetCapitalization(StockController):
return frappe.db.get_value( return frappe.db.get_value(
"Asset", "Asset",
asset, asset,
["name", "item_code", "company", "status", "docstatus", "is_composite_asset"], ["name", "item_code", "company", "status", "docstatus", "asset_type"],
as_dict=1, 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 = self.stock_items_total + self.asset_items_total + self.service_items_total
self.total_value = flt(self.total_value, self.precision("total_value")) 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_incoming_rate = self.total_value / self.target_qty
def update_stock_ledger(self): def update_stock_ledger(self):
sl_entries = [] sl_entries = []
@@ -489,7 +471,7 @@ class AssetCapitalization(StockController):
for item in self.asset_items: for item in self.asset_items:
asset = frappe.get_doc("Asset", item.asset) asset = frappe.get_doc("Asset", item.asset)
if not asset.is_composite_component: if asset.asset_type != "Composite Component":
if asset.calculate_depreciation: if asset.calculate_depreciation:
notes = _( notes = _(
"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}." "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): def get_composite_component_value(self):
composite_component_value = 0 composite_component_value = 0
for item in self.asset_items: for item in self.asset_items:
asset = frappe.db.get_value("Asset", item.asset, ["is_composite_component"], as_dict=True) asset = frappe.db.get_value("Asset", item.asset, ["asset_type"], as_dict=True)
if asset and asset.is_composite_component: if asset and asset.asset_type == "Composite Component":
composite_component_value += flt(item.asset_value, item.precision("asset_value")) composite_component_value += flt(item.asset_value, item.precision("asset_value"))
return composite_component_value return composite_component_value
def get_gl_entries_for_target_item( def get_gl_entries_for_target_item(
self, gl_entries, target_account, target_against, precision, composite_component_value 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)
total_value = flt(self.total_value - composite_component_value, precision) if total_value:
if total_value: # Capitalization
# Capitalization gl_entries.append(
gl_entries.append( self.get_gl_dict(
self.get_gl_dict( {
{ "account": target_account,
"account": target_account, "against": ", ".join(target_against),
"against": ", ".join(target_against), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": total_value,
"debit": total_value, "cost_center": self.get("cost_center"),
"cost_center": self.get("cost_center"), },
}, item=self,
item=self,
)
) )
)
def update_target_asset(self): def update_target_asset(self):
total_target_asset_value = flt(self.total_value, self.precision("total_value")) 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): def set_consumed_asset_status(self, asset):
if self.docstatus == 1: if self.docstatus == 1:
if self.target_is_fixed_asset: asset.set_status("Capitalized")
asset.set_status("Capitalized") add_asset_activity(
add_asset_activity( asset.name,
asset.name, _("Asset capitalized after Asset Capitalization {0} was submitted").format(
_("Asset capitalized after Asset Capitalization {0} was submitted").format( get_link_to_form("Asset Capitalization", self.name)
get_link_to_form("Asset Capitalization", self.name) ),
), )
)
else: else:
asset.set_status() asset.set_status()
add_asset_activity( add_asset_activity(
@@ -630,7 +610,7 @@ class AssetCapitalization(StockController):
@frappe.whitelist() @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() out = frappe._dict()
# Get Item Details # Get Item Details
@@ -640,17 +620,6 @@ def get_target_item_details(item_code=None, company=None):
# Set Item Details # Set Item Details
out.target_item_name = item.item_name 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 # Cost Center
item_defaults = get_item_defaults(item.name, company) item_defaults = get_item_defaults(item.name, company)
@@ -667,7 +636,7 @@ def get_target_item_details(item_code=None, company=None):
@frappe.whitelist() @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() out = frappe._dict()
# Get Asset Details # Get Asset Details

View File

@@ -10,12 +10,14 @@ from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
from erpnext.assets.doctype.asset.test_asset import ( from erpnext.assets.doctype.asset.test_asset import (
create_asset, create_asset,
create_asset_data, create_asset_data,
create_fixed_asset_item,
set_depreciation_settings_in_company, set_depreciation_settings_in_company,
) )
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc, get_asset_depr_schedule_doc,
) )
from erpnext.stock.doctype.item.test_item import create_item 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 ( from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
make_serial_batch_bundle, make_serial_batch_bundle,
) )
@@ -61,7 +63,7 @@ class TestAssetCapitalization(IntegrationTestCase):
wip_composite_asset = create_asset( wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset", asset_name="Asset Capitalization WIP Composite Asset",
is_composite_asset=1, asset_type="Composite Asset",
warehouse="Stores - TCP1", warehouse="Stores - TCP1",
company=company, company=company,
) )
@@ -81,7 +83,6 @@ class TestAssetCapitalization(IntegrationTestCase):
) )
# Test Asset Capitalization values # 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].valuation_rate, stock_rate)
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount) self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
@@ -156,7 +157,7 @@ class TestAssetCapitalization(IntegrationTestCase):
wip_composite_asset = create_asset( wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset", asset_name="Asset Capitalization WIP Composite Asset",
is_composite_asset=1, asset_type="Composite Asset",
warehouse="Stores - TCP1", warehouse="Stores - TCP1",
company=company, company=company,
) )
@@ -176,8 +177,6 @@ class TestAssetCapitalization(IntegrationTestCase):
) )
# Test Asset Capitalization values # 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].valuation_rate, stock_rate)
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount) self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
self.assertEqual(asset_capitalization.stock_items_total, stock_amount) self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
@@ -245,7 +244,7 @@ class TestAssetCapitalization(IntegrationTestCase):
wip_composite_asset = create_asset( wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset", asset_name="Asset Capitalization WIP Composite Asset",
is_composite_asset=1, asset_type="Composite Asset",
warehouse="Stores - TCP1", warehouse="Stores - TCP1",
company=company, company=company,
) )
@@ -262,8 +261,6 @@ class TestAssetCapitalization(IntegrationTestCase):
) )
# Test Asset Capitalization values # 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].valuation_rate, stock_rate)
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount) self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
self.assertEqual(asset_capitalization.stock_items_total, stock_amount) self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
@@ -313,7 +310,7 @@ class TestAssetCapitalization(IntegrationTestCase):
wip_composite_asset = create_asset( wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset", asset_name="Asset Capitalization WIP Composite Asset",
is_composite_asset=1, asset_type="Composite Asset",
warehouse="Stores - TCP1", warehouse="Stores - TCP1",
company=company, company=company,
) )
@@ -361,33 +358,45 @@ class TestAssetCapitalization(IntegrationTestCase):
wip_composite_asset = create_asset( wip_composite_asset = create_asset(
asset_name="Asset Capitalization WIP Composite Asset", asset_name="Asset Capitalization WIP Composite Asset",
is_composite_asset=1, asset_type="Composite Asset",
warehouse="Stores - TCP1", warehouse="Stores - TCP1",
company=company, company=company,
) )
consumed_asset_value = 100000 consumed_asset_value = 100000
consumed_asset = create_asset( item = create_fixed_asset_item("Asset Capitalization Consumable Asset")
asset_name="Asset Capitalization Consumable Asset",
asset_value=consumed_asset_value, pr = make_purchase_receipt(
submit=1, item_code=item.item_code,
warehouse="Stores - _TC", qty=1,
is_composite_component=1, rate=consumed_asset_value,
company=company, 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 # Create and submit Asset Captitalization
asset_capitalization = create_asset_capitalization( asset_capitalization = create_asset_capitalization(
target_asset=wip_composite_asset.name, target_asset=wip_composite_asset.name,
target_asset_location="Test Location", target_asset_location="Test Location",
consumed_asset=consumed_asset.name, consumed_asset=consumed_asset_doc.name,
company=company, company=company,
submit=1, submit=1,
) )
# Test Asset Capitalization values # Test Asset Capitalization values
self.assertEqual(asset_capitalization.target_qty, 1)
self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value) self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value)
actual_gle = get_actual_gle_dict(asset_capitalization.name) 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_item_code": target_item_code,
"target_asset": target_asset.name, "target_asset": target_asset.name,
"target_asset_location": "Test Location", "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, "finance_book": args.finance_book,
} }
) )
@@ -516,7 +522,7 @@ def create_depreciation_asset(**args):
args = frappe._dict(args) args = frappe._dict(args)
asset = frappe.new_doc("Asset") asset = frappe.new_doc("Asset")
asset.is_existing_asset = 1 asset.asset_type = args.asset_type or "Existing Asset"
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.asset_owner = "Company" asset.asset_owner = "Company"

View File

@@ -87,7 +87,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
calculate_depreciation=1, calculate_depreciation=1,
depreciation_method="Straight Line", depreciation_method="Straight Line",
available_for_use_date="2023-10-10", available_for_use_date="2023-10-10",
is_existing_asset=1, asset_type="Existing Asset",
opening_number_of_booked_depreciations=9, opening_number_of_booked_depreciations=9,
opening_accumulated_depreciation=265, opening_accumulated_depreciation=265,
depreciation_start_date="2024-07-31", depreciation_start_date="2024-07-31",
@@ -127,7 +127,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
calculate_depreciation=1, calculate_depreciation=1,
depreciation_method="Straight Line", depreciation_method="Straight Line",
available_for_use_date="2023-10-10", available_for_use_date="2023-10-10",
is_existing_asset=1, asset_type="Existing Asset",
opening_number_of_booked_depreciations=9, opening_number_of_booked_depreciations=9,
opening_accumulated_depreciation=265.30, opening_accumulated_depreciation=265.30,
depreciation_start_date="2024-07-31", depreciation_start_date="2024-07-31",
@@ -165,7 +165,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
calculate_depreciation=1, calculate_depreciation=1,
depreciation_method="Straight Line", depreciation_method="Straight Line",
available_for_use_date="2023-11-01", available_for_use_date="2023-11-01",
is_existing_asset=1, asset_type="Existing Asset",
opening_number_of_booked_depreciations=4, opening_number_of_booked_depreciations=4,
opening_accumulated_depreciation=223.15, opening_accumulated_depreciation=223.15,
depreciation_start_date="2024-12-31", depreciation_start_date="2024-12-31",
@@ -529,7 +529,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
depreciation_start_date="2023-03-31", depreciation_start_date="2023-03-31",
frequency_of_depreciation=1, frequency_of_depreciation=1,
total_number_of_depreciations=12, total_number_of_depreciations=12,
is_existing_asset=1, asset_type="Existing Asset",
opening_accumulated_depreciation=64.52, opening_accumulated_depreciation=64.52,
opening_number_of_booked_depreciations=2, opening_number_of_booked_depreciations=2,
submit=1, submit=1,
@@ -851,7 +851,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
depreciation_start_date="2023-03-31", depreciation_start_date="2023-03-31",
frequency_of_depreciation=1, frequency_of_depreciation=1,
total_number_of_depreciations=12, total_number_of_depreciations=12,
is_existing_asset=1, asset_type="Existing Asset",
opening_accumulated_depreciation=64.52, opening_accumulated_depreciation=64.52,
opening_number_of_booked_depreciations=2, opening_number_of_booked_depreciations=2,
submit=1, submit=1,
@@ -925,7 +925,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
depreciation_start_date="2021-12-31", depreciation_start_date="2021-12-31",
frequency_of_depreciation=12, frequency_of_depreciation=12,
total_number_of_depreciations=3, total_number_of_depreciations=3,
is_existing_asset=1, asset_type="Existing Asset",
submit=1, submit=1,
) )
post_depreciation_entries(date="2021-12-31") post_depreciation_entries(date="2021-12-31")
@@ -1014,7 +1014,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
depreciation_start_date="2021-12-31", depreciation_start_date="2021-12-31",
frequency_of_depreciation=12, frequency_of_depreciation=12,
total_number_of_depreciations=3, total_number_of_depreciations=3,
is_existing_asset=1, asset_type="Existing Asset",
submit=1, submit=1,
) )
post_depreciation_entries(date="2021-12-31") post_depreciation_entries(date="2021-12-31")
@@ -1093,7 +1093,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
rate_of_depreciation=50, rate_of_depreciation=50,
frequency_of_depreciation=12, frequency_of_depreciation=12,
total_number_of_depreciations=3, total_number_of_depreciations=3,
is_existing_asset=1, asset_type="Existing Asset",
submit=1, submit=1,
) )
post_depreciation_entries(date="2021-12-31") post_depreciation_entries(date="2021-12-31")

View File

@@ -32,6 +32,7 @@
{ {
"fieldname": "purpose", "fieldname": "purpose",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"label": "Purpose", "label": "Purpose",
"options": "\nIssue\nReceipt\nTransfer\nTransfer and Issue", "options": "\nIssue\nReceipt\nTransfer\nTransfer and Issue",
"reqd": 1 "reqd": 1
@@ -97,7 +98,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-05-30 17:01:55.864353", "modified": "2026-03-09 17:19:02.087333",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Movement", "name": "Asset Movement",

View File

@@ -9,9 +9,9 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"naming_series", "naming_series",
"company",
"asset", "asset",
"asset_name", "asset_name",
"company",
"column_break_2", "column_break_2",
"repair_status", "repair_status",
"failure_date", "failure_date",
@@ -28,10 +28,6 @@
"column_break_ajbh", "column_break_ajbh",
"column_break_hkem", "column_break_hkem",
"repair_cost", "repair_cost",
"accounting_dimensions_section",
"cost_center",
"column_break_14",
"project",
"stock_consumption_details_section", "stock_consumption_details_section",
"stock_items", "stock_items",
"section_break_ltbb", "section_break_ltbb",
@@ -43,7 +39,12 @@
"capitalize_repair_cost", "capitalize_repair_cost",
"increase_in_asset_life", "increase_in_asset_life",
"column_break_xebe", "column_break_xebe",
"total_repair_cost" "total_repair_cost",
"accounting_dimensions_section",
"cost_center",
"column_break_14",
"project",
"connection_tab"
], ],
"fields": [ "fields": [
{ {
@@ -149,8 +150,7 @@
{ {
"fieldname": "accounting_details", "fieldname": "accounting_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1, "hide_border": 1
"label": "Repair Purchase Invoices"
}, },
{ {
"fieldname": "stock_items", "fieldname": "stock_items",
@@ -206,6 +206,7 @@
{ {
"fieldname": "invoices", "fieldname": "invoices",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Repair Purchase Invoices",
"mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0;", "mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0;",
"no_copy": 1, "no_copy": 1,
"options": "Asset Repair Purchase Invoice" "options": "Asset Repair Purchase Invoice"
@@ -244,6 +245,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval: doc.consumed_items_cost",
"fieldname": "consumed_items_cost", "fieldname": "consumed_items_cost",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Consumed Items Cost" "label": "Consumed Items Cost"
@@ -256,7 +258,13 @@
"depends_on": "capitalize_repair_cost", "depends_on": "capitalize_repair_cost",
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break", "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, "index_web_pages_for_search": 1,
@@ -267,7 +275,7 @@
"link_fieldname": "asset_repair" "link_fieldname": "asset_repair"
} }
], ],
"modified": "2026-01-06 15:48:13.862505", "modified": "2026-02-06 14:57:54.257572",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Repair", "name": "Asset Repair",

View File

@@ -360,7 +360,7 @@ class TestAssetRepair(IntegrationTestCase):
self.assertEqual(stock_entry.asset_repair, asset_repair.name) self.assertEqual(stock_entry.asset_repair, asset_repair.name)
def test_gl_entries_with_capitalized_asset_repair(self): 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_repair = create_asset_repair(
asset=asset, capitalize_repair_cost=1, item="_Test Non Stock Item", submit=1 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: if args.asset:
asset = args.asset asset = args.asset
else: 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 = frappe.new_doc("Asset Repair")
asset_repair.update( asset_repair.update(
{ {

View File

@@ -144,7 +144,7 @@ def get_conditions(filters):
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]] conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
if filters.get("only_existing_assets"): 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"): if filters.get("asset_category"):
conditions["asset_category"] = filters.get("asset_category") conditions["asset_category"] = filters.get("asset_category")
if filters.get("cost_center"): if filters.get("cost_center"):
@@ -274,7 +274,7 @@ def get_asset_depreciation_amount_map(filters, finance_book):
) )
if filters.only_existing_assets: 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: if filters.asset_category:
query = query.where(asset.asset_category == filters.asset_category) query = query.where(asset.asset_category == filters.asset_category)
if filters.cost_center: if filters.cost_center:
@@ -325,7 +325,7 @@ def get_asset_value_adjustment_map(filters, finance_book):
) )
if filters.only_existing_assets: 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: if filters.asset_category:
query = query.where(asset.asset_category == filters.asset_category) query = query.where(asset.asset_category == filters.asset_category)
if filters.cost_center: if filters.cost_center:

View File

@@ -252,6 +252,7 @@
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "schedule_date", "fieldname": "schedule_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Required By" "label": "Required By"
}, },
{ {
@@ -1327,7 +1328,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-03-02 00:40:47.119584", "modified": "2026-03-09 17:15:29.184682",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@@ -49,7 +49,6 @@
{ {
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"label": "Series", "label": "Series",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "naming_series", "oldfieldname": "naming_series",
@@ -77,6 +76,7 @@
"fieldname": "vendor", "fieldname": "vendor",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Supplier", "label": "Supplier",
"no_copy": 1, "no_copy": 1,
@@ -95,6 +95,7 @@
"fieldname": "transaction_date", "fieldname": "transaction_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1,
"label": "Date", "label": "Date",
"oldfieldname": "transaction_date", "oldfieldname": "transaction_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
@@ -147,7 +148,6 @@
"depends_on": "eval:doc.use_html == 0", "depends_on": "eval:doc.use_html == 0",
"fieldname": "message_for_supplier", "fieldname": "message_for_supplier",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"in_list_view": 1,
"label": "Message for Supplier", "label": "Message for Supplier",
"mandatory_depends_on": "eval:doc.use_html == 0", "mandatory_depends_on": "eval:doc.use_html == 0",
"print_hide": 1 "print_hide": 1
@@ -225,6 +225,8 @@
{ {
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "status", "oldfieldname": "status",
@@ -263,6 +265,7 @@
{ {
"fieldname": "schedule_date", "fieldname": "schedule_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Required Date" "label": "Required Date"
}, },
{ {
@@ -350,7 +353,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-03-01 23:38:48.079274", "modified": "2026-03-09 17:15:29.774614",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Request for Quotation", "name": "Request for Quotation",

View File

@@ -178,6 +178,7 @@
"default": "Company", "default": "Company",
"fieldname": "supplier_type", "fieldname": "supplier_type",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"label": "Supplier Type", "label": "Supplier Type",
"options": "Company\nIndividual\nPartnership", "options": "Company\nIndividual\nPartnership",
"reqd": 1 "reqd": 1
@@ -238,6 +239,7 @@
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Billing Currency", "label": "Billing Currency",
"no_copy": 1, "no_copy": 1,
"options": "Currency" "options": "Currency"
@@ -515,7 +517,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2026-02-10 21:28:01.101808", "modified": "2026-03-09 17:15:25.465759",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Supplier", "name": "Supplier",

View File

@@ -34,7 +34,7 @@ class TestPurchaseOrder(IntegrationTestCase):
self.assertEqual(sq.get("items")[1].rate, 300) self.assertEqual(sq.get("items")[1].rate, 300)
self.assertEqual(sq.get("items")[1].description, "test") 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 = frappe.copy_doc(self.globalTestRecords["Supplier Quotation"][0])
sq.submit() sq.submit()
trans_item = json.dumps( 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( self.assertRaises(
frappe.ValidationError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name frappe.ValidationError, update_child_qty_rate, "Supplier Quotation", trans_item, sq.name
) )

View File

@@ -2525,13 +2525,14 @@ class AccountsController(TransactionBase):
grand_total = flt(self.get("rounded_total") or self.grand_total) grand_total = flt(self.get("rounded_total") or self.grand_total)
automatically_fetch_payment_terms = 0 automatically_fetch_payment_terms = 0
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice", "Sales Order"):
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
po_or_so, doctype, fieldname = self.get_order_details() po_or_so, doctype, fieldname = self.get_order_details()
automatically_fetch_payment_terms = cint( automatically_fetch_payment_terms = cint(
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms") 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 self.get("total_advance"):
if party_account_currency == self.company_currency: if party_account_currency == self.company_currency:
@@ -2547,7 +2548,7 @@ class AccountsController(TransactionBase):
if not self.get("payment_schedule"): if not self.get("payment_schedule"):
if ( if (
self.doctype in ["Sales Invoice", "Purchase Invoice"] self.doctype in ["Sales Invoice", "Purchase Invoice", "Sales Order"]
and automatically_fetch_payment_terms and automatically_fetch_payment_terms
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype) and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
): ):
@@ -2605,16 +2606,18 @@ class AccountsController(TransactionBase):
if not self.get("items"): if not self.get("items"):
return None, None, None return None, None, None
if self.doctype == "Sales Invoice": if self.doctype == "Sales Invoice":
po_or_so = self.get("items")[0].get("sales_order") prev_doc = self.get("items")[0].get("sales_order")
po_or_so_doctype = "Sales Order" prev_doctype = "Sales Order"
po_or_so_doctype_name = "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: else:
po_or_so = self.get("items")[0].get("purchase_order") prev_doc = self.get("items")[0].get("prevdoc_docname")
po_or_so_doctype = "Purchase Order" prev_doctype = "Quotation"
po_or_so_doctype_name = "purchase_order" prev_doctype_name = "prevdoc_docname"
return prev_doc, prev_doctype, prev_doctype_name
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype): 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): 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 frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order") or False
return 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(): if not flt(new_data.get("qty")) and not is_allowed_zero_qty():
frappe.throw( 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")) new_data.get("idx"), frappe.bold(new_data.get("item_code"))
), ),
title=_("Invalid Qty"), title=_("Invalid Qty"),
) )
if parent_doctype == "Sales Order" and flt(new_data.get("qty")) < flt(child_item.delivered_qty): qty_limits = {
frappe.throw(_("Cannot set quantity less than delivered quantity")) "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): if parent_doctype in qty_limits:
frappe.throw(_("Cannot set quantity less than received quantity")) 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 in ["Quotation", "Supplier Quotation"]:
if (parent_doctype == "Quotation" and not ordered_items) or ( 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" if parent_doctype == "Quotation"
else purchased_items.get(child_item.name) else purchased_items.get(child_item.name)
) )
if qty_to_check: 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: if flt(new_data.get("qty")) < qty_to_check:
frappe.throw(_("Cannot reduce quantity than ordered or purchased quantity")) 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 continue
validate_quantity(child_item, d) validate_quantity_and_rate(child_item, d)
if parent_doctype in ["Quotation", "Supplier Quotation"]:
if not rate_unchanged:
frappe.throw(_("Rates cannot be modified for quoted items"))
if flt(child_item.get("qty")) != flt(d.get("qty")): if flt(child_item.get("qty")) != flt(d.get("qty")):
any_qty_changed = True any_qty_changed = True

View File

@@ -1439,6 +1439,16 @@ class StockController(AccountsController):
continue continue
if qi_required: # validate row only if inspection is required on item level 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) self.validate_qi_presence(row)
if self.docstatus == 1: if self.docstatus == 1:
self.validate_qi_submission(row) self.validate_qi_submission(row)
@@ -1446,16 +1456,6 @@ class StockController(AccountsController):
def validate_qi_presence(self, row): def validate_qi_presence(self, row):
"""Check if QI is present on row level. Warn on save and stop on submit if missing.""" """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: if not row.quality_inspection:
msg = _("Row #{0}: Quality Inspection is required for Item {1}").format( msg = _("Row #{0}: Quality Inspection is required for Item {1}").format(
row.idx, frappe.bold(row.item_code) row.idx, frappe.bold(row.item_code)

View 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
}

View File

@@ -219,6 +219,16 @@ website_route_rules = [
{"from_route": "/tasks", "to_route": "Task"}, {"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 = [ standard_portal_menu_items = [
{"title": "Projects", "route": "/project", "reference_doctype": "Project", "role": "Customer"}, {"title": "Projects", "route": "/project", "reference_doctype": "Project", "role": "Customer"},
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -637,11 +637,18 @@ erpnext.bom.BomController = class BomController extends erpnext.TransactionContr
} }
buying_price_list(doc) { 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) { 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); this.apply_price_list(null, true);
} }
} }

View File

@@ -15,18 +15,18 @@ frappe.treeview_settings["BOM"] = {
get_tree_root: false, get_tree_root: false,
show_expand_all: false, show_expand_all: false,
get_label: function (node) { get_label: function (node) {
if (node.data.qty) { if (node.is_root && node.data.value != "BOM") {
const escape = frappe.utils.escape_html; frappe.model.with_doc("BOM", node.data.value, function () {
let label = escape(node.data.item_code); var bom = frappe.model.get_doc("BOM", node.data.value);
if (node.data.item_name && node.data.item_code !== node.data.item_name) { node.data.item_name = bom.item_name || "";
label += `: ${escape(node.data.item_name)}`; node.data.item_code = bom.item || "";
} node.data.qty = bom.quantity || "";
return `${label} <span class="badge badge-pill badge-light">${node.data.qty} ${escape( node.data.stock_uom = bom.uom || "";
__(node.data.stock_uom) return get_bom_node(node);
)}</span>`; });
} else {
return node.data.item_code || node.data.value;
} }
return get_bom_node(node);
}, },
onload: function (me) { onload: function (me) {
var label = frappe.get_route()[0] + "/" + frappe.get_route()[1]; var label = frappe.get_route()[0] + "/" + frappe.get_route()[1];
@@ -78,3 +78,22 @@ frappe.treeview_settings["BOM"] = {
}, },
view_template: "bom_item_preview", 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;
}
}

View File

@@ -1321,9 +1321,9 @@ class JobCard(Document):
def is_work_order_closed(self): def is_work_order_closed(self):
if self.work_order: 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 True
return False return False

View File

@@ -461,10 +461,11 @@ frappe.ui.form.on("Work Order", {
var added_min = false; var added_min = false;
// produced qty // 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({ bars.push({
title: title, title: title,
width: (frm.doc.produced_qty / frm.doc.qty) * 100 + "%", width: (flt(produced_qty) / frm.doc.qty) * 100 + "%",
progress_class: "progress-bar-success", progress_class: "progress-bar-success",
}); });
if (bars[0].width == "0%") { if (bars[0].width == "0%") {
@@ -481,14 +482,27 @@ frappe.ui.form.on("Work Order", {
if (pending_complete > 0) { if (pending_complete > 0) {
var width = (pending_complete / frm.doc.qty) * 100 - added_min; var width = (pending_complete / frm.doc.qty) * 100 - added_min;
title = __("{0} items in progress", [pending_complete]); 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({ bars.push({
title: title, title: title,
width: (width > 100 ? "99.5" : width) + "%", width: (width > 100 ? "99.5" : width) + "%",
progress_class: "progress-bar-warning", progress_class: progress_class,
}); });
message = message + ". " + title; message = message + ". " + title;
} }
} }
//process loss qty
if (frm.doc.process_loss_qty) { if (frm.doc.process_loss_qty) {
var process_loss_width = (frm.doc.process_loss_qty / frm.doc.qty) * 100; var process_loss_width = (frm.doc.process_loss_qty / frm.doc.qty) * 100;
title = __("{0} items lost during process.", [frm.doc.process_loss_qty]); title = __("{0} items lost during process.", [frm.doc.process_loss_qty]);
@@ -499,6 +513,19 @@ frappe.ui.form.on("Work Order", {
}); });
message = message + ". " + title; 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); frm.dashboard.add_progress(__("Status"), bars, message);
}, },

View File

@@ -713,19 +713,25 @@ class WorkOrder(Document):
self.db_set("disassembled_qty", self.disassembled_qty) self.db_set("disassembled_qty", self.disassembled_qty)
def get_transferred_or_manufactured_qty(self, purpose, fieldname): def get_transferred_or_manufactured_qty(self, purpose, fieldname):
table = frappe.qb.DocType("Stock Entry") parent = frappe.qb.DocType("Stock Entry")
query = frappe.qb.from_(table).where(
(table.work_order == self.name) & (table.docstatus == 1) & (table.purpose == purpose) 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": 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: else:
query = query.select(Sum(table.fg_completed_qty)) query = query.select(Sum(parent.fg_completed_qty))
query = query.where(
table.is_additional_transfer_entry == cint(fieldname == "additional_transferred_qty")
)
return flt(query.run()[0][0]) return flt(query.run()[0][0])

View File

@@ -74,7 +74,6 @@ erpnext.patches.v12_0.make_item_manufacturer
erpnext.patches.v12_0.move_item_tax_to_item_tax_template erpnext.patches.v12_0.move_item_tax_to_item_tax_template
erpnext.patches.v11_1.set_variant_based_on erpnext.patches.v11_1.set_variant_based_on
erpnext.patches.v11_1.woocommerce_set_creation_user erpnext.patches.v11_1.woocommerce_set_creation_user
erpnext.patches.v11_1.rename_depends_on_lwp
execute:frappe.delete_doc("Report", "Inactive Items") execute:frappe.delete_doc("Report", "Inactive Items")
erpnext.patches.v11_1.delete_scheduling_tool erpnext.patches.v11_1.delete_scheduling_tool
erpnext.patches.v12_0.rename_tolerance_fields 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.v15_0.delete_quotation_lost_record_detail
erpnext.patches.v16_0.add_portal_redirects erpnext.patches.v16_0.add_portal_redirects
erpnext.patches.v16_0.complete_onboarding_steps_for_older_sites #2 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

View 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)

View File

@@ -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()

View File

@@ -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

View File

@@ -205,7 +205,7 @@ frappe.ui.form.on("Project", {
collect_progress: function (frm) { collect_progress: function (frm) {
if (frm.doc.collect_progress && !frm.doc.subject) { 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]));
} }
}, },
}); });

View File

@@ -12,29 +12,21 @@
"project_name", "project_name",
"status", "status",
"project_type", "project_type",
"is_active",
"percent_complete_method", "percent_complete_method",
"percent_complete",
"column_break_5", "column_break_5",
"project_template", "project_template",
"expected_start_date",
"expected_end_date",
"priority", "priority",
"department", "department",
"customer_details", "is_active",
"customer", "percent_complete",
"column_break_14",
"sales_order",
"users_section",
"users",
"copied_from",
"section_break0",
"notes",
"section_break_18", "section_break_18",
"expected_start_date",
"actual_start_date", "actual_start_date",
"actual_time", "actual_time",
"column_break_20", "column_break_20",
"expected_end_date",
"actual_end_date", "actual_end_date",
"costing_tab",
"project_details", "project_details",
"estimated_costing", "estimated_costing",
"total_costing_amount", "total_costing_amount",
@@ -50,7 +42,7 @@
"gross_margin", "gross_margin",
"column_break_37", "column_break_37",
"per_gross_margin", "per_gross_margin",
"monitor_progress", "monitor_progress_tab",
"collect_progress", "collect_progress",
"holiday_list", "holiday_list",
"frequency", "frequency",
@@ -63,7 +55,18 @@
"weekly_time_to_send", "weekly_time_to_send",
"column_break_45", "column_break_45",
"subject", "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": [ "fields": [
{ {
@@ -115,6 +118,7 @@
"bold": 1, "bold": 1,
"fieldname": "percent_complete", "fieldname": "percent_complete",
"fieldtype": "Percent", "fieldtype": "Percent",
"in_list_view": 1,
"label": "% Completed", "label": "% Completed",
"no_copy": 1, "no_copy": 1,
"read_only": 1 "read_only": 1
@@ -135,6 +139,7 @@
"bold": 1, "bold": 1,
"fieldname": "expected_start_date", "fieldname": "expected_start_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Expected Start Date", "label": "Expected Start Date",
"oldfieldname": "project_start_date", "oldfieldname": "project_start_date",
"oldfieldtype": "Date" "oldfieldtype": "Date"
@@ -231,7 +236,7 @@
"collapsible": 1, "collapsible": 1,
"fieldname": "section_break_18", "fieldname": "section_break_18",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Start and End Dates" "label": "Timeline"
}, },
{ {
"fieldname": "actual_start_date", "fieldname": "actual_start_date",
@@ -258,7 +263,6 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible": 1,
"fieldname": "project_details", "fieldname": "project_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Costing and Billing", "label": "Costing and Billing",
@@ -329,7 +333,6 @@
"options": "Cost Center" "options": "Cost Center"
}, },
{ {
"collapsible": 1,
"fieldname": "margin", "fieldname": "margin",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Margin", "label": "Margin",
@@ -357,12 +360,6 @@
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"read_only": 1 "read_only": 1
}, },
{
"collapsible": 1,
"fieldname": "monitor_progress",
"fieldtype": "Section Break",
"label": "Monitor Progress"
},
{ {
"default": "0", "default": "0",
"fieldname": "collect_progress", "fieldname": "collect_progress",
@@ -455,6 +452,27 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Subject", "label": "Subject",
"mandatory_depends_on": "collect_progress" "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", "icon": "fa fa-puzzle-piece",
@@ -462,7 +480,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 4, "max_attachments": 4,
"modified": "2025-08-21 17:57:58.314809", "modified": "2026-03-09 17:15:24.426294",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Project", "name": "Project",

View File

@@ -19,6 +19,13 @@ frappe.ui.form.on("Project Template", {
frappe.ui.form.on("Project Template Task", { frappe.ui.form.on("Project Template Task", {
task: function (frm, cdt, cdn) { task: function (frm, cdt, cdn) {
var row = locals[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) => { frappe.db.get_value("Task", row.task, "subject", (value) => {
row.subject = value.subject; row.subject = value.subject;
refresh_field("tasks"); refresh_field("tasks");

View File

@@ -13,7 +13,6 @@
"type", "type",
"color", "color",
"is_group", "is_group",
"is_template",
"column_break0", "column_break0",
"status", "status",
"priority", "priority",
@@ -21,17 +20,21 @@
"parent_task", "parent_task",
"completed_by", "completed_by",
"completed_on", "completed_on",
"section_break_dafi",
"is_template",
"column_break_vvfp",
"start",
"duration",
"sb_timeline", "sb_timeline",
"exp_start_date", "exp_start_date",
"expected_time", "expected_time",
"start",
"column_break_11", "column_break_11",
"exp_end_date", "exp_end_date",
"progress", "progress",
"duration",
"is_milestone", "is_milestone",
"sb_details", "sb_details",
"description", "description",
"dependencies_tab",
"sb_depends_on", "sb_depends_on",
"depends_on", "depends_on",
"depends_on_tasks", "depends_on_tasks",
@@ -44,12 +47,13 @@
"total_costing_amount", "total_costing_amount",
"column_break_20", "column_break_20",
"total_billing_amount", "total_billing_amount",
"more_info_tab",
"sb_more_info", "sb_more_info",
"company",
"review_date", "review_date",
"closing_date", "closing_date",
"column_break_22", "column_break_22",
"department", "department",
"company",
"lft", "lft",
"rgt", "rgt",
"old_parent", "old_parent",
@@ -78,7 +82,6 @@
"oldfieldname": "project", "oldfieldname": "project",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Project", "options": "Project",
"remember_last_selected_value": 1,
"search_index": 1 "search_index": 1
}, },
{ {
@@ -218,7 +221,6 @@
{ {
"fieldname": "sb_depends_on", "fieldname": "sb_depends_on",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Dependencies",
"oldfieldtype": "Section Break" "oldfieldtype": "Section Break"
}, },
{ {
@@ -298,10 +300,9 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible": 1,
"fieldname": "sb_more_info", "fieldname": "sb_more_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "More Info" "label": "Additional Info"
}, },
{ {
"depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"", "depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"",
@@ -334,8 +335,7 @@
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company", "options": "Company"
"remember_last_selected_value": 1
}, },
{ {
"fieldname": "lft", "fieldname": "lft",
@@ -368,6 +368,7 @@
"options": "User" "options": "User"
}, },
{ {
"allow_in_quick_entry": 1,
"default": "0", "default": "0",
"fieldname": "is_template", "fieldname": "is_template",
"fieldtype": "Check", "fieldtype": "Check",
@@ -397,6 +398,24 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Template Task" "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", "icon": "fa fa-check",
@@ -404,11 +423,11 @@
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"max_attachments": 5, "max_attachments": 5,
"modified": "2025-10-16 08:39:12.214577", "modified": "2026-03-04 11:47:10.454548",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Task", "name": "Task",
"naming_rule": "Expression (old style)", "naming_rule": "Expression",
"nsm_parent_field": "parent_task", "nsm_parent_field": "parent_task",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
@@ -425,6 +444,7 @@
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "subject", "search_fields": "subject",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"show_preview_popup": 1, "show_preview_popup": 1,

View File

@@ -138,6 +138,8 @@ class Task(NestedSet):
def validate_status(self): def validate_status(self):
if self.is_template and self.status != "Template": if self.is_template and self.status != "Template":
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": if self.status != self.get_db_value("status") and self.status == "Completed":
for d in self.depends_on: for d in self.depends_on:
if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"):

View File

@@ -260,6 +260,33 @@ frappe.ui.form.on("Timesheet", {
parent_project: function (frm) { parent_project: function (frm) {
set_project_in_timelog(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", { frappe.ui.form.on("Timesheet Detail", {

View File

@@ -18,28 +18,29 @@
"column_break_3", "column_break_3",
"status", "status",
"parent_project", "parent_project",
"employee_detail",
"employee",
"employee_name",
"department",
"column_break_9",
"user", "user",
"start_date", "start_date",
"end_date", "end_date",
"employee_detail",
"employee",
"department",
"column_break_9",
"employee_name",
"section_break_5", "section_break_5",
"time_logs", "time_logs",
"working_hours", "working_hours",
"total_hours", "total_hours",
"billing_tab",
"billing_details", "billing_details",
"total_billable_hours", "total_billable_hours",
"total_billable_amount",
"total_costing_amount",
"base_total_billable_amount", "base_total_billable_amount",
"base_total_billed_amount",
"base_total_costing_amount", "base_total_costing_amount",
"column_break_10", "column_break_10",
"total_billed_hours", "total_billed_hours",
"total_billable_amount",
"total_billed_amount", "total_billed_amount",
"total_costing_amount", "base_total_billed_amount",
"per_billed", "per_billed",
"section_break_18", "section_break_18",
"note", "note",
@@ -176,7 +177,6 @@
"read_only": 1 "read_only": 1
}, },
{ {
"collapsible": 1,
"fieldname": "billing_details", "fieldname": "billing_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Billing Details", "label": "Billing Details",
@@ -304,13 +304,18 @@
"fieldname": "exchange_rate", "fieldname": "exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Exchange Rate" "label": "Exchange Rate"
},
{
"fieldname": "billing_tab",
"fieldtype": "Tab Break",
"label": "Billing"
} }
], ],
"icon": "fa fa-clock-o", "icon": "fa fa-clock-o",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2025-12-19 13:48:23.453636", "modified": "2026-03-04 11:56:51.438298",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Projects", "module": "Projects",
"name": "Timesheet", "name": "Timesheet",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -543,7 +543,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
schedules: selected, schedules: selected,
}, },
}); });
frappe.model.sync(pr_name);
frappe.set_route("Form", "Payment Request", pr_name.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(); this.validate_has_items();
erpnext.utils.view_serial_batch_nos(this.frm); erpnext.utils.view_serial_batch_nos(this.frm);
this.set_route_options_for_new_doc(); this.set_route_options_for_new_doc();
erpnext.toggle_serial_batch_fields(this.frm);
} }
set_route_options_for_new_doc() { set_route_options_for_new_doc() {
@@ -1307,6 +1308,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (this.frm.doc.transaction_date) { if (this.frm.doc.transaction_date) {
this.frm.transaction_date = this.frm.doc.transaction_date; this.frm.transaction_date = this.frm.doc.transaction_date;
frappe.ui.form.trigger(this.frm.doc.doctype, "currency"); frappe.ui.form.trigger(this.frm.doc.doctype, "currency");
this.recalculate_terms();
} }
} }

View File

@@ -19,6 +19,77 @@ $.extend(erpnext, {
return currency_list; 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 () { toggle_naming_series: function () {
if ( if (
cur_frm && cur_frm &&

View File

@@ -240,6 +240,7 @@
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Billing Currency", "label": "Billing Currency",
"no_copy": 1, "no_copy": 1,
"options": "Currency" "options": "Currency"
@@ -639,7 +640,7 @@
"link_fieldname": "party" "link_fieldname": "party"
} }
], ],
"modified": "2026-02-02 15:39:55.920831", "modified": "2026-03-09 17:15:26.040050",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Customer", "name": "Customer",

View File

@@ -213,6 +213,7 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Company", "label": "Company",
"oldfieldname": "company", "oldfieldname": "company",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@@ -1127,7 +1128,7 @@
"idx": 82, "idx": 82,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-02-06 17:34:22.170032", "modified": "2026-03-09 17:15:31.941114",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@@ -7,7 +7,7 @@ import json
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.mapper import get_mapped_doc 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 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 child_filter = d.name in filtered_items if filtered_items else True
return child_filter return child_filter
automatically_fetch_payment_terms = cint(
frappe.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
)
doclist = get_mapped_doc( doclist = get_mapped_doc(
"Quotation", "Quotation",
source_name, source_name,
@@ -453,6 +457,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar
"Quotation": { "Quotation": {
"doctype": "Sales Order", "doctype": "Sales Order",
"validation": {"docstatus": ["=", 1]}, "validation": {"docstatus": ["=", 1]},
"field_no_map": ["payment_terms_template"],
}, },
"Quotation Item": { "Quotation Item": {
"doctype": "Sales Order 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 Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True}, "Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
"Payment Schedule": {"doctype": "Payment Schedule", "add_if_empty": True},
}, },
target_doc, target_doc,
set_missing_values, set_missing_values,
ignore_permissions=ignore_permissions, ignore_permissions=ignore_permissions,
) )
if automatically_fetch_payment_terms:
doclist.set_payment_schedule()
return doclist return doclist

View File

@@ -59,8 +59,22 @@ class TestQuotation(IntegrationTestCase):
qo.payment_schedule[0].due_date = add_days(qo.transaction_date, -2) qo.payment_schedule[0].due_date = add_days(qo.transaction_date, -2)
self.assertRaises(frappe.ValidationError, qo.save) self.assertRaises(frappe.ValidationError, qo.save)
def test_update_child_disallow_rate_change(self): def test_update_child_rate_change(self):
qo = make_quotation(qty=4) 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( trans_item = json.dumps(
[ [
{ {
@@ -68,10 +82,35 @@ class TestQuotation(IntegrationTestCase):
"rate": 5000, "rate": 5000,
"qty": qo.items[0].qty, "qty": qo.items[0].qty,
"docname": qo.items[0].name, "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) 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): def test_update_child_removing_item(self):
qo = make_quotation(qty=10) qo = make_quotation(qty=10)
@@ -143,6 +182,10 @@ class TestQuotation(IntegrationTestCase):
self.assertTrue(quotation.payment_schedule) self.assertTrue(quotation.payment_schedule)
@IntegrationTestCase.change_settings(
"Accounts Settings",
{"automatically_fetch_payment_terms": 1},
)
def test_make_sales_order_terms_copied(self): def test_make_sales_order_terms_copied(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order from erpnext.selling.doctype.quotation.quotation import make_sales_order
@@ -285,7 +328,11 @@ class TestQuotation(IntegrationTestCase):
@IntegrationTestCase.change_settings( @IntegrationTestCase.change_settings(
"Accounts 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): def test_make_sales_order_with_terms(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order from erpnext.selling.doctype.quotation.quotation import make_sales_order
@@ -323,10 +370,13 @@ class TestQuotation(IntegrationTestCase):
sales_order.save() sales_order.save()
self.assertEqual(sales_order.payment_schedule[0].payment_amount, 8906.00) 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].payment_amount, 8906.00)
self.assertEqual( 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): def test_valid_till_before_transaction_date(self):
@@ -1026,6 +1076,56 @@ class TestQuotation(IntegrationTestCase):
quotation.reload() quotation.reload()
self.assertEqual(quotation.status, "Open") 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): def enable_calculate_bundle_price(enable=1):
selling_settings = frappe.get_doc("Selling Settings") selling_settings = frappe.get_doc("Selling Settings")

View File

@@ -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_add_rows", true);
frm.set_df_property("packed_items", "cannot_delete_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) { refresh: function (frm) {
frm.fields_dict["items"].grid.update_docfield_property( 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. // Hide `Reserve Stock` field description in submitted or cancelled Sales Order.
if (frm.doc.docstatus > 0) { if (frm.doc.docstatus > 0) {
frm.set_df_property("reserve_stock", "description", null); 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) { create_stock_reservation_entries(frm) {
const dialog = new frappe.ui.Dialog({ const dialog = new frappe.ui.Dialog({
title: __("Stock Reservation"), 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 })); 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),
});
}
}

View File

@@ -123,6 +123,7 @@
"company_contact_person", "company_contact_person",
"payment_schedule_section", "payment_schedule_section",
"payment_terms_section", "payment_terms_section",
"ignore_default_payment_terms_template",
"payment_terms_template", "payment_terms_template",
"payment_schedule", "payment_schedule",
"terms_section_break", "terms_section_break",
@@ -1733,6 +1734,14 @@
"fieldtype": "Time", "fieldtype": "Time",
"label": "Time", "label": "Time",
"mandatory_depends_on": "is_internal_customer" "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, "grid_page_length": 50,
@@ -1740,7 +1749,7 @@
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-03-02 00:42:18.834823", "modified": "2026-03-04 18:04:05.873483",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",

View File

@@ -116,6 +116,7 @@ class SalesOrder(SellingController):
grand_total: DF.Currency grand_total: DF.Currency
group_same_items: DF.Check group_same_items: DF.Check
has_unit_price_items: DF.Check has_unit_price_items: DF.Check
ignore_default_payment_terms_template: DF.Check
ignore_pricing_rule: DF.Check ignore_pricing_rule: DF.Check
in_words: DF.Data | None in_words: DF.Data | None
incoterm: DF.Link | None incoterm: DF.Link | None

View File

@@ -95,6 +95,7 @@
"ordered_qty", "ordered_qty",
"planned_qty", "planned_qty",
"production_plan_qty", "production_plan_qty",
"requested_qty",
"column_break_69", "column_break_69",
"work_order_qty", "work_order_qty",
"delivered_qty", "delivered_qty",
@@ -1010,13 +1011,21 @@
"fieldtype": "Float", "fieldtype": "Float",
"label": "Finished Good Qty", "label": "Finished Good Qty",
"mandatory_depends_on": "eval:parent.is_subcontracted" "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, "grid_page_length": 50,
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2026-02-20 16:39:00.200328", "modified": "2026-02-21 16:39:00.200328",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order Item", "name": "Sales Order Item",

View File

@@ -80,6 +80,7 @@ class SalesOrderItem(Document):
quotation_item: DF.Data | None quotation_item: DF.Data | None
rate: DF.Currency rate: DF.Currency
rate_with_margin: DF.Currency rate_with_margin: DF.Currency
requested_qty: DF.Float
reserve_stock: DF.Check reserve_stock: DF.Check
returned_qty: DF.Float returned_qty: DF.Float
stock_qty: DF.Float stock_qty: DF.Float

View File

@@ -329,7 +329,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2026-02-12 10:38:34.605126", "modified": "2026-02-27 00:47:46.003305",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Selling Settings", "name": "Selling Settings",

View File

@@ -166,6 +166,7 @@
"default": "0", "default": "0",
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"in_list_view": 1,
"label": "Is Group" "label": "Is Group"
}, },
{ {
@@ -187,7 +188,6 @@
"fieldname": "parent_company", "fieldname": "parent_company",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Parent Company", "label": "Parent Company",
"options": "Company" "options": "Company"
}, },
@@ -245,6 +245,7 @@
"fieldname": "default_currency", "fieldname": "default_currency",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Default Currency", "label": "Default Currency",
"options": "Currency", "options": "Currency",
"reqd": 1 "reqd": 1
@@ -468,6 +469,7 @@
"default": "1", "default": "1",
"fieldname": "enable_perpetual_inventory", "fieldname": "enable_perpetual_inventory",
"fieldtype": "Check", "fieldtype": "Check",
"in_list_view": 1,
"label": "Enable Perpetual Inventory" "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.", "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", "fieldname": "accounts_frozen_till_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Accounts Frozen Till Date" "label": "Accounts Frozen Till Date"
}, },
{ {
@@ -955,7 +956,6 @@
{ {
"fieldname": "role_allowed_for_frozen_entries", "fieldname": "role_allowed_for_frozen_entries",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Roles Allowed to Set and Edit Frozen Account Entries", "label": "Roles Allowed to Set and Edit Frozen Account Entries",
"options": "Role" "options": "Role"
}, },
@@ -970,7 +970,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2025-11-16 16:51:27.624096", "modified": "2026-03-09 17:15:33.819426",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@@ -183,13 +183,11 @@ class Employee(NestedSet):
throw(_("Please enter relieving date.")) throw(_("Please enter relieving date."))
def validate_for_enabled_user_id(self, enabled): def validate_for_enabled_user_id(self, enabled):
if self.status != "Active":
return
if enabled is None: if enabled is None:
frappe.throw(_("User {0} does not exist").format(self.user_id)) 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): def validate_duplicate_user_id(self):
Employee = frappe.qb.DocType("Employee") Employee = frappe.qb.DocType("Employee")

View File

@@ -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"
}

View 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
}

View File

@@ -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": ""
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -221,6 +221,8 @@ def set_defaults_for_tests():
frappe.db.set_default(key, value) 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", "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): def insert_record(records):
from frappe.desk.page.setup_wizard.setup_wizard import make_records from frappe.desk.page.setup_wizard.setup_wizard import make_records

View File

@@ -246,6 +246,7 @@
"default": "Today", "default": "Today",
"fieldname": "posting_date", "fieldname": "posting_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Date", "label": "Date",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "posting_date", "oldfieldname": "posting_date",
@@ -971,6 +972,7 @@
{ {
"fieldname": "per_billed", "fieldname": "per_billed",
"fieldtype": "Percent", "fieldtype": "Percent",
"in_list_view": 1,
"label": "% Amount Billed", "label": "% Amount Billed",
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
@@ -1061,6 +1063,7 @@
"default": "Draft", "default": "Draft",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
@@ -1078,7 +1081,6 @@
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"fieldname": "per_installed", "fieldname": "per_installed",
"fieldtype": "Percent", "fieldtype": "Percent",
"in_list_view": 1,
"label": "% Installed", "label": "% Installed",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "per_installed", "oldfieldname": "per_installed",
@@ -1212,6 +1214,7 @@
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"fieldname": "per_returned", "fieldname": "per_returned",
"fieldtype": "Percent", "fieldtype": "Percent",
"in_list_view": 1,
"label": "% Returned", "label": "% Returned",
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
@@ -1449,7 +1452,7 @@
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-02-10 14:35:08.523130", "modified": "2026-03-09 17:15:27.932956",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@@ -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) { refresh: function (frm) {
frm.trigger("toggle_has_serial_batch_fields");
if (frm.doc.is_stock_item) { if (frm.doc.is_stock_item) {
frm.add_custom_button( frm.add_custom_button(
__("Stock Balance"), __("Stock Balance"),

View File

@@ -452,6 +452,7 @@
"fieldname": "batch_number_series", "fieldname": "batch_number_series",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Batch Number Series", "label": "Batch Number Series",
"show_description_on_click": 1,
"translatable": 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.", "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", "fieldname": "serial_no_series",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Serial Number Series" "label": "Serial Number Series",
"show_description_on_click": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
@@ -985,7 +987,7 @@
"image_field": "image", "image_field": "image",
"links": [], "links": [],
"make_attachments_public": 1, "make_attachments_public": 1,
"modified": "2026-02-05 17:20:35.605734", "modified": "2026-03-05 16:29:31.653447",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -159,6 +159,7 @@
"default": "Today", "default": "Today",
"fieldname": "transaction_date", "fieldname": "transaction_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1,
"label": "Transaction Date", "label": "Transaction Date",
"no_copy": 1, "no_copy": 1,
"oldfieldname": "transaction_date", "oldfieldname": "transaction_date",
@@ -282,7 +283,6 @@
{ {
"fieldname": "set_warehouse", "fieldname": "set_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Set Target Warehouse", "label": "Set Target Warehouse",
"options": "Warehouse" "options": "Warehouse"
}, },
@@ -377,7 +377,7 @@
"idx": 70, "idx": 70,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-01-21 12:48:40.792323", "modified": "2026-03-09 17:15:30.124509",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Material Request", "name": "Material Request",

View File

@@ -90,7 +90,7 @@ class MaterialRequest(BuyingController):
{ {
"source_dt": "Material Request Item", "source_dt": "Material Request Item",
"target_dt": "Sales Order Item", "target_dt": "Sales Order Item",
"target_field": "ordered_qty", "target_field": "requested_qty",
"target_parent_dt": "Sales Order", "target_parent_dt": "Sales Order",
"target_parent_field": "", "target_parent_field": "",
"join_field": "sales_order_item", "join_field": "sales_order_item",
@@ -280,6 +280,8 @@ class MaterialRequest(BuyingController):
def on_cancel(self): def on_cancel(self):
self.update_requested_qty_in_production_plan(cancel=True) self.update_requested_qty_in_production_plan(cancel=True)
self.update_requested_qty() self.update_requested_qty()
if self.material_request_type == "Purchase":
self.update_prevdoc_status()
def get_mr_items_ordered_qty(self, mr_items): def get_mr_items_ordered_qty(self, mr_items):
mr_items_ordered_qty = {} mr_items_ordered_qty = {}

View File

@@ -119,6 +119,8 @@ frappe.ui.form.on("Pick List", {
refresh: (frm) => { refresh: (frm) => {
frm.trigger("add_get_items_button"); frm.trigger("add_get_items_button");
frm.trigger("update_warehouse_property"); frm.trigger("update_warehouse_property");
erpnext.toggle_serial_batch_fields(frm);
if (frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
const status_completed = frm.doc.status === "Completed"; const status_completed = frm.doc.status === "Completed";

View File

@@ -938,6 +938,7 @@
{ {
"fieldname": "per_billed", "fieldname": "per_billed",
"fieldtype": "Percent", "fieldtype": "Percent",
"in_list_view": 1,
"label": "% Amount Billed", "label": "% Amount Billed",
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
@@ -1302,7 +1303,7 @@
"idx": 261, "idx": 261,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2026-02-23 16:56:41.075091", "modified": "2026-03-09 17:15:28.602690",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt", "name": "Purchase Receipt",

View File

@@ -107,6 +107,7 @@ class SerialandBatchBundle(Document):
self.autoname() self.autoname()
def validate(self): def validate(self):
self.validate_allow_to_set_serial_batch()
if self.docstatus == 1 and self.voucher_detail_no: if self.docstatus == 1 and self.voucher_detail_no:
self.validate_voucher_detail_no() self.validate_voucher_detail_no()
@@ -143,6 +144,15 @@ class SerialandBatchBundle(Document):
self.calculate_qty_and_amount() self.calculate_qty_and_amount()
self.set_child_details() 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): def validate_serial_no_status(self):
serial_nos = [d.serial_no for d in self.entries if d.serial_no] serial_nos = [d.serial_no for d in self.entries if d.serial_no]
invalid_serial_nos = frappe.get_all( invalid_serial_nos = frappe.get_all(

View File

@@ -245,6 +245,7 @@ frappe.ui.form.on("Stock Entry", {
refresh: function (frm) { refresh: function (frm) {
frm.trigger("get_items_from_transit_entry"); frm.trigger("get_items_from_transit_entry");
frm.trigger("toggle_warehouse_fields"); frm.trigger("toggle_warehouse_fields");
erpnext.toggle_serial_batch_fields(frm);
if (!frm.doc.docstatus && !frm.doc.subcontracting_inward_order) { if (!frm.doc.docstatus && !frm.doc.subcontracting_inward_order) {
frm.trigger("validate_purpose_consumption"); 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) { conversion_factor(frm, cdt, cdn) {
frm.events.set_rate_and_fg_qty(frm, cdt, cdn); frm.events.set_rate_and_fg_qty(frm, cdt, cdn);
}, },

View File

@@ -76,6 +76,8 @@ frappe.ui.form.on("Stock Reconciliation", {
}, },
refresh: function (frm) { refresh: function (frm) {
erpnext.toggle_serial_batch_fields(frm);
if (frm.doc.docstatus < 1) { if (frm.doc.docstatus < 1) {
frm.add_custom_button(__("Fetch Items from Warehouse"), function () { frm.add_custom_button(__("Fetch Items from Warehouse"), function () {
frm.events.get_items(frm); frm.events.get_items(frm);

View File

@@ -38,6 +38,7 @@
"allow_internal_transfer_at_arms_length_price", "allow_internal_transfer_at_arms_length_price",
"validate_material_transfer_warehouses", "validate_material_transfer_warehouses",
"serial_and_batch_item_settings_tab", "serial_and_batch_item_settings_tab",
"enable_serial_and_batch_no_for_item",
"section_break_7", "section_break_7",
"allow_existing_serial_no", "allow_existing_serial_no",
"do_not_use_batchwise_valuation", "do_not_use_batchwise_valuation",
@@ -48,9 +49,8 @@
"use_serial_batch_fields", "use_serial_batch_fields",
"do_not_update_serial_batch_on_creation_of_auto_bundle", "do_not_update_serial_batch_on_creation_of_auto_bundle",
"allow_negative_stock_for_batch", "allow_negative_stock_for_batch",
"serial_and_batch_bundle_section",
"set_serial_and_batch_bundle_naming_based_on_naming_series",
"section_break_gnhq", "section_break_gnhq",
"set_serial_and_batch_bundle_naming_based_on_naming_series",
"use_naming_series", "use_naming_series",
"column_break_wslv", "column_break_wslv",
"naming_series_prefix", "naming_series_prefix",
@@ -158,6 +158,7 @@
"label": "Convert Item Description to Clean HTML in Transactions" "label": "Convert Item Description to Clean HTML in Transactions"
}, },
{ {
"depends_on": "enable_serial_and_batch_no_for_item",
"fieldname": "section_break_7", "fieldname": "section_break_7",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Serial & Batch Item Settings" "label": "Serial & Batch Item Settings"
@@ -487,11 +488,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Auto Reserve Stock" "label": "Auto Reserve Stock"
}, },
{
"fieldname": "serial_and_batch_bundle_section",
"fieldtype": "Section Break",
"label": "Serial and Batch Bundle"
},
{ {
"default": "0", "default": "0",
"fieldname": "set_serial_and_batch_bundle_naming_based_on_naming_series", "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" "label": "Set Serial and Batch Bundle Naming Based on Naming Series"
}, },
{ {
"depends_on": "enable_serial_and_batch_no_for_item",
"fieldname": "section_break_gnhq", "fieldname": "section_break_gnhq",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
@@ -554,6 +551,11 @@
"fieldname": "allow_negative_stock_for_batch", "fieldname": "allow_negative_stock_for_batch",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow Negative Stock for Batch" "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, "hide_toolbar": 1,
@@ -562,7 +564,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2026-02-25 09:56:34.105949", "modified": "2026-02-25 10:56:34.105949",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Settings", "name": "Stock Settings",

View File

@@ -47,6 +47,7 @@ class StockSettings(Document):
disable_serial_no_and_batch_selector: DF.Check disable_serial_no_and_batch_selector: DF.Check
do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check do_not_update_serial_batch_on_creation_of_auto_bundle: DF.Check
do_not_use_batchwise_valuation: DF.Check do_not_use_batchwise_valuation: DF.Check
enable_serial_and_batch_no_for_item: DF.Check
enable_stock_reservation: DF.Check enable_stock_reservation: DF.Check
item_group: DF.Link | None item_group: DF.Link | None
item_naming_by: DF.Literal["Item Code", "Naming Series"] item_naming_by: DF.Literal["Item Code", "Naming Series"]
@@ -82,6 +83,7 @@ class StockSettings(Document):
"default_warehouse", "default_warehouse",
"set_qty_in_transactions_based_on_serial_no_input", "set_qty_in_transactions_based_on_serial_no_input",
"use_serial_batch_fields", "use_serial_batch_fields",
"enable_serial_and_batch_no_for_item",
"set_serial_and_batch_bundle_naming_based_on_naming_series", "set_serial_and_batch_bundle_naming_based_on_naming_series",
]: ]:
frappe.db.set_default(key, self.get(key, "")) frappe.db.set_default(key, self.get(key, ""))
@@ -104,6 +106,7 @@ class StockSettings(Document):
) )
self.validate_warehouses() self.validate_warehouses()
self.validate_serial_and_batch_no_settings()
self.cant_change_valuation_method() self.cant_change_valuation_method()
self.validate_clean_description_html() self.validate_clean_description_html()
self.validate_pending_reposts() self.validate_pending_reposts()
@@ -112,6 +115,25 @@ class StockSettings(Document):
self.change_precision_for_for_sales() self.change_precision_for_for_sales()
self.change_precision_for_purchase() 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): def validate_warehouses(self):
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"] warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
for field in warehouse_fields: for field in warehouse_fields:

View File

@@ -352,7 +352,7 @@ class SerialBatchBundle:
"Serial and Batch Entry", {"parent": self.sle.serial_and_batch_bundle, "docstatus": 0} "Serial and Batch Entry", {"parent": self.sle.serial_and_batch_bundle, "docstatus": 0}
) )
> 0 > 0
): ) and not self.sle.is_cancelled:
frappe.throw( frappe.throw(
_("Serial and Batch Bundle {0} is not submitted").format( _("Serial and Batch Bundle {0} is not submitted").format(
bold(self.sle.serial_and_batch_bundle) bold(self.sle.serial_and_batch_bundle)

View File

@@ -30,6 +30,7 @@ frappe.ui.form.on("Subcontracting Receipt", {
refresh: (frm) => { refresh: (frm) => {
frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" }; frappe.dynamic_link = { doc: frm.doc, fieldname: "supplier", doctype: "Supplier" };
erpnext.toggle_serial_batch_fields(frm);
if (frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
frm.add_custom_button( frm.add_custom_button(
__("Stock Ledger"), __("Stock Ledger"),

View File

@@ -8,7 +8,7 @@
<form action="/search_help" style="display: flex;"> <form action="/search_help" style="display: flex;">
<input name='q' class='form-control' type='text' <input name='q' class='form-control' type='text'
style='max-width: 400px; display: inline-block; margin-right: 10px;' 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 %}> {% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
<input type='submit' <input type='submit'
class='btn btn-sm btn-light btn-search' value="{{ _("Search") }}"> class='btn btn-sm btn-light btn-search' value="{{ _("Search") }}">

View 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"
}