revert(manufacturing): remove material coverage changes on version 15 (#58715)

This commit is contained in:
Mihir Kandoi
2026-09-04 14:32:57 +05:30
committed by GitHub
parent 0611a6a12b
commit 7cbf8b8660
9 changed files with 19 additions and 399 deletions

View File

@@ -206,43 +206,6 @@ class TestJobCard(FrappeTestCase):
# transfer was made for 2 fg qty in first transfer Stock Entry # transfer was made for 2 fg qty in first transfer Stock Entry
self.assertEqual(transfer_entry_2.fg_completed_qty, 0) self.assertEqual(transfer_entry_2.fg_completed_qty, 0)
def test_material_request_stock_entry_uses_job_card_coverage(self):
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
self.transfer_material_against = "Job Card"
self.source_warehouse = "Stores - _TC"
job_card = frappe.get_last_doc("Job Card", {"work_order": self.work_order.name})
mr = make_material_request(job_card.name)
mr.schedule_date = today()
for row in mr.items:
row.qty = flt(row.qty) / 2
row.stock_qty = flt(row.stock_qty) / 2
mr.submit()
stock_entry = make_stock_entry(mr.name)
self.assertEqual(stock_entry.fg_completed_qty, job_card.for_quantity / 2)
selected_row = mr.items[0]
try:
frappe.flags.selected_children = {"items": [selected_row.name]}
selected_stock_entry = make_stock_entry(mr.name)
finally:
frappe.flags.selected_children = None
self.assertEqual(
[row.job_card_item for row in selected_stock_entry.items], [selected_row.job_card_item]
)
self.assertEqual(selected_stock_entry.fg_completed_qty, 0)
for row in mr.items:
transferred_qty = flt(row.stock_qty) / 2
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", transferred_qty)
frappe.db.set_value(row.doctype, row.name, "ordered_qty", transferred_qty)
mr.reload()
repeated_stock_entry = make_stock_entry(mr.name)
self.assertEqual(repeated_stock_entry.fg_completed_qty, job_card.for_quantity / 4)
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1}) @change_settings("Manufacturing Settings", {"job_card_excess_transfer": 1})
def test_job_card_excess_material_transfer(self): def test_job_card_excess_material_transfer(self):
"Test transferring more than required RM against Job Card." "Test transferring more than required RM against Job Card."
@@ -653,7 +616,6 @@ class TestJobCard(FrappeTestCase):
self.assertEqual(ste.job_card, job_card_name) self.assertEqual(ste.job_card, job_card_name)
self.assertEqual(ste.from_bom, 1.0) self.assertEqual(ste.from_bom, 1.0)
self.assertEqual(ste.bom_no, work_order.bom_no) self.assertEqual(ste.bom_no, work_order.bom_no)
self.assertEqual(ste.fg_completed_qty, frappe.get_value("Job Card", job_card_name, "for_quantity"))
def test_job_card_proccess_qty_and_completed_qty(self): def test_job_card_proccess_qty_and_completed_qty(self):
from erpnext.manufacturing.doctype.routing.test_routing import ( from erpnext.manufacturing.doctype.routing.test_routing import (

View File

@@ -1,22 +0,0 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from collections.abc import Mapping
from frappe.utils import flt
def get_minimum_material_coverage_fraction(
required_qty: Mapping[str, float], transferred_qty: Mapping[str, float], precision: int
) -> float:
"""Return the least-covered component ratio at the configured quantity precision."""
coverage = []
for item_code, required in required_qty.items():
transferred = flt(transferred_qty.get(item_code))
# Stored values can differ after the digits that the user can enter or see.
if flt(transferred, precision) == flt(required, precision):
coverage.append(1.0)
else:
coverage.append(transferred / required)
return min(coverage, default=0.0)

View File

@@ -1461,11 +1461,9 @@ class TestWorkOrder(FrappeTestCase):
del transfer_entry.get("items")[0] # transfer only one RM del transfer_entry.get("items")[0] # transfer only one RM
transfer_entry.submit() transfer_entry.submit()
# One required item is still missing, so no finished-good quantity is covered yet. # WO's "Material Transferred for Mfg" shows all is transferred, one RM is pending
work_order.reload() work_order.reload()
self.assertEqual(transfer_entry.fg_completed_qty, 0) self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
self.assertEqual(work_order.material_transferred_for_manufacturing, 0)
self.assertEqual(work_order.status, "In Process")
self.assertEqual(work_order.required_items[0].transferred_qty, 0) self.assertEqual(work_order.required_items[0].transferred_qty, 0)
self.assertEqual(work_order.required_items[1].transferred_qty, 2) self.assertEqual(work_order.required_items[1].transferred_qty, 2)
@@ -1485,47 +1483,6 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(work_order.required_items[0].transferred_qty, 1) self.assertEqual(work_order.required_items[0].transferred_qty, 1)
self.assertEqual(work_order.required_items[1].transferred_qty, 2) self.assertEqual(work_order.required_items[1].transferred_qty, 2)
def test_material_transfer_claim_follows_actual_coverage(self):
work_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
test_stock_entry.make_stock_entry(
item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0
)
test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=20,
basic_rate=1000.0,
)
transfer_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 4)
)
for row in transfer_entry.items:
if row.item_code == "_Test Item":
row.qty = 1
transfer_entry.submit()
work_order.reload()
self.assertEqual(transfer_entry.fg_completed_qty, 1)
self.assertEqual(work_order.material_transferred_for_manufacturing, 1)
remainder_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 3)
)
remainder_entry.submit()
work_order.reload()
self.assertEqual(remainder_entry.fg_completed_qty, 3)
self.assertEqual(work_order.material_transferred_for_manufacturing, 4)
def test_material_coverage_cap_skips_manufacture_entry(self):
work_order = make_wo_order_test_record(planned_start_date=now(), qty=1)
manufacture_entry = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
manufacture_entry.pro_doc = work_order
manufacture_entry._action = "submit"
self.assertFalse(manufacture_entry._should_cap_completed_qty())
def test_material_transferred_min_fraction_on_partial_pick_list(self): def test_material_transferred_min_fraction_on_partial_pick_list(self):
"""Pick-list flow (fg_completed_qty = 0): 'Material Transferred for Manufacturing' """Pick-list flow (fg_completed_qty = 0): 'Material Transferred for Manufacturing'
must reflect the least-transferred required item (the bottleneck), instead of being must reflect the least-transferred required item (the bottleneck), instead of being
@@ -1588,97 +1545,6 @@ class TestWorkOrder(FrappeTestCase):
work_order.reload() work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, 2.0) self.assertEqual(work_order.material_transferred_for_manufacturing, 2.0)
def test_material_transferred_ignores_hidden_precision_difference(self):
work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
test_stock_entry.make_stock_entry(
item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0
)
test_stock_entry.make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC",
qty=10,
basic_rate=1000.0,
)
precision = work_order.precision("required_qty", "required_items")
hidden_difference = 4 / (10 ** (precision + 1))
row = work_order.required_items[0]
row.db_set("required_qty", flt(row.required_qty) + hidden_difference, update_modified=False)
work_order.reload()
required_qty = {row.item_code: flt(row.required_qty) for row in work_order.required_items}
transfer_entry = frappe.get_doc(
make_stock_entry(work_order.name, "Material Transfer for Manufacture", 0)
)
for item in transfer_entry.items:
item.qty = flt(required_qty[item.item_code], precision)
item.transfer_qty = item.qty
transfer_entry.submit()
work_order.reload()
self.assertEqual(
flt(work_order.required_items[0].required_qty, precision),
flt(work_order.required_items[0].transferred_qty, precision),
)
self.assertEqual(work_order.material_transferred_for_manufacturing, work_order.qty)
def test_repair_material_transfer_precision_patch(self):
from erpnext.patches.v16_0.repair_work_order_material_transfer import (
execute,
get_precision_affected_work_orders,
)
precision = frappe.get_precision("Work Order Item", "required_qty")
hidden_difference = 4 / (10 ** (precision + 1))
work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for index, row in enumerate(work_order.required_items):
required_qty = flt(row.required_qty) + (hidden_difference if index == 0 else 0)
row.db_set(
{
"required_qty": required_qty,
"transferred_qty": flt(required_qty, precision),
},
update_modified=False,
)
work_order.db_set("material_transferred_for_manufacturing", 1.99, update_modified=False)
partial_work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for row in partial_work_order.required_items:
row.db_set("transferred_qty", row.required_qty, update_modified=False)
partial_row = partial_work_order.required_items[0]
partial_row.db_set(
"transferred_qty",
flt(partial_row.required_qty, precision) - (1 / (10**precision)),
update_modified=False,
)
partial_work_order.db_set("material_transferred_for_manufacturing", 1.99, update_modified=False)
terminal_work_orders = []
for status in ("Stopped", "Closed", "Completed"):
terminal_work_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for row in terminal_work_order.required_items:
row.db_set("transferred_qty", row.required_qty, update_modified=False)
terminal_work_order.db_set(
{"material_transferred_for_manufacturing": 1.99, "status": status},
update_modified=False,
)
terminal_work_orders.append(terminal_work_order)
updates = get_precision_affected_work_orders()
self.assertIn(work_order.name, updates)
self.assertNotIn(partial_work_order.name, updates)
for terminal_work_order in terminal_work_orders:
self.assertNotIn(terminal_work_order.name, updates)
execute()
work_order.reload()
partial_work_order.reload()
self.assertEqual(work_order.material_transferred_for_manufacturing, work_order.qty)
self.assertEqual(partial_work_order.material_transferred_for_manufacturing, 1.99)
for terminal_work_order in terminal_work_orders:
terminal_work_order.reload()
self.assertEqual(terminal_work_order.material_transferred_for_manufacturing, 1.99)
def test_status_in_process_when_only_one_required_item_transferred(self): def test_status_in_process_when_only_one_required_item_transferred(self):
"""Stock Entry created from a Pick List that picked only one of the required items: """Stock Entry created from a Pick List that picked only one of the required items:
min-fraction keeps material_transferred_for_manufacturing at 0, but the work order must min-fraction keeps material_transferred_for_manufacturing at 0, but the work order must

