Merge pull request #39835 from s-aga-r/FIX-9623
fix: create SBB for `transfer_qty` in SE
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json
index a1dbddc..45be1e3 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/br_planilha_de_contas.json
@@ -56,7 +56,9 @@
"Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {},
"Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {},
"Im\u00f3veis Destinados \u00e0 Venda": {},
- "Insumos (materiais diretos)": {},
+ "Insumos (materiais diretos)": {
+ "account_type": "Stock"
+ },
"Insumos Agropecu\u00e1rios": {},
"Mercadorias para Revenda": {},
"Outras 11": {},
@@ -146,6 +148,65 @@
"root_type": "Asset"
},
"CUSTOS DE PRODU\u00c7\u00c3O": {
+ "CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
+ "CUSTO DOS PRODUTOS VENDIDOS": {
+ "CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
+ "Custos dos Produtos Vendidos em Geral": {
+ "account_type": "Cost of Goods Sold"
+ },
+ "Outros Custos 4": {},
+ "account_type": "Cost of Goods Sold"
+ },
+ "CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
+ "Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
+ "Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
+ "Outras": {}
+ },
+ "CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
+ "Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
+ "Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
+ "Outros Custos 6": {}
+ },
+ "CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
+ "Custos dos Produtos para Sa\u00fade - Gratuidades": {},
+ "Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
+ "Outros Custos 5": {}
+ },
+ "account_type": "Cost of Goods Sold"
+ },
+ "CUSTO DOS SERVI\u00c7OS PRESTADOS": {
+ "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
+ "Custo dos Servi\u00e7os Prestados em Geral": {},
+ "Outros Custos": {}
+ },
+ "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
+ "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
+ "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
+ "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
+ "Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
+ "Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
+ "Outros Custos 2": {}
+ },
+ "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
+ "Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
+ "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
+ "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
+ "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
+ "Custo dos Servi\u00e7os Prestados a Gratuidade": {},
+ "Custo dos Servi\u00e7os Prestados ao PROUNI": {},
+ "Outros Custos 1": {}
+ },
+ "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
+ "Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
+ "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
+ "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
+ "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
+ "Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
+ "Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
+ "Outros Custos 3": {}
+ }
+ }
+ },
"CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": {
"CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": {
"Alimenta\u00e7\u00e3o do Trabalhador": {},
@@ -621,7 +682,9 @@
"Receita das Unidades Imobili\u00e1rias Vendidas": {},
"Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {},
"Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {},
- "Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {},
+ "Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {
+ "account_type": "Income Account"
+ },
"Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {}
}
}
@@ -645,65 +708,6 @@
}
},
"RESULTADO OPERACIONAL": {
- "CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
- "CUSTO DOS PRODUTOS VENDIDOS": {
- "CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
- "Custos dos Produtos Vendidos em Geral": {
- "account_type": "Cost of Goods Sold"
- },
- "Outros Custos 4": {},
- "account_type": "Cost of Goods Sold"
- },
- "CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
- "Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
- "Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
- "Outras": {}
- },
- "CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
- "Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
- "Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
- "Outros Custos 6": {}
- },
- "CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
- "Custos dos Produtos para Sa\u00fade - Gratuidades": {},
- "Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
- "Outros Custos 5": {}
- },
- "account_type": "Cost of Goods Sold"
- },
- "CUSTO DOS SERVI\u00c7OS PRESTADOS": {
- "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
- "Custo dos Servi\u00e7os Prestados em Geral": {},
- "Outros Custos": {}
- },
- "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
- "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
- "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
- "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
- "Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
- "Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
- "Outros Custos 2": {}
- },
- "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
- "Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
- "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
- "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
- "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
- "Custo dos Servi\u00e7os Prestados a Gratuidade": {},
- "Custo dos Servi\u00e7os Prestados ao PROUNI": {},
- "Outros Custos 1": {}
- },
- "CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
- "Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
- "Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
- "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
- "Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
- "Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
- "Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
- "Outros Custos 3": {}
- }
- }
- },
"DESPESAS OPERACIONAIS": {
"DESPESAS OPERACIONAIS 1": {
"DESPESAS OPERACIONAIS 2": {
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index c61c332..e3897bf 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -85,7 +85,14 @@
frappe.throw(
_(
"The currency of invoice {} ({}) is different from the currency of this dunning ({})."
- ).format(row.sales_invoice, invoice_currency, self.currency)
+ ).format(
+ frappe.get_desk_link(
+ "Sales Invoice",
+ row.sales_invoice,
+ ),
+ invoice_currency,
+ self.currency,
+ )
)
def validate_overdue_payments(self):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 74c835c..8ffbaa1 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -156,14 +156,18 @@
if self.doctype == "Stock Reconciliation":
qty = row.qty
type_of_transaction = "Inward"
+ warehouse = row.warehouse
else:
- qty = row.stock_qty
+ qty = row.stock_qty if self.doctype != "Stock Entry" else row.transfer_qty
type_of_transaction = get_type_of_transaction(self, row)
+ warehouse = (
+ row.warehouse if self.doctype != "Stock Entry" else row.s_warehouse or row.t_warehouse
+ )
sn_doc = SerialBatchCreation(
{
"item_code": row.item_code,
- "warehouse": row.warehouse,
+ "warehouse": warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": self.doctype,
@@ -938,6 +942,9 @@
"Stock Reconciliation",
)
+ if not frappe.get_all("Putaway Rule", limit=1):
+ return
+
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
valid_doctype = False
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
index d3ad51f..63e3fa3 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
@@ -7,6 +7,7 @@
"field_order": [
"raw_materials_consumption_section",
"material_consumption",
+ "get_rm_cost_from_consumption_entry",
"column_break_3",
"backflush_raw_materials_based_on",
"capacity_planning",
@@ -202,13 +203,20 @@
"fieldname": "set_op_cost_and_scrape_from_sub_assemblies",
"fieldtype": "Check",
"label": "Set Operating Cost / Scrape Items From Sub-assemblies"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.material_consumption",
+ "fieldname": "get_rm_cost_from_consumption_entry",
+ "fieldtype": "Check",
+ "label": "Get Raw Materials Cost from Consumption Entry"
}
],
"icon": "icon-wrench",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-12-28 16:37:44.874096",
+ "modified": "2024-02-08 19:00:37.561244",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
index 463ba9f..9a50111 100644
--- a/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
+++ b/erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.py
@@ -26,6 +26,7 @@
default_scrap_warehouse: DF.Link | None
default_wip_warehouse: DF.Link | None
disable_capacity_planning: DF.Check
+ get_rm_cost_from_consumption_entry: DF.Check
job_card_excess_transfer: DF.Check
make_serial_no_batch_from_work_order: DF.Check
material_consumption: DF.Check
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
index d07bf0f..06c1b49 100644
--- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
+++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
@@ -38,7 +38,8 @@
"in_list_view": 1,
"label": "Item Code",
"options": "Item",
- "reqd": 1
+ "reqd": 1,
+ "search_index": 1
},
{
"fieldname": "item_name",
@@ -53,7 +54,8 @@
"in_standard_filter": 1,
"label": "For Warehouse",
"options": "Warehouse",
- "reqd": 1
+ "reqd": 1,
+ "search_index": 1
},
{
"columns": 1,
@@ -141,7 +143,8 @@
"fieldname": "from_warehouse",
"fieldtype": "Link",
"label": "From Warehouse",
- "options": "Warehouse"
+ "options": "Warehouse",
+ "search_index": 1
},
{
"fetch_from": "item_code.safety_stock",
@@ -199,7 +202,7 @@
],
"istable": 1,
"links": [],
- "modified": "2023-09-12 12:09:08.358326",
+ "modified": "2024-02-11 16:21:11.977018",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 257b60c..54c3893 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -298,7 +298,8 @@
"no_copy": 1,
"options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nClosed\nCancelled\nMaterial Requested",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "amended_from",
@@ -436,7 +437,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-12-26 16:31:13.740777",
+ "modified": "2024-02-11 15:42:47.642481",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 6e9d1fc..b942842 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -1768,23 +1768,23 @@
return reserved_qty_for_production_plan - reserved_qty_for_production
+@frappe.request_cache
def get_non_completed_production_plans():
table = frappe.qb.DocType("Production Plan")
child = frappe.qb.DocType("Production Plan Item")
- query = (
+ return (
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(table.name)
+ .distinct()
.where(
(table.docstatus == 1)
& (table.status.notin(["Completed", "Closed"]))
& (child.planned_qty > child.ordered_qty)
)
- ).run(as_dict=True)
-
- return list(set([d.name for d in query]))
+ ).run(pluck="name")
def get_raw_materials_of_sub_assembly_items(
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index f6e9a07..efe9f53 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1776,6 +1776,52 @@
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies", 0
)
+ @change_settings(
+ "Manufacturing Settings", {"material_consumption": 1, "get_rm_cost_from_consumption_entry": 1}
+ )
+ def test_get_rm_cost_from_consumption_entry(self):
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
+ make_stock_entry as make_stock_entry_test_record,
+ )
+
+ rm = make_item(properties={"is_stock_item": 1}).name
+ fg = make_item(properties={"is_stock_item": 1}).name
+
+ make_stock_entry_test_record(
+ purpose="Material Receipt",
+ item_code=rm,
+ target="Stores - _TC",
+ qty=10,
+ basic_rate=100,
+ )
+ make_stock_entry_test_record(
+ purpose="Material Receipt",
+ item_code=rm,
+ target="Stores - _TC",
+ qty=10,
+ basic_rate=200,
+ )
+
+ bom = make_bom(item=fg, raw_materials=[rm], rate=150).name
+ wo = make_wo_order_test_record(
+ production_item=fg,
+ bom_no=bom,
+ qty=10,
+ )
+
+ mte = frappe.get_doc(make_stock_entry(wo.name, "Material Transfer for Manufacture", 10))
+ mte.items[0].s_warehouse = "Stores - _TC"
+ mte.insert().submit()
+
+ mce = frappe.get_doc(make_stock_entry(wo.name, "Material Consumption for Manufacture", 10))
+ mce.insert().submit()
+
+ me = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 10))
+ me.insert().submit()
+
+ valuation_rate = sum([item.valuation_rate * item.transfer_qty for item in mce.items]) / 10
+ self.assertEqual(me.items[0].valuation_rate, valuation_rate)
+
def prepare_boms_for_sub_assembly_test():
if not frappe.db.exists("BOM", {"item": "Test Final SF Item 1"}):
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 1996e19..63c74b6 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -447,7 +447,8 @@
"no_copy": 1,
"options": "Production Plan",
"print_hide": 1,
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "production_plan_item",
@@ -592,7 +593,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2023-08-11 18:35:49.852069",
+ "modified": "2024-02-11 15:47:13.454422",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
index f354d45..0f4d693 100644
--- a/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
+++ b/erpnext/manufacturing/doctype/work_order_item/work_order_item.json
@@ -36,7 +36,8 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item Code",
- "options": "Item"
+ "options": "Item",
+ "search_index": 1
},
{
"fieldname": "source_warehouse",
@@ -141,7 +142,7 @@
],
"istable": 1,
"links": [],
- "modified": "2022-09-28 10:50:43.512562",
+ "modified": "2024-02-11 15:45:32.318374",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Item",
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 5ae48ee..b5189b8 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -20,6 +20,7 @@
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.sales_order import (
WarehouseRequired,
+ create_pick_list,
make_delivery_note,
make_material_request,
make_raw_material_request,
@@ -2023,6 +2024,83 @@
frappe.db.get_value(so.doctype, so.name, "advance_payment_status"), "Not Requested"
)
+ def test_pick_list_without_rejected_materials(self):
+ serial_and_batch_item = make_item(
+ "_Test Serial and Batch Item for Rejected Materials",
+ properties={
+ "has_serial_no": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BAT-TSBIFRM-.#####",
+ "serial_no_series": "SN-TSBIFRM-.#####",
+ },
+ ).name
+
+ serial_item = make_item(
+ "_Test Serial Item for Rejected Materials",
+ properties={
+ "has_serial_no": 1,
+ "serial_no_series": "SN-TSIFRM-.#####",
+ },
+ ).name
+
+ batch_item = make_item(
+ "_Test Batch Item for Rejected Materials",
+ properties={
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BAT-TBIFRM-.#####",
+ },
+ ).name
+
+ normal_item = make_item("_Test Normal Item for Rejected Materials").name
+
+ warehouse = "_Test Warehouse - _TC"
+ rejected_warehouse = "_Test Dummy Rejected Warehouse - _TC"
+
+ if not frappe.db.exists("Warehouse", rejected_warehouse):
+ frappe.get_doc(
+ {
+ "doctype": "Warehouse",
+ "warehouse_name": rejected_warehouse,
+ "company": "_Test Company",
+ "warehouse_group": "_Test Warehouse Group",
+ "is_rejected_warehouse": 1,
+ }
+ ).insert()
+
+ se = make_stock_entry(item_code=normal_item, qty=1, to_warehouse=warehouse, do_not_submit=True)
+ for item in [serial_and_batch_item, serial_item, batch_item]:
+ se.append("items", {"item_code": item, "qty": 1, "t_warehouse": warehouse})
+
+ se.save()
+ se.submit()
+
+ se = make_stock_entry(
+ item_code=normal_item, qty=1, to_warehouse=rejected_warehouse, do_not_submit=True
+ )
+ for item in [serial_and_batch_item, serial_item, batch_item]:
+ se.append("items", {"item_code": item, "qty": 1, "t_warehouse": rejected_warehouse})
+
+ se.save()
+ se.submit()
+
+ so = make_sales_order(item_code=normal_item, qty=2, do_not_submit=True)
+
+ for item in [serial_and_batch_item, serial_item, batch_item]:
+ so.append("items", {"item_code": item, "qty": 2, "warehouse": warehouse})
+
+ so.save()
+ so.submit()
+
+ pick_list = create_pick_list(so.name)
+
+ pick_list.save()
+ for row in pick_list.locations:
+ self.assertEqual(row.qty, 1.0)
+ self.assertFalse(row.warehouse == rejected_warehouse)
+ self.assertTrue(row.warehouse == warehouse)
+
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index feb4583..949c109 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -1122,6 +1122,7 @@
frappe.throw(_("Item {0} is cancelled").format(item_code))
+@frappe.request_cache
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"""returns last purchase details in stock uom"""
# get last purchase order item details
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index e80218a..77a3d6d 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -228,9 +228,17 @@
const qty_fields = ['actual_qty', 'projected_qty', 'min_order_qty'];
if(!r.exc) {
- $.each(r.message, function(k, v) {
- if(!d[k] || in_list(qty_fields, k)) d[k] = v;
+ $.each(r.message, function(key, value) {
+ if(!d[key] || qty_fields.includes(key)) {
+ d[key] = value;
+ }
});
+
+ if (d.price_list_rate != r.message.price_list_rate) {
+ d.price_list_rate = r.message.price_list_rate;
+
+ frappe.model.set_value(d.doctype, d.name, "rate", d.price_list_rate);
+ }
}
}
});
@@ -432,7 +440,6 @@
item.amount = flt(item.qty) * flt(item.rate);
frappe.model.set_value(doctype, name, "amount", item.amount);
refresh_field("amount", item.name, item.parentfield);
- frm.events.get_item_data(frm, item, false);
},
item_code: function(frm, doctype, name) {
@@ -452,7 +459,12 @@
set_schedule_date(frm);
}
}
- }
+ },
+
+ conversion_factor: function(frm, doctype, name) {
+ const item = locals[doctype][name];
+ frm.events.get_item_data(frm, item, false);
+ },
});
erpnext.buying.MaterialRequestController = class MaterialRequestController extends erpnext.buying.BuyingController {
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json
index 5dc07c9..c7239b5 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.json
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.json
@@ -35,6 +35,7 @@
"received_qty",
"rate_and_amount_section_break",
"rate",
+ "price_list_rate",
"col_break3",
"amount",
"accounting_details_section",
@@ -473,13 +474,22 @@
"fieldtype": "Link",
"label": "WIP Composite Asset",
"options": "Asset"
+ },
+ {
+ "fieldname": "price_list_rate",
+ "fieldtype": "Currency",
+ "hidden": 1,
+ "label": "Price List Rate",
+ "options": "currency",
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-14 18:37:59.599115",
+ "modified": "2024-02-08 16:30:56.137858",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request Item",
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.py b/erpnext/stock/doctype/material_request_item/material_request_item.py
index 2bed596..d23d041 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.py
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.py
@@ -41,6 +41,7 @@
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
+ price_list_rate: DF.Currency
production_plan: DF.Link | None
project: DF.Link | None
projected_qty: DF.Float
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index aa0e125..3cc2956 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -77,6 +77,9 @@
},
freeze: 1,
freeze_message: __("Setting Item Locations..."),
+ callback(r) {
+ refresh_field("locations");
+ }
});
}
},
diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json
index bd84aad..0c47434 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.json
+++ b/erpnext/stock/doctype/pick_list/pick_list.json
@@ -16,6 +16,7 @@
"for_qty",
"column_break_4",
"parent_warehouse",
+ "consider_rejected_warehouses",
"get_item_locations",
"section_break_6",
"scan_barcode",
@@ -184,11 +185,18 @@
"report_hide": 1,
"reqd": 1,
"search_index": 1
+ },
+ {
+ "default": "0",
+ "description": "Enable it if users want to consider rejected materials to dispatch.",
+ "fieldname": "consider_rejected_warehouses",
+ "fieldtype": "Check",
+ "label": "Consider Rejected Warehouses"
}
],
"is_submittable": 1,
"links": [],
- "modified": "2024-02-01 16:17:44.877426",
+ "modified": "2024-02-02 16:17:44.877426",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
@@ -260,4 +268,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index e2edb20..0e1f8d7 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -348,9 +348,9 @@
picked_items_details = self.get_picked_items_details(items)
self.item_location_map = frappe._dict()
- from_warehouses = None
+ from_warehouses = [self.parent_warehouse] if self.parent_warehouse else []
if self.parent_warehouse:
- from_warehouses = get_descendants_of("Warehouse", self.parent_warehouse)
+ from_warehouses.extend(get_descendants_of("Warehouse", self.parent_warehouse))
# Create replica before resetting, to handle empty table on update after submit.
locations_replica = self.get("locations")
@@ -369,6 +369,7 @@
self.item_count_map.get(item_code),
self.company,
picked_item_details=picked_items_details.get(item_code),
+ consider_rejected_warehouses=self.consider_rejected_warehouses,
),
)
@@ -710,6 +711,7 @@
company,
ignore_validation=False,
picked_item_details=None,
+ consider_rejected_warehouses=False,
):
locations = []
total_picked_qty = (
@@ -725,18 +727,34 @@
required_qty,
company,
total_picked_qty,
+ consider_rejected_warehouses=consider_rejected_warehouses,
)
elif has_serial_no:
locations = get_available_item_locations_for_serialized_item(
- item_code, from_warehouses, required_qty, company, total_picked_qty
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ total_picked_qty,
+ consider_rejected_warehouses=consider_rejected_warehouses,
)
elif has_batch_no:
locations = get_available_item_locations_for_batched_item(
- item_code, from_warehouses, required_qty, company, total_picked_qty
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ total_picked_qty,
+ consider_rejected_warehouses=consider_rejected_warehouses,
)
else:
locations = get_available_item_locations_for_other_item(
- item_code, from_warehouses, required_qty, company, total_picked_qty
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ total_picked_qty,
+ consider_rejected_warehouses=consider_rejected_warehouses,
)
total_qty_available = sum(location.get("qty") for location in locations)
@@ -775,6 +793,7 @@
required_qty,
company,
total_picked_qty=0,
+ consider_rejected_warehouses=False,
):
# Get batch nos by FIFO
locations = get_available_item_locations_for_batched_item(
@@ -782,6 +801,7 @@
from_warehouses,
required_qty,
company,
+ consider_rejected_warehouses=consider_rejected_warehouses,
)
if locations:
@@ -811,7 +831,12 @@
def get_available_item_locations_for_serialized_item(
- item_code, from_warehouses, required_qty, company, total_picked_qty=0
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ total_picked_qty=0,
+ consider_rejected_warehouses=False,
):
picked_serial_nos = get_picked_serial_nos(item_code, from_warehouses)
@@ -828,6 +853,10 @@
else:
query = query.where(Coalesce(sn.warehouse, "") != "")
+ if not consider_rejected_warehouses:
+ if rejected_warehouses := get_rejected_warehouses():
+ query = query.where(sn.warehouse.notin(rejected_warehouses))
+
serial_nos = query.run(as_list=True)
warehouse_serial_nos_map = frappe._dict()
@@ -860,7 +889,12 @@
def get_available_item_locations_for_batched_item(
- item_code, from_warehouses, required_qty, company, total_picked_qty=0
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ total_picked_qty=0,
+ consider_rejected_warehouses=False,
):
locations = []
data = get_auto_batch_nos(
@@ -875,7 +909,14 @@
)
warehouse_wise_batches = frappe._dict()
+ rejected_warehouses = get_rejected_warehouses()
+
for d in data:
+ if (
+ not consider_rejected_warehouses and rejected_warehouses and d.warehouse in rejected_warehouses
+ ):
+ continue
+
if d.warehouse not in warehouse_wise_batches:
warehouse_wise_batches.setdefault(d.warehouse, defaultdict(float))
@@ -898,7 +939,12 @@
def get_available_item_locations_for_other_item(
- item_code, from_warehouses, required_qty, company, total_picked_qty=0
+ item_code,
+ from_warehouses,
+ required_qty,
+ company,
+ total_picked_qty=0,
+ consider_rejected_warehouses=False,
):
bin = frappe.qb.DocType("Bin")
query = (
@@ -915,6 +961,10 @@
wh = frappe.qb.DocType("Warehouse")
query = query.from_(wh).where((bin.warehouse == wh.name) & (wh.company == company))
+ if not consider_rejected_warehouses:
+ if rejected_warehouses := get_rejected_warehouses():
+ query = query.where(bin.warehouse.notin(rejected_warehouses))
+
item_locations = query.run(as_dict=True)
return item_locations
@@ -1236,3 +1286,15 @@
item.serial_no = location.serial_no
item.batch_no = location.batch_no
item.material_request_item = location.material_request_item
+
+
+def get_rejected_warehouses():
+ if not hasattr(frappe.local, "rejected_warehouses"):
+ frappe.local.rejected_warehouses = []
+
+ if not frappe.local.rejected_warehouses:
+ frappe.local.rejected_warehouses = frappe.get_all(
+ "Warehouse", filters={"is_rejected_warehouse": 1}, pluck="name"
+ )
+
+ return frappe.local.rejected_warehouses
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 4c1de72..276b2f4 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -904,14 +904,62 @@
return flt(outgoing_items_cost / total_fg_qty)
def get_basic_rate_for_manufactured_item(self, finished_item_qty, outgoing_items_cost=0) -> float:
+ settings = frappe.get_single("Manufacturing Settings")
scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
- # Get raw materials cost from BOM if multiple material consumption entries
- if not outgoing_items_cost and frappe.db.get_single_value(
- "Manufacturing Settings", "material_consumption", cache=True
- ):
- bom_items = self.get_bom_raw_materials(finished_item_qty)
- outgoing_items_cost = sum([flt(row.qty) * flt(row.rate) for row in bom_items.values()])
+ if settings.material_consumption:
+ if settings.get_rm_cost_from_consumption_entry and self.work_order:
+
+ # Validate only if Material Consumption Entry exists for the Work Order.
+ if frappe.db.exists(
+ "Stock Entry",
+ {
+ "docstatus": 1,
+ "work_order": self.work_order,
+ "purpose": "Material Consumption for Manufacture",
+ },
+ ):
+ for item in self.items:
+ if not item.is_finished_item and not item.is_scrap_item:
+ label = frappe.get_meta(settings.doctype).get_label("get_rm_cost_from_consumption_entry")
+ frappe.throw(
+ _(
+ "Row {0}: As {1} is enabled, raw materials cannot be added to {2} entry. Use {3} entry to consume raw materials."
+ ).format(
+ item.idx,
+ frappe.bold(label),
+ frappe.bold("Manufacture"),
+ frappe.bold("Material Consumption for Manufacture"),
+ )
+ )
+
+ if frappe.db.exists(
+ "Stock Entry", {"docstatus": 1, "work_order": self.work_order, "purpose": "Manufacture"}
+ ):
+ frappe.throw(
+ _("Only one {0} entry can be created against the Work Order {1}").format(
+ frappe.bold("Manufacture"), frappe.bold(self.work_order)
+ )
+ )
+
+ SE = frappe.qb.DocType("Stock Entry")
+ SE_ITEM = frappe.qb.DocType("Stock Entry Detail")
+
+ outgoing_items_cost = (
+ frappe.qb.from_(SE)
+ .left_join(SE_ITEM)
+ .on(SE.name == SE_ITEM.parent)
+ .select(Sum(SE_ITEM.valuation_rate * SE_ITEM.transfer_qty))
+ .where(
+ (SE.docstatus == 1)
+ & (SE.work_order == self.work_order)
+ & (SE.purpose == "Material Consumption for Manufacture")
+ )
+ ).run()[0][0] or 0
+
+ elif not outgoing_items_cost:
+ bom_items = self.get_bom_raw_materials(finished_item_qty)
+ outgoing_items_cost = sum([flt(row.qty) * flt(row.rate) for row in bom_items.values()])
return flt((outgoing_items_cost - scrap_items_cost) / finished_item_qty)
@@ -982,6 +1030,9 @@
already_picked_serial_nos = []
for row in self.items:
+ if row.use_serial_batch_fields and (row.serial_no or row.batch_no):
+ continue
+
if not row.s_warehouse:
continue
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index 0f67e47..271cbbc 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -92,9 +92,6 @@
else:
args.qty = cint(args.qty)
- if args.serial_no or args.batch_no:
- args.use_serial_batch_fields = True
-
# purpose
if not args.purpose:
if args.source and args.target:
@@ -136,7 +133,7 @@
serial_number = args.serial_no
bundle_id = None
- if args.serial_no or args.batch_no or args.batches:
+ if not args.use_serial_batch_fields and (args.serial_no or args.batch_no or args.batches):
batches = frappe._dict({})
if args.batch_no:
batches = frappe._dict({args.batch_no: args.qty})
@@ -164,7 +161,11 @@
.name
)
- args.serial_no = serial_number
+ args["serial_no"] = ""
+ args["batch_no"] = ""
+
+ else:
+ args.serial_no = serial_number
s.append(
"items",
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index af91536..6c3faa6 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1596,6 +1596,7 @@
qty=4,
to_warehouse="_Test Warehouse - _TC",
batch_no=batch.name,
+ use_serial_batch_fields=1,
do_not_save=True,
)
@@ -1754,6 +1755,51 @@
mr.cancel()
mr.delete()
+ def test_use_serial_and_batch_fields(self):
+ item = make_item(
+ "Test Use Serial and Batch Item SN Item",
+ {"has_serial_no": 1, "is_stock_item": 1},
+ )
+
+ serial_nos = [
+ "Test Use Serial and Batch Item SN Item - SN 001",
+ "Test Use Serial and Batch Item SN Item - SN 002",
+ ]
+
+ se = make_stock_entry(
+ item_code=item.name,
+ qty=2,
+ to_warehouse="_Test Warehouse - _TC",
+ use_serial_batch_fields=1,
+ serial_no="\n".join(serial_nos),
+ )
+
+ self.assertTrue(se.items[0].use_serial_batch_fields)
+ self.assertFalse(se.items[0].serial_no)
+ self.assertTrue(se.items[0].serial_and_batch_bundle)
+
+ for serial_no in serial_nos:
+ self.assertTrue(frappe.db.exists("Serial No", serial_no))
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Active")
+
+ se1 = make_stock_entry(
+ item_code=item.name,
+ qty=2,
+ from_warehouse="_Test Warehouse - _TC",
+ use_serial_batch_fields=1,
+ serial_no="\n".join(serial_nos),
+ )
+
+ se1.reload()
+
+ self.assertTrue(se1.items[0].use_serial_batch_fields)
+ self.assertFalse(se1.items[0].serial_no)
+ self.assertTrue(se1.items[0].serial_and_batch_bundle)
+
+ for serial_no in serial_nos:
+ self.assertTrue(frappe.db.exists("Serial No", serial_no))
+ self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered")
+
def make_serialized_item(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 43b2ad2..7b0cade 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -13,6 +13,7 @@
"column_break_3",
"is_group",
"parent_warehouse",
+ "is_rejected_warehouse",
"column_break_4",
"account",
"company",
@@ -249,13 +250,20 @@
{
"fieldname": "column_break_qajx",
"fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "description": "If yes, then this warehouse will be used to store rejected materials",
+ "fieldname": "is_rejected_warehouse",
+ "fieldtype": "Check",
+ "label": "Is Rejected Warehouse"
}
],
"icon": "fa fa-building",
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2023-05-29 13:10:43.333160",
+ "modified": "2024-01-24 16:27:28.299520",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",