Merge branch 'develop' of github.com:aerele/erpnext into pricing_rule

This commit is contained in:
Bhavan23
2024-09-05 10:44:28 +05:30
41 changed files with 9593 additions and 17380 deletions

View File

@@ -658,7 +658,7 @@ frappe.ui.form.on("Payment Entry", {
frm.set_value("source_exchange_rate", 1); frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) { } else if (frm.doc.paid_from) {
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) { 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({ frappe.call({
method: "erpnext.setup.utils.get_exchange_rate", method: "erpnext.setup.utils.get_exchange_rate",
args: { args: {

View File

@@ -1267,7 +1267,11 @@ class PurchaseInvoice(BuyingController):
def update_gross_purchase_amount_for_linked_assets(self, item): def update_gross_purchase_amount_for_linked_assets(self, item):
assets = frappe.db.get_all( assets = frappe.db.get_all(
"Asset", "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"], fields=["name", "asset_quantity"],
) )
for asset in assets: for asset in assets:

View File

@@ -2081,7 +2081,7 @@ def make_delivery_note(source_name, target_doc=None):
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: doc.delivered_by_supplier != 1, "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": { "Sales Team": {
"doctype": "Sales Team", "doctype": "Sales Team",
"field_map": {"incentives": "incentives"}, "field_map": {"incentives": "incentives"},

View File

@@ -670,6 +670,11 @@ frappe.ui.form.on("Asset", {
if (item.asset_location) { if (item.asset_location) {
frm.set_value("location", 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);
}
}); });
}, },

View File

@@ -33,14 +33,16 @@
"dimension_col_break", "dimension_col_break",
"purchase_details_section", "purchase_details_section",
"purchase_receipt", "purchase_receipt",
"purchase_receipt_item",
"purchase_invoice", "purchase_invoice",
"purchase_invoice_item",
"purchase_date",
"available_for_use_date", "available_for_use_date",
"total_asset_cost",
"additional_asset_cost",
"column_break_23", "column_break_23",
"gross_purchase_amount", "gross_purchase_amount",
"asset_quantity", "asset_quantity",
"purchase_date", "additional_asset_cost",
"total_asset_cost",
"section_break_23", "section_break_23",
"calculate_depreciation", "calculate_depreciation",
"column_break_33", "column_break_33",
@@ -536,6 +538,20 @@
"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"
},
{
"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, "idx": 72,
@@ -579,7 +595,7 @@
"link_fieldname": "target_asset" "link_fieldname": "target_asset"
} }
], ],
"modified": "2024-08-01 16:39:09.340973", "modified": "2024-08-26 23:28:29.095139",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset", "name": "Asset",

View File

@@ -94,7 +94,9 @@ class Asset(AccountsController):
purchase_amount: DF.Currency purchase_amount: DF.Currency
purchase_date: DF.Date | None purchase_date: DF.Date | None
purchase_invoice: DF.Link | None purchase_invoice: DF.Link | None
purchase_invoice_item: DF.Link | None
purchase_receipt: DF.Link | None purchase_receipt: DF.Link | None
purchase_receipt_item: DF.Link | None
split_from: DF.Link | None split_from: DF.Link | None
status: DF.Literal[ status: DF.Literal[
"Draft", "Draft",

View File

@@ -31,6 +31,12 @@ class TestAssetCapitalization(unittest.TestCase):
def test_capitalization_with_perpetual_inventory(self): def test_capitalization_with_perpetual_inventory(self):
company = "_Test Company with perpetual inventory" company = "_Test Company with perpetual inventory"
set_depreciation_settings_in_company(company=company) 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 # Variables
consumed_asset_value = 100000 consumed_asset_value = 100000
@@ -215,6 +221,12 @@ class TestAssetCapitalization(unittest.TestCase):
def test_capitalization_with_wip_composite_asset(self): def test_capitalization_with_wip_composite_asset(self):
company = "_Test Company with perpetual inventory" company = "_Test Company with perpetual inventory"
set_depreciation_settings_in_company(company=company) 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_rate = 1000
stock_qty = 2 stock_qty = 2

View File

@@ -5,7 +5,6 @@ frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Asset Value Adjustment", { frappe.ui.form.on("Asset Value Adjustment", {
setup: function (frm) { setup: function (frm) {
frm.add_fetch("company", "cost_center", "cost_center");
frm.set_query("cost_center", function () { frm.set_query("cost_center", function () {
return { return {
filters: { 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) { onload: function (frm) {
@@ -37,7 +44,7 @@ frappe.ui.form.on("Asset Value Adjustment", {
}, },
asset: function (frm) { asset: function (frm) {
frm.trigger("set_current_asset_value"); frm.trigger("set_acc_dimension");
}, },
finance_book: function (frm) { 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,
},
});
}
},
}); });

View File

@@ -17,6 +17,7 @@
"new_asset_value", "new_asset_value",
"column_break_11", "column_break_11",
"difference_amount", "difference_amount",
"difference_account",
"journal_entry", "journal_entry",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
@@ -54,6 +55,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Journal Entry", "label": "Journal Entry",
"options": "Journal Entry", "options": "Journal Entry",
"no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{ {
@@ -79,6 +81,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "New Asset Value", "label": "New Asset Value",
"no_copy": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -120,12 +123,20 @@
{ {
"fieldname": "column_break_11", "fieldname": "column_break_11",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "difference_account",
"fieldtype": "Link",
"label": "Difference Account",
"no_copy": 1,
"options": "Account",
"reqd": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:06:36.004049", "modified": "2024-08-13 16:21:18.639208",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Value Adjustment", "name": "Asset Value Adjustment",

View File

@@ -34,6 +34,7 @@ class AssetValueAdjustment(Document):
cost_center: DF.Link | None cost_center: DF.Link | None
current_asset_value: DF.Currency current_asset_value: DF.Currency
date: DF.Date date: DF.Date
difference_account: DF.Link
difference_amount: DF.Currency difference_amount: DF.Currency
finance_book: DF.Link | None finance_book: DF.Link | None
journal_entry: DF.Link | None journal_entry: DF.Link | None
@@ -47,6 +48,7 @@ class AssetValueAdjustment(Document):
def on_submit(self): def on_submit(self):
self.make_depreciation_entry() self.make_depreciation_entry()
self.set_value_after_depreciation()
self.update_asset(self.new_asset_value) self.update_asset(self.new_asset_value)
add_asset_activity( add_asset_activity(
self.asset, self.asset,
@@ -76,7 +78,10 @@ class AssetValueAdjustment(Document):
) )
def set_difference_amount(self): 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): def set_current_asset_value(self):
if not self.current_asset_value and self.asset: if not self.current_asset_value and self.asset:
@@ -85,7 +90,7 @@ class AssetValueAdjustment(Document):
def make_depreciation_entry(self): def make_depreciation_entry(self):
asset = frappe.get_doc("Asset", self.asset) asset = frappe.get_doc("Asset", self.asset)
( (
_, fixed_asset_account,
accumulated_depreciation_account, accumulated_depreciation_account,
depreciation_expense_account, depreciation_expense_account,
) = get_depreciation_accounts(asset.asset_category, asset.company) ) = get_depreciation_accounts(asset.asset_category, asset.company)
@@ -95,28 +100,41 @@ class AssetValueAdjustment(Document):
) )
je = frappe.new_doc("Journal Entry") je = frappe.new_doc("Journal Entry")
je.voucher_type = "Depreciation Entry" je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series je.naming_series = depreciation_series
je.posting_date = self.date je.posting_date = self.date
je.company = self.company 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 je.finance_book = self.finance_book
credit_entry = { entry_template = {
"account": accumulated_depreciation_account, "cost_center": self.cost_center or depreciation_cost_center,
"credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center,
"reference_type": "Asset", "reference_type": "Asset",
"reference_name": self.asset, "reference_name": asset.name,
} }
debit_entry = { if self.difference_amount < 0:
"account": depreciation_expense_account, credit_entry = {
"debit_in_account_currency": self.difference_amount, "account": fixed_asset_account,
"cost_center": depreciation_cost_center or self.cost_center, "credit_in_account_currency": -self.difference_amount,
"reference_type": "Asset", **entry_template,
"reference_name": self.asset, }
} 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() 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.flags.ignore_validate_update_after_submit = True
asset.save() 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)

View File

@@ -93,8 +93,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
self.assertEqual(first_asset_depr_schedule.status, "Cancelled") self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 4625.29), ("_Test Difference Account - _TC", 4625.29, 0.0),
("_Test Depreciations - _TC", 4625.29, 0.0), ("_Test Fixed Asset - _TC", 0.0, 4625.29),
) )
gle = frappe.db.sql( gle = frappe.db.sql(
@@ -177,8 +177,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
# Test gl entry creted from asset value adjustemnet # Test gl entry creted from asset value adjustemnet
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 5625.29), ("_Test Difference Account - _TC", 5625.29, 0.0),
("_Test Depreciations - _TC", 5625.29, 0.0), ("_Test Fixed Asset - _TC", 0.0, 5625.29),
) )
gle = frappe.db.sql( gle = frappe.db.sql(
@@ -259,6 +259,39 @@ class TestAssetValueAdjustment(unittest.TestCase):
self.assertEqual(schedules, expected_schedules) 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): def make_asset_value_adjustment(**args):
args = frappe._dict(args) args = frappe._dict(args)
@@ -272,7 +305,22 @@ def make_asset_value_adjustment(**args):
"new_asset_value": args.new_asset_value, "new_asset_value": args.new_asset_value,
"current_asset_value": args.current_asset_value, "current_asset_value": args.current_asset_value,
"cost_center": args.cost_center or "Main - _TC", "cost_center": args.cost_center or "Main - _TC",
"difference_account": make_difference_account(),
} }
).insert() ).insert()
return doc 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

