refactor: backport old subcontracting code

This commit is contained in:
s-aga-r 2022-06-18 15:46:59 +05:30
parent ca24b5287e
commit 6d89b2fa28
41 changed files with 1047 additions and 459 deletions

View File

@ -572,6 +572,10 @@ frappe.ui.form.on("Purchase Invoice", {
}, },
is_subcontracted: function(frm) { is_subcontracted: function(frm) {
if (frm.doc.is_old_subcontracting_flow) {
erpnext.buying.get_default_bom(frm);
}
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
}, },

View File

@ -513,6 +513,10 @@ class PurchaseInvoice(BuyingController):
# because updating ordered qty in bin depends upon updated ordered qty in PO # because updating ordered qty in bin depends upon updated ordered qty in PO
if self.update_stock == 1: if self.update_stock == 1:
self.update_stock_ledger() self.update_stock_ledger()
if self.is_old_subcontracting_flow:
self.set_consumed_qty_in_subcontract_order()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "items") update_serial_nos_after_submit(self, "items")
@ -1416,6 +1420,9 @@ class PurchaseInvoice(BuyingController):
self.update_stock_ledger() self.update_stock_ledger()
self.delete_auto_created_batches() self.delete_auto_created_batches()
if self.is_old_subcontracting_flow:
self.set_consumed_qty_in_subcontract_order()
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()
if self.update_stock == 1: if self.update_stock == 1:

View File

@ -616,11 +616,13 @@
"search_index": 1 "search_index": 1
}, },
{ {
"depends_on": "eval:parent.is_old_subcontracting_flow",
"fieldname": "bom", "fieldname": "bom",
"fieldtype": "Link", "fieldtype": "Link",
"label": "BOM", "label": "BOM",
"options": "BOM", "options": "BOM",
"read_only": 1 "read_only": 1,
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
}, },
{ {
"default": "0", "default": "0",
@ -872,7 +874,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-11-15 17:04:07.191013", "modified": "2022-06-15 16:02:15.196835",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",
@ -880,5 +882,6 @@
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

@ -7,6 +7,19 @@ frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Purchase Order", { frappe.ui.form.on("Purchase Order", {
setup: function(frm) { setup: function(frm) {
if (frm.doc.is_old_subcontracting_flow) {
frm.set_query("reserve_warehouse", "supplied_items", function() {
return {
filters: {
"company": frm.doc.company,
"name": ['!=', frm.doc.supplier_warehouse],
"is_group": 0
}
}
});
}
frm.set_indicator_formatter('item_code', frm.set_indicator_formatter('item_code',
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" }) function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
@ -19,7 +32,10 @@ frappe.ui.form.on("Purchase Order", {
frm.set_query("fg_item", "items", function() { frm.set_query("fg_item", "items", function() {
return { return {
filters: {'is_sub_contracted_item': 1} filters: {
'is_sub_contracted_item': 1,
'default_bom': ['!=', '']
}
} }
}); });
}, },
@ -28,6 +44,44 @@ frappe.ui.form.on("Purchase Order", {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
}, },
refresh: function(frm) {
if(frm.doc.is_old_subcontracting_flow)
frm.trigger('get_materials_from_supplier');
},
get_materials_from_supplier: function(frm) {
let po_details = [];
if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
frm.doc.supplied_items.forEach(d => {
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
po_details.push(d.name)
}
});
}
if (po_details && po_details.length) {
frm.add_custom_button(__('Return of Components'), () => {
frm.call({
method: 'erpnext.controllers.subcontracting_controller.get_materials_from_supplier',
freeze: true,
freeze_message: __('Creating Stock Entry'),
args: {
subcontract_order: frm.doc.name,
rm_details: po_details,
order_doctype: cur_frm.doc.doctype
},
callback: function(r) {
if (r && r.message) {
const doc = frappe.model.sync(r.message);
frappe.set_route("Form", doc[0].doctype, doc[0].name);
}
}
});
}, __('Create'));
}
},
onload: function(frm) { onload: function(frm) {
set_schedule_date(frm); set_schedule_date(frm);
if (!frm.doc.transaction_date){ if (!frm.doc.transaction_date){
@ -67,7 +121,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
'Purchase Receipt': 'Purchase Receipt', 'Purchase Receipt': 'Purchase Receipt',
'Purchase Invoice': 'Purchase Invoice', 'Purchase Invoice': 'Purchase Invoice',
'Payment Entry': 'Payment', 'Payment Entry': 'Payment',
'Subcontracting Order': 'Subcontracting Order' 'Subcontracting Order': 'Subcontracting Order',
'Stock Entry': 'Material to Supplier'
} }
super.setup(); super.setup();
@ -138,7 +193,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
if(flt(doc.per_received) < 100 && allow_receipt) { if(flt(doc.per_received) < 100 && allow_receipt) {
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create')); cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
if (doc.is_subcontracted) { if (doc.is_subcontracted) {
cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create')); if (doc.is_old_subcontracting_flow) {
if (me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'), function() { me.make_stock_entry(); }, __("Transfer"));
}
}
else {
cur_frm.add_custom_button(__('Subcontracting Order'), this.make_subcontracting_order, __('Create'));
}
} }
} }
if(flt(doc.per_billed) < 100) if(flt(doc.per_billed) < 100)
@ -206,6 +268,143 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
set_schedule_date(this.frm); set_schedule_date(this.frm);
} }
has_unsupplied_items() {
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty);
}
make_stock_entry() {
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
var me = this;
if(items.length >= 1){
me.raw_material_data = [];
me.show_dialog = 1;
let title = __('Transfer Material to Supplier');
let fields = [
{fieldtype:'Section Break', label: __('Raw Materials')},
{fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'),
fields: [
{
fieldtype:'Data',
fieldname:'item_code',
label: __('Item'),
read_only:1,
in_list_view:1
},
{
fieldtype:'Data',
fieldname:'rm_item_code',
label: __('Raw Material'),
read_only:1,
in_list_view:1
},
{
fieldtype:'Float',
read_only:1,
fieldname:'qty',
label: __('Quantity'),
read_only:1,
in_list_view:1
},
{
fieldtype:'Data',
read_only:1,
fieldname:'warehouse',
label: __('Reserve Warehouse'),
in_list_view:1
},
{
fieldtype:'Float',
read_only:1,
fieldname:'rate',
label: __('Rate'),
hidden:1
},
{
fieldtype:'Float',
read_only:1,
fieldname:'amount',
label: __('Amount'),
hidden:1
},
{
fieldtype:'Link',
read_only:1,
fieldname:'uom',
label: __('UOM'),
hidden:1
}
],
data: me.raw_material_data,
get_data: function() {
return me.raw_material_data;
}
}
]
me.dialog = new frappe.ui.Dialog({
title: title, fields: fields
});
if (me.frm.doc['supplied_items']) {
me.frm.doc['supplied_items'].forEach((item, index) => {
if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) {
me.raw_material_data.push ({
'name':item.name,
'item_code': item.main_item_code,
'rm_item_code': item.rm_item_code,
'item_name': item.rm_item_code,
'qty': item.required_qty - item.supplied_qty,
'warehouse':item.reserve_warehouse,
'rate':item.rate,
'amount':item.amount,
'stock_uom':item.stock_uom
});
me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
}
})
}
me.dialog.get_field('sub_con_rm_items').check_all_rows()
me.dialog.show()
this.dialog.set_primary_action(__('Transfer'), function() {
me.values = me.dialog.get_values();
if(me.values) {
me.values.sub_con_rm_items.map((row,i) => {
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
let row_id = i+1;
frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id]));
}
})
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
me.dialog.hide()
}
});
}
me.dialog.get_close_btn().on('click', () => {
me.dialog.hide();
});
}
_make_rm_stock_entry(rm_items) {
frappe.call({
method:"erpnext.controllers.subcontracting_controller.make_rm_stock_entry",
args: {
subcontract_order: cur_frm.doc.name,
rm_items: rm_items,
order_doctype: cur_frm.doc.doctype
}
,
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
}
make_inter_company_order(frm) { make_inter_company_order(frm) {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order", method: "erpnext.buying.doctype.purchase_order.purchase_order.make_inter_company_sales_order",
@ -444,6 +643,20 @@ cur_frm.fields_dict['items'].grid.get_field('project').get_query = function(doc,
} }
} }
if (cur_frm.doc.is_old_subcontracting_flow) {
cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt, cdn) {
var d = locals[cdt][cdn]
return {
filters: [
['BOM', 'item', '=', d.item_code],
['BOM', 'is_active', '=', '1'],
['BOM', 'docstatus', '=', '1'],
['BOM', 'company', '=', doc.company]
]
}
}
}
function set_schedule_date(frm) { function set_schedule_date(frm) {
if(frm.doc.schedule_date){ if(frm.doc.schedule_date){
erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date"); erpnext.utils.copy_value_in_all_rows(frm.doc, frm.doc.doctype, frm.doc.name, "items", "schedule_date");
@ -451,3 +664,9 @@ function set_schedule_date(frm) {
} }
frappe.provide("erpnext.buying"); frappe.provide("erpnext.buying");
frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
if (frm.doc.is_old_subcontracting_flow) {
erpnext.buying.get_default_bom(frm);
}
});

View File

@ -452,10 +452,6 @@
"options": "Warehouse", "options": "Warehouse",
"print_hide": 1 "print_hide": 1
}, },
{
"fieldname": "col_break_warehouse",
"fieldtype": "Column Break"
},
{ {
"default": "0", "default": "0",
"fieldname": "is_subcontracted", "fieldname": "is_subcontracted",

View File

@ -24,6 +24,7 @@ from erpnext.controllers.buying_controller import BuyingController
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.item.item import get_item_defaults, get_last_purchase_details from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
from erpnext.stock.utils import get_bin
form_grid_templates = {"items": "templates/form_grid/item_grid.html"} form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
@ -68,6 +69,11 @@ class PurchaseOrder(BuyingController):
self.validate_with_previous_doc() self.validate_with_previous_doc()
self.validate_for_subcontracting() self.validate_for_subcontracting()
self.validate_minimum_order_qty() self.validate_minimum_order_qty()
if self.is_old_subcontracting_flow:
self.validate_bom_for_subcontracting_items()
self.create_raw_materials_supplied()
self.validate_fg_item_for_subcontracting() self.validate_fg_item_for_subcontracting()
self.set_received_qty_for_drop_ship_items() self.set_received_qty_for_drop_ship_items()
validate_inter_company_party( validate_inter_company_party(
@ -191,8 +197,17 @@ class PurchaseOrder(BuyingController):
).format(item_code, qty, itemwise_min_order_qty.get(item_code)) ).format(item_code, qty, itemwise_min_order_qty.get(item_code))
) )
def validate_bom_for_subcontracting_items(self):
for item in self.items:
if not item.bom:
frappe.throw(
_("Row #{0}: BOM is not specified for subcontracting item {0}").format(
item.idx, item.item_code
)
)
def validate_fg_item_for_subcontracting(self): def validate_fg_item_for_subcontracting(self):
if self.is_subcontracted: if self.is_subcontracted and not self.is_old_subcontracting_flow:
for item in self.items: for item in self.items:
if not item.fg_item: if not item.fg_item:
frappe.throw( frappe.throw(
@ -207,6 +222,10 @@ class PurchaseOrder(BuyingController):
"Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}" "Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}"
).format(item.idx, item.fg_item, item.item_code) ).format(item.idx, item.fg_item, item.item_code)
) )
elif 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: if not item.fg_item_qty:
frappe.throw( frappe.throw(
_("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format( _("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format(
@ -305,6 +324,7 @@ class PurchaseOrder(BuyingController):
self.set_status(update=True, status=status) self.set_status(update=True, status=status)
self.update_requested_qty() self.update_requested_qty()
self.update_ordered_qty() self.update_ordered_qty()
self.update_reserved_qty_for_subcontract()
self.notify_update() self.notify_update()
clear_doctype_notifications(self) clear_doctype_notifications(self)
@ -318,6 +338,7 @@ class PurchaseOrder(BuyingController):
self.update_requested_qty() self.update_requested_qty()
self.update_ordered_qty() self.update_ordered_qty()
self.validate_budget() self.validate_budget()
self.update_reserved_qty_for_subcontract()
frappe.get_doc("Authorization Control").validate_approving_authority( frappe.get_doc("Authorization Control").validate_approving_authority(
self.doctype, self.company, self.base_grand_total self.doctype, self.company, self.base_grand_total
@ -337,6 +358,7 @@ class PurchaseOrder(BuyingController):
if self.has_drop_ship_item(): if self.has_drop_ship_item():
self.update_delivered_qty_in_sales_order() self.update_delivered_qty_in_sales_order()
self.update_reserved_qty_for_subcontract()
self.check_on_hold_or_closed_status() self.check_on_hold_or_closed_status()
frappe.db.set(self, "status", "Cancelled") frappe.db.set(self, "status", "Cancelled")
@ -406,6 +428,13 @@ class PurchaseOrder(BuyingController):
if item.delivered_by_supplier == 1: if item.delivered_by_supplier == 1:
item.received_qty = item.qty item.received_qty = item.qty
def update_reserved_qty_for_subcontract(self):
if self.is_old_subcontracting_flow:
for d in self.supplied_items:
if d.rm_item_code:
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
stock_bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
def update_receiving_percentage(self): def update_receiving_percentage(self):
total_qty, received_qty = 0.0, 0.0 total_qty, received_qty = 0.0, 0.0
for item in self.items: for item in self.items:
@ -649,4 +678,16 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
target_doc.populate_items_table() target_doc.populate_items_table()
if target_doc.set_warehouse:
for item in target_doc.items:
item.warehouse = target_doc.set_warehouse
else:
source_doc = frappe.get_doc("Purchase Order", source_name)
if source_doc.set_warehouse:
for item in target_doc.items:
item.warehouse = source_doc.set_warehouse
else:
for idx, item in enumerate(target_doc.items):
item.warehouse = source_doc.items[idx].warehouse
return target_doc return target_doc

View File

@ -22,6 +22,6 @@ def get_data():
"label": _("Reference"), "label": _("Reference"),
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"], "items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
}, },
{"label": _("Sub-contracting"), "items": ["Subcontracting Order"]}, {"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]},
], ],
} }

View File

@ -574,18 +574,20 @@
"read_only": 1 "read_only": 1
}, },
{ {
"depends_on": "eval:parent.is_old_subcontracting_flow",
"fieldname": "bom", "fieldname": "bom",
"fieldtype": "Link", "fieldtype": "Link",
"label": "BOM", "label": "BOM",
"options": "BOM", "options": "BOM",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:parent.is_old_subcontracting_flow",
"fieldname": "include_exploded_items", "fieldname": "include_exploded_items",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 1,
"label": "Include Exploded Items", "label": "Include Exploded Items",
"print_hide": 1 "print_hide": 1
}, },
@ -849,27 +851,27 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"depends_on": "eval:parent.is_subcontracted", "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
"fieldname": "fg_item", "fieldname": "fg_item",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Finished Good Item", "label": "Finished Good Item",
"mandatory_depends_on": "eval:parent.is_subcontracted", "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
"options": "Item" "options": "Item"
}, },
{ {
"default": "1", "default": "1",
"depends_on": "eval:parent.is_subcontracted", "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
"fieldname": "fg_item_qty", "fieldname": "fg_item_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Finished Good Item Qty", "label": "Finished Good Item Qty",
"mandatory_depends_on": "eval:parent.is_subcontracted" "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow"
} }
], ],
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-04-07 14:53:16.684010", "modified": "2022-06-16 06:00:01.624317",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@ -27,18 +27,16 @@ frappe.query_reports["Subcontract Order Summary"] = {
reqd: 1 reqd: 1
}, },
{ {
label: __("Subcontracting Order"), label: __("Order Type"),
fieldname: "order_type",
fieldtype: "Select",
options: ["Purchase Order", "Subcontracting Order"],
default: "Subcontracting Order"
},
{
label: __("Subcontract Order"),
fieldname: "name", fieldname: "name",
fieldtype: "Link", fieldtype: "Data"
options: "Subcontracting Order",
get_query: function () {
return {
filters: {
docstatus: 1,
company: frappe.query_report.get_filter_value('company')
}
}
}
} }
] ]
}; };