View File

@@ -31,9 +31,6 @@ from erpnext.manufacturing.doctype.bom.bom import (
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import (
get_mins_between_operations, get_mins_between_operations,
) )
from erpnext.manufacturing.doctype.work_order.services.material_coverage import (
get_minimum_material_coverage_fraction,
)
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.batch.batch import make_batch from erpnext.stock.doctype.batch.batch import make_batch
from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
@@ -461,7 +458,8 @@ class WorkOrder(Document):
return status return status
def has_transferred_material(self): def has_transferred_material(self):
"""True if any raw material was transferred against this work order.""" """True if any raw material was transferred against this work order via a pick list
(these leave material_transferred_for_manufacturing at 0 via the min-fraction rule)."""
ste = frappe.qb.DocType("Stock Entry") ste = frappe.qb.DocType("Stock Entry")
ste_child = frappe.qb.DocType("Stock Entry Detail") ste_child = frappe.qb.DocType("Stock Entry Detail")
qty = ( qty = (
@@ -474,6 +472,7 @@ class WorkOrder(Document):
& (ste.docstatus == 1) & (ste.docstatus == 1)
& (ste.purpose == "Material Transfer for Manufacture") & (ste.purpose == "Material Transfer for Manufacture")
& (ste.is_return == 0) & (ste.is_return == 0)
& (ste.pick_list.isnotnull())
) )
).run()[0][0] ).run()[0][0]
return flt(qty) > 0 return flt(qty) > 0
@@ -1304,13 +1303,20 @@ class WorkOrder(Document):
self.recompute_material_transferred_for_manufacturing(transferred_items) self.recompute_material_transferred_for_manufacturing(transferred_items)
def recompute_material_transferred_for_manufacturing(self, transferred_items): def recompute_material_transferred_for_manufacturing(self, transferred_items):
"""Set transferred quantity from the raw materials that have actually moved.""" """Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty."""
# Job Card transfers use the minimum completed quantity across operations. # Job Card transfers use the minimum completed quantity across operations.
if self.operations and self.transfer_material_against == "Job Card": if self.operations and self.transfer_material_against == "Job Card":
return return
claimed_qty = self.get_transferred_or_manufactured_qty("Material Transfer for Manufacture") # When fg_completed_qty > 0 (direct stock entries, excess transfer), preserve the
# SUM(fg_completed_qty) approach so excess-transfer tracking works correctly.
sum_fg_completed_qty = self.get_transferred_or_manufactured_qty("Material Transfer for Manufacture")
if sum_fg_completed_qty:
self.db_set("material_transferred_for_manufacturing", sum_fg_completed_qty)
return
# Pick list flow sets fg_completed_qty=0; use min-fraction of actual item transfers
# so partial availability does not prematurely mark the work order as fully transferred.
required_by_item = {} required_by_item = {}
for row in self.required_items: for row in self.required_items:
if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0: if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0:
@@ -1320,13 +1326,12 @@ class WorkOrder(Document):
if not required_by_item: if not required_by_item:
return return
min_fraction = get_minimum_material_coverage_fraction( min_fraction = min(
required_by_item, flt(transferred_items.get(item_code) or 0) / required_qty
transferred_items, for item_code, required_qty in required_by_item.items()
self.precision("required_qty", "required_items"),
) )
covered_qty = min_fraction * flt(self.qty) min_fraction = min(min_fraction, 1.0)
material_transferred = min(covered_qty, max(flt(self.qty), claimed_qty)) material_transferred = min_fraction * flt(self.qty)
self.db_set("material_transferred_for_manufacturing", material_transferred) self.db_set("material_transferred_for_manufacturing", material_transferred)
def update_returned_qty(self): def update_returned_qty(self):

