mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-16 21:35:09 +00:00
feat: allow to set valuation rate for Rejected Materials (#47582)
(cherry picked from commit ca0e53dd78)
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
This commit is contained in:
@@ -12,21 +12,25 @@
|
|||||||
"column_break_4",
|
"column_break_4",
|
||||||
"maintain_same_rate_action",
|
"maintain_same_rate_action",
|
||||||
"role_to_override_stop_action",
|
"role_to_override_stop_action",
|
||||||
"transaction_settings_section",
|
"section_break_xmlt",
|
||||||
"po_required",
|
"po_required",
|
||||||
"pr_required",
|
|
||||||
"blanket_order_allowance",
|
"blanket_order_allowance",
|
||||||
|
"column_break_sbwq",
|
||||||
|
"pr_required",
|
||||||
"project_update_frequency",
|
"project_update_frequency",
|
||||||
"column_break_12",
|
"transaction_settings_section",
|
||||||
"maintain_same_rate",
|
"column_break_fcyl",
|
||||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||||
"allow_multiple_items",
|
"allow_zero_qty_in_supplier_quotation",
|
||||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
|
||||||
"disable_last_purchase_rate",
|
|
||||||
"show_pay_button",
|
|
||||||
"use_transaction_date_exchange_rate",
|
"use_transaction_date_exchange_rate",
|
||||||
"allow_zero_qty_in_request_for_quotation",
|
"allow_zero_qty_in_request_for_quotation",
|
||||||
"allow_zero_qty_in_supplier_quotation",
|
"column_break_12",
|
||||||
|
"maintain_same_rate",
|
||||||
|
"allow_multiple_items",
|
||||||
|
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
|
"set_valuation_rate_for_rejected_materials",
|
||||||
|
"disable_last_purchase_rate",
|
||||||
|
"show_pay_button",
|
||||||
"allow_zero_qty_in_purchase_order",
|
"allow_zero_qty_in_purchase_order",
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
@@ -231,6 +235,26 @@
|
|||||||
"fieldname": "allow_zero_qty_in_supplier_quotation",
|
"fieldname": "allow_zero_qty_in_supplier_quotation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Allow Supplier Quotation with Zero Quantity"
|
"label": "Allow Supplier Quotation with Zero Quantity"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_xmlt",
|
||||||
|
"fieldtype": "Section Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_sbwq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_fcyl",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
|
"description": "If enabled, the system will generate an accounting entry for materials rejected in the Purchase Receipt.",
|
||||||
|
"fieldname": "set_valuation_rate_for_rejected_materials",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Set Valuation Rate for Rejected Materials"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -239,7 +263,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-05-06 15:21:49.639642",
|
"modified": "2025-05-16 15:56:38.321369",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class BuyingSettings(Document):
|
|||||||
project_update_frequency: DF.Literal["Each Transaction", "Manual"]
|
project_update_frequency: DF.Literal["Each Transaction", "Manual"]
|
||||||
role_to_override_stop_action: DF.Link | None
|
role_to_override_stop_action: DF.Link | None
|
||||||
set_landed_cost_based_on_purchase_invoice_rate: DF.Check
|
set_landed_cost_based_on_purchase_invoice_rate: DF.Check
|
||||||
|
set_valuation_rate_for_rejected_materials: DF.Check
|
||||||
show_pay_button: DF.Check
|
show_pay_button: DF.Check
|
||||||
supp_master_name: DF.Literal["Supplier Name", "Naming Series", "Auto Name"]
|
supp_master_name: DF.Literal["Supplier Name", "Naming Series", "Auto Name"]
|
||||||
supplier_group: DF.Link | None
|
supplier_group: DF.Link | None
|
||||||
@@ -57,6 +58,9 @@ class BuyingSettings(Document):
|
|||||||
hide_name_field=False,
|
hide_name_field=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not self.bill_for_rejected_quantity_in_purchase_invoice:
|
||||||
|
self.set_valuation_rate_for_rejected_materials = 0
|
||||||
|
|
||||||
def before_save(self):
|
def before_save(self):
|
||||||
self.check_maintain_same_rate()
|
self.check_maintain_same_rate()
|
||||||
|
|
||||||
|
|||||||
@@ -658,6 +658,10 @@ class BuyingController(SubcontractingController):
|
|||||||
sl_entries.append(from_warehouse_sle)
|
sl_entries.append(from_warehouse_sle)
|
||||||
|
|
||||||
if flt(d.rejected_qty) != 0:
|
if flt(d.rejected_qty) != 0:
|
||||||
|
valuation_rate_for_rejected_item = 0.0
|
||||||
|
if frappe.db.get_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials"):
|
||||||
|
valuation_rate_for_rejected_item = d.valuation_rate
|
||||||
|
|
||||||
sl_entries.append(
|
sl_entries.append(
|
||||||
self.get_sl_entries(
|
self.get_sl_entries(
|
||||||
d,
|
d,
|
||||||
@@ -666,7 +670,7 @@ class BuyingController(SubcontractingController):
|
|||||||
"actual_qty": flt(
|
"actual_qty": flt(
|
||||||
flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")
|
flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")
|
||||||
),
|
),
|
||||||
"incoming_rate": 0.0,
|
"incoming_rate": valuation_rate_for_rejected_item,
|
||||||
"serial_and_batch_bundle": d.rejected_serial_and_batch_bundle,
|
"serial_and_batch_bundle": d.rejected_serial_and_batch_bundle,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -496,6 +496,14 @@ class PurchaseReceipt(BuyingController):
|
|||||||
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
|
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
|
||||||
credit_amount = outgoing_amount
|
credit_amount = outgoing_amount
|
||||||
|
|
||||||
|
if item.get("rejected_qty") and frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
|
):
|
||||||
|
outgoing_amount += abs(
|
||||||
|
get_stock_value_difference(self.name, item.name, item.rejected_warehouse)
|
||||||
|
)
|
||||||
|
credit_amount = outgoing_amount
|
||||||
|
|
||||||
if credit_amount:
|
if credit_amount:
|
||||||
if not account:
|
if not account:
|
||||||
validate_account("Stock or Asset Received But Not Billed")
|
validate_account("Stock or Asset Received But Not Billed")
|
||||||
@@ -629,6 +637,14 @@ class PurchaseReceipt(BuyingController):
|
|||||||
valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount")
|
valuation_amount_as_per_doc - flt(stock_value_diff), item.precision("base_net_amount")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if item.get("rejected_qty") and frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
|
):
|
||||||
|
rejected_item_cost = abs(
|
||||||
|
get_stock_value_difference(self.name, item.name, item.rejected_warehouse)
|
||||||
|
)
|
||||||
|
divisional_loss -= rejected_item_cost
|
||||||
|
|
||||||
if divisional_loss:
|
if divisional_loss:
|
||||||
loss_account = (
|
loss_account = (
|
||||||
self.get_company_default("default_expense_account", ignore_validation=True)
|
self.get_company_default("default_expense_account", ignore_validation=True)
|
||||||
@@ -726,13 +742,23 @@ class PurchaseReceipt(BuyingController):
|
|||||||
make_sub_contracting_gl_entries(d)
|
make_sub_contracting_gl_entries(d)
|
||||||
make_divisional_loss_gl_entry(d, outgoing_amount)
|
make_divisional_loss_gl_entry(d, outgoing_amount)
|
||||||
elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
|
elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
|
||||||
d.rejected_warehouse and d.rejected_warehouse not in warehouse_with_no_account
|
not frappe.db.get_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials")
|
||||||
|
and d.rejected_warehouse
|
||||||
|
and d.rejected_warehouse not in warehouse_with_no_account
|
||||||
):
|
):
|
||||||
warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse)
|
warehouse_with_no_account.append(d.warehouse or d.rejected_warehouse)
|
||||||
|
|
||||||
if d.is_fixed_asset and d.landed_cost_voucher_amount:
|
if d.is_fixed_asset and d.landed_cost_voucher_amount:
|
||||||
self.update_assets(d, d.valuation_rate)
|
self.update_assets(d, d.valuation_rate)
|
||||||
|
|
||||||
|
if d.rejected_qty and frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
|
):
|
||||||
|
stock_value_diff = get_stock_value_difference(self.name, d.name, d.rejected_warehouse)
|
||||||
|
stock_asset_account_name = warehouse_account[d.rejected_warehouse]["account"]
|
||||||
|
|
||||||
|
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
||||||
|
|
||||||
if warehouse_with_no_account:
|
if warehouse_with_no_account:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("No accounting entries for the following warehouses")
|
_("No accounting entries for the following warehouses")
|
||||||
|
|||||||
@@ -4199,6 +4199,126 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
# Test 3 - OverAllowanceError should be thrown as qty is greater than qty in DN
|
# Test 3 - OverAllowanceError should be thrown as qty is greater than qty in DN
|
||||||
self.assertRaises(erpnext.controllers.status_updater.OverAllowanceError, pr.submit)
|
self.assertRaises(erpnext.controllers.status_updater.OverAllowanceError, pr.submit)
|
||||||
|
|
||||||
|
def test_valuation_rate_for_rejected_materials(self):
|
||||||
|
item = make_item("Test Item with Rej Material Valuation", {"is_stock_item": 1})
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
warehouse = create_warehouse(
|
||||||
|
"_Test In-ward Warehouse",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
)
|
||||||
|
|
||||||
|
rej_warehouse = create_warehouse(
|
||||||
|
"_Test Warehouse - Rejected Material",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 1)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item.name,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
company=company,
|
||||||
|
warehouse=warehouse,
|
||||||
|
rejected_qty=5,
|
||||||
|
rejected_warehouse=rej_warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_received_but_not_billed_account = frappe.get_value(
|
||||||
|
"Company",
|
||||||
|
company,
|
||||||
|
"stock_received_but_not_billed",
|
||||||
|
)
|
||||||
|
|
||||||
|
rejected_item_cost = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"warehouse": rej_warehouse,
|
||||||
|
},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(rejected_item_cost, 500)
|
||||||
|
|
||||||
|
srbnb_cost = frappe.db.get_value(
|
||||||
|
"GL Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"account": stock_received_but_not_billed_account,
|
||||||
|
},
|
||||||
|
"credit",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(srbnb_cost, 1500)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)
|
||||||
|
|
||||||
|
def test_no_valuation_rate_for_rejected_materials(self):
|
||||||
|
item = make_item("Test Item with Rej Material No Valuation", {"is_stock_item": 1})
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
warehouse = create_warehouse(
|
||||||
|
"_Test In-ward Warehouse",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
)
|
||||||
|
|
||||||
|
rej_warehouse = create_warehouse(
|
||||||
|
"_Test Warehouse - Rejected Material",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item.name,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
company=company,
|
||||||
|
warehouse=warehouse,
|
||||||
|
rejected_qty=5,
|
||||||
|
rejected_warehouse=rej_warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_received_but_not_billed_account = frappe.get_value(
|
||||||
|
"Company",
|
||||||
|
company,
|
||||||
|
"stock_received_but_not_billed",
|
||||||
|
)
|
||||||
|
|
||||||
|
rejected_item_cost = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"warehouse": rej_warehouse,
|
||||||
|
},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(rejected_item_cost, 0.0)
|
||||||
|
|
||||||
|
srbnb_cost = frappe.db.get_value(
|
||||||
|
"GL Entry",
|
||||||
|
{
|
||||||
|
"voucher_type": "Purchase Receipt",
|
||||||
|
"voucher_no": pr.name,
|
||||||
|
"account": stock_received_but_not_billed_account,
|
||||||
|
},
|
||||||
|
"credit",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(srbnb_cost, 1000)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
Reference in New Issue
Block a user