View File

@ -8,7 +8,7 @@ from frappe import _
def execute(filters=None): def execute(filters=None):
columns, data = [], [] columns, data = [], []
columns = get_columns() columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
return columns, data return columns, data
@ -20,42 +20,45 @@ def get_data(report_filters):
if orders: if orders:
supplied_items = get_supplied_items(orders, report_filters) supplied_items = get_supplied_items(orders, report_filters)
sco_details = prepare_subcontracted_data(orders, supplied_items) order_details = prepare_subcontracted_data(orders, supplied_items)
get_subcontracted_data(sco_details, data) get_subcontracted_data(order_details, data)
return data return data
def get_subcontracted_orders(report_filters): def get_subcontracted_orders(report_filters):
fields = [ fields = [
"`tabSubcontracting Order Item`.`parent` as sco_id", f"`tab{report_filters.order_type} Item`.`parent` as order_id",
"`tabSubcontracting Order Item`.`item_code`", f"`tab{report_filters.order_type} Item`.`item_code`",
"`tabSubcontracting Order Item`.`item_name`", f"`tab{report_filters.order_type} Item`.`item_name`",
"`tabSubcontracting Order Item`.`qty`", f"`tab{report_filters.order_type} Item`.`qty`",
"`tabSubcontracting Order Item`.`name`", f"`tab{report_filters.order_type} Item`.`name`",
"`tabSubcontracting Order Item`.`received_qty`", f"`tab{report_filters.order_type} Item`.`received_qty`",
"`tabSubcontracting Order`.`status`", f"`tab{report_filters.order_type}`.`status`",
] ]
filters = get_filters(report_filters) filters = get_filters(report_filters)
return frappe.get_all("Subcontracting Order", fields=fields, filters=filters) or [] return frappe.get_all(report_filters.order_type, fields=fields, filters=filters) or []
def get_filters(report_filters): def get_filters(report_filters):
filters = [ filters = [
["Subcontracting Order", "docstatus", "=", 1], [report_filters.order_type, "docstatus", "=", 1],
[ [
"Subcontracting Order", report_filters.order_type,
"transaction_date", "transaction_date",
"between", "between",
(report_filters.from_date, report_filters.to_date), (report_filters.from_date, report_filters.to_date),
], ],
] ]
if report_filters.order_type == "Purchase Order":
filters.append(["Purchase Order", "is_old_subcontracting_flow", "=", 1])
for field in ["name", "company"]: for field in ["name", "company"]:
if report_filters.get(field): if report_filters.get(field):
filters.append(["Subcontracting Order", field, "=", report_filters.get(field)]) filters.append([report_filters.order_type, field, "=", report_filters.get(field)])
return filters return filters
@ -70,15 +73,21 @@ def get_supplied_items(orders, report_filters):
"rm_item_code", "rm_item_code",
"required_qty", "required_qty",
"supplied_qty", "supplied_qty",
"returned_qty",
"total_supplied_qty", "total_supplied_qty",
"consumed_qty", "consumed_qty",
"reference_name", "reference_name",
] ]
filters = {"parent": ("in", [d.sco_id for d in orders]), "docstatus": 1} filters = {"parent": ("in", [d.order_id for d in orders]), "docstatus": 1}
supplied_items = {} supplied_items = {}
for row in frappe.get_all("Subcontracting Order Supplied Item", fields=fields, filters=filters): supplied_items_table = (
"Purchase Order Item Supplied"
if report_filters.order_type == "Purchase Order"
else "Subcontracting Order Supplied Item"
)
for row in frappe.get_all(supplied_items_table, fields=fields, filters=filters):
new_key = (row.parent, row.reference_name, row.main_item_code) new_key = (row.parent, row.reference_name, row.main_item_code)
supplied_items.setdefault(new_key, []).append(row) supplied_items.setdefault(new_key, []).append(row)
@ -87,24 +96,24 @@ def get_supplied_items(orders, report_filters):
def prepare_subcontracted_data(orders, supplied_items): def prepare_subcontracted_data(orders, supplied_items):
sco_details = {} order_details = {}
for row in orders: for row in orders:
key = (row.sco_id, row.name, row.item_code) key = (row.order_id, row.name, row.item_code)
if key not in sco_details: if key not in order_details:
sco_details.setdefault(key, frappe._dict({"sco_item": row, "supplied_items": []})) order_details.setdefault(key, frappe._dict({"order_item": row, "supplied_items": []}))
details = sco_details[key] details = order_details[key]
if supplied_items.get(key): if supplied_items.get(key):
for supplied_item in supplied_items[key]: for supplied_item in supplied_items[key]:
details["supplied_items"].append(supplied_item) details["supplied_items"].append(supplied_item)
return sco_details return order_details
def get_subcontracted_data(sco_details, data): def get_subcontracted_data(order_details, data):
for key, details in sco_details.items(): for key, details in order_details.items():
res = details.sco_item res = details.order_item
for index, row in enumerate(details.supplied_items): for index, row in enumerate(details.supplied_items):
if index != 0: if index != 0:
res = {} res = {}
@ -113,13 +122,13 @@ def get_subcontracted_data(sco_details, data):
data.append(res) data.append(res)
def get_columns(): def get_columns(filters):
return [ return [
{ {
"label": _("Subcontracting Order"), "label": _("Subcontract Order"),
"fieldname": "sco_id", "fieldname": "order_id",
"fieldtype": "Link", "fieldtype": "Link",
"options": "Subcontracting Order", "options": filters.order_type,
"width": 100, "width": 100,
}, },
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80}, {"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 80},
@ -142,4 +151,5 @@ def get_columns():
{"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 110}, {"label": _("Required Qty"), "fieldname": "required_qty", "fieldtype": "Float", "width": 110},
{"label": _("Supplied Qty"), "fieldname": "supplied_qty", "fieldtype": "Float", "width": 110}, {"label": _("Supplied Qty"), "fieldname": "supplied_qty", "fieldtype": "Float", "width": 110},
{"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120}, {"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120},
{"label": _("Returned Qty"), "fieldname": "returned_qty", "fieldtype": "Float", "width": 110},
] ]

View File

@ -4,6 +4,13 @@
frappe.query_reports["Subcontracted Item To Be Received"] = { frappe.query_reports["Subcontracted Item To Be Received"] = {
"filters": [ "filters": [
{
label: __("Order Type"),
fieldname: "order_type",
fieldtype: "Select",
options: ["Purchase Order", "Subcontracting Order"],
default: "Subcontracting Order"
},
{ {
fieldname: "supplier", fieldname: "supplier",
label: __("Supplier"), label: __("Supplier"),

View File

@ -13,7 +13,7 @@
"name": "Subcontracted Item To Be Received", "name": "Subcontracted Item To Be Received",
"owner": "Administrator", "owner": "Administrator",
"prepared_report": 0, "prepared_report": 0,
"ref_doctype": "Purchase Order", "ref_doctype": "Subcontracting Order",
"report_name": "Subcontracted Item To Be Received", "report_name": "Subcontracted Item To Be Received",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": [

View File

@ -11,18 +11,18 @@ def execute(filters=None):
frappe.msgprint(_("To Date must be greater than From Date")) frappe.msgprint(_("To Date must be greater than From Date"))
data = [] data = []
columns = get_columns() columns = get_columns(filters)
get_data(data, filters) get_data(data, filters)
return columns, data return columns, data
def get_columns(): def get_columns(filters):
return [ return [
{ {
"label": _("Subcontracting Order"), "label": _("Subcontract Order"),
"fieldtype": "Link", "fieldtype": "Link",
"fieldname": "subcontracting_order", "fieldname": "subcontract_order",
"options": "Subcontracting Order", "options": filters.order_type,
"width": 150, "width": 150,
}, },
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150}, {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150},
@ -57,14 +57,14 @@ def get_columns():
def get_data(data, filters): def get_data(data, filters):
sco = get_sco(filters) orders = get_subcontract_orders(filters)
sco_name = [v.name for v in sco] orders_name = [order.name for order in orders]
sub_items = get_subcontracting_order_item_supplied(sco_name) subcontracted_items = get_subcontract_order_supplied_item(filters.order_type, orders_name)
for item in sub_items: for item in subcontracted_items:
for order in sco: for order in orders:
if order.name == item.parent and item.received_qty < item.qty: if order.name == item.parent and item.received_qty < item.qty:
row = { row = {
"subcontracting_order": item.parent, "subcontract_order": item.parent,
"date": order.transaction_date, "date": order.transaction_date,
"supplier": order.supplier, "supplier": order.supplier,
"fg_item_code": item.item_code, "fg_item_code": item.item_code,
@ -76,21 +76,25 @@ def get_data(data, filters):
data.append(row) data.append(row)
def get_sco(filters): def get_subcontract_orders(filters):
record_filters = [ record_filters = [
["supplier", "=", filters.supplier], ["supplier", "=", filters.supplier],
["transaction_date", "<=", filters.to_date], ["transaction_date", "<=", filters.to_date],
["transaction_date", ">=", filters.from_date], ["transaction_date", ">=", filters.from_date],
["docstatus", "=", 1], ["docstatus", "=", 1],
] ]
if filters.order_type == "Purchase Order":
record_filters.append(["is_old_subcontracting_flow", "=", 1])
return frappe.get_all( return frappe.get_all(
"Subcontracting Order", filters=record_filters, fields=["name", "transaction_date", "supplier"] filters.order_type, filters=record_filters, fields=["name", "transaction_date", "supplier"]
) )
def get_subcontracting_order_item_supplied(sco): def get_subcontract_order_supplied_item(order_type, orders):
return frappe.get_all( return frappe.get_all(
"Subcontracting Order Item", f"{order_type} Item",
filters=[("parent", "IN", sco)], filters=[("parent", "IN", orders)],
fields=["parent", "item_code", "item_name", "qty", "received_qty"], fields=["parent", "item_code", "item_name", "qty", "received_qty"],
) )

View File

@ -50,6 +50,7 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
col, data = execute( col, data = execute(
filters=frappe._dict( filters=frappe._dict(
{ {
"order_type": "Subcontracting Order",
"supplier": sco.supplier, "supplier": sco.supplier,
"from_date": frappe.utils.get_datetime( "from_date": frappe.utils.get_datetime(
frappe.utils.add_to_date(sco.transaction_date, days=-10) frappe.utils.add_to_date(sco.transaction_date, days=-10)
@ -60,7 +61,7 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
) )
self.assertEqual(data[0]["pending_qty"], 5) self.assertEqual(data[0]["pending_qty"], 5)
self.assertEqual(data[0]["received_qty"], 5) self.assertEqual(data[0]["received_qty"], 5)
self.assertEqual(data[0]["subcontracting_order"], sco.name) self.assertEqual(data[0]["subcontract_order"], sco.name)
self.assertEqual(data[0]["supplier"], sco.supplier) self.assertEqual(data[0]["supplier"], sco.supplier)

View File

@ -4,6 +4,13 @@
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = { frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
"filters": [ "filters": [
{
label: __("Order Type"),
fieldname: "order_type",
fieldtype: "Select",
options: ["Purchase Order", "Subcontracting Order"],
default: "Subcontracting Order"
},
{ {
fieldname: "supplier", fieldname: "supplier",
label: __("Supplier"), label: __("Supplier"),

View File

@ -13,7 +13,7 @@
"name": "Subcontracted Raw Materials To Be Transferred", "name": "Subcontracted Raw Materials To Be Transferred",
"owner": "Administrator", "owner": "Administrator",
"prepared_report": 0, "prepared_report": 0,
"ref_doctype": "Purchase Order", "ref_doctype": "Subcontracting Order",
"report_name": "Subcontracted Raw Materials To Be Transferred", "report_name": "Subcontracted Raw Materials To Be Transferred",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": [

View File

@ -10,19 +10,19 @@ def execute(filters=None):
if filters.from_date >= filters.to_date: if filters.from_date >= filters.to_date:
frappe.msgprint(_("To Date must be greater than From Date")) frappe.msgprint(_("To Date must be greater than From Date"))
columns = get_columns() columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
return columns, data or [] return columns, data or []
def get_columns(): def get_columns(filters):
return [ return [
{ {
"label": _("Purchase Order"), "label": _("Subcontract Order"),
"fieldtype": "Link", "fieldtype": "Link",
"fieldname": "purchase_order", "fieldname": "subcontract_order",
"options": "Purchase Order", "options": filters.order_type,
"width": 200, "width": 200,
}, },
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150}, {"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150},
@ -46,10 +46,10 @@ def get_columns():
def get_data(filters): def get_data(filters):
sco_rm_item_details = get_sco_items_to_supply(filters) order_rm_item_details = get_order_items_to_supply(filters)
data = [] data = []
for row in sco_rm_item_details: for row in order_rm_item_details:
transferred_qty = row.get("transferred_qty") or 0 transferred_qty = row.get("transferred_qty") or 0
if transferred_qty < row.get("reqd_qty", 0): if transferred_qty < row.get("reqd_qty", 0):
pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty) pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty)
@ -59,22 +59,33 @@ def get_data(filters):
return data return data
def get_sco_items_to_supply(filters): def get_order_items_to_supply(filters):
supplied_items_table = (
"Purchase Order Item Supplied"
if filters.order_type == "Purchase Order"
else "Subcontracting Order Supplied Item"
)
record_filters = [
[filters.order_type, "per_received", "<", "100"],
[filters.order_type, "supplier", "=", filters.supplier],
[filters.order_type, "transaction_date", "<=", filters.to_date],
[filters.order_type, "transaction_date", ">=", filters.from_date],
[filters.order_type, "docstatus", "=", 1],
]
if filters.order_type == "Purchase Order":
record_filters.append([filters.order_type, "is_old_subcontracting_flow", "=", 1])
return frappe.db.get_all( return frappe.db.get_all(
"Subcontracting Order", filters.order_type,
fields=[ fields=[
"name as subcontracting_order", "name as subcontract_order",
"transaction_date as date", "transaction_date as date",
"supplier as supplier", "supplier as supplier",
"`tabSubcontracting Order Supplied Item`.rm_item_code as rm_item_code", f"`tab{supplied_items_table}`.rm_item_code as rm_item_code",
"`tabSubcontracting Order Supplied Item`.required_qty as reqd_qty", f"`tab{supplied_items_table}`.required_qty as reqd_qty",
"`tabSubcontracting Order Supplied Item`.supplied_qty as transferred_qty", f"`tab{supplied_items_table}`.supplied_qty as transferred_qty",
],
filters=[
["Subcontracting Order", "per_received", "<", "100"],
["Subcontracting Order", "supplier", "=", filters.supplier],
["Subcontracting Order", "transaction_date", "<=", filters.to_date],
["Subcontracting Order", "transaction_date", ">=", filters.from_date],
["Subcontracting Order", "docstatus", "=", 1],
], ],
filters=record_filters,
) )

View File

@ -9,14 +9,12 @@ from frappe.tests.utils import FrappeTestCase
from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import ( from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcontracted_raw_materials_to_be_transferred import (
execute, execute,
) )
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.controllers.tests.test_subcontracting_controller import (
get_subcontracting_order, get_subcontracting_order,
make_service_item, make_service_item,
) )
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_rm_stock_entry,
)
class TestSubcontractedItemToBeTransferred(FrappeTestCase): class TestSubcontractedItemToBeTransferred(FrappeTestCase):
@ -45,6 +43,7 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase):
col, data = execute( col, data = execute(
filters=frappe._dict( filters=frappe._dict(
{ {
"order_type": "Subcontracting Order",
"supplier": sco.supplier, "supplier": sco.supplier,
"from_date": frappe.utils.get_datetime( "from_date": frappe.utils.get_datetime(
frappe.utils.add_to_date(sco.transaction_date, days=-10) frappe.utils.add_to_date(sco.transaction_date, days=-10)
@ -55,12 +54,12 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase):
) )
sco.reload() sco.reload()
sco_data = [row for row in data if row.get("subcontracting_order") == sco.name] sco_data = [row for row in data if row.get("subcontract_order") == sco.name]
# Alphabetically sort to be certain of order # Alphabetically sort to be certain of order
sco_data = sorted(sco_data, key=lambda i: i["rm_item_code"]) sco_data = sorted(sco_data, key=lambda i: i["rm_item_code"])
self.assertEqual(len(sco_data), 2) self.assertEqual(len(sco_data), 2)
self.assertEqual(sco_data[0]["subcontracting_order"], sco.name) self.assertEqual(sco_data[0]["subcontract_order"], sco.name)
self.assertEqual(sco_data[0]["rm_item_code"], "_Test Item") self.assertEqual(sco_data[0]["rm_item_code"], "_Test Item")
self.assertEqual(sco_data[0]["p_qty"], 8) self.assertEqual(sco_data[0]["p_qty"], 8)

View File

@ -2657,6 +2657,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
parent.update_ordered_qty() parent.update_ordered_qty()
parent.update_ordered_and_reserved_qty() parent.update_ordered_and_reserved_qty()
parent.update_receiving_percentage() parent.update_receiving_percentage()
if parent.is_old_subcontracting_flow:
parent.update_reserved_qty_for_subcontract()
parent.create_raw_materials_supplied()
parent.save()
else: # Sales Order else: # Sales Order
parent.validate_warehouse() parent.validate_warehouse()
parent.update_reserved_qty() parent.update_reserved_qty()

View File

@ -11,7 +11,7 @@ from erpnext.accounts.doctype.budget.budget import validate_expense_against_budg
from erpnext.accounts.party import get_party_details from erpnext.accounts.party import get_party_details
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.subcontracting_controller import SubcontractingController
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.utils import get_incoming_rate
@ -20,7 +20,10 @@ class QtyMismatchError(ValidationError):
pass pass
class BuyingController(StockController): class BuyingController(SubcontractingController):
def __setup__(self):
self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"]
def get_feed(self): def get_feed(self):
if self.get("supplier_name"): if self.get("supplier_name"):
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
@ -51,6 +54,8 @@ class BuyingController(StockController):
# sub-contracting # sub-contracting
self.validate_for_subcontracting() self.validate_for_subcontracting()
if self.get("is_old_subcontracting_flow"):
self.create_raw_materials_supplied()
self.set_landed_cost_voucher_amount() self.set_landed_cost_voucher_amount()
if self.doctype in ("Purchase Receipt", "Purchase Invoice"): if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
@ -251,9 +256,18 @@ class BuyingController(StockController):
) )
qty_in_stock_uom = flt(item.qty * item.conversion_factor) qty_in_stock_uom = flt(item.qty * item.conversion_factor)
item.valuation_rate = ( if self.get("is_old_subcontracting_flow"):
item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount) item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
) / qty_in_stock_uom item.valuation_rate = (
item.base_net_amount
+ item.item_tax_amount
+ item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)
) / qty_in_stock_uom
else:
item.valuation_rate = (
item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
) / qty_in_stock_uom
else: else:
item.valuation_rate = 0.0 item.valuation_rate = 0.0
@ -312,6 +326,19 @@ class BuyingController(StockController):
if self.is_subcontracted: if self.is_subcontracted:
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse: if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype)) frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
if self.get("is_old_subcontracting_flow"):
for item in self.get("items"):
if item in self.sub_contracted_items and not item.bom:
frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
if self.doctype != "Purchase Order":
return
for row in self.get("supplied_items"):
if not row.reserve_warehouse:
msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
frappe.throw(_(msg))
else: else:
for item in self.get("items"): for item in self.get("items"):
if item.get("bom"): if item.get("bom"):
@ -440,7 +467,9 @@ class BuyingController(StockController):
sle.update( sle.update(
{ {
"incoming_rate": incoming_rate, "incoming_rate": incoming_rate,
"recalculate_rate": 1 if (self.is_subcontracted and d.fg_item) or d.from_warehouse else 0, "recalculate_rate": 1
if (self.is_subcontracted and (d.bom or d.fg_item)) or d.from_warehouse
else 0,
} }
) )
sl_entries.append(sle) sl_entries.append(sle)
@ -468,6 +497,8 @@ class BuyingController(StockController):
) )
) )
if self.get("is_old_subcontracting_flow"):
self.make_sl_entries_for_supplier_warehouse(sl_entries)
self.make_sl_entries( self.make_sl_entries(
sl_entries, sl_entries,
allow_negative_stock=allow_negative_stock, allow_negative_stock=allow_negative_stock,
@ -494,6 +525,8 @@ class BuyingController(StockController):
) )
po_obj.update_ordered_qty(po_item_rows) po_obj.update_ordered_qty(po_item_rows)
if self.get("is_old_subcontracting_flow"):
po_obj.update_reserved_qty_for_subcontract()
def on_submit(self): def on_submit(self):
if self.get("is_return"): if self.get("is_return"):
@ -718,7 +751,10 @@ class BuyingController(StockController):
if self.doctype == "Material Request": if self.doctype == "Material Request":
return return
validate_item_type(self, "is_purchase_item", "purchase") if self.get("is_old_subcontracting_flow"):
validate_item_type(self, "is_sub_contracted_item", "subcontracted")
else:
validate_item_type(self, "is_purchase_item", "purchase")
def get_asset_item_details(asset_items): def get_asset_item_details(asset_items):

View File

@ -2,6 +2,7 @@
# For license information, please see license.txt # For license information, please see license.txt
import copy import copy
import json
from collections import defaultdict from collections import defaultdict
import frappe import frappe
@ -14,13 +15,40 @@ from erpnext.stock.utils import get_incoming_rate
class SubcontractingController(StockController): class SubcontractingController(StockController):
def __init__(self, *args, **kwargs):
super(SubcontractingController, self).__init__(*args, **kwargs)
if self.get("is_old_subcontracting_flow"):
self.subcontract_data = frappe._dict(
{
"order_doctype": "Purchase Order",
"order_field": "purchase_order",
"rm_detail_field": "po_detail",
"receipt_supplied_items_field": "Purchase Receipt Item Supplied",
"order_supplied_items_field": "Purchase Order Item Supplied",
}
)
else:
self.subcontract_data = frappe._dict(
{
"order_doctype": "Subcontracting Order",
"order_field": "subcontracting_order",
"rm_detail_field": "sco_rm_detail",
"receipt_supplied_items_field": "Subcontracting Receipt Supplied Item",
"order_supplied_items_field": "Subcontracting Order Supplied Item",
}
)
def before_validate(self): def before_validate(self):
self.remove_empty_rows() if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
self.set_items_conversion_factor() self.remove_empty_rows()
self.set_items_conversion_factor()
def validate(self): def validate(self):
self.validate_items() if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
self.create_raw_materials_supplied() self.validate_items()
self.create_raw_materials_supplied()
else:
super(SubcontractingController, self).validate()
def remove_empty_rows(self): def remove_empty_rows(self):
for key in ["service_items", "items", "supplied_items"]: for key in ["service_items", "items", "supplied_items"]:
@ -54,7 +82,10 @@ class SubcontractingController(StockController):
def __get_data_before_save(self): def __get_data_before_save(self):
item_dict = {} item_dict = {}
if self.doctype == "Subcontracting Receipt" and self._doc_before_save: if (
self.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]
and self._doc_before_save
):
for row in self._doc_before_save.get("items"): for row in self._doc_before_save.get("items"):
item_dict[row.name] = (row.item_code, row.qty) item_dict[row.name] = (row.item_code, row.qty)
@ -64,7 +95,7 @@ class SubcontractingController(StockController):
self.__changed_name = [] self.__changed_name = []
self.__reference_name = [] self.__reference_name = []
if self.doctype == "Subcontracting Order" or self.is_new(): if self.doctype in ["Purchase Order", "Subcontracting Order"] or self.is_new():
self.set(self.raw_material_table, []) self.set(self.raw_material_table, [])
return return
@ -93,36 +124,38 @@ class SubcontractingController(StockController):
self.alternative_item_details = frappe._dict() self.alternative_item_details = frappe._dict()
self.__get_backflush_based_on() self.__get_backflush_based_on()
def __get_subcontracting_orders(self): def __get_subcontract_orders(self):
self.subcontracting_orders = [] self.subcontract_orders = []
if self.doctype == "Subcontracting Order": if self.doctype in ["Purchase Order", "Subcontracting Order"]:
return return
self.subcontracting_orders = [ self.subcontract_orders = [
item.subcontracting_order for item in self.items if item.subcontracting_order item.get(self.subcontract_data.order_field)
for item in self.items
if item.get(self.subcontract_data.order_field)
] ]
def __get_pending_qty_to_receive(self): def __get_pending_qty_to_receive(self):
"""Get qty to be received against the subcontracting order.""" """Get qty to be received against the subcontract order."""
self.qty_to_be_received = defaultdict(float) self.qty_to_be_received = defaultdict(float)
if ( if (
self.doctype != "Subcontracting Order" self.doctype != self.subcontract_data.order_doctype
and self.backflush_based_on != "BOM" and self.backflush_based_on != "BOM"
and self.subcontracting_orders and self.subcontract_orders
): ):
for row in frappe.get_all( for row in frappe.get_all(
"Subcontracting Order Item", f"{self.subcontract_data.order_doctype} Item",
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"], fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
filters={"docstatus": 1, "parent": ("in", self.subcontracting_orders)}, filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
): ):
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
def __get_transferred_items(self): def __get_transferred_items(self):
fields = ["`tabStock Entry`.`subcontracting_order`"] fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"]
alias_dict = { alias_dict = {
"item_code": "rm_item_code", "item_code": "rm_item_code",
"subcontracted_item": "main_item_code", "subcontracted_item": "main_item_code",
@ -145,7 +178,7 @@ class SubcontractingController(StockController):
"s_warehouse", "s_warehouse",
"t_warehouse", "t_warehouse",
"item_group", "item_group",
"sco_rm_detail", self.subcontract_data.rm_detail_field,
] ]
if self.backflush_based_on == "BOM": if self.backflush_based_on == "BOM":
@ -157,7 +190,7 @@ class SubcontractingController(StockController):
filters = [ filters = [
["Stock Entry", "docstatus", "=", 1], ["Stock Entry", "docstatus", "=", 1],
["Stock Entry", "purpose", "=", "Send to Subcontractor"], ["Stock Entry", "purpose", "=", "Send to Subcontractor"],
["Stock Entry", "subcontracting_order", "in", self.subcontracting_orders], ["Stock Entry", self.subcontract_data.order_field, "in", self.subcontract_orders],
] ]
return frappe.get_all("Stock Entry", fields=fields, filters=filters) return frappe.get_all("Stock Entry", fields=fields, filters=filters)
@ -168,21 +201,21 @@ class SubcontractingController(StockController):
def __get_received_items(self, doctype): def __get_received_items(self, doctype):
fields = [] fields = []
self.sco_field = "subcontracting_order" for field in ["name", self.subcontract_data.order_field, "parent"]:
for field in ["name", self.sco_field, "parent"]:
fields.append(f"`tab{doctype} Item`.`{field}`") fields.append(f"`tab{doctype} Item`.`{field}`")
filters = [ filters = [
[doctype, "docstatus", "=", 1], [doctype, "docstatus", "=", 1],
[f"{doctype} Item", self.sco_field, "in", self.subcontracting_orders], [f"{doctype} Item", self.subcontract_data.order_field, "in", self.subcontract_orders],
] ]
if doctype == "Purchase Invoice":
filters.append(["Purchase Invoice", "update_stock", "=", 1])
return frappe.get_all(f"{doctype}", fields=fields, filters=filters) return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
def __get_consumed_items(self, doctype, scr_items): def __get_consumed_items(self, doctype, receipt_items):
return frappe.get_all( return frappe.get_all(
"Subcontracting Receipt Supplied Item", self.subcontract_data.receipt_supplied_items_field,
fields=[ fields=[
"serial_no", "serial_no",
"rm_item_code", "rm_item_code",
@ -191,26 +224,26 @@ class SubcontractingController(StockController):
"consumed_qty", "consumed_qty",
"main_item_code", "main_item_code",
], ],
filters={"docstatus": 1, "reference_name": ("in", list(scr_items)), "parenttype": doctype}, filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
) )
def __update_consumed_materials(self, doctype, return_consumed_items=False): def __update_consumed_materials(self, doctype, return_consumed_items=False):
"""Deduct the consumed materials from the available materials.""" """Deduct the consumed materials from the available materials."""
scr_items = self.__get_received_items(doctype) receipt_items = self.__get_received_items(doctype)
if not scr_items: if not receipt_items:
return ([], {}) if return_consumed_items else None return ([], {}) if return_consumed_items else None
scr_items = { receipt_items = {
item.name: item.get(self.get("sco_field") or "subcontracting_order") for item in scr_items item.name: item.get(self.subcontract_data.order_field) for item in receipt_items
} }
consumed_materials = self.__get_consumed_items(doctype, scr_items.keys()) consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys())
if return_consumed_items: if return_consumed_items:
return (consumed_materials, scr_items) return (consumed_materials, receipt_items)
for row in consumed_materials: for row in consumed_materials:
key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name)) key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
if not self.available_materials.get(key): if not self.available_materials.get(key):
continue continue
@ -226,16 +259,16 @@ class SubcontractingController(StockController):
def get_available_materials(self): def get_available_materials(self):
"""Get the available raw materials which has been transferred to the supplier. """Get the available raw materials which has been transferred to the supplier.
available_materials = { available_materials = {
(item_code, subcontracted_item, subcontracting_order): { (item_code, subcontracted_item, subcontract_order): {
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details 'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
} }
} }
""" """
if not self.subcontracting_orders: if not self.subcontract_orders:
return return
for row in self.__get_transferred_items(): for row in self.__get_transferred_items():
key = (row.rm_item_code, row.main_item_code, row.subcontracting_order) key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
if key not in self.available_materials: if key not in self.available_materials:
self.available_materials.setdefault( self.available_materials.setdefault(
@ -246,14 +279,16 @@ class SubcontractingController(StockController):
"serial_no": [], "serial_no": [],
"batch_no": defaultdict(float), "batch_no": defaultdict(float),
"item_details": row, "item_details": row,
"sco_rm_details": [], f"{self.subcontract_data.rm_detail_field}s": [],
} }
), ),
) )
details = self.available_materials[key] details = self.available_materials[key]
details.qty += row.qty details.qty += row.qty
details.sco_rm_details.append(row.sco_rm_detail) details[f"{self.subcontract_data.rm_detail_field}s"].append(
row.get(self.subcontract_data.rm_detail_field)
)
if row.serial_no: if row.serial_no:
details.serial_no.extend(get_serial_nos(row.serial_no)) details.serial_no.extend(get_serial_nos(row.serial_no))
@ -264,7 +299,11 @@ class SubcontractingController(StockController):
self.__set_alternative_item_details(row) self.__set_alternative_item_details(row)
self.__transferred_items = copy.deepcopy(self.available_materials) self.__transferred_items = copy.deepcopy(self.available_materials)
self.__update_consumed_materials("Subcontracting Receipt") if self.get("is_old_subcontracting_flow"):
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
self.__update_consumed_materials(doctype)
else:
self.__update_consumed_materials("Subcontracting Receipt")
def __remove_changed_rows(self): def __remove_changed_rows(self):
if not self.__changed_name: if not self.__changed_name:
@ -317,7 +356,7 @@ class SubcontractingController(StockController):
) )
def __update_reserve_warehouse(self, row, item): def __update_reserve_warehouse(self, row, item):
if self.doctype == "Subcontracting Order": if self.doctype == self.subcontract_data.order_doctype:
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
def __set_alternative_item(self, bom_item): def __set_alternative_item(self, bom_item):
@ -325,7 +364,7 @@ class SubcontractingController(StockController):
bom_item.update(self.alternative_item_details[bom_item.rm_item_code]) bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
def __set_serial_nos(self, item_row, rm_obj): def __set_serial_nos(self, item_row, rm_obj):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order) key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]: if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)] used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
rm_obj.serial_no = "\n".join(used_serial_nos) rm_obj.serial_no = "\n".join(used_serial_nos)
@ -340,7 +379,7 @@ class SubcontractingController(StockController):
"consumed_qty": qty, "consumed_qty": qty,
"batch_no": batch_no, "batch_no": batch_no,
"required_qty": qty, "required_qty": qty,
"subcontracting_order": item_row.subcontracting_order, self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field),
} }
) )
@ -351,7 +390,7 @@ class SubcontractingController(StockController):
rm_obj.consumed_qty = consumed_qty rm_obj.consumed_qty = consumed_qty
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty): def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
key = (rm_obj.rm_item_code, item_row.item_code, item_row.subcontracting_order) key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]: if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
new_rm_obj = None new_rm_obj = None
@ -397,16 +436,18 @@ class SubcontractingController(StockController):
) )
rm_obj.rate = get_incoming_rate(args) rm_obj.rate = get_incoming_rate(args)
if self.doctype == "Subcontracting Order": if self.doctype == self.subcontract_data.order_doctype:
rm_obj.required_qty = qty rm_obj.required_qty = qty
rm_obj.amount = rm_obj.required_qty * rm_obj.rate rm_obj.amount = rm_obj.required_qty * rm_obj.rate
else: else:
rm_obj.consumed_qty = 0 rm_obj.consumed_qty = 0
rm_obj.subcontracting_order = item_row.subcontracting_order setattr(
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
)
self.__set_batch_nos(bom_item, item_row, rm_obj, qty) self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
def __get_qty_based_on_material_transfer(self, item_row, transfer_item): def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.subcontracting_order) key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
if self.qty_to_be_received == item_row.qty: if self.qty_to_be_received == item_row.qty:
return transfer_item.qty return transfer_item.qty
@ -427,13 +468,13 @@ class SubcontractingController(StockController):
has_supplied_items = True if self.get(self.raw_material_table) else False has_supplied_items = True if self.get(self.raw_material_table) else False
for row in self.items: for row in self.items:
if self.doctype != "Subcontracting Order" and ( if self.doctype != self.subcontract_data.order_doctype and (
(self.__changed_name and row.name not in self.__changed_name) (self.__changed_name and row.name not in self.__changed_name)
or (has_supplied_items and not self.__changed_name) or (has_supplied_items and not self.__changed_name)
): ):
continue continue
if self.doctype == "Subcontracting Order" or self.backflush_based_on == "BOM": if self.doctype == self.subcontract_data.order_doctype or self.backflush_based_on == "BOM":
for bom_item in self.__get_materials_from_bom( for bom_item in self.__get_materials_from_bom(
row.item_code, row.bom, row.get("include_exploded_items") row.item_code, row.bom, row.get("include_exploded_items")
): ):
@ -445,17 +486,22 @@ class SubcontractingController(StockController):
elif self.backflush_based_on != "BOM": elif self.backflush_based_on != "BOM":
for key, transfer_item in self.available_materials.items(): for key, transfer_item in self.available_materials.items():
if (key[1], key[2]) == (row.item_code, row.subcontracting_order) and transfer_item.qty > 0: if (key[1], key[2]) == (
row.item_code,
row.get(self.subcontract_data.order_field),
) and transfer_item.qty > 0:
qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0 qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
transfer_item.qty -= qty transfer_item.qty -= qty
self.__add_supplied_item(row, transfer_item.get("item_details"), qty) self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
if self.qty_to_be_received: if self.qty_to_be_received:
self.qty_to_be_received[(row.item_code, row.subcontracting_order)] -= row.qty self.qty_to_be_received[
(row.item_code, row.get(self.subcontract_data.order_field))
] -= row.qty
def __prepare_supplied_items(self): def __prepare_supplied_items(self):
self.initialized_fields() self.initialized_fields()
self.__get_subcontracting_orders() self.__get_subcontract_orders()
self.__get_pending_qty_to_receive() self.__get_pending_qty_to_receive()
self.get_available_materials() self.get_available_materials()
self.__remove_changed_rows() self.__remove_changed_rows()
@ -465,8 +511,10 @@ class SubcontractingController(StockController):
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get( if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
"batch_no" "batch_no"
): ):
link = get_link_to_form("Subcontracting Order", row.subcontracting_order) link = get_link_to_form(
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Subcontracting Order {link}' self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
)
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the {self.subcontract_data.order_doctype} {link}'
frappe.throw(_(msg), title=_("Incorrect Batch Consumed")) frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
def __validate_serial_no(self, row, key): def __validate_serial_no(self, row, key):
@ -476,16 +524,18 @@ class SubcontractingController(StockController):
if incorrect_sn: if incorrect_sn:
incorrect_sn = "\n".join(incorrect_sn) incorrect_sn = "\n".join(incorrect_sn)
link = get_link_to_form("Subcontracting Order", row.subcontracting_order) link = get_link_to_form(
msg = f"The Serial Nos {incorrect_sn} has not supplied against the Subcontracting Order {link}" self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
)
msg = f"The Serial Nos {incorrect_sn} has not supplied against the {self.subcontract_data.order_doctype} {link}"
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed")) frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
def __validate_supplied_items(self): def __validate_supplied_items(self):
if self.doctype != "Subcontracting Receipt": if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
return return
for row in self.get(self.raw_material_table): for row in self.get(self.raw_material_table):
key = (row.rm_item_code, row.main_item_code, row.subcontracting_order) key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
if not self.__transferred_items or not self.__transferred_items.get(key): if not self.__transferred_items or not self.__transferred_items.get(key):
return return
@ -493,6 +543,9 @@ class SubcontractingController(StockController):
self.__validate_serial_no(row, key) self.__validate_serial_no(row, key)
def set_materials_for_subcontracted_items(self, raw_material_table): def set_materials_for_subcontracted_items(self, raw_material_table):
if self.doctype == "Purchase Invoice" and not self.update_stock:
return
self.raw_material_table = raw_material_table self.raw_material_table = raw_material_table
self.__identify_change_in_item_table() self.__identify_change_in_item_table()
self.__prepare_supplied_items() self.__prepare_supplied_items()
@ -501,16 +554,16 @@ class SubcontractingController(StockController):
def create_raw_materials_supplied(self, raw_material_table="supplied_items"): def create_raw_materials_supplied(self, raw_material_table="supplied_items"):
self.set_materials_for_subcontracted_items(raw_material_table) self.set_materials_for_subcontracted_items(raw_material_table)
if self.doctype == "Subcontracting Receipt": if self.doctype in ["Subcontracting Receipt", "Purchase Receipt", "Purchase Invoice"]:
for item in self.get("items"): for item in self.get("items"):
item.rm_supp_cost = 0.0 item.rm_supp_cost = 0.0
def __update_consumed_qty_in_sco(self, itemwise_consumed_qty): def __update_consumed_qty_in_subcontract_order(self, itemwise_consumed_qty):
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"] fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
filters = {"docstatus": 1, "parent": ("in", self.subcontracting_orders)} filters = {"docstatus": 1, "parent": ("in", self.subcontract_orders)}
for row in frappe.get_all( for row in frappe.get_all(
"Subcontracting Order Supplied Item", fields=fields, filters=filters, order_by="idx" self.subcontract_data.order_supplied_items_field, fields=fields, filters=filters, order_by="idx"
): ):
key = (row.rm_item_code, row.main_item_code, row.parent) key = (row.rm_item_code, row.main_item_code, row.parent)
consumed_qty = itemwise_consumed_qty.get(key, 0) consumed_qty = itemwise_consumed_qty.get(key, 0)
@ -520,22 +573,31 @@ class SubcontractingController(StockController):
itemwise_consumed_qty[key] -= consumed_qty itemwise_consumed_qty[key] -= consumed_qty
frappe.db.set_value( frappe.db.set_value(
"Subcontracting Order Supplied Item", row.name, "consumed_qty", consumed_qty self.subcontract_data.order_supplied_items_field, row.name, "consumed_qty", consumed_qty
) )
def set_consumed_qty_in_sco(self): def set_consumed_qty_in_subcontract_order(self):
# Update consumed qty back in the subcontracting order # Update consumed qty back in the subcontract order
self.__get_subcontracting_orders() if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
itemwise_consumed_qty = defaultdict(float) "is_old_subcontracting_flow"
consumed_items, scr_items = self.__update_consumed_materials( ):
"Subcontracting Receipt", return_consumed_items=True self.__get_subcontract_orders()
) itemwise_consumed_qty = defaultdict(float)
if self.get("is_old_subcontracting_flow"):
doctypes = ["Purchase Receipt", "Purchase Invoice"]
else:
doctypes = ["Subcontracting Receipt"]
for row in consumed_items: for doctype in doctypes:
key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name)) consumed_items, receipt_items = self.__update_consumed_materials(
itemwise_consumed_qty[key] += row.consumed_qty doctype, return_consumed_items=True
)
self.__update_consumed_qty_in_sco(itemwise_consumed_qty) for row in consumed_items:
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
itemwise_consumed_qty[key] += row.consumed_qty
self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty)
def update_ordered_and_reserved_qty(self): def update_ordered_and_reserved_qty(self):
sco_map = {} sco_map = {}
@ -618,10 +680,30 @@ class SubcontractingController(StockController):
via_landed_cost_voucher=via_landed_cost_voucher, via_landed_cost_voucher=via_landed_cost_voucher,
) )
def get_supplied_items_cost(self, item_row_id): def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
supplied_items_cost = 0.0 supplied_items_cost = 0.0
for item in self.get("supplied_items"): for item in self.get("supplied_items"):
if item.reference_name == item_row_id: if item.reference_name == item_row_id:
if (
self.get("is_old_subcontracting_flow")
and reset_outgoing_rate
and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item")
):
rate = get_incoming_rate(
{
"item_code": item.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": -1 * item.consumed_qty,
"serial_no": item.serial_no,
"batch_no": item.batch_no,
}
)
if rate > 0:
item.rate = rate
item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount")) item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount"))
supplied_items_cost += item.amount supplied_items_cost += item.amount
@ -631,13 +713,25 @@ class SubcontractingController(StockController):
if self.doctype == "Subcontracting Order": if self.doctype == "Subcontracting Order":
self.update_status() self.update_status()
elif self.doctype == "Subcontracting Receipt": elif self.doctype == "Subcontracting Receipt":
self.__get_subcontracting_orders self.__get_subcontract_orders
if self.subcontracting_orders: if self.subcontract_orders:
for sco in set(self.subcontracting_orders): for sco in set(self.subcontract_orders):
sco_doc = frappe.get_doc("Subcontracting Order", sco) sco_doc = frappe.get_doc("Subcontracting Order", sco)
sco_doc.update_status() sco_doc.update_status()
@frappe.whitelist()
def get_current_stock(self):
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
for item in self.get("supplied_items"):
if self.supplier_warehouse:
actual_qty = frappe.db.get_value(
"Bin",
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
"actual_qty",
)
item.current_stock = flt(actual_qty) or 0
@property @property
def sub_contracted_items(self): def sub_contracted_items(self):
if not hasattr(self, "_sub_contracted_items"): if not hasattr(self, "_sub_contracted_items"):
@ -650,3 +744,159 @@ class SubcontractingController(StockController):
self._sub_contracted_items = [item.name for item in items] self._sub_contracted_items = [item.name for item in items]
return self._sub_contracted_items return self._sub_contracted_items
def get_item_details(items):
item = frappe.qb.DocType("Item")
item_list = (
frappe.qb.from_(item)
.select(item.item_code, item.description, item.allow_alternative_item)
.where(item.name.isin(items))
.run(as_dict=True)
)
item_details = {}
for item in item_list:
item_details[item.item_code] = item
return item_details
@frappe.whitelist()
def make_rm_stock_entry(subcontract_order, rm_items, order_doctype="Subcontracting Order"):
rm_items_list = rm_items
if isinstance(rm_items, str):
rm_items_list = json.loads(rm_items)
elif not rm_items:
frappe.throw(_("No Items available for transfer"))
if rm_items_list:
fg_items = list(set(item["item_code"] for item in rm_items_list))
else:
frappe.throw(_("No Items selected for transfer"))
if subcontract_order:
subcontract_order = frappe.get_doc(order_doctype, subcontract_order)
if fg_items:
items = tuple(set(item["rm_item_code"] for item in rm_items_list))
item_wh = get_item_details(items)
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.purpose = "Send to Subcontractor"
if order_doctype == "Purchase Order":
stock_entry.purchase_order = subcontract_order.name
else:
stock_entry.subcontracting_order = subcontract_order.name
stock_entry.supplier = subcontract_order.supplier
stock_entry.supplier_name = subcontract_order.supplier_name
stock_entry.supplier_address = subcontract_order.supplier_address
stock_entry.address_display = subcontract_order.address_display
stock_entry.company = subcontract_order.company
stock_entry.to_warehouse = subcontract_order.supplier_warehouse
stock_entry.set_stock_entry_type()
if order_doctype == "Purchase Order":
rm_detail_field = "po_detail"
else:
rm_detail_field = "sco_rm_detail"
for item_code in fg_items:
for rm_item_data in rm_items_list:
if rm_item_data["item_code"] == item_code:
rm_item_code = rm_item_data["rm_item_code"]
items_dict = {
rm_item_code: {
rm_detail_field: rm_item_data.get("name"),
"item_name": rm_item_data["item_name"],
"description": item_wh.get(rm_item_code, {}).get("description", ""),
"qty": rm_item_data["qty"],
"from_warehouse": rm_item_data["warehouse"],
"stock_uom": rm_item_data["stock_uom"],
"serial_no": rm_item_data.get("serial_no"),
"batch_no": rm_item_data.get("batch_no"),
"main_item_code": rm_item_data["item_code"],
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
return stock_entry.as_dict()
else:
frappe.throw(_("No Items selected for transfer"))
return subcontract_order.name
def add_items_in_ste(
ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_detail", batch_no=None
):
item = ste_doc.append("items", row.item_details)
rm_detail = list(set(row.get(f"{rm_detail_field}s")).intersection(rm_details))
item.update(
{
"qty": qty,
"batch_no": batch_no,
"basic_rate": row.item_details["rate"],
rm_detail_field: rm_detail[0] if rm_detail else "",
"s_warehouse": row.item_details["t_warehouse"],
"t_warehouse": row.item_details["s_warehouse"],
"item_code": row.item_details["rm_item_code"],
"subcontracted_item": row.item_details["main_item_code"],
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
}
)
def make_return_stock_entry_for_subcontract(
available_materials, order_doc, rm_details, order_doctype="Subcontracting Order"
):
ste_doc = frappe.new_doc("Stock Entry")
ste_doc.purpose = "Material Transfer"
if order_doctype == "Purchase Order":
ste_doc.purchase_order = order_doc.name
rm_detail_field = "po_detail"
else:
ste_doc.subcontracting_order = order_doc.name
rm_detail_field = "sco_rm_detail"
ste_doc.company = order_doc.company
ste_doc.is_return = 1
for key, value in available_materials.items():
if not value.qty:
continue
if value.batch_no:
for batch_no, qty in value.batch_no.items():
if qty > 0:
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no)
else:
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field)
ste_doc.set_stock_entry_type()
ste_doc.calculate_rate_and_amount()
return ste_doc
@frappe.whitelist()
def get_materials_from_supplier(
subcontract_order, rm_details, order_doctype="Subcontracting Order"
):
if isinstance(rm_details, str):
rm_details = json.loads(rm_details)
doc = frappe.get_cached_doc(order_doctype, subcontract_order)
doc.initialized_fields()
doc.subcontract_orders = [doc.name]
doc.get_available_materials()
if not doc.available_materials:
frappe.throw(
_("Materials are already received against the {0} {1}").format(order_doctype, subcontract_order)
)
return make_return_stock_entry_for_subcontract(
doc.available_materials, doc, rm_details, order_doctype
)

