mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-17 08:28:44 +00:00
refactor(buying): extract Purchase Order SubcontractingService
Move subcontracting integration into buying/doctype/purchase_order/services/subcontracting.py: - set_service_items_for_finished_goods (called by production plan work-order planning) and can_update_items (onload + child_item_update) stay as controller delegators - validate_fg_item_for_subcontracting, auto_create_subcontracting_order and update_subcontracting_order_status move into the service; validate(), on_submit and update_status repointed No behaviour change.
This commit is contained in:
@@ -16,6 +16,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
validate_inter_company_party,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.buying.doctype.purchase_order.services.subcontracting import SubcontractingService
|
||||
from erpnext.buying.utils import validate_for_items
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.controllers.status_updater import get_allowance_for
|
||||
@@ -24,13 +25,6 @@ from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
|
||||
)
|
||||
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
|
||||
from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import (
|
||||
get_subcontracting_boms_for_finished_goods,
|
||||
)
|
||||
|
||||
from .mapper import (
|
||||
make_subcontracting_order,
|
||||
)
|
||||
|
||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
@@ -214,7 +208,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.validate_minimum_order_qty()
|
||||
validate_against_blanket_order(self)
|
||||
|
||||
self.validate_fg_item_for_subcontracting()
|
||||
SubcontractingService(self).validate_fg_item_for_subcontracting()
|
||||
|
||||
if not self.advance_payment_status:
|
||||
self.advance_payment_status = "Not Initiated"
|
||||
@@ -322,35 +316,6 @@ class PurchaseOrder(BuyingController):
|
||||
).format(item_code, qty, itemwise_min_order_qty.get(item_code))
|
||||
)
|
||||
|
||||
def validate_fg_item_for_subcontracting(self):
|
||||
if self.is_subcontracted:
|
||||
for item in self.items:
|
||||
if not item.fg_item:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
elif not item.bom and not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
|
||||
else:
|
||||
for item in self.items:
|
||||
item.set("fg_item", None)
|
||||
item.set("fg_item_qty", 0)
|
||||
|
||||
def get_schedule_dates(self):
|
||||
for d in self.get("items"):
|
||||
if d.material_request_item and not d.schedule_date:
|
||||
@@ -413,7 +378,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.set_status(update=True, status=status)
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
self.update_subcontracting_order_status()
|
||||
SubcontractingService(self).update_subcontracting_order_status()
|
||||
self.update_blanket_order()
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
@@ -442,7 +407,7 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
|
||||
|
||||
self.auto_create_subcontracting_order()
|
||||
SubcontractingService(self).auto_create_subcontracting_order()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = (
|
||||
@@ -621,34 +586,10 @@ class PurchaseOrder(BuyingController):
|
||||
self.db_set("per_received", 0, update_modified=False)
|
||||
|
||||
def set_service_items_for_finished_goods(self):
|
||||
if not self.is_subcontracted:
|
||||
return
|
||||
|
||||
finished_goods_without_service_item = {
|
||||
d.fg_item for d in self.items if (not d.item_code and d.fg_item)
|
||||
}
|
||||
|
||||
if subcontracting_boms := get_subcontracting_boms_for_finished_goods(
|
||||
finished_goods_without_service_item
|
||||
):
|
||||
for item in self.items:
|
||||
if not item.item_code and item.fg_item in subcontracting_boms:
|
||||
subcontracting_bom = subcontracting_boms[item.fg_item]
|
||||
|
||||
item.item_code = subcontracting_bom.service_item
|
||||
item.qty = flt(item.fg_item_qty) * flt(subcontracting_bom.conversion_factor)
|
||||
item.uom = subcontracting_bom.service_item_uom
|
||||
SubcontractingService(self).set_service_items_for_finished_goods()
|
||||
|
||||
def can_update_items(self) -> bool:
|
||||
result = True
|
||||
|
||||
if self.is_subcontracted:
|
||||
if frappe.db.exists(
|
||||
"Subcontracting Order", {"purchase_order": self.name, "docstatus": ["!=", 2]}
|
||||
):
|
||||
result = False
|
||||
|
||||
return result
|
||||
return SubcontractingService(self).can_update_items()
|
||||
|
||||
def has_pending_receivable_qty(self) -> bool:
|
||||
"""Return True if any non-drop-ship item can still be received,
|
||||
@@ -687,22 +628,6 @@ class PurchaseOrder(BuyingController):
|
||||
"Sales Order Item", sales_order_item, "ordered_qty", prev_ordered_qty - qty_in_stock_uom
|
||||
)
|
||||
|
||||
def auto_create_subcontracting_order(self):
|
||||
if self.is_subcontracted:
|
||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||
make_subcontracting_order(self.name, save=True, notify=True)
|
||||
|
||||
def update_subcontracting_order_status(self):
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
update_subcontracting_order_status as update_sco_status,
|
||||
)
|
||||
|
||||
if self.is_subcontracted:
|
||||
sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": self.name, "docstatus": 1})
|
||||
|
||||
if sco:
|
||||
update_sco_status(sco, "Closed" if self.status == "Closed" else None)
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
"""Subcontracting integration for Purchase Order."""
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
|
||||
class SubcontractingService:
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
|
||||
def validate_fg_item_for_subcontracting(self) -> None:
|
||||
doc = self.doc
|
||||
if doc.is_subcontracted:
|
||||
for item in doc.items:
|
||||
if not item.fg_item:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item is not specified for service item {1}").format(
|
||||
item.idx, item.item_code
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
elif not item.bom and not frappe.get_value("Item", item.fg_item, "default_bom"):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Default BOM not found for FG Item {1}").format(
|
||||
item.idx, item.fg_item
|
||||
)
|
||||
)
|
||||
if not item.fg_item_qty:
|
||||
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
|
||||
else:
|
||||
for item in doc.items:
|
||||
item.set("fg_item", None)
|
||||
item.set("fg_item_qty", 0)
|
||||
|
||||
def set_service_items_for_finished_goods(self) -> None:
|
||||
from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import (
|
||||
get_subcontracting_boms_for_finished_goods,
|
||||
)
|
||||
|
||||
doc = self.doc
|
||||
if not doc.is_subcontracted:
|
||||
return
|
||||
|
||||
finished_goods_without_service_item = {
|
||||
d.fg_item for d in doc.items if (not d.item_code and d.fg_item)
|
||||
}
|
||||
|
||||
if subcontracting_boms := get_subcontracting_boms_for_finished_goods(
|
||||
finished_goods_without_service_item
|
||||
):
|
||||
for item in doc.items:
|
||||
if not item.item_code and item.fg_item in subcontracting_boms:
|
||||
subcontracting_bom = subcontracting_boms[item.fg_item]
|
||||
|
||||
item.item_code = subcontracting_bom.service_item
|
||||
item.qty = flt(item.fg_item_qty) * flt(subcontracting_bom.conversion_factor)
|
||||
item.uom = subcontracting_bom.service_item_uom
|
||||
|
||||
def can_update_items(self) -> bool:
|
||||
result = True
|
||||
|
||||
if self.doc.is_subcontracted:
|
||||
if frappe.db.exists(
|
||||
"Subcontracting Order", {"purchase_order": self.doc.name, "docstatus": ["!=", 2]}
|
||||
):
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
||||
def auto_create_subcontracting_order(self) -> None:
|
||||
from erpnext.buying.doctype.purchase_order.mapper import make_subcontracting_order
|
||||
|
||||
if self.doc.is_subcontracted:
|
||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||
make_subcontracting_order(self.doc.name, save=True, notify=True)
|
||||
|
||||
def update_subcontracting_order_status(self) -> None:
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
update_subcontracting_order_status as update_sco_status,
|
||||
)
|
||||
|
||||
doc = self.doc
|
||||
if doc.is_subcontracted:
|
||||
sco = frappe.db.get_value("Subcontracting Order", {"purchase_order": doc.name, "docstatus": 1})
|
||||
|
||||
if sco:
|
||||
update_sco_status(sco, "Closed" if doc.status == "Closed" else None)
|
||||
Reference in New Issue
Block a user