View File

@@ -735,7 +735,7 @@ def make_purchase_receipt(source_name, target_doc=None):
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
and doc.delivered_by_supplier != 1, 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, target_doc,
set_missing_values, set_missing_values,
@@ -811,7 +811,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"postprocess": update_item, "postprocess": update_item,
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)), "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( doc = get_mapped_doc(

View File

@@ -824,6 +824,8 @@ class BuyingController(SubcontractingController):
"asset_quantity": asset_quantity, "asset_quantity": asset_quantity,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None, "purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,
"purchase_invoice": self.name if self.doctype == "Purchase Invoice" 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,
} }
) )

View File

@@ -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_js = "erpnext.bundle.js"
app_include_css = "erpnext.bundle.css" app_include_css = "erpnext.bundle.css"
@@ -547,6 +547,8 @@ accounting_dimension_doctypes = [
"Payment Reconciliation", "Payment Reconciliation",
"Payment Reconciliation Allocation", "Payment Reconciliation Allocation",
"Payment Request", "Payment Request",
"Asset Movement Item",
"Asset Depreciation Schedule",
] ]
get_matching_queries = ( get_matching_queries = (

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,18 +5,17 @@
"document_type": "Document", "document_type": "Document",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"bom_and_work_order_tab",
"raw_materials_consumption_section", "raw_materials_consumption_section",
"material_consumption", "material_consumption",
"get_rm_cost_from_consumption_entry", "get_rm_cost_from_consumption_entry",
"column_break_3", "column_break_3",
"backflush_raw_materials_based_on", "backflush_raw_materials_based_on",
"capacity_planning", "validate_components_quantities_per_bom",
"disable_capacity_planning", "bom_section",
"allow_overtime", "update_bom_costs_automatically",
"allow_production_on_holidays", "column_break_lhyt",
"column_break_5", "manufacture_sub_assembly_in_operation",
"capacity_planning_for_days",
"mins_between_operations",
"section_break_6", "section_break_6",
"default_wip_warehouse", "default_wip_warehouse",
"default_fg_warehouse", "default_fg_warehouse",
@@ -30,8 +29,14 @@
"add_corrective_operation_cost_in_finished_good_valuation", "add_corrective_operation_cost_in_finished_good_valuation",
"column_break_24", "column_break_24",
"job_card_excess_transfer", "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", "other_settings_section",
"update_bom_costs_automatically",
"set_op_cost_and_scrape_from_sub_assemblies", "set_op_cost_and_scrape_from_sub_assemblies",
"column_break_23", "column_break_23",
"make_serial_no_batch_from_work_order" "make_serial_no_batch_from_work_order"
@@ -149,7 +154,7 @@
{ {
"fieldname": "raw_materials_consumption_section", "fieldname": "raw_materials_consumption_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Raw Materials Consumption" "label": "Raw Materials Consumption "
}, },
{ {
"fieldname": "column_break_16", "fieldname": "column_break_16",
@@ -183,8 +188,8 @@
}, },
{ {
"fieldname": "job_card_section", "fieldname": "job_card_section",
"fieldtype": "Section Break", "fieldtype": "Tab Break",
"label": "Job Card" "label": "Job Card and Capacity Planning"
}, },
{ {
"fieldname": "column_break_24", "fieldname": "column_break_24",
@@ -210,13 +215,41 @@
"fieldname": "get_rm_cost_from_consumption_entry", "fieldname": "get_rm_cost_from_consumption_entry",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Get Raw Materials Cost from Consumption Entry" "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", "icon": "icon-wrench",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-03-27 13:10:04.700433", "modified": "2024-09-02 12:12:03.132567",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing Settings", "name": "Manufacturing Settings",

View File

@@ -29,15 +29,22 @@ class ManufacturingSettings(Document):
get_rm_cost_from_consumption_entry: DF.Check get_rm_cost_from_consumption_entry: DF.Check
job_card_excess_transfer: DF.Check job_card_excess_transfer: DF.Check
make_serial_no_batch_from_work_order: DF.Check make_serial_no_batch_from_work_order: DF.Check
manufacture_sub_assembly_in_operation: DF.Check
material_consumption: DF.Check material_consumption: DF.Check
mins_between_operations: DF.Int mins_between_operations: DF.Int
overproduction_percentage_for_sales_order: DF.Percent overproduction_percentage_for_sales_order: DF.Percent
overproduction_percentage_for_work_order: DF.Percent overproduction_percentage_for_work_order: DF.Percent
set_op_cost_and_scrape_from_sub_assemblies: DF.Check set_op_cost_and_scrape_from_sub_assemblies: DF.Check
update_bom_costs_automatically: DF.Check update_bom_costs_automatically: DF.Check
validate_components_quantities_per_bom: DF.Check
# end: auto-generated types # 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(): def get_mins_between_operations():

View File

@@ -2103,6 +2103,59 @@ class TestWorkOrder(FrappeTestCase):
stock_entry.submit() 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): def make_operation(**kwargs):
kwargs = frappe._dict(kwargs) kwargs = frappe._dict(kwargs)

View File

@@ -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.drop_index_posting_datetime_from_sle
erpnext.patches.v15_0.add_disassembly_order_stock_entry_type #1 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_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

View 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

View File

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

View File

@@ -117,14 +117,17 @@ erpnext.financial_statements = {
erpnext.financial_statements.filters = get_filters(); erpnext.financial_statements.filters = get_filters();
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today()); 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) { if (!filters.period_start_date || !filters.period_end_date) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year); frappe.model.with_doc("Fiscal Year", fiscal_year, function (r) {
frappe.query_report.set_filter_value({ var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
period_start_date: fy.year_start_date, frappe.query_report.set_filter_value({
period_end_date: fy.year_end_date, period_start_date: fy.year_start_date,
period_end_date: fy.year_end_date,
});
}); });
}); }
if (report.page) { if (report.page) {
const views_menu = report.page.add_custom_button_group(__("Financial Statements")); const views_menu = report.page.add_custom_button_group(__("Financial Statements"));

View File

@@ -20,7 +20,11 @@ $.extend(erpnext, {
}, },
toggle_naming_series: function () { 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); cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal ? true : false);
} }
}, },