View File

@ -9,13 +9,15 @@ from frappe.tests.utils import FrappeTestCase
from frappe.utils import cint from frappe.utils import cint
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.controllers.subcontracting_controller import (
get_materials_from_supplier,
make_rm_stock_entry,
)
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
get_materials_from_supplier,
make_rm_stock_entry,
make_subcontracting_receipt, make_subcontracting_receipt,
) )

View File

@ -64,4 +64,8 @@ def delete_and_patch_duplicate_bins():
bin.update(qty_dict) bin.update(qty_dict)
bin.update_reserved_qty_for_production() bin.update_reserved_qty_for_production()
bin.update_reserved_qty_for_sub_contracting() bin.update_reserved_qty_for_sub_contracting()
if frappe.db.count(
"Purchase Order", {"status": ["!=", "Completed"], "is_old_subcontracting_flow": 1}
):
bin.update_reserved_qty_for_sub_contracting(subcontract_doctype="Purchase Order")
bin.db_update() bin.db_update()

View File

@ -83,9 +83,17 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
this.frm.set_query("item_code", "items", function() { this.frm.set_query("item_code", "items", function() {
if (me.frm.doc.is_subcontracted) { if (me.frm.doc.is_subcontracted) {
var filters = {'supplier': me.frm.doc.supplier};
if (me.frm.doc.is_old_subcontracting_flow) {
filters["is_sub_contracted_item"] = 1;
}
else {
filters["is_stock_item"] = 0;
}
return{ return{
query: "erpnext.controllers.queries.item_query", query: "erpnext.controllers.queries.item_query",
filters:{ 'supplier': me.frm.doc.supplier, 'is_stock_item': 0 } filters: filters
} }
} }
else { else {

View File

@ -471,7 +471,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
cost_center: item.cost_center, cost_center: item.cost_center,
tax_category: me.frm.doc.tax_category, tax_category: me.frm.doc.tax_category,
item_tax_template: item.item_tax_template, item_tax_template: item.item_tax_template,
child_docname: item.name child_docname: item.name,
is_old_subcontracting_flow: me.frm.doc.is_old_subcontracting_flow,
} }
}, },

View File

@ -486,7 +486,11 @@ erpnext.utils.update_child_items = function(opts) {
filters = {"is_sales_item": 1}; filters = {"is_sales_item": 1};
} else if (frm.doc.doctype == 'Purchase Order') { } else if (frm.doc.doctype == 'Purchase Order') {
if (frm.doc.is_subcontracted) { if (frm.doc.is_subcontracted) {
filters = {"is_sub_contracted_item": 1}; if (frm.doc.is_old_subcontracting_flow) {
filters = {"is_sub_contracted_item": 1};
} else {
filters = {"is_stock_item": 0};
}
} else { } else {
filters = {"is_purchase_item": 1}; filters = {"is_purchase_item": 1};
} }

View File

@ -40,23 +40,37 @@ class Bin(Document):
self.db_set("reserved_qty_for_production", flt(self.reserved_qty_for_production)) self.db_set("reserved_qty_for_production", flt(self.reserved_qty_for_production))
self.db_set("projected_qty", self.projected_qty) self.db_set("projected_qty", self.projected_qty)
def update_reserved_qty_for_sub_contracting(self): def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"):
# reserved qty # reserved qty
sco = frappe.qb.DocType("Subcontracting Order") subcontract_order = frappe.qb.DocType(subcontract_doctype)
supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item") supplied_item = frappe.qb.DocType(
"Purchase Order Item Supplied"
if subcontract_doctype == "Purchase Order"
else "Subcontracting Order Supplied Item"
)
conditions = (
(supplied_item.rm_item_code == self.item_code)
& (subcontract_order.name == supplied_item.parent)
& (subcontract_order.per_received < 100)
& (supplied_item.reserve_warehouse == self.warehouse)
& (
(
(subcontract_order.is_old_subcontracting_flow == 1)
& (subcontract_order.status != "Closed")
& (subcontract_order.docstatus == 1)
)
if subcontract_doctype == "Purchase Order"
else (subcontract_order.docstatus == 1)
)
)
reserved_qty_for_sub_contract = ( reserved_qty_for_sub_contract = (
frappe.qb.from_(sco) frappe.qb.from_(subcontract_order)
.from_(supplied_item) .from_(supplied_item)
.select(Sum(Coalesce(supplied_item.required_qty, 0))) .select(Sum(Coalesce(supplied_item.required_qty, 0)))
.where( .where(conditions)
(supplied_item.rm_item_code == self.item_code)
& (sco.name == supplied_item.parent)
& (sco.docstatus == 1)
& (sco.per_received < 100)
& (supplied_item.reserve_warehouse == self.warehouse)
)
).run()[0][0] or 0.0 ).run()[0][0] or 0.0
se = frappe.qb.DocType("Stock Entry") se = frappe.qb.DocType("Stock Entry")
@ -69,23 +83,34 @@ class Bin(Document):
else: else:
qty_field = se_item.transfer_qty qty_field = se_item.transfer_qty
conditions = (
(se.docstatus == 1)
& (se.purpose == "Send to Subcontractor")
& ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code))
& (se.name == se_item.parent)
& (subcontract_order.docstatus == 1)
& (subcontract_order.per_received < 100)
& (
(
(Coalesce(se.purchase_order, "") != "")
& (subcontract_order.name == se.purchase_order)
& (subcontract_order.is_old_subcontracting_flow == 1)
& (subcontract_order.status != "Closed")
)
if subcontract_doctype == "Purchase Order"
else (
(Coalesce(se.subcontracting_order, "") != "")
& (subcontract_order.name == se.subcontracting_order)
)
)
)
materials_transferred = ( materials_transferred = (
frappe.qb.from_(se) frappe.qb.from_(se)
.from_(se_item) .from_(se_item)
.from_(sco) .from_(subcontract_order)
.select( .select(Sum(qty_field))
Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)) .where(conditions)
)
.where(
(se.docstatus == 1)
& (se.purpose == "Send to Subcontractor")
& (Coalesce(se.subcontracting_order, "") != "")
& ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code))
& (se.name == se_item.parent)
& (sco.name == se.subcontracting_order)
& (sco.docstatus == 1)
& (sco.per_received < 100)
)
).run()[0][0] or 0.0 ).run()[0][0] or 0.0
if reserved_qty_for_sub_contract > materials_transferred: if reserved_qty_for_sub_contract > materials_transferred:

