Merge pull request #36811 from s-aga-r/SCR-SCRAP-ITEMS
feat: provision to add scrap items in Subcontracting Receipt
This commit is contained in:
commit
eb5c61be1e
@ -436,24 +436,6 @@ class BuyingController(SubcontractingController):
|
|||||||
|
|
||||||
# validate rate with ref PR
|
# validate rate with ref PR
|
||||||
|
|
||||||
def validate_rejected_warehouse(self):
|
|
||||||
for item in self.get("items"):
|
|
||||||
if flt(item.rejected_qty) and not item.rejected_warehouse:
|
|
||||||
if self.rejected_warehouse:
|
|
||||||
item.rejected_warehouse = self.rejected_warehouse
|
|
||||||
|
|
||||||
if not item.rejected_warehouse:
|
|
||||||
frappe.throw(
|
|
||||||
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
|
|
||||||
item.idx, item.item_code
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
|
|
||||||
frappe.throw(
|
|
||||||
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
|
|
||||||
)
|
|
||||||
|
|
||||||
# validate accepted and rejected qty
|
# validate accepted and rejected qty
|
||||||
def validate_accepted_rejected_qty(self):
|
def validate_accepted_rejected_qty(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
|||||||
@ -55,6 +55,23 @@ class SubcontractingController(StockController):
|
|||||||
else:
|
else:
|
||||||
super(SubcontractingController, self).validate()
|
super(SubcontractingController, self).validate()
|
||||||
|
|
||||||
|
def validate_rejected_warehouse(self):
|
||||||
|
for item in self.get("items"):
|
||||||
|
if flt(item.rejected_qty) and not item.rejected_warehouse:
|
||||||
|
if self.rejected_warehouse:
|
||||||
|
item.rejected_warehouse = self.rejected_warehouse
|
||||||
|
else:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
|
||||||
|
item.idx, item.item_code
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
|
||||||
|
)
|
||||||
|
|
||||||
def remove_empty_rows(self):
|
def remove_empty_rows(self):
|
||||||
for key in ["service_items", "items", "supplied_items"]:
|
for key in ["service_items", "items", "supplied_items"]:
|
||||||
if self.get(key):
|
if self.get(key):
|
||||||
@ -80,23 +97,27 @@ class SubcontractingController(StockController):
|
|||||||
if not is_stock_item:
|
if not is_stock_item:
|
||||||
frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name))
|
frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name))
|
||||||
|
|
||||||
if not is_sub_contracted_item:
|
if not item.get("is_scrap_item"):
|
||||||
frappe.throw(
|
if not is_sub_contracted_item:
|
||||||
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
|
frappe.throw(
|
||||||
)
|
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
|
||||||
|
)
|
||||||
|
|
||||||
if item.bom:
|
if item.bom:
|
||||||
bom = frappe.get_doc("BOM", item.bom)
|
is_active, bom_item = frappe.get_value("BOM", item.bom, ["is_active", "item"])
|
||||||
if not bom.is_active:
|
|
||||||
frappe.throw(
|
if not is_active:
|
||||||
_("Row {0}: Please select an active BOM for Item {1}.").format(item.idx, item.item_name)
|
frappe.throw(
|
||||||
)
|
_("Row {0}: Please select an active BOM for Item {1}.").format(item.idx, item.item_name)
|
||||||
if bom.item != item.item_code:
|
)
|
||||||
frappe.throw(
|
if bom_item != item.item_code:
|
||||||
_("Row {0}: Please select an valid BOM for Item {1}.").format(item.idx, item.item_name)
|
frappe.throw(
|
||||||
)
|
_("Row {0}: Please select an valid BOM for Item {1}.").format(item.idx, item.item_name)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
frappe.throw(_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name))
|
||||||
else:
|
else:
|
||||||
frappe.throw(_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name))
|
item.bom = None
|
||||||
|
|
||||||
def __get_data_before_save(self):
|
def __get_data_before_save(self):
|
||||||
item_dict = {}
|
item_dict = {}
|
||||||
@ -874,19 +895,24 @@ class SubcontractingController(StockController):
|
|||||||
|
|
||||||
if self.total_additional_costs:
|
if self.total_additional_costs:
|
||||||
if self.distribute_additional_costs_based_on == "Amount":
|
if self.distribute_additional_costs_based_on == "Amount":
|
||||||
total_amt = sum(flt(item.amount) for item in self.get("items"))
|
total_amt = sum(
|
||||||
|
flt(item.amount) for item in self.get("items") if not item.get("is_scrap_item")
|
||||||
|
)
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.additional_cost_per_qty = (
|
if not item.get("is_scrap_item"):
|
||||||
(item.amount * self.total_additional_costs) / total_amt
|
item.additional_cost_per_qty = (
|
||||||
) / item.qty
|
(item.amount * self.total_additional_costs) / total_amt
|
||||||
|
) / item.qty
|
||||||
else:
|
else:
|
||||||
total_qty = sum(flt(item.qty) for item in self.get("items"))
|
total_qty = sum(flt(item.qty) for item in self.get("items") if not item.get("is_scrap_item"))
|
||||||
additional_cost_per_qty = self.total_additional_costs / total_qty
|
additional_cost_per_qty = self.total_additional_costs / total_qty
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.additional_cost_per_qty = additional_cost_per_qty
|
if not item.get("is_scrap_item"):
|
||||||
|
item.additional_cost_per_qty = additional_cost_per_qty
|
||||||
else:
|
else:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.additional_cost_per_qty = 0
|
if not item.get("is_scrap_item"):
|
||||||
|
item.additional_cost_per_qty = 0
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_current_stock(self):
|
def get_current_stock(self):
|
||||||
|
|||||||
@ -203,7 +203,10 @@ def get_mapped_subcontracting_receipt(source_name, target_doc=None):
|
|||||||
{
|
{
|
||||||
"Subcontracting Order": {
|
"Subcontracting Order": {
|
||||||
"doctype": "Subcontracting Receipt",
|
"doctype": "Subcontracting Receipt",
|
||||||
"field_map": {"supplier_warehouse": "supplier_warehouse"},
|
"field_map": {
|
||||||
|
"supplier_warehouse": "supplier_warehouse",
|
||||||
|
"set_warehouse": "set_warehouse",
|
||||||
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"docstatus": ["=", 1],
|
"docstatus": ["=", 1],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -591,6 +591,13 @@ def create_subcontracting_order(**args):
|
|||||||
for idx, val in enumerate(sco.items):
|
for idx, val in enumerate(sco.items):
|
||||||
val.warehouse = warehouses[idx]
|
val.warehouse = warehouses[idx]
|
||||||
|
|
||||||
|
warehouses = set()
|
||||||
|
for item in sco.items:
|
||||||
|
warehouses.add(item.warehouse)
|
||||||
|
|
||||||
|
if len(warehouses) == 1:
|
||||||
|
sco.set_warehouse = list(warehouses)[0]
|
||||||
|
|
||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
sco.insert()
|
sco.insert()
|
||||||
if not args.do_not_submit:
|
if not args.do_not_submit:
|
||||||
|
|||||||
@ -22,7 +22,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
|
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
|
||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
show_cancelled_entries: frm.doc.docstatus === 2
|
show_cancelled_entries: frm.doc.docstatus === 2
|
||||||
};
|
}
|
||||||
frappe.set_route('query-report', 'Stock Ledger');
|
frappe.set_route('query-report', 'Stock Ledger');
|
||||||
}, __('View'));
|
}, __('View'));
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
group_by: 'Group by Voucher (Consolidated)',
|
group_by: 'Group by Voucher (Consolidated)',
|
||||||
show_cancelled_entries: frm.doc.docstatus === 2
|
show_cancelled_entries: frm.doc.docstatus === 2
|
||||||
};
|
}
|
||||||
frappe.set_route('query-report', 'General Ledger');
|
frappe.set_route('query-report', 'General Ledger');
|
||||||
}, __('View'));
|
}, __('View'));
|
||||||
}
|
}
|
||||||
@ -94,7 +94,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
is_group: 0
|
is_group: 0
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('rejected_warehouse', () => {
|
frm.set_query('rejected_warehouse', () => {
|
||||||
@ -103,7 +103,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
is_group: 0
|
is_group: 0
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('supplier_warehouse', () => {
|
frm.set_query('supplier_warehouse', () => {
|
||||||
@ -112,7 +112,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
company: frm.doc.company,
|
company: frm.doc.company,
|
||||||
is_group: 0
|
is_group: 0
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('warehouse', 'items', () => ({
|
frm.set_query('warehouse', 'items', () => ({
|
||||||
@ -129,10 +129,12 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
frm.set_query('expense_account', 'items', () => ({
|
frm.set_query('expense_account', 'items', () => {
|
||||||
|
return {
|
||||||
query: 'erpnext.controllers.queries.get_expense_account',
|
query: 'erpnext.controllers.queries.get_expense_account',
|
||||||
filters: { 'company': frm.doc.company }
|
filters: { 'company': frm.doc.company }
|
||||||
}));
|
}
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query('batch_no', 'items', (doc, cdt, cdn) => {
|
frm.set_query('batch_no', 'items', (doc, cdt, cdn) => {
|
||||||
var row = locals[cdt][cdn];
|
var row = locals[cdt][cdn];
|
||||||
@ -140,7 +142,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
filters: {
|
filters: {
|
||||||
item: row.item_code
|
item: row.item_code
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('batch_no', 'supplied_items', (doc, cdt, cdn) => {
|
frm.set_query('batch_no', 'supplied_items', (doc, cdt, cdn) => {
|
||||||
@ -149,7 +151,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
filters: {
|
filters: {
|
||||||
item: row.rm_item_code
|
item: row.rm_item_code
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
frm.set_query('serial_and_batch_bundle', 'supplied_items', (doc, cdt, cdn) => {
|
frm.set_query('serial_and_batch_bundle', 'supplied_items', (doc, cdt, cdn) => {
|
||||||
@ -171,7 +173,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
'item_code': row.doc.rm_item_code,
|
'item_code': row.doc.rm_item_code,
|
||||||
'voucher_type': frm.doc.doctype,
|
'voucher_type': frm.doc.doctype,
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let batch_no_field = frm.get_docfield('items', 'batch_no');
|
let batch_no_field = frm.get_docfield('items', 'batch_no');
|
||||||
@ -180,7 +182,7 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
return {
|
return {
|
||||||
'item': row.doc.item_code
|
'item': row.doc.item_code
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -190,15 +192,37 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
|||||||
transaction_controller.setup_quality_inspection();
|
transaction_controller.setup_quality_inspection();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get_scrap_items: (frm) => {
|
||||||
|
frappe.call({
|
||||||
|
doc: frm.doc,
|
||||||
|
method: 'get_scrap_items',
|
||||||
|
args: {
|
||||||
|
recalculate_rate: true
|
||||||
|
},
|
||||||
|
freeze: true,
|
||||||
|
freeze_message: __('Getting Scrap Items'),
|
||||||
|
callback: (r) => {
|
||||||
|
if (!r.exc) {
|
||||||
|
frm.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Landed Cost Taxes and Charges', {
|
frappe.ui.form.on('Landed Cost Taxes and Charges', {
|
||||||
amount: (frm, cdt, cdn) => {
|
amount: (frm, cdt, cdn) => {
|
||||||
|
set_missing_values(frm);
|
||||||
frm.events.set_base_amount(frm, cdt, cdn);
|
frm.events.set_base_amount(frm, cdt, cdn);
|
||||||
},
|
},
|
||||||
|
|
||||||
expense_account: (frm, cdt, cdn) => {
|
expense_account: (frm, cdt, cdn) => {
|
||||||
frm.events.set_account_currency(frm, cdt, cdn);
|
frm.events.set_account_currency(frm, cdt, cdn);
|
||||||
|
},
|
||||||
|
|
||||||
|
additional_costs_remove: (frm) => {
|
||||||
|
set_missing_values(frm);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -214,6 +238,16 @@ frappe.ui.form.on('Subcontracting Receipt Item', {
|
|||||||
rate(frm) {
|
rate(frm) {
|
||||||
set_missing_values(frm);
|
set_missing_values(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
recalculate_rate(frm) {
|
||||||
|
if (frm.doc.recalculate_rate) {
|
||||||
|
set_missing_values(frm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
items_remove: (frm) => {
|
||||||
|
set_missing_values(frm);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Subcontracting Receipt Supplied Item', {
|
frappe.ui.form.on('Subcontracting Receipt Supplied Item', {
|
||||||
@ -225,7 +259,7 @@ frappe.ui.form.on('Subcontracting Receipt Supplied Item', {
|
|||||||
let set_warehouse_in_children = (child_table, warehouse_field, warehouse) => {
|
let set_warehouse_in_children = (child_table, warehouse_field, warehouse) => {
|
||||||
let transaction_controller = new erpnext.TransactionController();
|
let transaction_controller = new erpnext.TransactionController();
|
||||||
transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse);
|
transaction_controller.autofill_warehouse(child_table, warehouse_field, warehouse);
|
||||||
};
|
}
|
||||||
|
|
||||||
let set_missing_values = (frm) => {
|
let set_missing_values = (frm) => {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
@ -235,4 +269,4 @@ let set_missing_values = (frm) => {
|
|||||||
if (!r.exc) frm.refresh();
|
if (!r.exc) frm.refresh();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
@ -40,6 +40,7 @@
|
|||||||
"col_break_warehouse",
|
"col_break_warehouse",
|
||||||
"supplier_warehouse",
|
"supplier_warehouse",
|
||||||
"items_section",
|
"items_section",
|
||||||
|
"get_scrap_items",
|
||||||
"items",
|
"items",
|
||||||
"section_break0",
|
"section_break0",
|
||||||
"total_qty",
|
"total_qty",
|
||||||
@ -285,7 +286,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "supplied_items",
|
"depends_on": "eval: (!doc.__islocal && doc.docstatus == 0 && doc.supplied_items)",
|
||||||
"fieldname": "get_current_stock",
|
"fieldname": "get_current_stock",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Get Current Stock",
|
"label": "Get Current Stock",
|
||||||
@ -626,12 +627,19 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Edit Posting Date and Time",
|
"label": "Edit Posting Date and Time",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: (!doc.__islocal && doc.docstatus == 0)",
|
||||||
|
"fieldname": "get_scrap_items",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Get Scrap Items",
|
||||||
|
"options": "get_scrap_items"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-06 18:43:16.171842",
|
"modified": "2023-08-26 10:52:04.050829",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt",
|
"name": "Subcontracting Receipt",
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from frappe.utils import cint, flt, getdate, nowdate
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||||
|
from erpnext.stock.stock_ledger import get_valuation_rate
|
||||||
|
|
||||||
|
|
||||||
class SubcontractingReceipt(SubcontractingController):
|
class SubcontractingReceipt(SubcontractingController):
|
||||||
@ -36,33 +37,6 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_status_updater_args(self):
|
|
||||||
if cint(self.is_return):
|
|
||||||
self.status_updater.extend(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"source_dt": "Subcontracting Receipt Item",
|
|
||||||
"target_dt": "Subcontracting Order Item",
|
|
||||||
"join_field": "subcontracting_order_item",
|
|
||||||
"target_field": "returned_qty",
|
|
||||||
"source_field": "-1 * qty",
|
|
||||||
"extra_cond": """ and exists (select name from `tabSubcontracting Receipt`
|
|
||||||
where name=`tabSubcontracting Receipt Item`.parent and is_return=1)""",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source_dt": "Subcontracting Receipt Item",
|
|
||||||
"target_dt": "Subcontracting Receipt Item",
|
|
||||||
"join_field": "subcontracting_receipt_item",
|
|
||||||
"target_field": "returned_qty",
|
|
||||||
"target_parent_dt": "Subcontracting Receipt",
|
|
||||||
"target_parent_field": "per_returned",
|
|
||||||
"target_ref_field": "received_qty",
|
|
||||||
"source_field": "-1 * received_qty",
|
|
||||||
"percent_join_field_parent": "return_against",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def before_validate(self):
|
def before_validate(self):
|
||||||
super(SubcontractingReceipt, self).before_validate()
|
super(SubcontractingReceipt, self).before_validate()
|
||||||
self.validate_items_qty()
|
self.validate_items_qty()
|
||||||
@ -71,15 +45,8 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.set_items_expense_account()
|
self.set_items_expense_account()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if (
|
self.reset_supplied_items()
|
||||||
frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on")
|
|
||||||
== "BOM"
|
|
||||||
):
|
|
||||||
self.supplied_items = []
|
|
||||||
super(SubcontractingReceipt, self).validate()
|
|
||||||
self.set_missing_values()
|
|
||||||
self.validate_posting_time()
|
self.validate_posting_time()
|
||||||
self.validate_rejected_warehouse()
|
|
||||||
|
|
||||||
if not self.get("is_return"):
|
if not self.get("is_return"):
|
||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
@ -87,15 +54,22 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
if getdate(self.posting_date) > getdate(nowdate()):
|
if getdate(self.posting_date) > getdate(nowdate()):
|
||||||
frappe.throw(_("Posting Date cannot be future date"))
|
frappe.throw(_("Posting Date cannot be future date"))
|
||||||
|
|
||||||
|
super(SubcontractingReceipt, self).validate()
|
||||||
|
|
||||||
|
if self.is_new() and self.get("_action") == "save" and not frappe.flags.in_test:
|
||||||
|
self.get_scrap_items()
|
||||||
|
|
||||||
|
self.set_missing_values()
|
||||||
|
|
||||||
|
if self.get("_action") == "submit":
|
||||||
|
self.validate_scrap_items()
|
||||||
|
self.validate_accepted_warehouse()
|
||||||
|
self.validate_rejected_warehouse()
|
||||||
|
|
||||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||||
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||||
self.get_current_stock()
|
self.get_current_stock()
|
||||||
|
|
||||||
def on_update(self):
|
|
||||||
for table_field in ["items", "supplied_items"]:
|
|
||||||
if self.get(table_field):
|
|
||||||
self.set_serial_and_batch_bundle(table_field)
|
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.validate_available_qty_for_consumption()
|
self.validate_available_qty_for_consumption()
|
||||||
self.update_status_updater_args()
|
self.update_status_updater_args()
|
||||||
@ -107,6 +81,11 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.repost_future_sle_and_gle()
|
self.repost_future_sle_and_gle()
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
|
def on_update(self):
|
||||||
|
for table_field in ["items", "supplied_items"]:
|
||||||
|
if self.get(table_field):
|
||||||
|
self.set_serial_and_batch_bundle(table_field)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.ignore_linked_doctypes = (
|
self.ignore_linked_doctypes = (
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
@ -124,108 +103,6 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.set_subcontracting_order_status()
|
self.set_subcontracting_order_status()
|
||||||
self.update_status()
|
self.update_status()
|
||||||
|
|
||||||
@frappe.whitelist()
|
|
||||||
def set_missing_values(self):
|
|
||||||
self.calculate_additional_costs()
|
|
||||||
self.calculate_supplied_items_qty_and_amount()
|
|
||||||
self.calculate_items_qty_and_amount()
|
|
||||||
|
|
||||||
def set_available_qty_for_consumption(self):
|
|
||||||
supplied_items_details = {}
|
|
||||||
|
|
||||||
sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
|
||||||
for item in self.get("items"):
|
|
||||||
supplied_items = (
|
|
||||||
frappe.qb.from_(sco_supplied_item)
|
|
||||||
.select(
|
|
||||||
sco_supplied_item.rm_item_code,
|
|
||||||
sco_supplied_item.reference_name,
|
|
||||||
(sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"),
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
(sco_supplied_item.parent == item.subcontracting_order)
|
|
||||||
& (sco_supplied_item.main_item_code == item.item_code)
|
|
||||||
& (sco_supplied_item.reference_name == item.subcontracting_order_item)
|
|
||||||
)
|
|
||||||
).run(as_dict=True)
|
|
||||||
|
|
||||||
if supplied_items:
|
|
||||||
supplied_items_details[item.name] = {}
|
|
||||||
|
|
||||||
for supplied_item in supplied_items:
|
|
||||||
supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty
|
|
||||||
else:
|
|
||||||
for item in self.get("supplied_items"):
|
|
||||||
item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get(
|
|
||||||
item.rm_item_code, 0
|
|
||||||
)
|
|
||||||
|
|
||||||
def calculate_supplied_items_qty_and_amount(self):
|
|
||||||
for item in self.get("supplied_items") or []:
|
|
||||||
item.amount = item.rate * item.consumed_qty
|
|
||||||
|
|
||||||
self.set_available_qty_for_consumption()
|
|
||||||
|
|
||||||
def calculate_items_qty_and_amount(self):
|
|
||||||
rm_supp_cost = {}
|
|
||||||
for item in self.get("supplied_items") or []:
|
|
||||||
if item.reference_name in rm_supp_cost:
|
|
||||||
rm_supp_cost[item.reference_name] += item.amount
|
|
||||||
else:
|
|
||||||
rm_supp_cost[item.reference_name] = item.amount
|
|
||||||
|
|
||||||
total_qty = total_amount = 0
|
|
||||||
for item in self.items:
|
|
||||||
if item.qty and item.name in rm_supp_cost:
|
|
||||||
item.rm_supp_cost = rm_supp_cost[item.name]
|
|
||||||
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
|
|
||||||
rm_supp_cost.pop(item.name)
|
|
||||||
|
|
||||||
if item.recalculate_rate:
|
|
||||||
item.rate = (
|
|
||||||
flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
|
|
||||||
)
|
|
||||||
|
|
||||||
item.received_qty = item.qty + flt(item.rejected_qty)
|
|
||||||
item.amount = item.qty * item.rate
|
|
||||||
total_qty += item.qty
|
|
||||||
total_amount += item.amount
|
|
||||||
else:
|
|
||||||
self.total_qty = total_qty
|
|
||||||
self.total = total_amount
|
|
||||||
|
|
||||||
def validate_rejected_warehouse(self):
|
|
||||||
for item in self.items:
|
|
||||||
if flt(item.rejected_qty) and not item.rejected_warehouse:
|
|
||||||
if self.rejected_warehouse:
|
|
||||||
item.rejected_warehouse = self.rejected_warehouse
|
|
||||||
|
|
||||||
if not item.rejected_warehouse:
|
|
||||||
frappe.throw(
|
|
||||||
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
|
|
||||||
item.idx, item.item_code
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
|
|
||||||
frappe.throw(
|
|
||||||
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_available_qty_for_consumption(self):
|
|
||||||
for item in self.get("supplied_items"):
|
|
||||||
precision = item.precision("consumed_qty")
|
|
||||||
if (
|
|
||||||
item.available_qty_for_consumption
|
|
||||||
and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0
|
|
||||||
):
|
|
||||||
msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)}
|
|
||||||
must be less than or equal to Available Qty For Consumption
|
|
||||||
{flt(item.available_qty_for_consumption, precision)}
|
|
||||||
in Consumed Items Table."""
|
|
||||||
|
|
||||||
frappe.throw(_(msg))
|
|
||||||
|
|
||||||
def validate_items_qty(self):
|
def validate_items_qty(self):
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if not (item.qty or item.rejected_qty):
|
if not (item.qty or item.rejected_qty):
|
||||||
@ -267,6 +144,236 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
if not item.expense_account:
|
if not item.expense_account:
|
||||||
item.expense_account = expense_account
|
item.expense_account = expense_account
|
||||||
|
|
||||||
|
def reset_supplied_items(self):
|
||||||
|
if (
|
||||||
|
frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on")
|
||||||
|
== "BOM"
|
||||||
|
):
|
||||||
|
self.supplied_items = []
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_scrap_items(self, recalculate_rate=False):
|
||||||
|
self.remove_scrap_items()
|
||||||
|
|
||||||
|
for item in list(self.items):
|
||||||
|
if item.bom:
|
||||||
|
bom = frappe.get_doc("BOM", item.bom)
|
||||||
|
for scrap_item in bom.scrap_items:
|
||||||
|
qty = flt(item.qty) * (flt(scrap_item.stock_qty) / flt(bom.quantity))
|
||||||
|
rate = (
|
||||||
|
get_valuation_rate(
|
||||||
|
scrap_item.item_code,
|
||||||
|
self.set_warehouse,
|
||||||
|
self.doctype,
|
||||||
|
self.name,
|
||||||
|
currency=erpnext.get_company_currency(self.company),
|
||||||
|
company=self.company,
|
||||||
|
)
|
||||||
|
or scrap_item.rate
|
||||||
|
)
|
||||||
|
self.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"is_scrap_item": 1,
|
||||||
|
"reference_name": item.name,
|
||||||
|
"item_code": scrap_item.item_code,
|
||||||
|
"item_name": scrap_item.item_name,
|
||||||
|
"qty": qty,
|
||||||
|
"stock_uom": scrap_item.stock_uom,
|
||||||
|
"recalculate_rate": 0,
|
||||||
|
"rate": rate,
|
||||||
|
"rm_cost_per_qty": 0,
|
||||||
|
"service_cost_per_qty": 0,
|
||||||
|
"additional_cost_per_qty": 0,
|
||||||
|
"scrap_cost_per_qty": 0,
|
||||||
|
"amount": qty * rate,
|
||||||
|
"warehouse": self.set_warehouse,
|
||||||
|
"rejected_warehouse": self.rejected_warehouse,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if recalculate_rate:
|
||||||
|
self.calculate_additional_costs()
|
||||||
|
self.calculate_items_qty_and_amount()
|
||||||
|
|
||||||
|
def remove_scrap_items(self, recalculate_rate=False):
|
||||||
|
for item in list(self.items):
|
||||||
|
if item.is_scrap_item:
|
||||||
|
self.remove(item)
|
||||||
|
else:
|
||||||
|
item.scrap_cost_per_qty = 0
|
||||||
|
|
||||||
|
if recalculate_rate:
|
||||||
|
self.calculate_items_qty_and_amount()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def set_missing_values(self):
|
||||||
|
self.set_available_qty_for_consumption()
|
||||||
|
self.calculate_additional_costs()
|
||||||
|
self.calculate_items_qty_and_amount()
|
||||||
|
|
||||||
|
def set_available_qty_for_consumption(self):
|
||||||
|
supplied_items_details = {}
|
||||||
|
|
||||||
|
sco_supplied_item = frappe.qb.DocType("Subcontracting Order Supplied Item")
|
||||||
|
for item in self.get("items"):
|
||||||
|
supplied_items = (
|
||||||
|
frappe.qb.from_(sco_supplied_item)
|
||||||
|
.select(
|
||||||
|
sco_supplied_item.rm_item_code,
|
||||||
|
sco_supplied_item.reference_name,
|
||||||
|
(sco_supplied_item.total_supplied_qty - sco_supplied_item.consumed_qty).as_("available_qty"),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(sco_supplied_item.parent == item.subcontracting_order)
|
||||||
|
& (sco_supplied_item.main_item_code == item.item_code)
|
||||||
|
& (sco_supplied_item.reference_name == item.subcontracting_order_item)
|
||||||
|
)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if supplied_items:
|
||||||
|
supplied_items_details[item.name] = {}
|
||||||
|
|
||||||
|
for supplied_item in supplied_items:
|
||||||
|
supplied_items_details[item.name][supplied_item.rm_item_code] = supplied_item.available_qty
|
||||||
|
else:
|
||||||
|
for item in self.get("supplied_items"):
|
||||||
|
item.available_qty_for_consumption = supplied_items_details.get(item.reference_name, {}).get(
|
||||||
|
item.rm_item_code, 0
|
||||||
|
)
|
||||||
|
|
||||||
|
def calculate_items_qty_and_amount(self):
|
||||||
|
rm_cost_map = {}
|
||||||
|
for item in self.get("supplied_items") or []:
|
||||||
|
item.amount = flt(item.consumed_qty) * flt(item.rate)
|
||||||
|
|
||||||
|
if item.reference_name in rm_cost_map:
|
||||||
|
rm_cost_map[item.reference_name] += item.amount
|
||||||
|
else:
|
||||||
|
rm_cost_map[item.reference_name] = item.amount
|
||||||
|
|
||||||
|
scrap_cost_map = {}
|
||||||
|
for item in self.get("items") or []:
|
||||||
|
if item.is_scrap_item:
|
||||||
|
item.amount = flt(item.qty) * flt(item.rate)
|
||||||
|
|
||||||
|
if item.reference_name in scrap_cost_map:
|
||||||
|
scrap_cost_map[item.reference_name] += item.amount
|
||||||
|
else:
|
||||||
|
scrap_cost_map[item.reference_name] = item.amount
|
||||||
|
|
||||||
|
total_qty = total_amount = 0
|
||||||
|
for item in self.get("items") or []:
|
||||||
|
if not item.is_scrap_item:
|
||||||
|
if item.qty:
|
||||||
|
if item.name in rm_cost_map:
|
||||||
|
item.rm_supp_cost = rm_cost_map[item.name]
|
||||||
|
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
|
||||||
|
rm_cost_map.pop(item.name)
|
||||||
|
|
||||||
|
if item.name in scrap_cost_map:
|
||||||
|
item.scrap_cost_per_qty = scrap_cost_map[item.name] / item.qty
|
||||||
|
scrap_cost_map.pop(item.name)
|
||||||
|
else:
|
||||||
|
item.scrap_cost_per_qty = 0
|
||||||
|
|
||||||
|
if item.recalculate_rate:
|
||||||
|
item.rate = (
|
||||||
|
flt(item.rm_cost_per_qty)
|
||||||
|
+ flt(item.service_cost_per_qty)
|
||||||
|
+ flt(item.additional_cost_per_qty)
|
||||||
|
- flt(item.scrap_cost_per_qty)
|
||||||
|
)
|
||||||
|
|
||||||
|
item.received_qty = flt(item.qty) + flt(item.rejected_qty)
|
||||||
|
item.amount = flt(item.qty) * flt(item.rate)
|
||||||
|
|
||||||
|
total_qty += flt(item.qty)
|
||||||
|
total_amount += item.amount
|
||||||
|
else:
|
||||||
|
self.total_qty = total_qty
|
||||||
|
self.total = total_amount
|
||||||
|
|
||||||
|
def validate_scrap_items(self):
|
||||||
|
for item in self.items:
|
||||||
|
if item.is_scrap_item:
|
||||||
|
if not item.qty:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Scrap Item Qty cannot be zero").format(item.idx),
|
||||||
|
)
|
||||||
|
|
||||||
|
if item.rejected_qty:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Rejected Qty cannot be set for Scrap Item {1}.").format(
|
||||||
|
item.idx, frappe.bold(item.item_code)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not item.reference_name:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Finished Good reference is mandatory for Scrap Item {1}.").format(
|
||||||
|
item.idx, frappe.bold(item.item_code)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_accepted_warehouse(self):
|
||||||
|
for item in self.get("items"):
|
||||||
|
if flt(item.qty) and not item.warehouse:
|
||||||
|
if self.set_warehouse:
|
||||||
|
item.warehouse = self.set_warehouse
|
||||||
|
else:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Accepted Warehouse is mandatory for the accepted Item {1}").format(
|
||||||
|
item.idx, item.item_code
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if item.get("warehouse") and (item.get("warehouse") == item.get("rejected_warehouse")):
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_available_qty_for_consumption(self):
|
||||||
|
for item in self.get("supplied_items"):
|
||||||
|
precision = item.precision("consumed_qty")
|
||||||
|
if (
|
||||||
|
item.available_qty_for_consumption
|
||||||
|
and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0
|
||||||
|
):
|
||||||
|
msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)}
|
||||||
|
must be less than or equal to Available Qty For Consumption
|
||||||
|
{flt(item.available_qty_for_consumption, precision)}
|
||||||
|
in Consumed Items Table."""
|
||||||
|
|
||||||
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
|
def update_status_updater_args(self):
|
||||||
|
if cint(self.is_return):
|
||||||
|
self.status_updater.extend(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"source_dt": "Subcontracting Receipt Item",
|
||||||
|
"target_dt": "Subcontracting Order Item",
|
||||||
|
"join_field": "subcontracting_order_item",
|
||||||
|
"target_field": "returned_qty",
|
||||||
|
"source_field": "-1 * qty",
|
||||||
|
"extra_cond": """ and exists (select name from `tabSubcontracting Receipt`
|
||||||
|
where name=`tabSubcontracting Receipt Item`.parent and is_return=1)""",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source_dt": "Subcontracting Receipt Item",
|
||||||
|
"target_dt": "Subcontracting Receipt Item",
|
||||||
|
"join_field": "subcontracting_receipt_item",
|
||||||
|
"target_field": "returned_qty",
|
||||||
|
"target_parent_dt": "Subcontracting Receipt",
|
||||||
|
"target_parent_field": "per_returned",
|
||||||
|
"target_ref_field": "received_qty",
|
||||||
|
"source_field": "-1 * received_qty",
|
||||||
|
"percent_join_field_parent": "return_against",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def update_status(self, status=None, update_modified=False):
|
def update_status(self, status=None, update_modified=False):
|
||||||
if not status:
|
if not status:
|
||||||
if self.docstatus == 0:
|
if self.docstatus == 0:
|
||||||
|
|||||||
@ -23,6 +23,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import (
|
|||||||
make_subcontracted_items,
|
make_subcontracted_items,
|
||||||
set_backflush_based_on,
|
set_backflush_based_on,
|
||||||
)
|
)
|
||||||
|
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
|
||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
@ -507,8 +508,6 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)
|
self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)
|
||||||
|
|
||||||
def test_subcontracting_receipt_raw_material_rate(self):
|
def test_subcontracting_receipt_raw_material_rate(self):
|
||||||
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
|
|
||||||
|
|
||||||
# Step - 1: Set Backflush Based On as "BOM"
|
# Step - 1: Set Backflush Based On as "BOM"
|
||||||
set_backflush_based_on("BOM")
|
set_backflush_based_on("BOM")
|
||||||
|
|
||||||
@ -625,6 +624,77 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
|||||||
# ValidationError should not be raised as `Inspection Required before Purchase` is disabled
|
# ValidationError should not be raised as `Inspection Required before Purchase` is disabled
|
||||||
scr2.submit()
|
scr2.submit()
|
||||||
|
|
||||||
|
def test_scrap_items_for_subcontracting_receipt(self):
|
||||||
|
set_backflush_based_on("BOM")
|
||||||
|
|
||||||
|
fg_item = "Subcontracted Item SA1"
|
||||||
|
|
||||||
|
# Create Raw Materials
|
||||||
|
raw_materials = [
|
||||||
|
make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name,
|
||||||
|
make_item(properties={"is_stock_item": 1, "valuation_rate": 200}).name,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create Scrap Items
|
||||||
|
scrap_item_1 = make_item(properties={"is_stock_item": 1, "valuation_rate": 10}).name
|
||||||
|
scrap_item_2 = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name
|
||||||
|
scrap_items = [scrap_item_1, scrap_item_2]
|
||||||
|
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"item_code": "Subcontracted Service Item 1",
|
||||||
|
"qty": 10,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": fg_item,
|
||||||
|
"fg_item_qty": 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create BOM with Scrap Items
|
||||||
|
bom = make_bom(
|
||||||
|
item=fg_item, raw_materials=raw_materials, rate=100, currency="INR", do_not_submit=True
|
||||||
|
)
|
||||||
|
for idx, item in enumerate(bom.items):
|
||||||
|
item.qty = 1 * (idx + 1)
|
||||||
|
for idx, item in enumerate(scrap_items):
|
||||||
|
bom.append(
|
||||||
|
"scrap_items",
|
||||||
|
{
|
||||||
|
"item_code": item,
|
||||||
|
"stock_qty": 1 * (idx + 1),
|
||||||
|
"rate": 10 * (idx + 1),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
bom.save()
|
||||||
|
bom.submit()
|
||||||
|
|
||||||
|
# Create PO and SCO
|
||||||
|
sco = get_subcontracting_order(service_items=service_items)
|
||||||
|
|
||||||
|
# Inward Raw Materials
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
|
||||||
|
# Transfer RM's to Subcontractor
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Subcontracting Receipt
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.save()
|
||||||
|
scr.get_scrap_items()
|
||||||
|
|
||||||
|
# Test - 1: Scrap Items should be fetched from BOM in items table with `is_scrap_item` = 1
|
||||||
|
scr_scrap_items = set([item.item_code for item in scr.items if item.is_scrap_item])
|
||||||
|
self.assertEqual(len(scr.items), 3) # 1 FG Item + 2 Scrap Items
|
||||||
|
self.assertEqual(scr_scrap_items, set(scrap_items))
|
||||||
|
|
||||||
|
scr.submit()
|
||||||
|
|
||||||
|
|
||||||
def make_return_subcontracting_receipt(**args):
|
def make_return_subcontracting_receipt(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"item_code",
|
"item_code",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"item_name",
|
"item_name",
|
||||||
|
"is_scrap_item",
|
||||||
"section_break_4",
|
"section_break_4",
|
||||||
"description",
|
"description",
|
||||||
"brand",
|
"brand",
|
||||||
@ -24,8 +25,6 @@
|
|||||||
"col_break2",
|
"col_break2",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"tracking_section",
|
|
||||||
"col_break_tracking_section",
|
|
||||||
"rate_and_amount",
|
"rate_and_amount",
|
||||||
"rate",
|
"rate",
|
||||||
"amount",
|
"amount",
|
||||||
@ -34,18 +33,20 @@
|
|||||||
"rm_cost_per_qty",
|
"rm_cost_per_qty",
|
||||||
"service_cost_per_qty",
|
"service_cost_per_qty",
|
||||||
"additional_cost_per_qty",
|
"additional_cost_per_qty",
|
||||||
|
"scrap_cost_per_qty",
|
||||||
"rm_supp_cost",
|
"rm_supp_cost",
|
||||||
"warehouse_and_reference",
|
"warehouse_and_reference",
|
||||||
"warehouse",
|
"warehouse",
|
||||||
"rejected_warehouse",
|
|
||||||
"subcontracting_order",
|
"subcontracting_order",
|
||||||
"column_break_40",
|
|
||||||
"schedule_date",
|
|
||||||
"quality_inspection",
|
|
||||||
"subcontracting_order_item",
|
"subcontracting_order_item",
|
||||||
"subcontracting_receipt_item",
|
"subcontracting_receipt_item",
|
||||||
"section_break_45",
|
"column_break_40",
|
||||||
|
"rejected_warehouse",
|
||||||
"bom",
|
"bom",
|
||||||
|
"quality_inspection",
|
||||||
|
"schedule_date",
|
||||||
|
"reference_name",
|
||||||
|
"section_break_45",
|
||||||
"serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
"col_break5",
|
"col_break5",
|
||||||
@ -85,12 +86,13 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.item_name",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "item_name",
|
"fieldname": "item_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"label": "Item Name",
|
"label": "Item Name",
|
||||||
"print_hide": 1,
|
"print_hide": 1
|
||||||
"reqd": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@ -99,11 +101,12 @@
|
|||||||
"label": "Description"
|
"label": "Description"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fetch_from": "item_code.description",
|
||||||
|
"fetch_if_empty": 1,
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Text Editor",
|
"fieldtype": "Text Editor",
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"print_width": "300px",
|
"print_width": "300px",
|
||||||
"reqd": 1,
|
|
||||||
"width": "300px"
|
"width": "300px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -157,6 +160,7 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
|
"read_only_depends_on": "eval: doc.is_scrap_item",
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -214,6 +218,8 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.is_scrap_item",
|
||||||
"fieldname": "rm_cost_per_qty",
|
"fieldname": "rm_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Raw Material Cost Per Qty",
|
"label": "Raw Material Cost Per Qty",
|
||||||
@ -221,6 +227,8 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.is_scrap_item",
|
||||||
"fieldname": "service_cost_per_qty",
|
"fieldname": "service_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Service Cost Per Qty",
|
"label": "Service Cost Per Qty",
|
||||||
@ -229,6 +237,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.is_scrap_item",
|
||||||
"fieldname": "additional_cost_per_qty",
|
"fieldname": "additional_cost_per_qty",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Additional Cost Per Qty",
|
"label": "Additional Cost Per Qty",
|
||||||
@ -260,6 +269,7 @@
|
|||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
|
"read_only_depends_on": "eval: doc.is_scrap_item",
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -295,7 +305,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_45",
|
"fieldname": "section_break_45",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Serial and Batch Details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.is_fixed_asset",
|
"depends_on": "eval:!doc.is_fixed_asset",
|
||||||
@ -321,7 +332,8 @@
|
|||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"label": "Rejected Serial No",
|
"label": "Rejected Serial No",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "subcontracting_order_item",
|
"fieldname": "subcontracting_order_item",
|
||||||
@ -345,7 +357,8 @@
|
|||||||
"label": "BOM",
|
"label": "BOM",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "BOM",
|
"options": "BOM",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"read_only_depends_on": "eval: doc.is_scrap_item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.brand",
|
"fetch_from": "item_code.brand",
|
||||||
@ -410,14 +423,6 @@
|
|||||||
"fieldname": "image_column",
|
"fieldname": "image_column",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "tracking_section",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "col_break_tracking_section",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@ -456,6 +461,7 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "0",
|
||||||
"depends_on": "returned_qty",
|
"depends_on": "returned_qty",
|
||||||
"fieldname": "returned_qty",
|
"fieldname": "returned_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
@ -471,9 +477,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "1",
|
"default": "1",
|
||||||
|
"depends_on": "eval: !doc.is_scrap_item",
|
||||||
"fieldname": "recalculate_rate",
|
"fieldname": "recalculate_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Recalculate Rate"
|
"label": "Recalculate Rate",
|
||||||
|
"read_only_depends_on": "eval: doc.is_scrap_item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "serial_and_batch_bundle",
|
"fieldname": "serial_and_batch_bundle",
|
||||||
@ -490,12 +498,40 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.bom",
|
||||||
|
"fieldname": "is_scrap_item",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Scrap Item",
|
||||||
|
"no_copy": 1,
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only_depends_on": "eval: doc.bom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval: !doc.is_scrap_item",
|
||||||
|
"fieldname": "scrap_cost_per_qty",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Scrap Cost Per Qty",
|
||||||
|
"no_copy": 1,
|
||||||
|
"non_negative": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reference_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Reference Name",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-06 18:43:45.599761",
|
"modified": "2023-08-25 20:09:03.069417",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt Item",
|
"name": "Subcontracting Receipt Item",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user