diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 810c5e12156..f74b9f4adb9 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -1653,7 +1653,7 @@ class JobCard(Document): ) def populate_manufacture_stock_entry(self, ste): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ManufactureStockEntry + from erpnext.stock.doctype.stock_entry.services.manufacturing import ManufactureStockEntry ste.make_stock_entry() ste.stock_entry.flags.ignore_mandatory = True diff --git a/erpnext/manufacturing/doctype/work_order/mapper.py b/erpnext/manufacturing/doctype/work_order/mapper.py index 009551bb301..844952330b0 100644 --- a/erpnext/manufacturing/doctype/work_order/mapper.py +++ b/erpnext/manufacturing/doctype/work_order/mapper.py @@ -518,7 +518,7 @@ def _set_pick_list_item_qty(source, target, source_parent, for_qty, max_finished @frappe.whitelist() def make_stock_return_entry(work_order: str): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( ManufactureStockEntry, ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/disassemble.py b/erpnext/stock/doctype/stock_entry/services/disassemble.py similarity index 99% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/disassemble.py rename to erpnext/stock/doctype/stock_entry/services/disassemble.py index 518ef9cdab8..84e07a24f75 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_handler/disassemble.py +++ b/erpnext/stock/doctype/stock_entry/services/disassemble.py @@ -9,13 +9,13 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.serial_batch_bundle import SerialBatchCreation from erpnext.stock.utils import get_combine_datetime -from .base import BaseStockEntry from .manufacturing import ( ceil_qty_if_uom_has_whole_number, get_bom_items, get_production_item_details, get_secondary_items, ) +from .stock_entry_base import BaseStockEntry def _qty_tolerance(precision: int) -> float: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/manufacturing.py b/erpnext/stock/doctype/stock_entry/services/manufacturing.py similarity index 98% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/manufacturing.py rename to erpnext/stock/doctype/stock_entry/services/manufacturing.py index 6ce37f7c4ef..f61b667380c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_handler/manufacturing.py +++ b/erpnext/stock/doctype/stock_entry/services/manufacturing.py @@ -16,8 +16,8 @@ from erpnext.stock.serial_batch_bundle import ( get_serial_nos_from_bundle, ) -from .base import BaseStockEntry from .serial_batch import create_serial_and_batch_bundle +from .stock_entry_base import BaseStockEntry class BaseManufactureStockEntry(BaseStockEntry): @@ -388,7 +388,12 @@ class ManufactureStockEntry(BaseManufactureStockEntry): self.add_secondary_items_from_job_card() def add_raw_materials(self): - if not frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + material_consumption = frappe.db.get_single_value("Manufacturing Settings", "material_consumption") + + if material_consumption and self.raw_materials_already_consumed(): + return + + if not material_consumption: if self.backflush_based_on == "BOM" or self.wo_doc.skip_transfer: self.add_raw_materials_based_on_work_order() else: @@ -398,6 +403,21 @@ class ManufactureStockEntry(BaseManufactureStockEntry): else: self.add_raw_materials_based_on_transfer() + def raw_materials_already_consumed(self) -> bool: + if not self.doc.work_order: + return False + + return bool( + frappe.db.exists( + "Stock Entry", + { + "work_order": self.doc.work_order, + "purpose": "Material Consumption for Manufacture", + "docstatus": 1, + }, + ) + ) + def add_unconsumed_raw_materials(self): wo = self.wo_doc if not wo: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/material_receipt_issue.py b/erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py similarity index 98% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/material_receipt_issue.py rename to erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py index 7ae15ed2ea2..27dac61c9b2 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_handler/material_receipt_issue.py +++ b/erpnext/stock/doctype/stock_entry/services/material_receipt_issue.py @@ -2,8 +2,8 @@ import frappe from frappe import _ from frappe.query_builder.functions import Sum -from .base import BaseStockEntry from .manufacturing import get_bom_items +from .stock_entry_base import BaseStockEntry class MaterialReceiptStockEntry(BaseStockEntry): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/material_transfer.py b/erpnext/stock/doctype/stock_entry/services/material_transfer.py similarity index 99% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/material_transfer.py rename to erpnext/stock/doctype/stock_entry/services/material_transfer.py index 661ea04b19b..c7d6ee05495 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_handler/material_transfer.py +++ b/erpnext/stock/doctype/stock_entry/services/material_transfer.py @@ -3,8 +3,8 @@ from frappe import _ from frappe.query_builder.functions import Sum from frappe.utils import cstr, flt -from .base import BaseStockEntry from .manufacturing import _check_bom_component_qty, get_bom_items +from .stock_entry_base import BaseStockEntry class BaseMaterialTransferStockEntry(BaseStockEntry): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/serial_batch.py b/erpnext/stock/doctype/stock_entry/services/serial_batch.py similarity index 99% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/serial_batch.py rename to erpnext/stock/doctype/stock_entry/services/serial_batch.py index 517affad752..f0b3207208a 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_handler/serial_batch.py +++ b/erpnext/stock/doctype/stock_entry/services/serial_batch.py @@ -8,7 +8,7 @@ from erpnext.manufacturing.doctype.bom.bom import get_backflush_based_on from erpnext.stock.serial_batch_bundle import SerialBatchCreation, get_serial_or_batch_items from erpnext.stock.utils import get_combine_datetime -from .base import BaseStockEntry +from .stock_entry_base import BaseStockEntry class StockEntrySABB(BaseStockEntry): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/base.py b/erpnext/stock/doctype/stock_entry/services/stock_entry_base.py similarity index 100% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/base.py rename to erpnext/stock/doctype/stock_entry/services/stock_entry_base.py diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/subcontracting.py b/erpnext/stock/doctype/stock_entry/services/subcontracting.py similarity index 99% rename from erpnext/stock/doctype/stock_entry/stock_entry_handler/subcontracting.py rename to erpnext/stock/doctype/stock_entry/services/subcontracting.py index ef60504b083..d4b4921794d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_handler/subcontracting.py +++ b/erpnext/stock/doctype/stock_entry/services/subcontracting.py @@ -8,7 +8,7 @@ from frappe.utils import flt from erpnext.stock.utils import get_bin -from .base import BaseStockEntry +from .stock_entry_base import BaseStockEntry class SendToSubcontractorStockEntry(BaseStockEntry): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 21f366c6239..e67b95d2857 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -37,20 +37,20 @@ from erpnext.stock.get_item_details import ( from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate from erpnext.stock.utils import get_incoming_rate -from .stock_entry_handler.disassemble import DisassembleStockEntry -from .stock_entry_handler.manufacturing import ( +from .services.disassemble import DisassembleStockEntry +from .services.manufacturing import ( ManufactureStockEntry, MaterialConsumptionForManufactureStockEntry, RepackStockEntry, ) -from .stock_entry_handler.material_receipt_issue import MaterialIssueStockEntry, MaterialReceiptStockEntry -from .stock_entry_handler.material_transfer import ( +from .services.material_receipt_issue import MaterialIssueStockEntry, MaterialReceiptStockEntry +from .services.material_transfer import ( MaterialRequestStockEntry, MaterialTransferForManufactureStockEntry, MaterialTransferStockEntry, ) -from .stock_entry_handler.serial_batch import StockEntrySABB -from .stock_entry_handler.subcontracting import SendToSubcontractorStockEntry +from .services.serial_batch import StockEntrySABB +from .services.subcontracting import SendToSubcontractorStockEntry class FinishedGoodError(frappe.ValidationError): diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_handler/__init__.py b/erpnext/stock/doctype/stock_entry/stock_entry_handler/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 0a72e7cc04a..a7f7f6d73f1 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2388,7 +2388,7 @@ class TestStockEntry(ERPNextTestSuite): "Stock Settings", {"sample_retention_warehouse": "_Test Warehouse 1 - _TC"} ) def test_sample_retention_stock_entry(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( move_sample_to_retention_warehouse, ) @@ -2558,7 +2558,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): # ── ceil_qty_if_uom_has_whole_number ────────────────────────────────────── def test_ceil_qty_rounds_up_for_whole_number_uom(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( ceil_qty_if_uom_has_whole_number, ) @@ -2567,7 +2567,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): frappe.set_value("UOM", "Nos", "must_be_whole_number", 0) def test_ceil_qty_no_rounding_for_decimal_uom(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( ceil_qty_if_uom_has_whole_number, ) @@ -2644,7 +2644,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): # ── get_production_item_details ──────────────────────────────────────────── def test_get_production_item_details_from_bom(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( get_production_item_details, ) @@ -2654,7 +2654,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): self.assertIsNotNone(result.stock_uom) def test_get_production_item_details_from_work_order(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( get_production_item_details, ) @@ -2680,7 +2680,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): # ── get_bom_items ────────────────────────────────────────────────────────── def test_get_bom_items_returns_raw_materials_with_structure(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import get_bom_items + from erpnext.stock.doctype.stock_entry.services.manufacturing import get_bom_items bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "is_default": 1, "docstatus": 1}) items = get_bom_items(bom_no) @@ -2690,7 +2690,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): self.assertIn("qty", item) def test_get_bom_items_scales_qty_proportionally(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import get_bom_items + from erpnext.stock.doctype.stock_entry.services.manufacturing import get_bom_items bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "is_default": 1, "docstatus": 1}) items_1 = {i["item_code"]: i["qty"] for i in get_bom_items(bom_no, qty=1)} @@ -2704,7 +2704,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): "Stock Settings", {"sample_retention_warehouse": "_Test Warehouse 1 - _TC"} ) def test_validate_sample_quantity_raises_when_sample_exceeds_received_qty(self): - from erpnext.stock.doctype.stock_entry.stock_entry_handler.manufacturing import ( + from erpnext.stock.doctype.stock_entry.services.manufacturing import ( validate_sample_quantity, ) @@ -2718,7 +2718,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): def test_get_expired_batches_includes_expired_batch(self): from erpnext.stock.doctype.batch.test_batch import make_new_batch - from erpnext.stock.doctype.stock_entry.stock_entry_handler.serial_batch import ( + from erpnext.stock.doctype.stock_entry.services.serial_batch import ( get_expired_batches, ) @@ -2735,7 +2735,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): def test_get_expired_batches_excludes_future_batch(self): from erpnext.stock.doctype.batch.test_batch import make_new_batch - from erpnext.stock.doctype.stock_entry.stock_entry_handler.serial_batch import ( + from erpnext.stock.doctype.stock_entry.services.serial_batch import ( get_expired_batches, ) @@ -2848,7 +2848,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): from erpnext.manufacturing.doctype.work_order.mapper import ( make_stock_entry as _make_stock_entry, ) - from erpnext.stock.doctype.stock_entry.stock_entry_handler.disassemble import ( + from erpnext.stock.doctype.stock_entry.services.disassemble import ( get_available_materials, ) @@ -2893,7 +2893,7 @@ class TestStockEntryCoverage(ERPNextTestSuite): from erpnext.manufacturing.doctype.work_order.mapper import ( make_stock_entry as _make_stock_entry, ) - from erpnext.stock.doctype.stock_entry.stock_entry_handler.disassemble import ( + from erpnext.stock.doctype.stock_entry.services.disassemble import ( get_available_materials, )