From 341fad04c9d4ca8854707f7f31037ee5c3778d52 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Fri, 29 May 2026 13:20:50 +0530 Subject: [PATCH] refactor(job_card): move mapping functions to mapper.py --- .../doctype/job_card/job_card.py | 182 +---------------- .../manufacturing/doctype/job_card/mapper.py | 191 ++++++++++++++++++ 2 files changed, 193 insertions(+), 180 deletions(-) create mode 100644 erpnext/manufacturing/doctype/job_card/mapper.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index 12dad352dc2..45be24b8ad7 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -8,7 +8,6 @@ from typing import Any import frappe from frappe import _, bold from frappe.model.document import Document -from frappe.model.mapper import get_mapped_doc from frappe.query_builder import Criterion from frappe.query_builder.functions import IfNull, Max, Min, Sum from frappe.utils import ( @@ -37,6 +36,8 @@ from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import get_subcontracting_boms_for_finished_goods, ) +from .mapper import make_corrective_job_card, make_material_request, make_stock_entry, make_subcontracting_po + class OverlapError(frappe.ValidationError): pass @@ -1547,49 +1548,6 @@ class JobCard(Document): return ste.stock_entry.as_dict() -@frappe.whitelist() -def make_subcontracting_po(source_name: str, target_doc: Document | str | None = None): - def set_missing_values(source, target): - _item_details = get_subcontracting_boms_for_finished_goods(source.finished_good) - - pending_qty = source.for_quantity - source.manufactured_qty - service_item_qty = flt(_item_details.service_item_qty) or 1.0 - fg_item_qty = flt(_item_details.finished_good_qty) or 1.0 - - target.is_subcontracted = 1 - target.supplier_warehouse = source.wip_warehouse - target.append( - "items", - { - "item_code": _item_details.service_item, - "fg_item": source.finished_good, - "uom": _item_details.service_item_uom, - "stock_uom": _item_details.service_item_uom, - "conversion_factor": _item_details.conversion_factor or 1, - "item_name": _item_details.service_item, - "qty": pending_qty * service_item_qty / fg_item_qty, - "fg_item_qty": pending_qty, - "job_card": source.name, - "bom": source.semi_fg_bom, - "warehouse": source.target_warehouse, - }, - ) - - doclist = get_mapped_doc( - "Job Card", - source_name, - { - "Job Card": { - "doctype": "Purchase Order", - }, - }, - target_doc, - set_missing_values, - ) - - return doclist - - @frappe.whitelist() def make_time_log(kwargs: str | dict): if isinstance(kwargs, str): @@ -1633,105 +1591,6 @@ def get_operations(doctype: str, txt: str, searchfield: str, start: int, page_le ) -@frappe.whitelist() -def make_material_request(source_name: str, target_doc: Document | str | None = None): - def update_item(obj, target, source_parent): - target.warehouse = source_parent.wip_warehouse - - def set_missing_values(source, target): - target.material_request_type = "Material Transfer" - - doclist = get_mapped_doc( - "Job Card", - source_name, - { - "Job Card": { - "doctype": "Material Request", - "field_map": { - "name": "job_card", - }, - }, - "Job Card Item": { - "doctype": "Material Request Item", - "field_map": {"required_qty": "qty", "uom": "stock_uom", "name": "job_card_item"}, - "postprocess": update_item, - }, - }, - target_doc, - set_missing_values, - ) - - return doclist - - -@frappe.whitelist() -def make_stock_entry(source_name: str, target_doc: Document | str | None = None): - def update_item(source, target, source_parent): - target.t_warehouse = source_parent.wip_warehouse - - if not target.conversion_factor: - target.conversion_factor = 1 - - pending_rm_qty = flt(source.required_qty) - flt(source.transferred_qty) - if pending_rm_qty > 0: - target.qty = pending_rm_qty - - def set_missing_values(source, target): - if source.finished_good and not source.target_warehouse: - frappe.throw(_("Please set the Target Warehouse in the Job Card")) - - if not source.skip_material_transfer or source.backflush_from_wip_warehouse: - if not source.wip_warehouse: - frappe.throw(_("Please set the WIP Warehouse in the Job Card")) - - target.purpose = "Material Transfer for Manufacture" - target.from_bom = 1 - - if source.semi_fg_bom: - target.bom_no = source.semi_fg_bom - - # avoid negative 'For Quantity' - pending_fg_qty = flt(source.get("for_quantity", 0)) - flt(source.get("transferred_qty", 0)) - target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0 - - target.set_missing_values() - target.set_stock_entry_type() - - wo_allows_alternate_item = frappe.db.get_value( - "Work Order", target.work_order, "allow_alternative_item" - ) - for item in target.items: - item.allow_alternative_item = int( - wo_allows_alternate_item - and frappe.get_cached_value("Item", item.item_code, "allow_alternative_item") - ) - - doclist = get_mapped_doc( - "Job Card", - source_name, - { - "Job Card": { - "doctype": "Stock Entry", - "field_map": {"name": "job_card", "for_quantity": "fg_completed_qty"}, - }, - "Job Card Item": { - "doctype": "Stock Entry Detail", - "field_map": { - "source_warehouse": "s_warehouse", - "required_qty": "qty", - "name": "job_card_item", - }, - "postprocess": update_item, - "condition": lambda doc: doc.required_qty > 0, - }, - }, - target_doc, - set_missing_values, - ) - - return doclist - - def time_diff_in_minutes(string_ed_date, string_st_date): return time_diff(string_ed_date, string_st_date).total_seconds() / 60 @@ -1782,40 +1641,3 @@ def get_job_details(start: Any, end: Any, filters: str | dict | None = None): events.append(job_card_data) return events - - -@frappe.whitelist() -def make_corrective_job_card( - source_name: str, - operation: str | None = None, - for_operation: str | None = None, - target_doc: Document | str | None = None, -): - def set_missing_values(source, target): - target.is_corrective_job_card = 1 - target.operation = operation - target.for_operation = for_operation - - target.set("time_logs", []) - target.set("employee", []) - target.set("items", []) - target.set("sub_operations", []) - target.set_sub_operations() - target.get_required_items() - - doclist = get_mapped_doc( - "Job Card", - source_name, - { - "Job Card": { - "doctype": "Job Card", - "field_map": { - "name": "for_job_card", - }, - } - }, - target_doc, - set_missing_values, - ) - - return doclist diff --git a/erpnext/manufacturing/doctype/job_card/mapper.py b/erpnext/manufacturing/doctype/job_card/mapper.py new file mode 100644 index 00000000000..0dce2daa157 --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card/mapper.py @@ -0,0 +1,191 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe +from frappe import _ +from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc +from frappe.utils import flt + +from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import ( + get_subcontracting_boms_for_finished_goods, +) + + +@frappe.whitelist() +def make_subcontracting_po(source_name: str, target_doc: Document | str | None = None): + def set_missing_values(source, target): + _item_details = get_subcontracting_boms_for_finished_goods(source.finished_good) + + pending_qty = source.for_quantity - source.manufactured_qty + service_item_qty = flt(_item_details.service_item_qty) or 1.0 + fg_item_qty = flt(_item_details.finished_good_qty) or 1.0 + + target.is_subcontracted = 1 + target.supplier_warehouse = source.wip_warehouse + target.append( + "items", + { + "item_code": _item_details.service_item, + "fg_item": source.finished_good, + "uom": _item_details.service_item_uom, + "stock_uom": _item_details.service_item_uom, + "conversion_factor": _item_details.conversion_factor or 1, + "item_name": _item_details.service_item, + "qty": pending_qty * service_item_qty / fg_item_qty, + "fg_item_qty": pending_qty, + "job_card": source.name, + "bom": source.semi_fg_bom, + "warehouse": source.target_warehouse, + }, + ) + + doclist = get_mapped_doc( + "Job Card", + source_name, + { + "Job Card": { + "doctype": "Purchase Order", + }, + }, + target_doc, + set_missing_values, + ) + + return doclist + + +@frappe.whitelist() +def make_material_request(source_name: str, target_doc: Document | str | None = None): + def update_item(obj, target, source_parent): + target.warehouse = source_parent.wip_warehouse + + def set_missing_values(source, target): + target.material_request_type = "Material Transfer" + + doclist = get_mapped_doc( + "Job Card", + source_name, + { + "Job Card": { + "doctype": "Material Request", + "field_map": { + "name": "job_card", + }, + }, + "Job Card Item": { + "doctype": "Material Request Item", + "field_map": {"required_qty": "qty", "uom": "stock_uom", "name": "job_card_item"}, + "postprocess": update_item, + }, + }, + target_doc, + set_missing_values, + ) + + return doclist + + +@frappe.whitelist() +def make_stock_entry(source_name: str, target_doc: Document | str | None = None): + def update_item(source, target, source_parent): + target.t_warehouse = source_parent.wip_warehouse + + if not target.conversion_factor: + target.conversion_factor = 1 + + pending_rm_qty = flt(source.required_qty) - flt(source.transferred_qty) + if pending_rm_qty > 0: + target.qty = pending_rm_qty + + def set_missing_values(source, target): + if source.finished_good and not source.target_warehouse: + frappe.throw(_("Please set the Target Warehouse in the Job Card")) + + if not source.skip_material_transfer or source.backflush_from_wip_warehouse: + if not source.wip_warehouse: + frappe.throw(_("Please set the WIP Warehouse in the Job Card")) + + target.purpose = "Material Transfer for Manufacture" + target.from_bom = 1 + + if source.semi_fg_bom: + target.bom_no = source.semi_fg_bom + + # avoid negative 'For Quantity' + pending_fg_qty = flt(source.get("for_quantity", 0)) - flt(source.get("transferred_qty", 0)) + target.fg_completed_qty = pending_fg_qty if pending_fg_qty > 0 else 0 + + target.set_missing_values() + target.set_stock_entry_type() + + wo_allows_alternate_item = frappe.db.get_value( + "Work Order", target.work_order, "allow_alternative_item" + ) + for item in target.items: + item.allow_alternative_item = int( + wo_allows_alternate_item + and frappe.get_cached_value("Item", item.item_code, "allow_alternative_item") + ) + + doclist = get_mapped_doc( + "Job Card", + source_name, + { + "Job Card": { + "doctype": "Stock Entry", + "field_map": {"name": "job_card", "for_quantity": "fg_completed_qty"}, + }, + "Job Card Item": { + "doctype": "Stock Entry Detail", + "field_map": { + "source_warehouse": "s_warehouse", + "required_qty": "qty", + "name": "job_card_item", + }, + "postprocess": update_item, + "condition": lambda doc: doc.required_qty > 0, + }, + }, + target_doc, + set_missing_values, + ) + + return doclist + + +@frappe.whitelist() +def make_corrective_job_card( + source_name: str, + operation: str | None = None, + for_operation: str | None = None, + target_doc: Document | str | None = None, +): + def set_missing_values(source, target): + target.is_corrective_job_card = 1 + target.operation = operation + target.for_operation = for_operation + + target.set("time_logs", []) + target.set("employee", []) + target.set("items", []) + target.set("sub_operations", []) + target.set_sub_operations() + target.get_required_items() + + doclist = get_mapped_doc( + "Job Card", + source_name, + { + "Job Card": { + "doctype": "Job Card", + "field_map": { + "name": "for_job_card", + }, + } + }, + target_doc, + set_missing_values, + ) + + return doclist