View File

@ -5,6 +5,7 @@ import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt from frappe.utils import flt
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.controllers.tests.test_subcontracting_controller import (
get_subcontracting_order, get_subcontracting_order,
make_service_item, make_service_item,
@ -21,7 +22,6 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
create_stock_reconciliation, create_stock_reconciliation,
) )
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_rm_stock_entry,
make_subcontracting_receipt, make_subcontracting_receipt,
) )

View File

@ -296,6 +296,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt
frappe.provide("erpnext.buying"); frappe.provide("erpnext.buying");
frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) { frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) {
if (frm.doc.is_old_subcontracting_flow) {
erpnext.buying.get_default_bom(frm);
}
frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted); frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
}); });

View File

@ -123,6 +123,7 @@ class PurchaseReceipt(BuyingController):
if getdate(self.posting_date) > getdate(nowdate()): if getdate(self.posting_date) > getdate(nowdate()):
throw(_("Posting Date cannot be future date")) throw(_("Posting Date cannot be future date"))
self.get_current_stock()
self.reset_default_field_value("set_warehouse", "items", "warehouse") self.reset_default_field_value("set_warehouse", "items", "warehouse")
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse") self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse") self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
@ -234,6 +235,7 @@ class PurchaseReceipt(BuyingController):
self.make_gl_entries() self.make_gl_entries()
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.set_consumed_qty_in_subcontract_order()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = frappe.db.sql( submit_rv = frappe.db.sql(
@ -269,6 +271,7 @@ class PurchaseReceipt(BuyingController):
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation") self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.delete_auto_created_batches() self.delete_auto_created_batches()
self.set_consumed_qty_in_subcontract_order()
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map from erpnext.accounts.general_ledger import process_gl_map

View File

@ -642,13 +642,15 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"depends_on": "eval:parent.is_old_subcontracting_flow",
"fieldname": "bom", "fieldname": "bom",
"fieldtype": "Link", "fieldtype": "Link",
"label": "BOM", "label": "BOM",
"no_copy": 1, "no_copy": 1,
"options": "BOM", "options": "BOM",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
}, },
{ {
"default": "0", "default": "0",

View File

@ -615,8 +615,15 @@ frappe.ui.form.on('Stock Entry', {
if (frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(frm, frm.doc.purpose); if (frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(frm, frm.doc.purpose);
}, },
purchase_order: (frm) => {
if (frm.doc.purchase_order) {
frm.set_value("subcontracting_order", "");
}
},
subcontracting_order: (frm) => { subcontracting_order: (frm) => {
if (frm.doc.subcontracting_order) { if (frm.doc.subcontracting_order) {
frm.set_value("purchase_order", "");
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order', method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order',
source_name: frm.doc.subcontracting_order, source_name: frm.doc.subcontracting_order,
@ -624,9 +631,6 @@ frappe.ui.form.on('Stock Entry', {
freeze: true, freeze: true,
}); });
} }
else {
frm.set_value("items", []);
}
}, },
}); });
@ -790,6 +794,16 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
return erpnext.queries.item({is_stock_item: 1}); return erpnext.queries.item({is_stock_item: 1});
}; };
this.frm.set_query("purchase_order", function() {
return {
"filters": {
"docstatus": 1,
"is_old_subcontracting_flow": 1,
"company": me.frm.doc.company
}
};
});
this.frm.set_query("subcontracting_order", function() { this.frm.set_query("subcontracting_order", function() {
return { return {
"filters": { "filters": {
@ -814,7 +828,12 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
} }
} }
this.frm.add_fetch("subcontracting_order", "supplier", "supplier"); if (me.frm.doc.purchase_order) {
this.frm.add_fetch("purchase_order", "supplier", "supplier");
}
else {
this.frm.add_fetch("subcontracting_order", "supplier", "supplier");
}
frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier' } frappe.dynamic_link = { doc: this.frm.doc, fieldname: 'supplier', doctype: 'Supplier' }
this.frm.set_query("supplier_address", erpnext.queries.address_query) this.frm.set_query("supplier_address", erpnext.queries.address_query)

View File

@ -148,11 +148,11 @@
"search_index": 1 "search_index": 1
}, },
{ {
"depends_on": "eval:doc.purpose==\"Send to Subcontractor\"",
"fieldname": "purchase_order", "fieldname": "purchase_order",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Purchase Order", "label": "Purchase Order",
"options": "Purchase Order", "options": "Purchase Order"
"read_only": 1
}, },
{ {
"depends_on": "eval:doc.purpose==\"Send to Subcontractor\"", "depends_on": "eval:doc.purpose==\"Send to Subcontractor\"",

View File

@ -62,6 +62,27 @@ form_grid_templates = {"items": "templates/form_grid/stock_entry_grid.html"}
class StockEntry(StockController): class StockEntry(StockController):
def __init__(self, *args, **kwargs):
super(StockEntry, self).__init__(*args, **kwargs)
if self.purchase_order:
self.subcontract_data = frappe._dict(
{
"order_doctype": "Purchase Order",
"order_field": "purchase_order",
"rm_detail_field": "po_detail",
"order_supplied_items_field": "Purchase Order Item Supplied",
}
)
else:
self.subcontract_data = frappe._dict(
{
"order_doctype": "Subcontracting Order",
"order_field": "subcontracting_order",
"rm_detail_field": "sco_rm_detail",
"order_supplied_items_field": "Subcontracting Order Supplied Item",
}
)
def get_feed(self): def get_feed(self):
return self.stock_entry_type return self.stock_entry_type
@ -134,8 +155,8 @@ class StockEntry(StockController):
update_serial_nos_after_submit(self, "items") update_serial_nos_after_submit(self, "items")
self.update_work_order() self.update_work_order()
self.validate_subcontracting_order() self.validate_subcontract_order()
self.update_subcontracting_order_supplied_items() self.update_subcontract_order_supplied_items()
self.update_subcontracting_order_status() self.update_subcontracting_order_status()
self.make_gl_entries() self.make_gl_entries()
@ -155,7 +176,7 @@ class StockEntry(StockController):
self.set_material_request_transfer_status("Completed") self.set_material_request_transfer_status("Completed")
def on_cancel(self): def on_cancel(self):
self.update_subcontracting_order_supplied_items() self.update_subcontract_order_supplied_items()
self.update_subcontracting_order_status() self.update_subcontracting_order_status()
if self.work_order and self.purpose == "Material Consumption for Manufacture": if self.work_order and self.purpose == "Material Consumption for Manufacture":
@ -809,8 +830,8 @@ class StockEntry(StockController):
serial_nos.append(sn) serial_nos.append(sn)
def validate_subcontracting_order(self): def validate_subcontract_order(self):
"""Throw exception if more raw material is transferred against Subcontracting Order than in """Throw exception if more raw material is transferred against Subcontract Order than in
the raw materials supplied table""" the raw materials supplied table"""
backflush_raw_materials_based_on = frappe.db.get_single_value( backflush_raw_materials_based_on = frappe.db.get_single_value(
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on" "Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
@ -818,28 +839,29 @@ class StockEntry(StockController):
qty_allowance = flt(frappe.db.get_single_value("Buying Settings", "over_transfer_allowance")) qty_allowance = flt(frappe.db.get_single_value("Buying Settings", "over_transfer_allowance"))
if not (self.purpose == "Send to Subcontractor" and self.subcontracting_order): if not (self.purpose == "Send to Subcontractor" and self.get(self.subcontract_data.order_field)):
return return
if backflush_raw_materials_based_on == "BOM": if backflush_raw_materials_based_on == "BOM":
subcontracting_order = frappe.get_doc("Subcontracting Order", self.subcontracting_order) subcontract_order = frappe.get_doc(
self.subcontract_data.order_doctype, self.get(self.subcontract_data.order_field)
)
for se_item in self.items: for se_item in self.items:
item_code = se_item.original_item or se_item.item_code item_code = se_item.original_item or se_item.item_code
precision = cint(frappe.db.get_default("float_precision")) or 3 precision = cint(frappe.db.get_default("float_precision")) or 3
required_qty = sum( required_qty = sum(
[ [flt(d.required_qty) for d in subcontract_order.supplied_items if d.rm_item_code == item_code]
flt(d.required_qty)
for d in subcontracting_order.supplied_items
if d.rm_item_code == item_code
]
) )
total_allowed = required_qty + (required_qty * (qty_allowance / 100)) total_allowed = required_qty + (required_qty * (qty_allowance / 100))
if not required_qty: if not required_qty:
bom_no = frappe.db.get_value( bom_no = frappe.db.get_value(
"Subcontracting Order Item", f"{self.subcontract_data.order_doctype} Item",
{"parent": self.subcontracting_order, "item_code": se_item.subcontracted_item}, {
"parent": self.get(self.subcontract_data.order_field),
"item_code": se_item.subcontracted_item,
},
"bom", "bom",
) )
@ -851,7 +873,7 @@ class StockEntry(StockController):
required_qty = sum( required_qty = sum(
[ [
flt(d.required_qty) flt(d.required_qty)
for d in subcontracting_order.supplied_items for d in subcontract_order.supplied_items
if d.rm_item_code == original_item_code if d.rm_item_code == original_item_code
] ]
) )
@ -860,43 +882,57 @@ class StockEntry(StockController):
if not required_qty: if not required_qty:
frappe.throw( frappe.throw(
_("Item {0} not found in 'Raw Materials Supplied' table in Subcontracting Order {1}").format( _("Item {0} not found in 'Raw Materials Supplied' table in {1} {2}").format(
se_item.item_code, self.subcontracting_order se_item.item_code,
self.subcontract_data.order_doctype,
self.get(self.subcontract_data.order_field),
) )
) )
parent = frappe.qb.DocType("Stock Entry") parent = frappe.qb.DocType("Stock Entry")
child = frappe.qb.DocType("Stock Entry Detail") child = frappe.qb.DocType("Stock Entry Detail")
conditions = (
(parent.docstatus == 1)
& (child.item_code == se_item.item_code)
& (
(parent.purchase_order == self.purchase_order)
if self.subcontract_data.order_doctype == "Purchase Order"
else (parent.subcontracting_order == self.subcontracting_order)
)
)
total_supplied = ( total_supplied = (
frappe.qb.from_(parent) frappe.qb.from_(parent)
.inner_join(child) .inner_join(child)
.on(parent.name == child.parent) .on(parent.name == child.parent)
.select(Sum(child.transfer_qty)) .select(Sum(child.transfer_qty))
.where(parent.docstatus == 1) .where(conditions)
.where(parent.subcontracting_order == self.subcontracting_order)
.where(child.item_code == se_item.item_code)
).run()[0][0] ).run()[0][0]
if flt(total_supplied, precision) > flt(total_allowed, precision): if flt(total_supplied, precision) > flt(total_allowed, precision):
frappe.throw( frappe.throw(
_( _("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format(
"Row {0}# Item {1} cannot be transferred more than {2} against Subcontracting Order {3}" se_item.idx,
).format( se_item.item_code,
se_item.idx, se_item.item_code, total_allowed, self.subcontracting_order total_allowed,
self.subcontract_data.order_doctype,
self.get(self.subcontract_data.order_field),
) )
) )
elif not se_item.sco_rm_detail: elif not se_item.get(self.subcontract_data.rm_detail_field):
filters = { filters = {
"parent": self.subcontracting_order, "parent": self.get(self.subcontract_data.order_field),
"docstatus": 1, "docstatus": 1,
"rm_item_code": se_item.item_code, "rm_item_code": se_item.item_code,
"main_item_code": se_item.subcontracted_item, "main_item_code": se_item.subcontracted_item,
} }
sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name") order_rm_detail = frappe.db.get_value(
if sco_rm_detail: self.subcontract_data.order_supplied_items_field, filters, "name"
se_item.db_set("sco_rm_detail", sco_rm_detail) )
if order_rm_detail:
se_item.db_set(self.subcontract_data.rm_detail_field, order_rm_detail)
elif backflush_raw_materials_based_on == "Material Transferred for Subcontract": elif backflush_raw_materials_based_on == "Material Transferred for Subcontract":
for row in self.items: for row in self.items:
if not row.subcontracted_item: if not row.subcontracted_item:
@ -905,17 +941,19 @@ class StockEntry(StockController):
row.idx, frappe.bold(row.item_code) row.idx, frappe.bold(row.item_code)
) )
) )
elif not row.sco_rm_detail: elif not row.get(self.subcontract_data.rm_detail_field):
filters = { filters = {
"parent": self.subcontracting_order, "parent": self.get(self.subcontract_data.order_field),
"docstatus": 1, "docstatus": 1,
"rm_item_code": row.item_code, "rm_item_code": row.item_code,
"main_item_code": row.subcontracted_item, "main_item_code": row.subcontracted_item,
} }
sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name") order_rm_detail = frappe.db.get_value(
if sco_rm_detail: self.subcontract_data.order_supplied_items_field, filters, "name"
row.db_set("sco_rm_detail", sco_rm_detail) )
if order_rm_detail:
row.db_set(self.subcontract_data.rm_detail_field, order_rm_detail)
def validate_bom(self): def validate_bom(self):
for d in self.get("items"): for d in self.get("items"):
@ -1263,12 +1301,12 @@ class StockEntry(StockController):
if ( if (
self.purpose == "Send to Subcontractor" self.purpose == "Send to Subcontractor"
and self.get("subcontracting_order") and self.get(self.subcontract_data.order_field)
and args.get("item_code") and args.get("item_code")
): ):
subcontract_items = frappe.get_all( subcontract_items = frappe.get_all(
"Subcontracting Order Supplied Item", self.subcontract_data.order_supplied_items_field,
{"parent": self.subcontracting_order, "rm_item_code": args.get("item_code")}, {"parent": self.get(self.subcontract_data.order_field), "rm_item_code": args.get("item_code")},
"main_item_code", "main_item_code",
) )
@ -1362,18 +1400,18 @@ class StockEntry(StockController):
item_dict = self.get_bom_raw_materials(self.fg_completed_qty) item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
# Get SCO Supplied Items Details # Get Subcontract Order Supplied Items Details
if self.subcontracting_order and self.purpose == "Send to Subcontractor": if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
# Get SCO Supplied Items Details # Get Subcontract Order Supplied Items Details
parent = frappe.qb.DocType("Subcontracting Order") parent = frappe.qb.DocType(self.subcontract_data.order_doctype)
child = frappe.qb.DocType("Subcontracting Order Supplied Item") child = frappe.qb.DocType(self.subcontract_data.order_supplied_items_field)
item_wh = ( item_wh = (
frappe.qb.from_(parent) frappe.qb.from_(parent)
.inner_join(child) .inner_join(child)
.on(parent.name == child.parent) .on(parent.name == child.parent)
.select(child.rm_item_code, child.reserve_warehouse) .select(child.rm_item_code, child.reserve_warehouse)
.where(parent.name == self.subcontracting_order) .where(parent.name == self.get(self.subcontract_data.order_field))
).run(as_list=True) ).run(as_list=True)
item_wh = frappe._dict(item_wh) item_wh = frappe._dict(item_wh)
@ -1381,8 +1419,8 @@ class StockEntry(StockController):
for item in item_dict.values(): for item in item_dict.values():
if self.pro_doc and cint(self.pro_doc.from_wip_warehouse): if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
item["from_warehouse"] = self.pro_doc.wip_warehouse item["from_warehouse"] = self.pro_doc.wip_warehouse
# Get Reserve Warehouse from SCO # Get Reserve Warehouse from Subcontract Order
if self.subcontracting_order and self.purpose == "Send to Subcontractor": if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
item["from_warehouse"] = item_wh.get(item.item_code) item["from_warehouse"] = item_wh.get(item.item_code)
item["to_warehouse"] = self.to_warehouse if self.purpose == "Send to Subcontractor" else "" item["to_warehouse"] = self.to_warehouse if self.purpose == "Send to Subcontractor" else ""
@ -1519,7 +1557,9 @@ class StockEntry(StockController):
fetch_qty_in_stock_uom=False, fetch_qty_in_stock_uom=False,
) )
used_alternative_items = get_used_alternative_items(work_order=self.work_order) used_alternative_items = get_used_alternative_items(
subcontract_order_field=self.subcontract_data.order_field, work_order=self.work_order
)
for item in item_dict.values(): for item in item_dict.values():
# if source warehouse presents in BOM set from_warehouse as bom source_warehouse # if source warehouse presents in BOM set from_warehouse as bom source_warehouse
if item["allow_alternative_item"]: if item["allow_alternative_item"]:
@ -1925,7 +1965,7 @@ class StockEntry(StockController):
se_child.is_process_loss = item_row.get("is_process_loss", 0) se_child.is_process_loss = item_row.get("is_process_loss", 0)
for field in [ for field in [
"sco_rm_detail", self.subcontract_data.rm_detail_field,
"original_item", "original_item",
"expense_account", "expense_account",
"description", "description",
@ -1999,33 +2039,37 @@ class StockEntry(StockController):
else: else:
frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code)) frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code))
def update_subcontracting_order_supplied_items(self): def update_subcontract_order_supplied_items(self):
if self.subcontracting_order and ( if self.get(self.subcontract_data.order_field) and (
self.purpose in ["Send to Subcontractor", "Material Transfer"] self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return
): ):
# Get SCO Supplied Items Details # Get Subcontract Order Supplied Items Details
sco_supplied_items = frappe.db.get_all( order_supplied_items = frappe.db.get_all(
"Subcontracting Order Supplied Item", self.subcontract_data.order_supplied_items_field,
filters={"parent": self.subcontracting_order}, filters={"parent": self.get(self.subcontract_data.order_field)},
fields=["name", "rm_item_code", "reserve_warehouse"], fields=["name", "rm_item_code", "reserve_warehouse"],
) )
# Get Items Supplied in Stock Entries against SCO # Get Items Supplied in Stock Entries against Subcontract Order
supplied_items = get_supplied_items(self.subcontracting_order) supplied_items = get_supplied_items(
self.get(self.subcontract_data.order_field),
self.subcontract_data.rm_detail_field,
self.subcontract_data.order_field,
)
for row in sco_supplied_items: for row in order_supplied_items:
key, item = row.name, {} key, item = row.name, {}
if not supplied_items.get(key): if not supplied_items.get(key):
# no stock transferred against SCO Supplied Items row # no stock transferred against Subcontract Order Supplied Items row
item = {"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0} item = {"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0}
else: else:
item = supplied_items.get(key) item = supplied_items.get(key)
frappe.db.set_value("Subcontracting Order Supplied Item", row.name, item) frappe.db.set_value(self.subcontract_data.order_supplied_items_field, row.name, item)
# RM Item-Reserve Warehouse Dict # RM Item-Reserve Warehouse Dict
item_wh = {x.get("rm_item_code"): x.get("reserve_warehouse") for x in sco_supplied_items} item_wh = {x.get("rm_item_code"): x.get("reserve_warehouse") for x in order_supplied_items}
for d in self.get("items"): for d in self.get("items"):
# Update reserved sub contracted quantity in bin based on Supplied Item Details and # Update reserved sub contracted quantity in bin based on Supplied Item Details and
@ -2382,13 +2426,13 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
return operating_cost_per_unit return operating_cost_per_unit
def get_used_alternative_items(subcontracting_order=None, work_order=None): def get_used_alternative_items(
subcontract_order=None, subcontract_order_field="subcontracting_order", work_order=None
):
cond = "" cond = ""
if subcontracting_order: if subcontract_order:
cond = "and ste.purpose = 'Send to Subcontractor' and ste.subcontracting_order = '{0}'".format( cond = f"and ste.purpose = 'Send to Subcontractor' and ste.{subcontract_order_field} = '{subcontract_order}'"
subcontracting_order
)
elif work_order: elif work_order:
cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format( cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(
work_order work_order
@ -2524,25 +2568,27 @@ def validate_sample_quantity(item_code, sample_quantity, qty, batch_no=None):
return sample_quantity return sample_quantity
def get_supplied_items(subcontracting_order): def get_supplied_items(
subcontract_order, rm_detail_field="sco_rm_detail", subcontract_order_field="subcontracting_order"
):
fields = [ fields = [
"`tabStock Entry Detail`.`transfer_qty`", "`tabStock Entry Detail`.`transfer_qty`",
"`tabStock Entry`.`is_return`", "`tabStock Entry`.`is_return`",
"`tabStock Entry Detail`.`sco_rm_detail`", f"`tabStock Entry Detail`.`{rm_detail_field}`",
"`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`item_code`",
] ]
filters = [ filters = [
["Stock Entry", "docstatus", "=", 1], ["Stock Entry", "docstatus", "=", 1],
["Stock Entry", "subcontracting_order", "=", subcontracting_order], ["Stock Entry", subcontract_order_field, "=", subcontract_order],
] ]
supplied_item_details = {} supplied_item_details = {}
for row in frappe.get_all("Stock Entry", fields=fields, filters=filters): for row in frappe.get_all("Stock Entry", fields=fields, filters=filters):
if not row.sco_rm_detail: if not row.get(rm_detail_field):
continue continue
key = row.sco_rm_detail key = row.get(rm_detail_field)
if key not in supplied_item_details: if key not in supplied_item_details:
supplied_item_details.setdefault( supplied_item_details.setdefault(
key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0}) key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0})

