mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-18 00:55:02 +00:00
Merge branch 'develop' of github.com:aerele/erpnext into pricing_rule
This commit is contained in:
@@ -658,7 +658,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_value("source_exchange_rate", 1);
|
||||
} else if (frm.doc.paid_from) {
|
||||
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
|
||||
@@ -1267,7 +1267,11 @@ class PurchaseInvoice(BuyingController):
|
||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset",
|
||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||
filters={
|
||||
"purchase_invoice": self.name,
|
||||
"item_code": item.item_code,
|
||||
"purchase_invoice_item": ("in", [item.name, ""]),
|
||||
},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
for asset in assets:
|
||||
|
||||
@@ -2081,7 +2081,7 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: doc.delivered_by_supplier != 1,
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||
"Sales Team": {
|
||||
"doctype": "Sales Team",
|
||||
"field_map": {"incentives": "incentives"},
|
||||
|
||||
@@ -670,6 +670,11 @@ frappe.ui.form.on("Asset", {
|
||||
if (item.asset_location) {
|
||||
frm.set_value("location", item.asset_location);
|
||||
}
|
||||
if (doctype === "Purchase Receipt") {
|
||||
frm.set_value("purchase_receipt_item", item.name);
|
||||
} else if (doctype === "Purchase Invoice") {
|
||||
frm.set_value("purchase_invoice_item", item.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -33,14 +33,16 @@
|
||||
"dimension_col_break",
|
||||
"purchase_details_section",
|
||||
"purchase_receipt",
|
||||
"purchase_receipt_item",
|
||||
"purchase_invoice",
|
||||
"purchase_invoice_item",
|
||||
"purchase_date",
|
||||
"available_for_use_date",
|
||||
"total_asset_cost",
|
||||
"additional_asset_cost",
|
||||
"column_break_23",
|
||||
"gross_purchase_amount",
|
||||
"asset_quantity",
|
||||
"purchase_date",
|
||||
"additional_asset_cost",
|
||||
"total_asset_cost",
|
||||
"section_break_23",
|
||||
"calculate_depreciation",
|
||||
"column_break_33",
|
||||
@@ -536,6 +538,20 @@
|
||||
"fieldname": "opening_number_of_booked_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Opening Number of Booked Depreciations"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_receipt_item",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Purchase Receipt Item",
|
||||
"options": "Purchase Receipt Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_invoice_item",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Purchase Invoice Item",
|
||||
"options": "Purchase Invoice Item"
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@@ -579,7 +595,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2024-08-01 16:39:09.340973",
|
||||
"modified": "2024-08-26 23:28:29.095139",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -94,7 +94,9 @@ class Asset(AccountsController):
|
||||
purchase_amount: DF.Currency
|
||||
purchase_date: DF.Date | None
|
||||
purchase_invoice: DF.Link | None
|
||||
purchase_invoice_item: DF.Link | None
|
||||
purchase_receipt: DF.Link | None
|
||||
purchase_receipt_item: DF.Link | None
|
||||
split_from: DF.Link | None
|
||||
status: DF.Literal[
|
||||
"Draft",
|
||||
|
||||
@@ -31,6 +31,12 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
def test_capitalization_with_perpetual_inventory(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
set_depreciation_settings_in_company(company=company)
|
||||
name = frappe.db.get_value(
|
||||
"Asset Category Account",
|
||||
filters={"parent": "Computers", "company_name": company},
|
||||
fieldname=["name"],
|
||||
)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||
|
||||
# Variables
|
||||
consumed_asset_value = 100000
|
||||
@@ -215,6 +221,12 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
def test_capitalization_with_wip_composite_asset(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
set_depreciation_settings_in_company(company=company)
|
||||
name = frappe.db.get_value(
|
||||
"Asset Category Account",
|
||||
filters={"parent": "Computers", "company_name": company},
|
||||
fieldname=["name"],
|
||||
)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||
|
||||
stock_rate = 1000
|
||||
stock_qty = 2
|
||||
|
||||
@@ -5,7 +5,6 @@ frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on("Asset Value Adjustment", {
|
||||
setup: function (frm) {
|
||||
frm.add_fetch("company", "cost_center", "cost_center");
|
||||
frm.set_query("cost_center", function () {
|
||||
return {
|
||||
filters: {
|
||||
@@ -22,6 +21,14 @@ frappe.ui.form.on("Asset Value Adjustment", {
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.set_query("difference_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
@@ -37,7 +44,7 @@ frappe.ui.form.on("Asset Value Adjustment", {
|
||||
},
|
||||
|
||||
asset: function (frm) {
|
||||
frm.trigger("set_current_asset_value");
|
||||
frm.trigger("set_acc_dimension");
|
||||
},
|
||||
|
||||
finance_book: function (frm) {
|
||||
@@ -60,4 +67,15 @@ frappe.ui.form.on("Asset Value Adjustment", {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set_acc_dimension: function (frm) {
|
||||
if (frm.doc.asset) {
|
||||
frm.call({
|
||||
method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_value_of_accounting_dimensions",
|
||||
args: {
|
||||
asset_name: frm.doc.asset,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"new_asset_value",
|
||||
"column_break_11",
|
||||
"difference_amount",
|
||||
"difference_account",
|
||||
"journal_entry",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
@@ -54,6 +55,7 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Journal Entry",
|
||||
"options": "Journal Entry",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -79,6 +81,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "New Asset Value",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -120,12 +123,20 @@
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Difference Account",
|
||||
"no_copy": 1,
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:36.004049",
|
||||
"modified": "2024-08-13 16:21:18.639208",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Value Adjustment",
|
||||
|
||||
@@ -34,6 +34,7 @@ class AssetValueAdjustment(Document):
|
||||
cost_center: DF.Link | None
|
||||
current_asset_value: DF.Currency
|
||||
date: DF.Date
|
||||
difference_account: DF.Link
|
||||
difference_amount: DF.Currency
|
||||
finance_book: DF.Link | None
|
||||
journal_entry: DF.Link | None
|
||||
@@ -47,6 +48,7 @@ class AssetValueAdjustment(Document):
|
||||
|
||||
def on_submit(self):
|
||||
self.make_depreciation_entry()
|
||||
self.set_value_after_depreciation()
|
||||
self.update_asset(self.new_asset_value)
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
@@ -76,7 +78,10 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
def set_difference_amount(self):
|
||||
self.difference_amount = flt(self.current_asset_value - self.new_asset_value)
|
||||
self.difference_amount = flt(self.new_asset_value - self.current_asset_value)
|
||||
|
||||
def set_value_after_depreciation(self):
|
||||
frappe.db.set_value("Asset", self.asset, "value_after_depreciation", self.new_asset_value)
|
||||
|
||||
def set_current_asset_value(self):
|
||||
if not self.current_asset_value and self.asset:
|
||||
@@ -85,7 +90,7 @@ class AssetValueAdjustment(Document):
|
||||
def make_depreciation_entry(self):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
(
|
||||
_,
|
||||
fixed_asset_account,
|
||||
accumulated_depreciation_account,
|
||||
depreciation_expense_account,
|
||||
) = get_depreciation_accounts(asset.asset_category, asset.company)
|
||||
@@ -95,28 +100,41 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = "Depreciation Entry"
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.naming_series = depreciation_series
|
||||
je.posting_date = self.date
|
||||
je.company = self.company
|
||||
je.remark = f"Depreciation Entry against {self.asset} worth {self.difference_amount}"
|
||||
je.remark = f"Revaluation Entry against {self.asset} worth {self.difference_amount}"
|
||||
je.finance_book = self.finance_book
|
||||
|
||||
credit_entry = {
|
||||
"account": accumulated_depreciation_account,
|
||||
"credit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center,
|
||||
entry_template = {
|
||||
"cost_center": self.cost_center or depreciation_cost_center,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": self.asset,
|
||||
"reference_name": asset.name,
|
||||
}
|
||||
|
||||
debit_entry = {
|
||||
"account": depreciation_expense_account,
|
||||
"debit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": self.asset,
|
||||
}
|
||||
if self.difference_amount < 0:
|
||||
credit_entry = {
|
||||
"account": fixed_asset_account,
|
||||
"credit_in_account_currency": -self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
debit_entry = {
|
||||
"account": self.difference_account,
|
||||
"debit_in_account_currency": -self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
elif self.difference_amount > 0:
|
||||
credit_entry = {
|
||||
"account": self.difference_account,
|
||||
"credit_in_account_currency": self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
debit_entry = {
|
||||
"account": fixed_asset_account,
|
||||
"debit_in_account_currency": self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
|
||||
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
|
||||
|
||||
@@ -179,3 +197,9 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_value_of_accounting_dimensions(asset_name):
|
||||
dimension_fields = [*frappe.get_list("Accounting Dimension", pluck="fieldname"), "cost_center"]
|
||||
return frappe.db.get_value("Asset", asset_name, fieldname=dimension_fields, as_dict=True)
|
||||
|
||||
@@ -93,8 +93,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 4625.29),
|
||||
("_Test Depreciations - _TC", 4625.29, 0.0),
|
||||
("_Test Difference Account - _TC", 4625.29, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 4625.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
@@ -177,8 +177,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
|
||||
# Test gl entry creted from asset value adjustemnet
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 5625.29),
|
||||
("_Test Depreciations - _TC", 5625.29, 0.0),
|
||||
("_Test Difference Account - _TC", 5625.29, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 5625.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
@@ -259,6 +259,39 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_difference_amount(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
|
||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||
asset_doc.calculate_depreciation = 1
|
||||
asset_doc.available_for_use_date = "2023-01-15"
|
||||
asset_doc.purchase_date = "2023-01-15"
|
||||
|
||||
asset_doc.append(
|
||||
"finance_books",
|
||||
{
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 12,
|
||||
"frequency_of_depreciation": 1,
|
||||
"depreciation_start_date": "2023-01-31",
|
||||
},
|
||||
)
|
||||
asset_doc.submit()
|
||||
|
||||
adj_doc = make_asset_value_adjustment(
|
||||
asset=asset_doc.name,
|
||||
current_asset_value=54000,
|
||||
new_asset_value=50000.0,
|
||||
date="2023-08-21",
|
||||
)
|
||||
adj_doc.submit()
|
||||
difference_amount = adj_doc.new_asset_value - adj_doc.current_asset_value
|
||||
self.assertEqual(difference_amount, -4000)
|
||||
asset_doc.load_from_db()
|
||||
self.assertEqual(asset_doc.value_after_depreciation, 50000.0)
|
||||
|
||||
|
||||
def make_asset_value_adjustment(**args):
|
||||
args = frappe._dict(args)
|
||||
@@ -272,7 +305,22 @@ def make_asset_value_adjustment(**args):
|
||||
"new_asset_value": args.new_asset_value,
|
||||
"current_asset_value": args.current_asset_value,
|
||||
"cost_center": args.cost_center or "Main - _TC",
|
||||
"difference_account": make_difference_account(),
|
||||
}
|
||||
).insert()
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def make_difference_account(**args):
|
||||
account = "_Test Difference Account - _TC"
|
||||
if not frappe.db.exists("Account", account):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "_Test Difference Account"
|
||||
acc.parent_account = "Direct Income - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.is_group = 0
|
||||
acc.insert()
|
||||
return acc.name
|
||||
else:
|
||||
return account
|
||||
|
||||
@@ -735,7 +735,7 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
and doc.delivered_by_supplier != 1,
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
|
||||
},
|
||||
target_doc,
|
||||
set_missing_values,
|
||||
@@ -811,7 +811,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
|
||||
}
|
||||
|
||||
doc = get_mapped_doc(
|
||||
|
||||
@@ -824,6 +824,8 @@ class BuyingController(SubcontractingController):
|
||||
"asset_quantity": asset_quantity,
|
||||
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
|
||||
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" else None,
|
||||
"purchase_receipt_item": row.name if self.doctype == "Purchase Receipt" else None,
|
||||
"purchase_invoice_item": row.name if self.doctype == "Purchase Invoice" else None,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ add_to_apps_screen = [
|
||||
}
|
||||
]
|
||||
|
||||
develop_version = "14.x.x-develop"
|
||||
develop_version = "15.x.x-develop"
|
||||
|
||||
app_include_js = "erpnext.bundle.js"
|
||||
app_include_css = "erpnext.bundle.css"
|
||||
@@ -547,6 +547,8 @@ accounting_dimension_doctypes = [
|
||||
"Payment Reconciliation",
|
||||
"Payment Reconciliation Allocation",
|
||||
"Payment Request",
|
||||
"Asset Movement Item",
|
||||
"Asset Depreciation Schedule",
|
||||
]
|
||||
|
||||
get_matching_queries = (
|
||||
|
||||
2534
erpnext/locale/ar.po
2534
erpnext/locale/ar.po
File diff suppressed because it is too large
Load Diff
2613
erpnext/locale/bs.po
2613
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
2906
erpnext/locale/de.po
2906
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
2542
erpnext/locale/eo.po
2542
erpnext/locale/eo.po
File diff suppressed because it is too large
Load Diff
2582
erpnext/locale/es.po
2582
erpnext/locale/es.po
File diff suppressed because it is too large
Load Diff
2564
erpnext/locale/fa.po
2564
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
2550
erpnext/locale/fr.po
2550
erpnext/locale/fr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2669
erpnext/locale/sv.po
2669
erpnext/locale/sv.po
File diff suppressed because it is too large
Load Diff
2793
erpnext/locale/tr.po
2793
erpnext/locale/tr.po
File diff suppressed because it is too large
Load Diff
@@ -5,18 +5,17 @@
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"bom_and_work_order_tab",
|
||||
"raw_materials_consumption_section",
|
||||
"material_consumption",
|
||||
"get_rm_cost_from_consumption_entry",
|
||||
"column_break_3",
|
||||
"backflush_raw_materials_based_on",
|
||||
"capacity_planning",
|
||||
"disable_capacity_planning",
|
||||
"allow_overtime",
|
||||
"allow_production_on_holidays",
|
||||
"column_break_5",
|
||||
"capacity_planning_for_days",
|
||||
"mins_between_operations",
|
||||
"validate_components_quantities_per_bom",
|
||||
"bom_section",
|
||||
"update_bom_costs_automatically",
|
||||
"column_break_lhyt",
|
||||
"manufacture_sub_assembly_in_operation",
|
||||
"section_break_6",
|
||||
"default_wip_warehouse",
|
||||
"default_fg_warehouse",
|
||||
@@ -30,8 +29,14 @@
|
||||
"add_corrective_operation_cost_in_finished_good_valuation",
|
||||
"column_break_24",
|
||||
"job_card_excess_transfer",
|
||||
"capacity_planning",
|
||||
"disable_capacity_planning",
|
||||
"allow_overtime",
|
||||
"allow_production_on_holidays",
|
||||
"column_break_5",
|
||||
"capacity_planning_for_days",
|
||||
"mins_between_operations",
|
||||
"other_settings_section",
|
||||
"update_bom_costs_automatically",
|
||||
"set_op_cost_and_scrape_from_sub_assemblies",
|
||||
"column_break_23",
|
||||
"make_serial_no_batch_from_work_order"
|
||||
@@ -149,7 +154,7 @@
|
||||
{
|
||||
"fieldname": "raw_materials_consumption_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Raw Materials Consumption"
|
||||
"label": "Raw Materials Consumption "
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
@@ -183,8 +188,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "job_card_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Job Card"
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Job Card and Capacity Planning"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_24",
|
||||
@@ -210,13 +215,41 @@
|
||||
"fieldname": "get_rm_cost_from_consumption_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Get Raw Materials Cost from Consumption Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_and_work_order_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "BOM and Production"
|
||||
},
|
||||
{
|
||||
"fieldname": "bom_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "BOM"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lhyt",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled then system will manufacture Sub-assembly against the Job Card (operation).",
|
||||
"fieldname": "manufacture_sub_assembly_in_operation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Manufacture Sub-assembly in Operation"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.backflush_raw_materials_based_on == \"BOM\"",
|
||||
"fieldname": "validate_components_quantities_per_bom",
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Components Quantities Per BOM"
|
||||
}
|
||||
],
|
||||
"icon": "icon-wrench",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:04.700433",
|
||||
"modified": "2024-09-02 12:12:03.132567",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
||||
@@ -29,15 +29,22 @@ class ManufacturingSettings(Document):
|
||||
get_rm_cost_from_consumption_entry: DF.Check
|
||||
job_card_excess_transfer: DF.Check
|
||||
make_serial_no_batch_from_work_order: DF.Check
|
||||
manufacture_sub_assembly_in_operation: DF.Check
|
||||
material_consumption: DF.Check
|
||||
mins_between_operations: DF.Int
|
||||
overproduction_percentage_for_sales_order: DF.Percent
|
||||
overproduction_percentage_for_work_order: DF.Percent
|
||||
set_op_cost_and_scrape_from_sub_assemblies: DF.Check
|
||||
update_bom_costs_automatically: DF.Check
|
||||
validate_components_quantities_per_bom: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
def before_save(self):
|
||||
self.reset_values()
|
||||
|
||||
def reset_values(self):
|
||||
if self.backflush_raw_materials_based_on != "BOM" and self.validate_components_quantities_per_bom:
|
||||
self.validate_components_quantities_per_bom = 0
|
||||
|
||||
|
||||
def get_mins_between_operations():
|
||||
|
||||
@@ -2103,6 +2103,59 @@ class TestWorkOrder(FrappeTestCase):
|
||||
|
||||
stock_entry.submit()
|
||||
|
||||
def test_components_qty_for_bom_based_manufacture_entry(self):
|
||||
frappe.db.set_single_value("Manufacturing Settings", "backflush_raw_materials_based_on", "BOM")
|
||||
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 1)
|
||||
|
||||
fg_item = "Test FG Item For Component Validation"
|
||||
source_warehouse = "Stores - _TC"
|
||||
raw_materials = ["Test Component Validation RM Item 1", "Test Component Validation RM Item 2"]
|
||||
|
||||
make_item(fg_item, {"is_stock_item": 1})
|
||||
for item in raw_materials:
|
||||
make_item(item, {"is_stock_item": 1})
|
||||
test_stock_entry.make_stock_entry(
|
||||
item_code=item,
|
||||
target=source_warehouse,
|
||||
qty=10,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
make_bom(item=fg_item, source_warehouse=source_warehouse, raw_materials=raw_materials)
|
||||
|
||||
wo = make_wo_order_test_record(
|
||||
item=fg_item,
|
||||
qty=10,
|
||||
source_warehouse=source_warehouse,
|
||||
)
|
||||
|
||||
transfer_entry = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
|
||||
transfer_entry.save()
|
||||
for row in transfer_entry.items:
|
||||
row.qty = 5
|
||||
|
||||
self.assertRaises(frappe.ValidationError, transfer_entry.save)
|
||||
|
||||
transfer_entry.reload()
|
||||
for row in transfer_entry.items:
|
||||
self.assertEqual(row.qty, 10)
|
||||
|
||||
transfer_entry.submit()
|
||||
|
||||
manufacture_entry = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
|
||||
manufacture_entry.save()
|
||||
for row in manufacture_entry.items:
|
||||
if not row.s_warehouse:
|
||||
continue
|
||||
|
||||
row.qty = 5
|
||||
|
||||
self.assertRaises(frappe.ValidationError, manufacture_entry.save)
|
||||
manufacture_entry.reload()
|
||||
manufacture_entry.submit()
|
||||
|
||||
frappe.db.set_single_value("Manufacturing Settings", "validate_components_quantities_per_bom", 0)
|
||||
|
||||
|
||||
def make_operation(**kwargs):
|
||||
kwargs = frappe._dict(kwargs)
|
||||
|
||||
@@ -378,3 +378,5 @@ erpnext.patches.v15_0.do_not_use_batchwise_valuation
|
||||
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle
|
||||
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1
|
||||
erpnext.patches.v15_0.set_standard_stock_entry_type
|
||||
erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
|
||||
erpnext.patches.v15_0.link_purchase_item_to_asset_doc
|
||||
|
||||
74
erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py
Normal file
74
erpnext/patches/v15_0/link_purchase_item_to_asset_doc.py
Normal file
@@ -0,0 +1,74 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
if frappe.db.has_column("Asset", "purchase_invoice_item") and frappe.db.has_column(
|
||||
"Asset", "purchase_receipt_item"
|
||||
):
|
||||
# Get all assets with their related Purchase Invoice and Purchase Receipt
|
||||
assets = frappe.get_all(
|
||||
"Asset",
|
||||
filters={"docstatus": 0},
|
||||
fields=[
|
||||
"name",
|
||||
"item_code",
|
||||
"purchase_invoice",
|
||||
"purchase_receipt",
|
||||
"gross_purchase_amount",
|
||||
"asset_quantity",
|
||||
"purchase_invoice_item",
|
||||
"purchase_receipt_item",
|
||||
],
|
||||
)
|
||||
|
||||
for asset in assets:
|
||||
# Get Purchase Invoice Items
|
||||
if asset.purchase_invoice and not asset.purchase_invoice_item:
|
||||
purchase_invoice_item = get_linked_item(
|
||||
"Purchase Invoice Item",
|
||||
asset.purchase_invoice,
|
||||
asset.item_code,
|
||||
asset.gross_purchase_amount,
|
||||
asset.asset_quantity,
|
||||
)
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_invoice_item", purchase_invoice_item)
|
||||
|
||||
# Get Purchase Receipt Items
|
||||
if asset.purchase_receipt and not asset.purchase_receipt_item:
|
||||
purchase_receipt_item = get_linked_item(
|
||||
"Purchase Receipt Item",
|
||||
asset.purchase_receipt,
|
||||
asset.item_code,
|
||||
asset.gross_purchase_amount,
|
||||
asset.asset_quantity,
|
||||
)
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_item", purchase_receipt_item)
|
||||
|
||||
|
||||
def get_linked_item(doctype, parent, item_code, amount, quantity):
|
||||
items = frappe.get_all(
|
||||
doctype,
|
||||
filters={
|
||||
"parenttype": doctype.replace(" Item", ""),
|
||||
"parent": parent,
|
||||
"item_code": item_code,
|
||||
},
|
||||
fields=["name", "rate", "amount", "qty", "landed_cost_voucher_amount"],
|
||||
)
|
||||
if len(items) == 1:
|
||||
# If only one item exists, return it directly
|
||||
return items[0].name
|
||||
|
||||
for item in items:
|
||||
landed_cost = item.get("landed_cost_voucher_amount", 0)
|
||||
# Check if the asset is grouped
|
||||
if quantity > 1:
|
||||
if item.amount + landed_cost == amount and item.qty == quantity:
|
||||
return item.name
|
||||
elif item.qty == quantity:
|
||||
return item.name
|
||||
else:
|
||||
if item.rate + (landed_cost / item.qty) == amount:
|
||||
return item.name
|
||||
|
||||
return items[0].name if items else None
|
||||
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
AssetValueAdjustment = frappe.qb.DocType("Asset Value Adjustment")
|
||||
|
||||
frappe.qb.update(AssetValueAdjustment).set(
|
||||
AssetValueAdjustment.difference_amount,
|
||||
AssetValueAdjustment.new_asset_value - AssetValueAdjustment.current_asset_value,
|
||||
).where(AssetValueAdjustment.docstatus != 2).run()
|
||||
@@ -117,14 +117,17 @@ erpnext.financial_statements = {
|
||||
erpnext.financial_statements.filters = get_filters();
|
||||
|
||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||
var filters = report.get_values();
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date,
|
||||
if (!filters.period_start_date || !filters.period_end_date) {
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
period_start_date: fy.year_start_date,
|
||||
period_end_date: fy.year_end_date,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (report.page) {
|
||||
const views_menu = report.page.add_custom_button_group(__("Financial Statements"));
|
||||
|
||||
@@ -20,7 +20,11 @@ $.extend(erpnext, {
|
||||
},
|
||||
|
||||
toggle_naming_series: function () {
|
||||
if (cur_frm && cur_frm.fields_dict.naming_series) {
|
||||
if (
|
||||
cur_frm &&
|
||||
cur_frm.fields_dict.naming_series &&
|
||||
cur_frm.meta.naming_rule == 'By "Naming Series" field'
|
||||
) {
|
||||
cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal ? true : false);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -368,8 +368,28 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
];
|
||||
}
|
||||
|
||||
get_batch_qty(batch_no, callback) {
|
||||
let warehouse = this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse;
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.batch.batch.get_batch_qty",
|
||||
args: {
|
||||
batch_no: batch_no,
|
||||
warehouse: warehouse,
|
||||
item_code: this.item.item_code,
|
||||
posting_date: this.frm.doc.posting_date,
|
||||
posting_time: this.frm.doc.posting_time,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
callback(flt(r.message));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get_dialog_table_fields() {
|
||||
let fields = [];
|
||||
let me = this;
|
||||
|
||||
if (this.item.has_serial_no) {
|
||||
fields.push({
|
||||
@@ -395,6 +415,15 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
fieldname: "batch_no",
|
||||
label: __("Batch No"),
|
||||
in_list_view: 1,
|
||||
change() {
|
||||
let doc = this.doc;
|
||||
if (!doc.qty && me.item.type_of_transaction === "Outward") {
|
||||
me.get_batch_qty(doc.batch_no, (qty) => {
|
||||
doc.qty = qty;
|
||||
this.grid.set_value("qty", qty, doc);
|
||||
});
|
||||
}
|
||||
},
|
||||
get_query: () => {
|
||||
let is_inward = false;
|
||||
if (
|
||||
|
||||
@@ -426,7 +426,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||
"postprocess": update_item,
|
||||
"condition": can_map_row,
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
||||
"Payment Schedule": {"doctype": "Payment Schedule", "add_if_empty": True},
|
||||
},
|
||||
@@ -491,7 +491,7 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
"postprocess": update_item,
|
||||
"condition": lambda row: not row.is_alternative,
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
||||
},
|
||||
target_doc,
|
||||
|
||||
@@ -936,7 +936,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
|
||||
|
||||
mapper = {
|
||||
"Sales Order": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
||||
}
|
||||
|
||||
@@ -1136,7 +1136,10 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
"condition": lambda doc: doc.qty
|
||||
and (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"reset_value": True,
|
||||
},
|
||||
"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
|
||||
},
|
||||
target_doc,
|
||||
|
||||
@@ -20,7 +20,6 @@ frappe.ui.form.on("Company", {
|
||||
},
|
||||
setup: function (frm) {
|
||||
frm.__rename_queue = "long";
|
||||
erpnext.company.setup_queries(frm);
|
||||
|
||||
frm.set_query("parent_company", function () {
|
||||
return {
|
||||
@@ -81,6 +80,8 @@ frappe.ui.form.on("Company", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
erpnext.company.setup_queries(frm);
|
||||
|
||||
frm.toggle_display("address_html", !frm.is_new());
|
||||
|
||||
if (!frm.is_new()) {
|
||||
|
||||
@@ -1048,7 +1048,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
|
||||
},
|
||||
"Sales Taxes and Charges": {
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"add_if_empty": True,
|
||||
"reset_value": True,
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
"Sales Team": {
|
||||
|
||||
@@ -828,7 +828,11 @@ class PurchaseReceipt(BuyingController):
|
||||
def update_assets(self, item, valuation_rate):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset",
|
||||
filters={"purchase_receipt": self.name, "item_code": item.item_code},
|
||||
filters={
|
||||
"purchase_receipt": self.name,
|
||||
"item_code": item.item_code,
|
||||
"purchase_receipt_item": ("in", [item.name, ""]),
|
||||
},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
|
||||
@@ -1201,7 +1205,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"add_if_empty": True,
|
||||
"reset_value": True,
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -235,6 +235,7 @@ class StockEntry(StockController):
|
||||
self.validate_serialized_batch()
|
||||
self.calculate_rate_and_amount()
|
||||
self.validate_putaway_capacity()
|
||||
self.validate_component_quantities()
|
||||
|
||||
if self.get("purpose") != "Manufacture":
|
||||
# ignore scrap item wh difference and empty source/target wh
|
||||
@@ -764,6 +765,34 @@ class StockEntry(StockController):
|
||||
title=_("Insufficient Stock"),
|
||||
)
|
||||
|
||||
def validate_component_quantities(self):
|
||||
if self.purpose not in ["Manufacture", "Material Transfer for Manufacture"]:
|
||||
return
|
||||
|
||||
if not frappe.db.get_single_value("Manufacturing Settings", "validate_components_quantities_per_bom"):
|
||||
return
|
||||
|
||||
if not self.fg_completed_qty:
|
||||
return
|
||||
|
||||
raw_materials = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||
|
||||
precision = frappe.get_precision("Stock Entry Detail", "qty")
|
||||
for row in self.items:
|
||||
if not row.s_warehouse:
|
||||
continue
|
||||
|
||||
if details := raw_materials.get(row.item_code):
|
||||
if flt(details.get("qty"), precision) != flt(row.qty, precision):
|
||||
frappe.throw(
|
||||
_("For the item {0}, the quantity should be {1} according to the BOM {2}.").format(
|
||||
frappe.bold(row.item_code),
|
||||
flt(details.get("qty"), precision),
|
||||
get_link_to_form("BOM", self.bom_no),
|
||||
),
|
||||
title=_("Incorrect Component Quantity"),
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_and_rate(self):
|
||||
"""
|
||||
|
||||
@@ -820,14 +820,10 @@ def get_price_list_rate(args, item_doc, out=None):
|
||||
if price_list_rate is None or frappe.db.get_single_value(
|
||||
"Stock Settings", "update_existing_price_list_rate"
|
||||
):
|
||||
if args.get("is_internal_supplier") or args.get("is_internal_customer"):
|
||||
return out
|
||||
insert_item_price(args)
|
||||
|
||||
if args.price_list and args.rate:
|
||||
insert_item_price(args)
|
||||
|
||||
if not price_list_rate:
|
||||
return out
|
||||
if price_list_rate is None:
|
||||
return out
|
||||
|
||||
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)
|
||||
|
||||
@@ -848,6 +844,14 @@ def get_price_list_rate(args, item_doc, out=None):
|
||||
|
||||
def insert_item_price(args):
|
||||
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
|
||||
if (
|
||||
not args.price_list
|
||||
or not args.rate
|
||||
or args.get("is_internal_supplier")
|
||||
or args.get("is_internal_customer")
|
||||
):
|
||||
return
|
||||
|
||||
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency and cint(
|
||||
frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
|
||||
):
|
||||
|
||||
@@ -1533,7 +1533,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
|
||||
operator = "<="
|
||||
voucher_condition = f"and creation < '{creation}'"
|
||||
|
||||
sle = frappe.db.sql(
|
||||
sle = frappe.db.sql( # nosemgrep
|
||||
f"""
|
||||
select *, posting_datetime as "timestamp"
|
||||
from `tabStock Ledger Entry`
|
||||
@@ -1630,6 +1630,7 @@ def get_stock_ledger_entries(
|
||||
if extra_cond:
|
||||
conditions += f"{extra_cond}"
|
||||
|
||||
# nosemgrep
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select *, posting_datetime as "timestamp"
|
||||
@@ -1745,7 +1746,7 @@ def get_valuation_rate(
|
||||
return batch_obj.get_incoming_rate()
|
||||
|
||||
# Get valuation rate from last sle for the same item and warehouse
|
||||
if last_valuation_rate := frappe.db.sql(
|
||||
if last_valuation_rate := frappe.db.sql( # nosemgrep
|
||||
"""select valuation_rate
|
||||
from `tabStock Ledger Entry` force index (item_warehouse)
|
||||
where
|
||||
@@ -1825,7 +1826,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
detail = next_stock_reco_detail[0]
|
||||
datetime_limit_condition = get_datetime_limit_condition(detail)
|
||||
|
||||
frappe.db.sql(
|
||||
frappe.db.sql( # nosemgrep
|
||||
f"""
|
||||
update `tabStock Ledger Entry`
|
||||
set qty_after_transaction = qty_after_transaction + {qty_shift}
|
||||
@@ -1991,8 +1992,8 @@ def is_negative_with_precision(neg_sle, is_batch=False):
|
||||
return qty_deficit < 0 and abs(qty_deficit) > 0.0001
|
||||
|
||||
|
||||
def get_future_sle_with_negative_qty(args):
|
||||
return frappe.db.sql(
|
||||
def get_future_sle_with_negative_qty(sle_args):
|
||||
return frappe.db.sql( # nosemgrep
|
||||
"""
|
||||
select
|
||||
qty_after_transaction, posting_date, posting_time,
|
||||
@@ -2008,13 +2009,13 @@ def get_future_sle_with_negative_qty(args):
|
||||
order by posting_date asc, posting_time asc
|
||||
limit 1
|
||||
""",
|
||||
args,
|
||||
sle_args,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
|
||||
def get_future_sle_with_negative_batch_qty(args):
|
||||
return frappe.db.sql(
|
||||
def get_future_sle_with_negative_batch_qty(sle_args):
|
||||
return frappe.db.sql( # nosemgrep
|
||||
"""
|
||||
with batch_ledger as (
|
||||
select
|
||||
@@ -2034,7 +2035,7 @@ def get_future_sle_with_negative_batch_qty(args):
|
||||
and posting_datetime >= %(posting_datetime)s
|
||||
limit 1
|
||||
""",
|
||||
args,
|
||||
sle_args,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user