View File

@@ -447,7 +447,6 @@ erpnext.patches.v16_0.rename_ar_ap_ageing_filter
erpnext.patches.v15_0.fix_titles erpnext.patches.v15_0.fix_titles
erpnext.patches.v16_0.backfill_repost_accounting_ledger_status erpnext.patches.v16_0.backfill_repost_accounting_ledger_status
erpnext.patches.v16_0.merge_seeded_item_group_root erpnext.patches.v16_0.merge_seeded_item_group_root
erpnext.patches.v16_0.repair_work_order_material_transfer
erpnext.patches.v16_0.remove_frappe_crm_custom_fields erpnext.patches.v16_0.remove_frappe_crm_custom_fields
erpnext.patches.v16_0.append_fieldname_to_pos_search_fields erpnext.patches.v16_0.append_fieldname_to_pos_search_fields
erpnext.patches.v16_0.add_transaction_roles_to_sms_settings erpnext.patches.v16_0.add_transaction_roles_to_sms_settings

View File

@@ -1,65 +0,0 @@
import frappe
from frappe.utils import flt
from pypika import functions as fn
from erpnext.manufacturing.doctype.work_order.services.material_coverage import (
get_minimum_material_coverage_fraction,
)
def execute():
updates = get_precision_affected_work_orders()
frappe.db.bulk_update("Work Order", updates, update_modified=False)
def get_precision_affected_work_orders():
"""Return Work Orders whose components cover the plan at quantity precision."""
work_orders = {}
for row in _get_candidate_rows():
work_order = work_orders.setdefault(
row.work_order,
{"qty": flt(row.qty), "required_qty": {}, "transferred_qty": {}},
)
item_code = row.item_code
work_order["required_qty"][item_code] = work_order["required_qty"].get(item_code, 0.0) + flt(
row.required_qty
)
work_order["transferred_qty"][item_code] = max(
work_order["transferred_qty"].get(item_code, 0.0), flt(row.transferred_qty)
)
precision = frappe.get_precision("Work Order Item", "required_qty")
return {
name: {"material_transferred_for_manufacturing": values["qty"]}
for name, values in work_orders.items()
if get_minimum_material_coverage_fraction(
values["required_qty"], values["transferred_qty"], precision
)
>= 1.0
}
def _get_candidate_rows():
work_order = frappe.qb.DocType("Work Order")
required_item = frappe.qb.DocType("Work Order Item")
return (
frappe.qb.from_(work_order)
.inner_join(required_item)
.on(required_item.parent == work_order.name)
.select(
work_order.name.as_("work_order"),
work_order.qty,
required_item.item_code,
required_item.required_qty,
required_item.transferred_qty,
)
.where(
(work_order.docstatus == 1)
& (work_order.status.notin(["Stopped", "Closed", "Completed"]))
& (fn.Coalesce(work_order.skip_transfer, 0) == 0)
& (fn.Coalesce(work_order.material_transferred_for_manufacturing, 0) < work_order.qty)
& (fn.Coalesce(work_order.transfer_material_against, "") != "Job Card")
& (required_item.include_item_in_manufacturing == 1)
& (required_item.required_qty > 0)
)
).run(as_dict=True)