View File

@ -240,8 +240,13 @@ def validate_item_details(args, item):
throw(_("Item {0} is a template, please select one of its variants").format(item.name)) throw(_("Item {0} is a template, please select one of its variants").format(item.name))
elif args.transaction_type == "buying" and args.doctype != "Material Request": elif args.transaction_type == "buying" and args.doctype != "Material Request":
if args.get("is_subcontracted") and item.is_stock_item: if args.get("is_subcontracted"):
throw(_("Item {0} must be a Non-Stock Item").format(item.name)) if args.get("is_old_subcontracting_flow"):
if item.is_sub_contracted_item != 1:
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
else:
if item.is_stock_item:
throw(_("Item {0} must be a Non-Stock Item").format(item.name))
def get_basic_details(args, item, overwrite_warehouse=True): def get_basic_details(args, item, overwrite_warehouse=True):

View File

@ -738,6 +738,13 @@ class update_entries_after(object):
"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate "Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
) )
# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted"):
doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
doc.update_valuation_rate(reset_outgoing_rate=False)
for d in doc.items + doc.supplied_items:
d.db_update()
def update_rate_on_subcontracting_receipt(self, sle, outgoing_rate): def update_rate_on_subcontracting_receipt(self, sle, outgoing_rate):
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "rate", outgoing_rate) frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "rate", outgoing_rate)