View File

@@ -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() { get_dialog_table_fields() {
let fields = []; let fields = [];
let me = this;
if (this.item.has_serial_no) { if (this.item.has_serial_no) {
fields.push({ fields.push({
@@ -395,6 +415,15 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
fieldname: "batch_no", fieldname: "batch_no",
label: __("Batch No"), label: __("Batch No"),
in_list_view: 1, 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: () => { get_query: () => {
let is_inward = false; let is_inward = false;
if ( if (

View File

@@ -426,7 +426,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
"postprocess": update_item, "postprocess": update_item,
"condition": can_map_row, "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}, "Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
"Payment Schedule": {"doctype": "Payment Schedule", "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, "postprocess": update_item,
"condition": lambda row: not row.is_alternative, "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}, "Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
}, },
target_doc, target_doc,

View File

@@ -936,7 +936,7 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
mapper = { mapper = {
"Sales Order": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}}, "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}, "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 "condition": lambda doc: doc.qty
and (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)), 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}, "Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
}, },
target_doc, target_doc,

View File

@@ -20,7 +20,6 @@ frappe.ui.form.on("Company", {
}, },
setup: function (frm) { setup: function (frm) {
frm.__rename_queue = "long"; frm.__rename_queue = "long";
erpnext.company.setup_queries(frm);
frm.set_query("parent_company", function () { frm.set_query("parent_company", function () {
return { return {
@@ -81,6 +80,8 @@ frappe.ui.form.on("Company", {
}, },
refresh: function (frm) { refresh: function (frm) {
erpnext.company.setup_queries(frm);
frm.toggle_display("address_html", !frm.is_new()); frm.toggle_display("address_html", !frm.is_new());
if (!frm.is_new()) { if (!frm.is_new()) {

View File

@@ -1048,7 +1048,7 @@ def make_sales_invoice(source_name, target_doc=None, args=None):
}, },
"Sales Taxes and Charges": { "Sales Taxes and Charges": {
"doctype": "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, "ignore": args.get("merge_taxes") if args else 0,
}, },
"Sales Team": { "Sales Team": {

View File

@@ -828,7 +828,11 @@ class PurchaseReceipt(BuyingController):
def update_assets(self, item, valuation_rate): def update_assets(self, item, valuation_rate):
assets = frappe.db.get_all( assets = frappe.db.get_all(
"Asset", "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"], fields=["name", "asset_quantity"],
) )
@@ -1201,7 +1205,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
}, },
"Purchase Taxes and Charges": { "Purchase Taxes and Charges": {
"doctype": "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, "ignore": args.get("merge_taxes") if args else 0,
}, },
}, },

View File

@@ -235,6 +235,7 @@ class StockEntry(StockController):
self.validate_serialized_batch() self.validate_serialized_batch()
self.calculate_rate_and_amount() self.calculate_rate_and_amount()
self.validate_putaway_capacity() self.validate_putaway_capacity()
self.validate_component_quantities()
if self.get("purpose") != "Manufacture": if self.get("purpose") != "Manufacture":
# ignore scrap item wh difference and empty source/target wh # ignore scrap item wh difference and empty source/target wh
@@ -764,6 +765,34 @@ class StockEntry(StockController):
title=_("Insufficient Stock"), 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() @frappe.whitelist()
def get_stock_and_rate(self): def get_stock_and_rate(self):
""" """

View File

@@ -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( if price_list_rate is None or frappe.db.get_single_value(
"Stock Settings", "update_existing_price_list_rate" "Stock Settings", "update_existing_price_list_rate"
): ):
if args.get("is_internal_supplier") or args.get("is_internal_customer"): insert_item_price(args)
return out
if args.price_list and args.rate: if price_list_rate is None:
insert_item_price(args) return out
if not price_list_rate:
return out
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate) 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): def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same""" """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( 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") frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
): ):

View File

@@ -1533,7 +1533,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
operator = "<=" operator = "<="
voucher_condition = f"and creation < '{creation}'" voucher_condition = f"and creation < '{creation}'"
sle = frappe.db.sql( sle = frappe.db.sql( # nosemgrep
f""" f"""
select *, posting_datetime as "timestamp" select *, posting_datetime as "timestamp"
from `tabStock Ledger Entry` from `tabStock Ledger Entry`
@@ -1630,6 +1630,7 @@ def get_stock_ledger_entries(
if extra_cond: if extra_cond:
conditions += f"{extra_cond}" conditions += f"{extra_cond}"
# nosemgrep
return frappe.db.sql( return frappe.db.sql(
""" """
select *, posting_datetime as "timestamp" select *, posting_datetime as "timestamp"
@@ -1745,7 +1746,7 @@ def get_valuation_rate(
return batch_obj.get_incoming_rate() return batch_obj.get_incoming_rate()
# Get valuation rate from last sle for the same item and warehouse # 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 """select valuation_rate
from `tabStock Ledger Entry` force index (item_warehouse) from `tabStock Ledger Entry` force index (item_warehouse)
where where
@@ -1825,7 +1826,7 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
detail = next_stock_reco_detail[0] detail = next_stock_reco_detail[0]
datetime_limit_condition = get_datetime_limit_condition(detail) datetime_limit_condition = get_datetime_limit_condition(detail)
frappe.db.sql( frappe.db.sql( # nosemgrep
f""" f"""
update `tabStock Ledger Entry` update `tabStock Ledger Entry`
set qty_after_transaction = qty_after_transaction + {qty_shift} 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 return qty_deficit < 0 and abs(qty_deficit) > 0.0001
def get_future_sle_with_negative_qty(args): def get_future_sle_with_negative_qty(sle_args):
return frappe.db.sql( return frappe.db.sql( # nosemgrep
""" """
select select
qty_after_transaction, posting_date, posting_time, 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 order by posting_date asc, posting_time asc
limit 1 limit 1
""", """,
args, sle_args,
as_dict=1, as_dict=1,
) )
def get_future_sle_with_negative_batch_qty(args): def get_future_sle_with_negative_batch_qty(sle_args):
return frappe.db.sql( return frappe.db.sql( # nosemgrep
""" """
with batch_ledger as ( with batch_ledger as (
select select
@@ -2034,7 +2035,7 @@ def get_future_sle_with_negative_batch_qty(args):
and posting_datetime >= %(posting_datetime)s and posting_datetime >= %(posting_datetime)s
limit 1 limit 1
""", """,
args, sle_args,
as_dict=1, as_dict=1,
) )