View File

@@ -806,8 +806,6 @@ def make_stock_entry(source_name, target_doc=None):
target.fg_completed_qty = job_card_details[0].for_quantity target.fg_completed_qty = job_card_details[0].for_quantity
target.from_bom = 1 target.from_bom = 1
target.cap_completed_qty_to_material_coverage()
doclist = get_mapped_doc( doclist = get_mapped_doc(
"Material Request", "Material Request",
source_name, source_name,

View File

@@ -32,9 +32,6 @@ from erpnext.manufacturing.doctype.bom.bom import (
get_scrap_items_from_sub_assemblies, get_scrap_items_from_sub_assemblies,
validate_bom_no, validate_bom_no,
) )
from erpnext.manufacturing.doctype.work_order.services.material_coverage import (
get_minimum_material_coverage_fraction,
)
from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.setup.doctype.brand.brand import get_brand_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.doctype.batch.batch import get_batch_qty from erpnext.stock.doctype.batch.batch import get_batch_qty
@@ -265,7 +262,6 @@ class StockEntry(StockController):
self.calculate_rate_and_amount() self.calculate_rate_and_amount()
self.validate_putaway_capacity() self.validate_putaway_capacity()
self.validate_component_and_quantities() self.validate_component_and_quantities()
self._cap_completed_qty_to_material_coverage()
self.validate_finished_good_serial_batch_for_work_order() self.validate_finished_good_serial_batch_for_work_order()
if not self.get("purpose") == "Manufacture": if not self.get("purpose") == "Manufacture":
@@ -1190,124 +1186,6 @@ class StockEntry(StockController):
title=_("Missing Item"), title=_("Missing Item"),
) )
def _cap_completed_qty_to_material_coverage(self):
if not self._should_cap_completed_qty():
return
# Keep an excessive claim intact so the Work Order allowance check can reject it.
max_qty = flt(self.pro_doc.qty)
overproduction_percentage = flt(
frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
)
to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt(
self.fg_completed_qty
)
transfer_limit_qty = max_qty + (max_qty * overproduction_percentage / 100)
if transfer_limit_qty < to_transfer_qty:
return
self.cap_completed_qty_to_material_coverage()
def cap_completed_qty_to_material_coverage(self):
required_qty, transferred_qty, target_qty, precision = self._get_material_coverage_data()
if not required_qty:
return
covered_before = self._get_covered_qty(required_qty, transferred_qty, target_qty, precision)
for row in self.items:
if self.job_card:
material_reference = row.job_card_item
transferred = flt(row.qty)
else:
material_reference = row.original_item or row.item_code
transferred = flt(row.qty) * flt(row.conversion_factor or 1)
if material_reference in required_qty and (self.job_card or row.s_warehouse):
transferred_qty[material_reference] += transferred
covered_after = self._get_covered_qty(required_qty, transferred_qty, target_qty, precision)
covered_by_entry = flt(max(covered_after - covered_before, 0), self.precision("fg_completed_qty"))
self.fg_completed_qty = min(flt(self.fg_completed_qty), covered_by_entry)
def _should_cap_completed_qty(self):
if self.get("_action") != "submit":
return False
if self.purpose != "Material Transfer for Manufacture":
return False
if not self.pro_doc or not self.fg_completed_qty:
return False
if self.is_return or self.get("is_additional_transfer_entry"):
return False
return not (self.pro_doc.operations and self.pro_doc.transfer_material_against == "Job Card")
def _get_material_coverage_data(self):
if self.job_card:
return self._get_job_card_material_qty()
return self._get_work_order_material_qty()
def _get_job_card_material_qty(self):
job_card = frappe.get_doc("Job Card", self.job_card)
required_qty = {}
transferred_qty = {}
for row in job_card.items:
if flt(row.required_qty) <= 0:
continue
required_qty[row.name] = flt(row.required_qty)
transferred_qty[row.name] = flt(row.transferred_qty)
return (
required_qty,
transferred_qty,
self._get_job_card_target_qty(job_card),
job_card.precision("required_qty", "items"),
)
def _get_job_card_target_qty(self, job_card):
required_by_item = {}
for row in job_card.items:
required_by_item[row.item_code] = required_by_item.get(row.item_code, 0.0) + flt(row.required_qty)
work_order_required_by_item = {}
work_order = frappe.get_doc("Work Order", job_card.work_order)
for row in work_order.required_items:
if job_card.operation != row.operation:
continue
work_order_required_by_item[row.item_code] = work_order_required_by_item.get(
row.item_code, 0.0
) + flt(row.required_qty)
target_qty = [
item_required * flt(work_order.qty) / work_order_required_by_item[item_code]
for item_code, item_required in required_by_item.items()
if work_order_required_by_item.get(item_code)
]
return min(target_qty) if target_qty else job_card.for_quantity
def _get_work_order_material_qty(self):
required_qty = {}
transferred_qty = {}
for row in self.pro_doc.required_items:
if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0:
continue
required_qty[row.item_code] = required_qty.get(row.item_code, 0.0) + flt(row.required_qty)
# Duplicate required-item rows each hold the aggregate transferred quantity.
transferred_qty[row.item_code] = max(
transferred_qty.get(row.item_code, 0.0), flt(row.transferred_qty)
)
return (
required_qty,
transferred_qty,
self.pro_doc.qty,
self.pro_doc.precision("required_qty", "required_items"),
)
def _get_covered_qty(self, required_qty, transferred_qty, target_qty, precision):
min_fraction = get_minimum_material_coverage_fraction(
required_qty,
transferred_qty,
precision,
)
return min_fraction * flt(target_qty)
def _validate_no_excess_transfer(self): def _validate_no_excess_transfer(self):
if self.is_return: if self.is_return:
return return