refactor: backport old subcontracting code
This commit is contained in:
parent
ca24b5287e
commit
6d89b2fa28
@ -572,6 +572,10 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
|
@ -513,6 +513,10 @@ class PurchaseInvoice(BuyingController):
|
||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||
if self.update_stock == 1:
|
||||
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
|
||||
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
@ -1416,6 +1420,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_stock_ledger()
|
||||
self.delete_auto_created_batches()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
if self.update_stock == 1:
|
||||
|
@ -616,11 +616,13 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -872,7 +874,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-15 17:04:07.191013",
|
||||
"modified": "2022-06-15 16:02:15.196835",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
@ -880,5 +882,6 @@
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
@ -7,6 +7,19 @@ frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on("Purchase Order", {
|
||||
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',
|
||||
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() {
|
||||
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);
|
||||
},
|
||||
|
||||
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) {
|
||||
set_schedule_date(frm);
|
||||
if (!frm.doc.transaction_date){
|
||||
@ -67,7 +121,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
'Purchase Receipt': 'Purchase Receipt',
|
||||
'Purchase Invoice': 'Purchase Invoice',
|
||||
'Payment Entry': 'Payment',
|
||||
'Subcontracting Order': 'Subcontracting Order'
|
||||
'Subcontracting Order': 'Subcontracting Order',
|
||||
'Stock Entry': 'Material to Supplier'
|
||||
}
|
||||
|
||||
super.setup();
|
||||
@ -138,7 +193,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
if(flt(doc.per_received) < 100 && allow_receipt) {
|
||||
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
|
||||
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)
|
||||
@ -206,6 +268,143 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
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) {
|
||||
frappe.model.open_mapped_doc({
|
||||
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) {
|
||||
if(frm.doc.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.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
|
||||
if (frm.doc.is_old_subcontracting_flow) {
|
||||
erpnext.buying.get_default_bom(frm);
|
||||
}
|
||||
});
|
@ -452,10 +452,6 @@
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break_warehouse",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_subcontracted",
|
||||
|
@ -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.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.utils import get_bin
|
||||
|
||||
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
|
||||
|
||||
@ -68,6 +69,11 @@ class PurchaseOrder(BuyingController):
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_for_subcontracting()
|
||||
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.set_received_qty_for_drop_ship_items()
|
||||
validate_inter_company_party(
|
||||
@ -191,8 +197,17 @@ class PurchaseOrder(BuyingController):
|
||||
).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):
|
||||
if self.is_subcontracted:
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
for item in self.items:
|
||||
if not item.fg_item:
|
||||
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}"
|
||||
).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:
|
||||
frappe.throw(
|
||||
_("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.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
|
||||
@ -318,6 +338,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty()
|
||||
self.validate_budget()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
|
||||
frappe.get_doc("Authorization Control").validate_approving_authority(
|
||||
self.doctype, self.company, self.base_grand_total
|
||||
@ -337,6 +358,7 @@ class PurchaseOrder(BuyingController):
|
||||
if self.has_drop_ship_item():
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
frappe.db.set(self, "status", "Cancelled")
|
||||
@ -406,6 +428,13 @@ class PurchaseOrder(BuyingController):
|
||||
if item.delivered_by_supplier == 1:
|
||||
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):
|
||||
total_qty, received_qty = 0.0, 0.0
|
||||
for item in self.items:
|
||||
@ -649,4 +678,16 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
|
||||
|
||||
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
|
||||
|
@ -22,6 +22,6 @@ def get_data():
|
||||
"label": _("Reference"),
|
||||
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
|
||||
},
|
||||
{"label": _("Sub-contracting"), "items": ["Subcontracting Order"]},
|
||||
{"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]},
|
||||
],
|
||||
}
|
||||
|
@ -574,18 +574,20 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1
|
||||
},
|
||||
@ -849,27 +851,27 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval:parent.is_subcontracted",
|
||||
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||
"fieldname": "fg_item_qty",
|
||||
"fieldtype": "Float",
|
||||
"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,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-07 14:53:16.684010",
|
||||
"modified": "2022-06-16 06:00:01.624317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
@ -27,18 +27,16 @@ frappe.query_reports["Subcontract Order Summary"] = {
|
||||
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",
|
||||
fieldtype: "Link",
|
||||
options: "Subcontracting Order",
|
||||
get_query: function () {
|
||||
return {
|
||||
filters: {
|
||||
docstatus: 1,
|
||||
company: frappe.query_report.get_filter_value('company')
|
||||
}
|
||||
}
|
||||
}
|
||||
fieldtype: "Data"
|
||||
}
|
||||
]
|
||||
};
|
@ -8,7 +8,7 @@ from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
columns = get_columns()
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
@ -20,42 +20,45 @@ def get_data(report_filters):
|
||||
|
||||
if orders:
|
||||
supplied_items = get_supplied_items(orders, report_filters)
|
||||
sco_details = prepare_subcontracted_data(orders, supplied_items)
|
||||
get_subcontracted_data(sco_details, data)
|
||||
order_details = prepare_subcontracted_data(orders, supplied_items)
|
||||
get_subcontracted_data(order_details, data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_subcontracted_orders(report_filters):
|
||||
fields = [
|
||||
"`tabSubcontracting Order Item`.`parent` as sco_id",
|
||||
"`tabSubcontracting Order Item`.`item_code`",
|
||||
"`tabSubcontracting Order Item`.`item_name`",
|
||||
"`tabSubcontracting Order Item`.`qty`",
|
||||
"`tabSubcontracting Order Item`.`name`",
|
||||
"`tabSubcontracting Order Item`.`received_qty`",
|
||||
"`tabSubcontracting Order`.`status`",
|
||||
f"`tab{report_filters.order_type} Item`.`parent` as order_id",
|
||||
f"`tab{report_filters.order_type} Item`.`item_code`",
|
||||
f"`tab{report_filters.order_type} Item`.`item_name`",
|
||||
f"`tab{report_filters.order_type} Item`.`qty`",
|
||||
f"`tab{report_filters.order_type} Item`.`name`",
|
||||
f"`tab{report_filters.order_type} Item`.`received_qty`",
|
||||
f"`tab{report_filters.order_type}`.`status`",
|
||||
]
|
||||
|
||||
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):
|
||||
filters = [
|
||||
["Subcontracting Order", "docstatus", "=", 1],
|
||||
[report_filters.order_type, "docstatus", "=", 1],
|
||||
[
|
||||
"Subcontracting Order",
|
||||
report_filters.order_type,
|
||||
"transaction_date",
|
||||
"between",
|
||||
(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"]:
|
||||
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
|
||||
|
||||
@ -70,15 +73,21 @@ def get_supplied_items(orders, report_filters):
|
||||
"rm_item_code",
|
||||
"required_qty",
|
||||
"supplied_qty",
|
||||
"returned_qty",
|
||||
"total_supplied_qty",
|
||||
"consumed_qty",
|
||||
"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 = {}
|
||||
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)
|
||||
|
||||
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):
|
||||
sco_details = {}
|
||||
order_details = {}
|
||||
for row in orders:
|
||||
key = (row.sco_id, row.name, row.item_code)
|
||||
if key not in sco_details:
|
||||
sco_details.setdefault(key, frappe._dict({"sco_item": row, "supplied_items": []}))
|
||||
key = (row.order_id, row.name, row.item_code)
|
||||
if key not in order_details:
|
||||
order_details.setdefault(key, frappe._dict({"order_item": row, "supplied_items": []}))
|
||||
|
||||
details = sco_details[key]
|
||||
details = order_details[key]
|
||||
|
||||
if supplied_items.get(key):
|
||||
for supplied_item in supplied_items[key]:
|
||||
details["supplied_items"].append(supplied_item)
|
||||
|
||||
return sco_details
|
||||
return order_details
|
||||
|
||||
|
||||
def get_subcontracted_data(sco_details, data):
|
||||
for key, details in sco_details.items():
|
||||
res = details.sco_item
|
||||
def get_subcontracted_data(order_details, data):
|
||||
for key, details in order_details.items():
|
||||
res = details.order_item
|
||||
for index, row in enumerate(details.supplied_items):
|
||||
if index != 0:
|
||||
res = {}
|
||||
@ -113,13 +122,13 @@ def get_subcontracted_data(sco_details, data):
|
||||
data.append(res)
|
||||
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
"label": _("Subcontracting Order"),
|
||||
"fieldname": "sco_id",
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldname": "order_id",
|
||||
"fieldtype": "Link",
|
||||
"options": "Subcontracting Order",
|
||||
"options": filters.order_type,
|
||||
"width": 100,
|
||||
},
|
||||
{"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": _("Supplied Qty"), "fieldname": "supplied_qty", "fieldtype": "Float", "width": 110},
|
||||
{"label": _("Consumed Qty"), "fieldname": "consumed_qty", "fieldtype": "Float", "width": 120},
|
||||
{"label": _("Returned Qty"), "fieldname": "returned_qty", "fieldtype": "Float", "width": 110},
|
||||
]
|
||||
|
@ -4,6 +4,13 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Item To Be Received"] = {
|
||||
"filters": [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order"
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
@ -13,7 +13,7 @@
|
||||
"name": "Subcontracted Item To Be Received",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"ref_doctype": "Subcontracting Order",
|
||||
"report_name": "Subcontracted Item To Be Received",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
|
@ -11,18 +11,18 @@ def execute(filters=None):
|
||||
frappe.msgprint(_("To Date must be greater than From Date"))
|
||||
|
||||
data = []
|
||||
columns = get_columns()
|
||||
columns = get_columns(filters)
|
||||
get_data(data, filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
"label": _("Subcontracting Order"),
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "subcontracting_order",
|
||||
"options": "Subcontracting Order",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 150,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "hidden": 1, "width": 150},
|
||||
@ -57,14 +57,14 @@ def get_columns():
|
||||
|
||||
|
||||
def get_data(data, filters):
|
||||
sco = get_sco(filters)
|
||||
sco_name = [v.name for v in sco]
|
||||
sub_items = get_subcontracting_order_item_supplied(sco_name)
|
||||
for item in sub_items:
|
||||
for order in sco:
|
||||
orders = get_subcontract_orders(filters)
|
||||
orders_name = [order.name for order in orders]
|
||||
subcontracted_items = get_subcontract_order_supplied_item(filters.order_type, orders_name)
|
||||
for item in subcontracted_items:
|
||||
for order in orders:
|
||||
if order.name == item.parent and item.received_qty < item.qty:
|
||||
row = {
|
||||
"subcontracting_order": item.parent,
|
||||
"subcontract_order": item.parent,
|
||||
"date": order.transaction_date,
|
||||
"supplier": order.supplier,
|
||||
"fg_item_code": item.item_code,
|
||||
@ -76,21 +76,25 @@ def get_data(data, filters):
|
||||
data.append(row)
|
||||
|
||||
|
||||
def get_sco(filters):
|
||||
def get_subcontract_orders(filters):
|
||||
record_filters = [
|
||||
["supplier", "=", filters.supplier],
|
||||
["transaction_date", "<=", filters.to_date],
|
||||
["transaction_date", ">=", filters.from_date],
|
||||
["docstatus", "=", 1],
|
||||
]
|
||||
|
||||
if filters.order_type == "Purchase Order":
|
||||
record_filters.append(["is_old_subcontracting_flow", "=", 1])
|
||||
|
||||
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(
|
||||
"Subcontracting Order Item",
|
||||
filters=[("parent", "IN", sco)],
|
||||
f"{order_type} Item",
|
||||
filters=[("parent", "IN", orders)],
|
||||
fields=["parent", "item_code", "item_name", "qty", "received_qty"],
|
||||
)
|
||||
|
@ -50,6 +50,7 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
|
||||
col, data = execute(
|
||||
filters=frappe._dict(
|
||||
{
|
||||
"order_type": "Subcontracting Order",
|
||||
"supplier": sco.supplier,
|
||||
"from_date": frappe.utils.get_datetime(
|
||||
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]["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)
|
||||
|
||||
|
||||
|
@ -4,6 +4,13 @@
|
||||
|
||||
frappe.query_reports["Subcontracted Raw Materials To Be Transferred"] = {
|
||||
"filters": [
|
||||
{
|
||||
label: __("Order Type"),
|
||||
fieldname: "order_type",
|
||||
fieldtype: "Select",
|
||||
options: ["Purchase Order", "Subcontracting Order"],
|
||||
default: "Subcontracting Order"
|
||||
},
|
||||
{
|
||||
fieldname: "supplier",
|
||||
label: __("Supplier"),
|
||||
|
@ -13,7 +13,7 @@
|
||||
"name": "Subcontracted Raw Materials To Be Transferred",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"ref_doctype": "Subcontracting Order",
|
||||
"report_name": "Subcontracted Raw Materials To Be Transferred",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
|
@ -10,19 +10,19 @@ def execute(filters=None):
|
||||
if filters.from_date >= filters.to_date:
|
||||
frappe.msgprint(_("To Date must be greater than From Date"))
|
||||
|
||||
columns = get_columns()
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data or []
|
||||
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"label": _("Subcontract Order"),
|
||||
"fieldtype": "Link",
|
||||
"fieldname": "purchase_order",
|
||||
"options": "Purchase Order",
|
||||
"fieldname": "subcontract_order",
|
||||
"options": filters.order_type,
|
||||
"width": 200,
|
||||
},
|
||||
{"label": _("Date"), "fieldtype": "Date", "fieldname": "date", "width": 150},
|
||||
@ -46,10 +46,10 @@ def get_columns():
|
||||
|
||||
|
||||
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 = []
|
||||
for row in sco_rm_item_details:
|
||||
for row in order_rm_item_details:
|
||||
transferred_qty = row.get("transferred_qty") or 0
|
||||
if transferred_qty < row.get("reqd_qty", 0):
|
||||
pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty)
|
||||
@ -59,22 +59,33 @@ def get_data(filters):
|
||||
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(
|
||||
"Subcontracting Order",
|
||||
filters.order_type,
|
||||
fields=[
|
||||
"name as subcontracting_order",
|
||||
"name as subcontract_order",
|
||||
"transaction_date as date",
|
||||
"supplier as supplier",
|
||||
"`tabSubcontracting Order Supplied Item`.rm_item_code as rm_item_code",
|
||||
"`tabSubcontracting Order Supplied Item`.required_qty as reqd_qty",
|
||||
"`tabSubcontracting Order Supplied Item`.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],
|
||||
f"`tab{supplied_items_table}`.rm_item_code as rm_item_code",
|
||||
f"`tab{supplied_items_table}`.required_qty as reqd_qty",
|
||||
f"`tab{supplied_items_table}`.supplied_qty as transferred_qty",
|
||||
],
|
||||
filters=record_filters,
|
||||
)
|
||||
|
@ -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 (
|
||||
execute,
|
||||
)
|
||||
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
get_subcontracting_order,
|
||||
make_service_item,
|
||||
)
|
||||
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):
|
||||
@ -45,6 +43,7 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase):
|
||||
col, data = execute(
|
||||
filters=frappe._dict(
|
||||
{
|
||||
"order_type": "Subcontracting Order",
|
||||
"supplier": sco.supplier,
|
||||
"from_date": frappe.utils.get_datetime(
|
||||
frappe.utils.add_to_date(sco.transaction_date, days=-10)
|
||||
@ -55,12 +54,12 @@ class TestSubcontractedItemToBeTransferred(FrappeTestCase):
|
||||
)
|
||||
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
|
||||
sco_data = sorted(sco_data, key=lambda i: i["rm_item_code"])
|
||||
|
||||
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]["p_qty"], 8)
|
||||
|
@ -2657,6 +2657,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
parent.update_ordered_qty()
|
||||
parent.update_ordered_and_reserved_qty()
|
||||
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
|
||||
parent.validate_warehouse()
|
||||
parent.update_reserved_qty()
|
||||
|
@ -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.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.stock_controller import StockController
|
||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
@ -20,7 +20,10 @@ class QtyMismatchError(ValidationError):
|
||||
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):
|
||||
if self.get("supplier_name"):
|
||||
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
|
||||
@ -51,6 +54,8 @@ class BuyingController(StockController):
|
||||
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
self.create_raw_materials_supplied()
|
||||
self.set_landed_cost_voucher_amount()
|
||||
|
||||
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)
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
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:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
@ -312,6 +326,19 @@ class BuyingController(StockController):
|
||||
if self.is_subcontracted:
|
||||
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))
|
||||
|
||||
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:
|
||||
for item in self.get("items"):
|
||||
if item.get("bom"):
|
||||
@ -440,7 +467,9 @@ class BuyingController(StockController):
|
||||
sle.update(
|
||||
{
|
||||
"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)
|
||||
@ -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(
|
||||
sl_entries,
|
||||
allow_negative_stock=allow_negative_stock,
|
||||
@ -494,6 +525,8 @@ class BuyingController(StockController):
|
||||
)
|
||||
|
||||
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):
|
||||
if self.get("is_return"):
|
||||
@ -718,7 +751,10 @@ class BuyingController(StockController):
|
||||
if self.doctype == "Material Request":
|
||||
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):
|
||||
|
@ -2,6 +2,7 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
import copy
|
||||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
@ -14,13 +15,40 @@ from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
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):
|
||||
self.remove_empty_rows()
|
||||
self.set_items_conversion_factor()
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.remove_empty_rows()
|
||||
self.set_items_conversion_factor()
|
||||
|
||||
def validate(self):
|
||||
self.validate_items()
|
||||
self.create_raw_materials_supplied()
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||
self.validate_items()
|
||||
self.create_raw_materials_supplied()
|
||||
else:
|
||||
super(SubcontractingController, self).validate()
|
||||
|
||||
def remove_empty_rows(self):
|
||||
for key in ["service_items", "items", "supplied_items"]:
|
||||
@ -54,7 +82,10 @@ class SubcontractingController(StockController):
|
||||
|
||||
def __get_data_before_save(self):
|
||||
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"):
|
||||
item_dict[row.name] = (row.item_code, row.qty)
|
||||
|
||||
@ -64,7 +95,7 @@ class SubcontractingController(StockController):
|
||||
self.__changed_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, [])
|
||||
return
|
||||
|
||||
@ -93,36 +124,38 @@ class SubcontractingController(StockController):
|
||||
self.alternative_item_details = frappe._dict()
|
||||
self.__get_backflush_based_on()
|
||||
|
||||
def __get_subcontracting_orders(self):
|
||||
self.subcontracting_orders = []
|
||||
def __get_subcontract_orders(self):
|
||||
self.subcontract_orders = []
|
||||
|
||||
if self.doctype == "Subcontracting Order":
|
||||
if self.doctype in ["Purchase Order", "Subcontracting Order"]:
|
||||
return
|
||||
|
||||
self.subcontracting_orders = [
|
||||
item.subcontracting_order for item in self.items if item.subcontracting_order
|
||||
self.subcontract_orders = [
|
||||
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):
|
||||
"""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)
|
||||
|
||||
if (
|
||||
self.doctype != "Subcontracting Order"
|
||||
self.doctype != self.subcontract_data.order_doctype
|
||||
and self.backflush_based_on != "BOM"
|
||||
and self.subcontracting_orders
|
||||
and self.subcontract_orders
|
||||
):
|
||||
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"],
|
||||
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
|
||||
|
||||
def __get_transferred_items(self):
|
||||
fields = ["`tabStock Entry`.`subcontracting_order`"]
|
||||
fields = [f"`tabStock Entry`.`{self.subcontract_data.order_field}`"]
|
||||
alias_dict = {
|
||||
"item_code": "rm_item_code",
|
||||
"subcontracted_item": "main_item_code",
|
||||
@ -145,7 +178,7 @@ class SubcontractingController(StockController):
|
||||
"s_warehouse",
|
||||
"t_warehouse",
|
||||
"item_group",
|
||||
"sco_rm_detail",
|
||||
self.subcontract_data.rm_detail_field,
|
||||
]
|
||||
|
||||
if self.backflush_based_on == "BOM":
|
||||
@ -157,7 +190,7 @@ class SubcontractingController(StockController):
|
||||
filters = [
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["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)
|
||||
@ -168,21 +201,21 @@ class SubcontractingController(StockController):
|
||||
|
||||
def __get_received_items(self, doctype):
|
||||
fields = []
|
||||
self.sco_field = "subcontracting_order"
|
||||
|
||||
for field in ["name", self.sco_field, "parent"]:
|
||||
for field in ["name", self.subcontract_data.order_field, "parent"]:
|
||||
fields.append(f"`tab{doctype} Item`.`{field}`")
|
||||
|
||||
filters = [
|
||||
[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)
|
||||
|
||||
def __get_consumed_items(self, doctype, scr_items):
|
||||
def __get_consumed_items(self, doctype, receipt_items):
|
||||
return frappe.get_all(
|
||||
"Subcontracting Receipt Supplied Item",
|
||||
self.subcontract_data.receipt_supplied_items_field,
|
||||
fields=[
|
||||
"serial_no",
|
||||
"rm_item_code",
|
||||
@ -191,26 +224,26 @@ class SubcontractingController(StockController):
|
||||
"consumed_qty",
|
||||
"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):
|
||||
"""Deduct the consumed materials from the available materials."""
|
||||
|
||||
scr_items = self.__get_received_items(doctype)
|
||||
if not scr_items:
|
||||
receipt_items = self.__get_received_items(doctype)
|
||||
if not receipt_items:
|
||||
return ([], {}) if return_consumed_items else None
|
||||
|
||||
scr_items = {
|
||||
item.name: item.get(self.get("sco_field") or "subcontracting_order") for item in scr_items
|
||||
receipt_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:
|
||||
return (consumed_materials, scr_items)
|
||||
return (consumed_materials, receipt_items)
|
||||
|
||||
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):
|
||||
continue
|
||||
|
||||
@ -226,16 +259,16 @@ class SubcontractingController(StockController):
|
||||
def get_available_materials(self):
|
||||
"""Get the available raw materials which has been transferred to the supplier.
|
||||
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
|
||||
}
|
||||
}
|
||||
"""
|
||||
if not self.subcontracting_orders:
|
||||
if not self.subcontract_orders:
|
||||
return
|
||||
|
||||
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:
|
||||
self.available_materials.setdefault(
|
||||
@ -246,14 +279,16 @@ class SubcontractingController(StockController):
|
||||
"serial_no": [],
|
||||
"batch_no": defaultdict(float),
|
||||
"item_details": row,
|
||||
"sco_rm_details": [],
|
||||
f"{self.subcontract_data.rm_detail_field}s": [],
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
details = self.available_materials[key]
|
||||
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:
|
||||
details.serial_no.extend(get_serial_nos(row.serial_no))
|
||||
@ -264,7 +299,11 @@ class SubcontractingController(StockController):
|
||||
self.__set_alternative_item_details(row)
|
||||
|
||||
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):
|
||||
if not self.__changed_name:
|
||||
@ -317,7 +356,7 @@ class SubcontractingController(StockController):
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
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])
|
||||
|
||||
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"]:
|
||||
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
||||
rm_obj.serial_no = "\n".join(used_serial_nos)
|
||||
@ -340,7 +379,7 @@ class SubcontractingController(StockController):
|
||||
"consumed_qty": qty,
|
||||
"batch_no": batch_no,
|
||||
"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
|
||||
|
||||
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"]:
|
||||
new_rm_obj = None
|
||||
@ -397,16 +436,18 @@ class SubcontractingController(StockController):
|
||||
)
|
||||
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.amount = rm_obj.required_qty * rm_obj.rate
|
||||
else:
|
||||
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)
|
||||
|
||||
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:
|
||||
return transfer_item.qty
|
||||
@ -427,13 +468,13 @@ class SubcontractingController(StockController):
|
||||
|
||||
has_supplied_items = True if self.get(self.raw_material_table) else False
|
||||
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)
|
||||
or (has_supplied_items and not self.__changed_name)
|
||||
):
|
||||
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(
|
||||
row.item_code, row.bom, row.get("include_exploded_items")
|
||||
):
|
||||
@ -445,17 +486,22 @@ class SubcontractingController(StockController):
|
||||
|
||||
elif self.backflush_based_on != "BOM":
|
||||
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
|
||||
transfer_item.qty -= qty
|
||||
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
||||
|
||||
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):
|
||||
self.initialized_fields()
|
||||
self.__get_subcontracting_orders()
|
||||
self.__get_subcontract_orders()
|
||||
self.__get_pending_qty_to_receive()
|
||||
self.get_available_materials()
|
||||
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(
|
||||
"batch_no"
|
||||
):
|
||||
link = get_link_to_form("Subcontracting Order", row.subcontracting_order)
|
||||
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Subcontracting Order {link}'
|
||||
link = get_link_to_form(
|
||||
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"))
|
||||
|
||||
def __validate_serial_no(self, row, key):
|
||||
@ -476,16 +524,18 @@ class SubcontractingController(StockController):
|
||||
|
||||
if incorrect_sn:
|
||||
incorrect_sn = "\n".join(incorrect_sn)
|
||||
link = get_link_to_form("Subcontracting Order", row.subcontracting_order)
|
||||
msg = f"The Serial Nos {incorrect_sn} has not supplied against the Subcontracting Order {link}"
|
||||
link = get_link_to_form(
|
||||
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"))
|
||||
|
||||
def __validate_supplied_items(self):
|
||||
if self.doctype != "Subcontracting Receipt":
|
||||
if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
||||
return
|
||||
|
||||
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):
|
||||
return
|
||||
|
||||
@ -493,6 +543,9 @@ class SubcontractingController(StockController):
|
||||
self.__validate_serial_no(row, key)
|
||||
|
||||
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.__identify_change_in_item_table()
|
||||
self.__prepare_supplied_items()
|
||||
@ -501,16 +554,16 @@ class SubcontractingController(StockController):
|
||||
def create_raw_materials_supplied(self, raw_material_table="supplied_items"):
|
||||
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"):
|
||||
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"]
|
||||
filters = {"docstatus": 1, "parent": ("in", self.subcontracting_orders)}
|
||||
filters = {"docstatus": 1, "parent": ("in", self.subcontract_orders)}
|
||||
|
||||
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)
|
||||
consumed_qty = itemwise_consumed_qty.get(key, 0)
|
||||
@ -520,22 +573,31 @@ class SubcontractingController(StockController):
|
||||
|
||||
itemwise_consumed_qty[key] -= consumed_qty
|
||||
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):
|
||||
# Update consumed qty back in the subcontracting order
|
||||
self.__get_subcontracting_orders()
|
||||
itemwise_consumed_qty = defaultdict(float)
|
||||
consumed_items, scr_items = self.__update_consumed_materials(
|
||||
"Subcontracting Receipt", return_consumed_items=True
|
||||
)
|
||||
def set_consumed_qty_in_subcontract_order(self):
|
||||
# Update consumed qty back in the subcontract order
|
||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
|
||||
"is_old_subcontracting_flow"
|
||||
):
|
||||
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:
|
||||
key = (row.rm_item_code, row.main_item_code, scr_items.get(row.reference_name))
|
||||
itemwise_consumed_qty[key] += row.consumed_qty
|
||||
for doctype in doctypes:
|
||||
consumed_items, receipt_items = self.__update_consumed_materials(
|
||||
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):
|
||||
sco_map = {}
|
||||
@ -618,10 +680,30 @@ class SubcontractingController(StockController):
|
||||
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
|
||||
for item in self.get("supplied_items"):
|
||||
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"))
|
||||
supplied_items_cost += item.amount
|
||||
|
||||
@ -631,13 +713,25 @@ class SubcontractingController(StockController):
|
||||
if self.doctype == "Subcontracting Order":
|
||||
self.update_status()
|
||||
elif self.doctype == "Subcontracting Receipt":
|
||||
self.__get_subcontracting_orders
|
||||
self.__get_subcontract_orders
|
||||
|
||||
if self.subcontracting_orders:
|
||||
for sco in set(self.subcontracting_orders):
|
||||
if self.subcontract_orders:
|
||||
for sco in set(self.subcontract_orders):
|
||||
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
||||
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
|
||||
def sub_contracted_items(self):
|
||||
if not hasattr(self, "_sub_contracted_items"):
|
||||
@ -650,3 +744,159 @@ class SubcontractingController(StockController):
|
||||
self._sub_contracted_items = [item.name for item in 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
|
||||
)
|
||||
|
@ -9,13 +9,15 @@ from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import cint
|
||||
|
||||
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.stock.doctype.item.test_item import make_item
|
||||
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.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
get_materials_from_supplier,
|
||||
make_rm_stock_entry,
|
||||
make_subcontracting_receipt,
|
||||
)
|
||||
|
||||
|
@ -64,4 +64,8 @@ def delete_and_patch_duplicate_bins():
|
||||
bin.update(qty_dict)
|
||||
bin.update_reserved_qty_for_production()
|
||||
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()
|
||||
|
@ -83,9 +83,17 @@ erpnext.buying.BuyingController = class BuyingController extends erpnext.Transac
|
||||
|
||||
this.frm.set_query("item_code", "items", function() {
|
||||
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{
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{ 'supplier': me.frm.doc.supplier, 'is_stock_item': 0 }
|
||||
filters: filters
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -471,7 +471,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
cost_center: item.cost_center,
|
||||
tax_category: me.frm.doc.tax_category,
|
||||
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,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -486,7 +486,11 @@ erpnext.utils.update_child_items = function(opts) {
|
||||
filters = {"is_sales_item": 1};
|
||||
} else if (frm.doc.doctype == 'Purchase Order') {
|
||||
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 {
|
||||
filters = {"is_purchase_item": 1};
|
||||
}
|
||||
|
@ -40,23 +40,37 @@ class Bin(Document):
|
||||
self.db_set("reserved_qty_for_production", flt(self.reserved_qty_for_production))
|
||||
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
|
||||
|
||||
sco = frappe.qb.DocType("Subcontracting Order")
|
||||
supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
||||
subcontract_order = frappe.qb.DocType(subcontract_doctype)
|
||||
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 = (
|
||||
frappe.qb.from_(sco)
|
||||
frappe.qb.from_(subcontract_order)
|
||||
.from_(supplied_item)
|
||||
.select(Sum(Coalesce(supplied_item.required_qty, 0)))
|
||||
.where(
|
||||
(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)
|
||||
)
|
||||
.where(conditions)
|
||||
).run()[0][0] or 0.0
|
||||
|
||||
se = frappe.qb.DocType("Stock Entry")
|
||||
@ -69,23 +83,34 @@ class Bin(Document):
|
||||
else:
|
||||
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 = (
|
||||
frappe.qb.from_(se)
|
||||
.from_(se_item)
|
||||
.from_(sco)
|
||||
.select(
|
||||
Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty))
|
||||
)
|
||||
.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)
|
||||
)
|
||||
.from_(subcontract_order)
|
||||
.select(Sum(qty_field))
|
||||
.where(conditions)
|
||||
).run()[0][0] or 0.0
|
||||
|
||||
if reserved_qty_for_sub_contract > materials_transferred:
|
||||
|
@ -5,6 +5,7 @@ import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.controllers.subcontracting_controller import make_rm_stock_entry
|
||||
from erpnext.controllers.tests.test_subcontracting_controller import (
|
||||
get_subcontracting_order,
|
||||
make_service_item,
|
||||
@ -21,7 +22,6 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
make_rm_stock_entry,
|
||||
make_subcontracting_receipt,
|
||||
)
|
||||
|
||||
|
@ -296,6 +296,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt
|
||||
frappe.provide("erpnext.buying");
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
|
@ -123,6 +123,7 @@ class PurchaseReceipt(BuyingController):
|
||||
if getdate(self.posting_date) > getdate(nowdate()):
|
||||
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("rejected_warehouse", "items", "rejected_warehouse")
|
||||
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||
@ -234,6 +235,7 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
self.make_gl_entries()
|
||||
self.repost_future_sle_and_gle()
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
def check_next_docstatus(self):
|
||||
submit_rv = frappe.db.sql(
|
||||
@ -269,6 +271,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.repost_future_sle_and_gle()
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
self.delete_auto_created_batches()
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
|
@ -642,13 +642,15 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_old_subcontracting_flow",
|
||||
"fieldname": "bom",
|
||||
"fieldtype": "Link",
|
||||
"label": "BOM",
|
||||
"no_copy": 1,
|
||||
"options": "BOM",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!parent.is_old_subcontracting_flow"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
|
@ -615,8 +615,15 @@ frappe.ui.form.on('Stock Entry', {
|
||||
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) => {
|
||||
if (frm.doc.subcontracting_order) {
|
||||
frm.set_value("purchase_order", "");
|
||||
erpnext.utils.map_current_doc({
|
||||
method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_items_from_subcontracting_order',
|
||||
source_name: frm.doc.subcontracting_order,
|
||||
@ -624,9 +631,6 @@ frappe.ui.form.on('Stock Entry', {
|
||||
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});
|
||||
};
|
||||
|
||||
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() {
|
||||
return {
|
||||
"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' }
|
||||
this.frm.set_query("supplier_address", erpnext.queries.address_query)
|
||||
|
@ -148,11 +148,11 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose==\"Send to Subcontractor\"",
|
||||
"fieldname": "purchase_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"read_only": 1
|
||||
"options": "Purchase Order"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.purpose==\"Send to Subcontractor\"",
|
||||
|
@ -62,6 +62,27 @@ form_grid_templates = {"items": "templates/form_grid/stock_entry_grid.html"}
|
||||
|
||||
|
||||
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):
|
||||
return self.stock_entry_type
|
||||
|
||||
@ -134,8 +155,8 @@ class StockEntry(StockController):
|
||||
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
self.update_work_order()
|
||||
self.validate_subcontracting_order()
|
||||
self.update_subcontracting_order_supplied_items()
|
||||
self.validate_subcontract_order()
|
||||
self.update_subcontract_order_supplied_items()
|
||||
self.update_subcontracting_order_status()
|
||||
|
||||
self.make_gl_entries()
|
||||
@ -155,7 +176,7 @@ class StockEntry(StockController):
|
||||
self.set_material_request_transfer_status("Completed")
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_subcontracting_order_supplied_items()
|
||||
self.update_subcontract_order_supplied_items()
|
||||
self.update_subcontracting_order_status()
|
||||
|
||||
if self.work_order and self.purpose == "Material Consumption for Manufacture":
|
||||
@ -809,8 +830,8 @@ class StockEntry(StockController):
|
||||
|
||||
serial_nos.append(sn)
|
||||
|
||||
def validate_subcontracting_order(self):
|
||||
"""Throw exception if more raw material is transferred against Subcontracting Order than in
|
||||
def validate_subcontract_order(self):
|
||||
"""Throw exception if more raw material is transferred against Subcontract Order than in
|
||||
the raw materials supplied table"""
|
||||
backflush_raw_materials_based_on = frappe.db.get_single_value(
|
||||
"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"))
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
item_code = se_item.original_item or se_item.item_code
|
||||
precision = cint(frappe.db.get_default("float_precision")) or 3
|
||||
required_qty = sum(
|
||||
[
|
||||
flt(d.required_qty)
|
||||
for d in subcontracting_order.supplied_items
|
||||
if d.rm_item_code == item_code
|
||||
]
|
||||
[flt(d.required_qty) for d in subcontract_order.supplied_items if d.rm_item_code == item_code]
|
||||
)
|
||||
|
||||
total_allowed = required_qty + (required_qty * (qty_allowance / 100))
|
||||
|
||||
if not required_qty:
|
||||
bom_no = frappe.db.get_value(
|
||||
"Subcontracting Order Item",
|
||||
{"parent": self.subcontracting_order, "item_code": se_item.subcontracted_item},
|
||||
f"{self.subcontract_data.order_doctype} Item",
|
||||
{
|
||||
"parent": self.get(self.subcontract_data.order_field),
|
||||
"item_code": se_item.subcontracted_item,
|
||||
},
|
||||
"bom",
|
||||
)
|
||||
|
||||
@ -851,7 +873,7 @@ class StockEntry(StockController):
|
||||
required_qty = sum(
|
||||
[
|
||||
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
|
||||
]
|
||||
)
|
||||
@ -860,43 +882,57 @@ class StockEntry(StockController):
|
||||
|
||||
if not required_qty:
|
||||
frappe.throw(
|
||||
_("Item {0} not found in 'Raw Materials Supplied' table in Subcontracting Order {1}").format(
|
||||
se_item.item_code, self.subcontracting_order
|
||||
_("Item {0} not found in 'Raw Materials Supplied' table in {1} {2}").format(
|
||||
se_item.item_code,
|
||||
self.subcontract_data.order_doctype,
|
||||
self.get(self.subcontract_data.order_field),
|
||||
)
|
||||
)
|
||||
|
||||
parent = frappe.qb.DocType("Stock Entry")
|
||||
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 = (
|
||||
frappe.qb.from_(parent)
|
||||
.inner_join(child)
|
||||
.on(parent.name == child.parent)
|
||||
.select(Sum(child.transfer_qty))
|
||||
.where(parent.docstatus == 1)
|
||||
.where(parent.subcontracting_order == self.subcontracting_order)
|
||||
.where(child.item_code == se_item.item_code)
|
||||
.where(conditions)
|
||||
).run()[0][0]
|
||||
|
||||
if flt(total_supplied, precision) > flt(total_allowed, precision):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row {0}# Item {1} cannot be transferred more than {2} against Subcontracting Order {3}"
|
||||
).format(
|
||||
se_item.idx, se_item.item_code, total_allowed, self.subcontracting_order
|
||||
_("Row {0}# Item {1} cannot be transferred more than {2} against {3} {4}").format(
|
||||
se_item.idx,
|
||||
se_item.item_code,
|
||||
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 = {
|
||||
"parent": self.subcontracting_order,
|
||||
"parent": self.get(self.subcontract_data.order_field),
|
||||
"docstatus": 1,
|
||||
"rm_item_code": se_item.item_code,
|
||||
"main_item_code": se_item.subcontracted_item,
|
||||
}
|
||||
|
||||
sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name")
|
||||
if sco_rm_detail:
|
||||
se_item.db_set("sco_rm_detail", sco_rm_detail)
|
||||
order_rm_detail = frappe.db.get_value(
|
||||
self.subcontract_data.order_supplied_items_field, filters, "name"
|
||||
)
|
||||
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":
|
||||
for row in self.items:
|
||||
if not row.subcontracted_item:
|
||||
@ -905,17 +941,19 @@ class StockEntry(StockController):
|
||||
row.idx, frappe.bold(row.item_code)
|
||||
)
|
||||
)
|
||||
elif not row.sco_rm_detail:
|
||||
elif not row.get(self.subcontract_data.rm_detail_field):
|
||||
filters = {
|
||||
"parent": self.subcontracting_order,
|
||||
"parent": self.get(self.subcontract_data.order_field),
|
||||
"docstatus": 1,
|
||||
"rm_item_code": row.item_code,
|
||||
"main_item_code": row.subcontracted_item,
|
||||
}
|
||||
|
||||
sco_rm_detail = frappe.db.get_value("Subcontracting Order Supplied Item", filters, "name")
|
||||
if sco_rm_detail:
|
||||
row.db_set("sco_rm_detail", sco_rm_detail)
|
||||
order_rm_detail = frappe.db.get_value(
|
||||
self.subcontract_data.order_supplied_items_field, filters, "name"
|
||||
)
|
||||
if order_rm_detail:
|
||||
row.db_set(self.subcontract_data.rm_detail_field, order_rm_detail)
|
||||
|
||||
def validate_bom(self):
|
||||
for d in self.get("items"):
|
||||
@ -1263,12 +1301,12 @@ class StockEntry(StockController):
|
||||
|
||||
if (
|
||||
self.purpose == "Send to Subcontractor"
|
||||
and self.get("subcontracting_order")
|
||||
and self.get(self.subcontract_data.order_field)
|
||||
and args.get("item_code")
|
||||
):
|
||||
subcontract_items = frappe.get_all(
|
||||
"Subcontracting Order Supplied Item",
|
||||
{"parent": self.subcontracting_order, "rm_item_code": args.get("item_code")},
|
||||
self.subcontract_data.order_supplied_items_field,
|
||||
{"parent": self.get(self.subcontract_data.order_field), "rm_item_code": args.get("item_code")},
|
||||
"main_item_code",
|
||||
)
|
||||
|
||||
@ -1362,18 +1400,18 @@ class StockEntry(StockController):
|
||||
|
||||
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
|
||||
|
||||
# Get SCO Supplied Items Details
|
||||
if self.subcontracting_order and self.purpose == "Send to Subcontractor":
|
||||
# Get SCO Supplied Items Details
|
||||
parent = frappe.qb.DocType("Subcontracting Order")
|
||||
child = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
||||
# Get Subcontract Order Supplied Items Details
|
||||
if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
|
||||
# Get Subcontract Order Supplied Items Details
|
||||
parent = frappe.qb.DocType(self.subcontract_data.order_doctype)
|
||||
child = frappe.qb.DocType(self.subcontract_data.order_supplied_items_field)
|
||||
|
||||
item_wh = (
|
||||
frappe.qb.from_(parent)
|
||||
.inner_join(child)
|
||||
.on(parent.name == child.parent)
|
||||
.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)
|
||||
|
||||
item_wh = frappe._dict(item_wh)
|
||||
@ -1381,8 +1419,8 @@ class StockEntry(StockController):
|
||||
for item in item_dict.values():
|
||||
if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
|
||||
item["from_warehouse"] = self.pro_doc.wip_warehouse
|
||||
# Get Reserve Warehouse from SCO
|
||||
if self.subcontracting_order and self.purpose == "Send to Subcontractor":
|
||||
# Get Reserve Warehouse from Subcontract Order
|
||||
if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
|
||||
item["from_warehouse"] = item_wh.get(item.item_code)
|
||||
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,
|
||||
)
|
||||
|
||||
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():
|
||||
# if source warehouse presents in BOM set from_warehouse as bom source_warehouse
|
||||
if item["allow_alternative_item"]:
|
||||
@ -1925,7 +1965,7 @@ class StockEntry(StockController):
|
||||
se_child.is_process_loss = item_row.get("is_process_loss", 0)
|
||||
|
||||
for field in [
|
||||
"sco_rm_detail",
|
||||
self.subcontract_data.rm_detail_field,
|
||||
"original_item",
|
||||
"expense_account",
|
||||
"description",
|
||||
@ -1999,33 +2039,37 @@ class StockEntry(StockController):
|
||||
else:
|
||||
frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code))
|
||||
|
||||
def update_subcontracting_order_supplied_items(self):
|
||||
if self.subcontracting_order and (
|
||||
self.purpose in ["Send to Subcontractor", "Material Transfer"]
|
||||
def update_subcontract_order_supplied_items(self):
|
||||
if self.get(self.subcontract_data.order_field) and (
|
||||
self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return
|
||||
):
|
||||
|
||||
# Get SCO Supplied Items Details
|
||||
sco_supplied_items = frappe.db.get_all(
|
||||
"Subcontracting Order Supplied Item",
|
||||
filters={"parent": self.subcontracting_order},
|
||||
# Get Subcontract Order Supplied Items Details
|
||||
order_supplied_items = frappe.db.get_all(
|
||||
self.subcontract_data.order_supplied_items_field,
|
||||
filters={"parent": self.get(self.subcontract_data.order_field)},
|
||||
fields=["name", "rm_item_code", "reserve_warehouse"],
|
||||
)
|
||||
|
||||
# Get Items Supplied in Stock Entries against SCO
|
||||
supplied_items = get_supplied_items(self.subcontracting_order)
|
||||
# Get Items Supplied in Stock Entries against Subcontract 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, {}
|
||||
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}
|
||||
else:
|
||||
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
|
||||
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"):
|
||||
# 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
|
||||
|
||||
|
||||
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 = ""
|
||||
|
||||
if subcontracting_order:
|
||||
cond = "and ste.purpose = 'Send to Subcontractor' and ste.subcontracting_order = '{0}'".format(
|
||||
subcontracting_order
|
||||
)
|
||||
if subcontract_order:
|
||||
cond = f"and ste.purpose = 'Send to Subcontractor' and ste.{subcontract_order_field} = '{subcontract_order}'"
|
||||
elif work_order:
|
||||
cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(
|
||||
work_order
|
||||
@ -2524,25 +2568,27 @@ def validate_sample_quantity(item_code, sample_quantity, qty, batch_no=None):
|
||||
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 = [
|
||||
"`tabStock Entry Detail`.`transfer_qty`",
|
||||
"`tabStock Entry`.`is_return`",
|
||||
"`tabStock Entry Detail`.`sco_rm_detail`",
|
||||
f"`tabStock Entry Detail`.`{rm_detail_field}`",
|
||||
"`tabStock Entry Detail`.`item_code`",
|
||||
]
|
||||
|
||||
filters = [
|
||||
["Stock Entry", "docstatus", "=", 1],
|
||||
["Stock Entry", "subcontracting_order", "=", subcontracting_order],
|
||||
["Stock Entry", subcontract_order_field, "=", subcontract_order],
|
||||
]
|
||||
|
||||
supplied_item_details = {}
|
||||
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
|
||||
|
||||
key = row.sco_rm_detail
|
||||
key = row.get(rm_detail_field)
|
||||
if key not in supplied_item_details:
|
||||
supplied_item_details.setdefault(
|
||||
key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0})
|
||||
|
@ -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))
|
||||
|
||||
elif args.transaction_type == "buying" and args.doctype != "Material Request":
|
||||
if args.get("is_subcontracted") and item.is_stock_item:
|
||||
throw(_("Item {0} must be a Non-Stock Item").format(item.name))
|
||||
if args.get("is_subcontracted"):
|
||||
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):
|
||||
|
@ -738,6 +738,13 @@ class update_entries_after(object):
|
||||
"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):
|
||||
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)
|
||||
|
@ -24,7 +24,8 @@ frappe.ui.form.on('Subcontracting Order', {
|
||||
return {
|
||||
filters: {
|
||||
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) {
|
||||
frm.add_custom_button(__('Return of Components'), () => {
|
||||
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_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) {
|
||||
if (r && r.message) {
|
||||
const doc = frappe.model.sync(r.message);
|
||||
@ -306,10 +311,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
|
||||
|
||||
make_rm_stock_entry(rm_items) {
|
||||
frappe.call({
|
||||
method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_rm_stock_entry',
|
||||
method: 'erpnext.controllers.subcontracting_controller.make_rm_stock_entry',
|
||||
args: {
|
||||
subcontracting_order: cur_frm.doc.name,
|
||||
rm_items: rm_items
|
||||
subcontract_order: cur_frm.doc.name,
|
||||
rm_items: rm_items,
|
||||
order_doctype: cur_frm.doc.doctype
|
||||
},
|
||||
callback: (r) => {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
|
@ -1,8 +1,6 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@ -42,6 +40,9 @@ class SubcontractingOrder(SubcontractingController):
|
||||
if not po.is_subcontracted:
|
||||
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:
|
||||
msg = f"Please submit Purchase Order {po.name} before proceeding."
|
||||
frappe.throw(_(msg))
|
||||
@ -227,143 +228,6 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None):
|
||||
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()
|
||||
def update_subcontracting_order_status(sco):
|
||||
if isinstance(sco, str):
|
||||
|
@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
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 (
|
||||
get_rm_items,
|
||||
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.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
make_rm_stock_entry,
|
||||
make_subcontracting_receipt,
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
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
|
||||
|
||||
@ -78,7 +78,7 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.set_subcontracting_order_status()
|
||||
self.set_consumed_qty_in_sco()
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
self.update_stock_ledger()
|
||||
|
||||
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.repost_future_sle_and_gle()
|
||||
self.delete_auto_created_batches()
|
||||
self.set_consumed_qty_in_sco()
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
self.set_subcontracting_order_status()
|
||||
self.update_status()
|
||||
|
||||
@ -162,17 +162,6 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
if not item.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):
|
||||
if self.docstatus >= 1 and not status:
|
||||
if self.docstatus == 1:
|
||||
|
@ -119,7 +119,7 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
receive more than the required qty in the 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,
|
||||
)
|
||||
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
|
||||
@ -188,8 +188,8 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
self.assertRaises(frappe.ValidationError, scr2.submit)
|
||||
|
||||
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 (
|
||||
make_rm_stock_entry,
|
||||
make_subcontracting_receipt,
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user