View File

@ -24,7 +24,8 @@ frappe.ui.form.on('Subcontracting Order', {
return { return {
filters: { filters: {
docstatus: 1, docstatus: 1,
is_subcontracted: 1 is_subcontracted: 1,
is_old_subcontracting_flow: 0
} }
}; };
}); });
@ -115,10 +116,14 @@ frappe.ui.form.on('Subcontracting Order', {
if (sco_rm_details && sco_rm_details.length) { if (sco_rm_details && sco_rm_details.length) {
frm.add_custom_button(__('Return of Components'), () => { frm.add_custom_button(__('Return of Components'), () => {
frm.call({ frm.call({
method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.get_materials_from_supplier', method: 'erpnext.controllers.subcontracting_controller.get_materials_from_supplier',
freeze: true, freeze: true,
freeze_message: __('Creating Stock Entry'), freeze_message: __('Creating Stock Entry'),
args: { subcontracting_order: frm.doc.name, sco_rm_details: sco_rm_details }, args: {
subcontract_order: frm.doc.name,
rm_details: sco_rm_details,
order_doctype: cur_frm.doc.doctype
},
callback: function (r) { callback: function (r) {
if (r && r.message) { if (r && r.message) {
const doc = frappe.model.sync(r.message); const doc = frappe.model.sync(r.message);
@ -306,10 +311,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
make_rm_stock_entry(rm_items) { make_rm_stock_entry(rm_items) {
frappe.call({ frappe.call({
method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_rm_stock_entry', method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry',
args: { args: {
subcontracting_order: cur_frm.doc.name, subcontract_order: cur_frm.doc.name,
rm_items: rm_items rm_items: rm_items,
order_doctype: cur_frm.doc.doctype
}, },
callback: (r) => { callback: (r) => {
var doclist = frappe.model.sync(r.message); var doclist = frappe.model.sync(r.message);

View File

@ -1,8 +1,6 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
import json
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
@ -42,6 +40,9 @@ class SubcontractingOrder(SubcontractingController):
if not po.is_subcontracted: if not po.is_subcontracted:
frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting.")) frappe.throw(_("Please select a valid Purchase Order that is configured for Subcontracting."))
if po.is_old_subcontracting_flow:
frappe.throw(_("Please select a valid Purchase Order that has Service Items."))
if po.docstatus != 1: if po.docstatus != 1:
msg = f"Please submit Purchase Order {po.name} before proceeding." msg = f"Please submit Purchase Order {po.name} before proceeding."
frappe.throw(_(msg)) frappe.throw(_(msg))
@ -227,143 +228,6 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None):
return target_doc return target_doc
def get_item_details(items):
item = frappe.qb.DocType("Item")
item_list = (
frappe.qb.from_(item)
.select(item.item_code, item.description, item.allow_alternative_item)
.where(item.name.isin(items))
.run(as_dict=True)
)
item_details = {}
for item in item_list:
item_details[item.item_code] = item
return item_details
@frappe.whitelist()
def make_rm_stock_entry(subcontracting_order, rm_items):
rm_items_list = rm_items
if isinstance(rm_items, str):
rm_items_list = json.loads(rm_items)
elif not rm_items:
frappe.throw(_("No Items available for transfer"))
if rm_items_list:
fg_items = list(set(item["item_code"] for item in rm_items_list))
else:
frappe.throw(_("No Items selected for transfer"))
if subcontracting_order:
subcontracting_order = frappe.get_doc("Subcontracting Order", subcontracting_order)
if fg_items:
items = tuple(set(item["rm_item_code"] for item in rm_items_list))
item_wh = get_item_details(items)
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.purpose = "Send to Subcontractor"
stock_entry.subcontracting_order = subcontracting_order.name
stock_entry.supplier = subcontracting_order.supplier
stock_entry.supplier_name = subcontracting_order.supplier_name
stock_entry.supplier_address = subcontracting_order.supplier_address
stock_entry.address_display = subcontracting_order.address_display
stock_entry.company = subcontracting_order.company
stock_entry.to_warehouse = subcontracting_order.supplier_warehouse
stock_entry.set_stock_entry_type()
for item_code in fg_items:
for rm_item_data in rm_items_list:
if rm_item_data["item_code"] == item_code:
rm_item_code = rm_item_data["rm_item_code"]
items_dict = {
rm_item_code: {
"sco_rm_detail": rm_item_data.get("name"),
"item_name": rm_item_data["item_name"],
"description": item_wh.get(rm_item_code, {}).get("description", ""),
"qty": rm_item_data["qty"],
"from_warehouse": rm_item_data["warehouse"],
"stock_uom": rm_item_data["stock_uom"],
"serial_no": rm_item_data.get("serial_no"),
"batch_no": rm_item_data.get("batch_no"),
"main_item_code": rm_item_data["item_code"],
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
}
}
stock_entry.add_to_stock_entry_detail(items_dict)
return stock_entry.as_dict()
else:
frappe.throw(_("No Items selected for transfer"))
return subcontracting_order.name
def add_items_in_ste(ste_doc, row, qty, sco_rm_details, batch_no=None):
item = ste_doc.append("items", row.item_details)
sco_rm_detail = list(set(row.sco_rm_details).intersection(sco_rm_details))
item.update(
{
"qty": qty,
"batch_no": batch_no,
"basic_rate": row.item_details["rate"],
"sco_rm_detail": sco_rm_detail[0] if sco_rm_detail else "",
"s_warehouse": row.item_details["t_warehouse"],
"t_warehouse": row.item_details["s_warehouse"],
"item_code": row.item_details["rm_item_code"],
"subcontracted_item": row.item_details["main_item_code"],
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
}
)
def make_return_stock_entry_for_subcontract(available_materials, sco_doc, sco_rm_details):
ste_doc = frappe.new_doc("Stock Entry")
ste_doc.purpose = "Material Transfer"
ste_doc.subcontracting_order = sco_doc.name
ste_doc.company = sco_doc.company
ste_doc.is_return = 1
for key, value in available_materials.items():
if not value.qty:
continue
if value.batch_no:
for batch_no, qty in value.batch_no.items():
if qty > 0:
add_items_in_ste(ste_doc, value, value.qty, sco_rm_details, batch_no)
else:
add_items_in_ste(ste_doc, value, value.qty, sco_rm_details)
ste_doc.set_stock_entry_type()
ste_doc.calculate_rate_and_amount()
return ste_doc
@frappe.whitelist()
def get_materials_from_supplier(subcontracting_order, sco_rm_details):
if isinstance(sco_rm_details, str):
sco_rm_details = json.loads(sco_rm_details)
doc = frappe.get_cached_doc("Subcontracting Order", subcontracting_order)
doc.initialized_fields()
doc.subcontracting_orders = [doc.name]
doc.get_available_materials()
if not doc.available_materials:
frappe.throw(
_("Materials are already received against the Subcontracting Order {0}").format(
subcontracting_order
)
)
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, sco_rm_details)
@frappe.whitelist() @frappe.whitelist()
def update_subcontracting_order_status(sco): def update_subcontracting_order_status(sco):
if isinstance(sco, str): if isinstance(sco, str):

View File

@ -7,6 +7,7 @@ import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.controllers.tests.test_subcontracting_controller import (
get_rm_items, get_rm_items,
get_subcontracting_order, get_subcontracting_order,
@ -22,7 +23,6 @@ from erpnext.controllers.tests.test_subcontracting_controller import (
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_rm_stock_entry,
make_subcontracting_receipt, make_subcontracting_receipt,
) )

View File

@ -3,7 +3,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint, flt, getdate, nowdate from frappe.utils import cint, getdate, nowdate
from erpnext.controllers.subcontracting_controller import SubcontractingController from erpnext.controllers.subcontracting_controller import SubcontractingController
@ -78,7 +78,7 @@ class SubcontractingReceipt(SubcontractingController):
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status() self.update_prevdoc_status()
self.set_subcontracting_order_status() self.set_subcontracting_order_status()
self.set_consumed_qty_in_sco() self.set_consumed_qty_in_subcontract_order()
self.update_stock_ledger() self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
@ -97,7 +97,7 @@ class SubcontractingReceipt(SubcontractingController):
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()
self.repost_future_sle_and_gle() self.repost_future_sle_and_gle()
self.delete_auto_created_batches() self.delete_auto_created_batches()
self.set_consumed_qty_in_sco() self.set_consumed_qty_in_subcontract_order()
self.set_subcontracting_order_status() self.set_subcontracting_order_status()
self.update_status() self.update_status()
@ -162,17 +162,6 @@ class SubcontractingReceipt(SubcontractingController):
if not item.expense_account: if not item.expense_account:
item.expense_account = expense_account item.expense_account = expense_account
@frappe.whitelist()
def get_current_stock(self):
for item in self.get("supplied_items"):
if self.supplier_warehouse:
actual_qty = frappe.db.get_value(
"Bin",
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
"actual_qty",
)
item.current_stock = flt(actual_qty) or 0
def update_status(self, status=None, update_modified=False): def update_status(self, status=None, update_modified=False):
if self.docstatus >= 1 and not status: if self.docstatus >= 1 and not status:
if self.docstatus == 1: if self.docstatus == 1:

View File

@ -119,7 +119,7 @@ class TestSubcontractingReceipt(FrappeTestCase):
receive more than the required qty in the SCO. receive more than the required qty in the SCO.
Expected Result: Error Raised for Over Receipt against SCO. Expected Result: Error Raised for Over Receipt against SCO.
""" """
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( from erpnext.controllers.subcontracting_controller import (
make_rm_stock_entry as make_subcontract_transfer_entry, make_rm_stock_entry as make_subcontract_transfer_entry,
) )
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
@ -188,8 +188,8 @@ class TestSubcontractingReceipt(FrappeTestCase):
self.assertRaises(frappe.ValidationError, scr2.submit) self.assertRaises(frappe.ValidationError, scr2.submit)
def test_subcontracted_scr_for_multi_transfer_batches(self): def test_subcontracted_scr_for_multi_transfer_batches(self):
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_rm_stock_entry,
make_subcontracting_receipt, make_subcontracting_receipt,
) )