feat: subcontract code refactor and enhancement

This commit is contained in:
Rohit Waghchaure
2021-05-24 20:11:15 +05:30
parent a2bb1b8acd
commit a94b89727c
24 changed files with 1418 additions and 555 deletions

View File

@@ -53,6 +53,38 @@ frappe.ui.form.on("Purchase Order", {
} else {
frm.set_value("tax_withholding_category", frm.supplier_tds);
}
},
refresh: function(frm) {
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.buying.doctype.purchase_order.purchase_order.get_materials_from_supplier',
freeze_message: __('Creating Stock Entry'),
args: { purchase_order: frm.doc.name, po_details: po_details },
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'));
}
}
});
@@ -217,7 +249,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
},
has_unsupplied_items: function() {
return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty)
return this.frm.doc['supplied_items'].some(item => item.required_qty > item.supplied_qty)
},
make_stock_entry: function() {
@@ -513,12 +545,14 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
],
primary_action: function() {
var data = d.get_values();
var content_msg = 'Reason for hold: ' + data.reason_for_hold;
frappe.call({
method: "frappe.desk.form.utils.add_comment",
args: {
reference_doctype: me.frm.doctype,
reference_name: me.frm.docname,
content: __('Reason for hold:') + " " +data.reason_for_hold,
content: __(content_msg),
comment_email: frappe.session.user,
comment_by: frappe.session.user_fullname
},

View File

@@ -609,6 +609,7 @@
"fieldname": "supplied_items",
"fieldtype": "Table",
"label": "Supplied Items",
"no_copy": 1,
"oldfieldname": "po_raw_material_details",
"oldfieldtype": "Table",
"options": "Purchase Order Item Supplied",
@@ -1377,7 +1378,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2021-04-19 00:55:30.781375",
"modified": "2021-05-30 15:17:53.663648",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -503,9 +503,10 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
@frappe.whitelist()
def make_rm_stock_entry(purchase_order, rm_items):
rm_items_list = rm_items
if isinstance(rm_items, string_types):
rm_items_list = json.loads(rm_items)
else:
elif not rm_items:
frappe.throw(_("No Items available for transfer"))
if rm_items_list:
@@ -543,6 +544,8 @@ def make_rm_stock_entry(purchase_order, rm_items):
'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')
}
@@ -582,3 +585,57 @@ def update_status(status, name):
def make_inter_company_sales_order(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
return make_inter_company_transaction("Purchase Order", source_name, target_doc)
@frappe.whitelist()
def get_materials_from_supplier(purchase_order, po_details):
if isinstance(po_details, string_types):
po_details = json.loads(po_details)
doc = frappe.get_cached_doc('Purchase Order', purchase_order)
doc.initialized_fields()
doc.purchase_orders = [doc.name]
doc.get_available_materials()
if not doc.available_materials:
frappe.throw(_('Materials are already received against the purchase order {0}')
.format(purchase_order))
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, po_details)
def make_return_stock_entry_for_subcontract(available_materials, po_doc, po_details):
ste_doc = frappe.new_doc('Stock Entry')
ste_doc.purpose = 'Material Transfer'
ste_doc.purchase_order = po_doc.name
ste_doc.company = po_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():
add_items_in_ste(ste_doc, value, value.qty, po_details, batch_no)
else:
add_items_in_ste(ste_doc, value, value.qty, po_details)
ste_doc.set_stock_entry_type()
ste_doc.calculate_rate_and_amount()
return ste_doc
def add_items_in_ste(ste_doc, row, qty, po_details, batch_no=None):
item = ste_doc.append('items', row.item_details)
po_detail = list(set(row.po_details).intersection(po_details))
item.update({
'qty': qty,
'batch_no': batch_no,
'basic_rate': row.item_details['rate'],
'po_detail': po_detail[0] if po_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 ''
})

View File

@@ -20,7 +20,6 @@ from erpnext.controllers.status_updater import OverAllowanceError
from erpnext.manufacturing.doctype.blanket_order.test_blanket_order import make_blanket_order
from erpnext.stock.doctype.batch.test_batch import make_new_batch
from erpnext.controllers.buying_controller import get_backflushed_subcontracted_raw_materials
class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_receipt(self):
@@ -771,7 +770,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
def test_exploded_items_in_subcontracted(self):
item_code = "_Test Subcontracted FG Item 1"
item_code = "_Test Subcontracted FG Item 11"
make_subcontracted_item(item_code=item_code)
po = create_purchase_order(item_code=item_code, qty=1,
@@ -853,76 +852,6 @@ class TestPurchaseOrder(unittest.TestCase):
update_backflush_based_on("BOM")
def test_backflushed_based_on_for_multiple_batches(self):
item_code = "_Test Subcontracted FG Item 2"
make_item('Sub Contracted Raw Material 2', {
'is_stock_item': 1,
'is_sub_contracted_item': 1
})
make_subcontracted_item(item_code=item_code, has_batch_no=1, create_new_batch=1,
raw_materials=["Sub Contracted Raw Material 2"])
update_backflush_based_on("Material Transferred for Subcontract")
order_qty = 500
po = create_purchase_order(item_code=item_code, qty=order_qty,
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
make_stock_entry(target="_Test Warehouse - _TC",
item_code = "Sub Contracted Raw Material 2", qty=552, basic_rate=100)
rm_items = [
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 2","item_name":"_Test Item",
"qty":552,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"}]
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
se.submit()
for batch in ["ABCD1", "ABCD2", "ABCD3", "ABCD4"]:
make_new_batch(batch_id=batch, item_code=item_code)
pr = make_purchase_receipt(po.name)
# partial receipt
pr.get('items')[0].qty = 30
pr.get('items')[0].batch_no = "ABCD1"
purchase_order = po.name
purchase_order_item = po.items[0].name
for batch_no, qty in {"ABCD2": 60, "ABCD3": 70, "ABCD4":40}.items():
pr.append("items", {
"item_code": pr.get('items')[0].item_code,
"item_name": pr.get('items')[0].item_name,
"uom": pr.get('items')[0].uom,
"stock_uom": pr.get('items')[0].stock_uom,
"warehouse": pr.get('items')[0].warehouse,
"conversion_factor": pr.get('items')[0].conversion_factor,
"cost_center": pr.get('items')[0].cost_center,
"rate": pr.get('items')[0].rate,
"qty": qty,
"batch_no": batch_no,
"purchase_order": purchase_order,
"purchase_order_item": purchase_order_item
})
pr.submit()
pr1 = make_purchase_receipt(po.name)
pr1.get('items')[0].qty = 300
pr1.get('items')[0].batch_no = "ABCD1"
pr1.save()
pr_key = ("Sub Contracted Raw Material 2", po.name)
consumed_qty = get_backflushed_subcontracted_raw_materials([po.name]).get(pr_key)
self.assertTrue(pr1.supplied_items[0].consumed_qty > 0)
self.assertTrue(pr1.supplied_items[0].consumed_qty, flt(552.0) - flt(consumed_qty))
update_backflush_based_on("BOM")
def test_supplied_qty_against_subcontracted_po(self):
item_code = "_Test Subcontracted FG Item 5"
make_item('Sub Contracted Raw Material 4', {
@@ -1117,22 +1046,29 @@ def create_purchase_order(**args):
po.conversion_factor = args.conversion_factor or 1
po.supplier_warehouse = args.supplier_warehouse or None
po.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 10,
"rate": args.rate or 500,
"schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get('include_exploded_items', 1),
"against_blanket_order": args.against_blanket_order
})
if args.rm_items:
for row in args.rm_items:
po.append("items", row)
else:
po.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 10,
"rate": args.rate or 500,
"schedule_date": add_days(nowdate(), 1),
"include_exploded_items": args.get('include_exploded_items', 1),
"against_blanket_order": args.against_blanket_order
})
po.set_missing_values()
if not args.do_not_save:
po.insert()
if not args.do_not_submit:
if po.is_subcontracted == "Yes":
supp_items = po.get("supplied_items")
for d in supp_items:
d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
if not d.reserve_warehouse:
d.reserve_warehouse = args.warehouse or "_Test Warehouse - _TC"
po.submit()
return po

View File

@@ -6,21 +6,25 @@
"engine": "InnoDB",
"field_order": [
"main_item_code",
"bom_detail_no",
"rm_item_code",
"column_break_3",
"stock_uom",
"reserve_warehouse",
"conversion_factor",
"column_break_6",
"rm_item_code",
"bom_detail_no",
"reference_name",
"reserve_warehouse",
"section_break2",
"rate",
"col_break2",
"amount",
"section_break1",
"required_qty",
"supplied_qty",
"col_break1",
"supplied_qty"
"returned_qty",
"total_supplied_qty",
"consumed_qty"
],
"fields": [
{
@@ -125,6 +129,8 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Supplied Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
@@ -142,13 +148,42 @@
{
"fieldname": "col_break2",
"fieldtype": "Column Break"
},
{
"fieldname": "consumed_qty",
"fieldtype": "Float",
"label": "Consumed Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "returned_qty",
"fieldtype": "Float",
"label": "Returned Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "total_supplied_qty",
"fieldtype": "Float",
"hidden": 1,
"label": "Total Supplied Qty",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
}
],
"hide_toolbar": 1,
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-09-18 17:26:09.703215",
"modified": "2021-06-01 00:41:54.123436",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",

View File

@@ -6,10 +6,11 @@
"engine": "InnoDB",
"field_order": [
"main_item_code",
"description",
"rm_item_code",
"item_name",
"bom_detail_no",
"col_break1",
"rm_item_code",
"description",
"stock_uom",
"conversion_factor",
"reference_name",
@@ -52,7 +53,6 @@
"fieldname": "description",
"fieldtype": "Text Editor",
"in_global_search": 1,
"in_list_view": 1,
"label": "Description",
"oldfieldname": "description",
"oldfieldtype": "Data",
@@ -87,12 +87,13 @@
"read_only": 1
},
{
"columns": 2,
"fieldname": "consumed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Consumed Qty",
"oldfieldname": "consumed_qty",
"oldfieldtype": "Currency",
"read_only": 1,
"reqd": 1
},
{
@@ -183,12 +184,18 @@
{
"fieldname": "col_break4",
"fieldtype": "Column Break"
},
{
"fieldname": "item_name",
"fieldtype": "Data",
"label": "Item Name",
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2020-09-18 17:26:09.703215",
"modified": "2021-05-29 17:22:14.977117",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Receipt Item Supplied",

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Subcontract Order Summary"] = {
"filters": [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
label: __("From Date"),
fieldname:"from_date",
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1
},
{
label: __("To Date"),
fieldname:"to_date",
fieldtype: "Date",
default: frappe.datetime.get_today(),
reqd: 1
},
{
label: __("Purchase Order"),
fieldname: "name",
fieldtype: "Link",
options: "Purchase Order",
get_query: function() {
return {
filters: {
docstatus: 1,
is_subcontracted: 'Yes',
company: frappe.query_report.get_filter_value('company')
}
}
}
}
]
};

View File

@@ -0,0 +1,32 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-05-31 14:43:32.417694",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"modified": "2021-05-31 14:43:32.417694",
"modified_by": "Administrator",
"module": "Buying",
"name": "Subcontract Order Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Purchase Order",
"report_name": "Subcontract Order Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Stock User"
},
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
}
]
}

View File

@@ -0,0 +1,158 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
def execute(filters=None):
columns, data = [], []
columns = get_columns()
data = get_data(filters)
return columns, data
def get_data(report_filters):
data = []
orders = get_subcontracted_orders(report_filters)
if orders:
supplied_items = get_supplied_items(orders, report_filters)
po_details = prepare_subcontracted_data(orders, supplied_items)
get_subcontracted_data(po_details, data)
return data
def get_subcontracted_orders(report_filters):
fields = ['`tabPurchase Order Item`.`parent` as po_id', '`tabPurchase Order Item`.`item_code`',
'`tabPurchase Order Item`.`item_name`', '`tabPurchase Order Item`.`qty`', '`tabPurchase Order Item`.`name`',
'`tabPurchase Order Item`.`received_qty`', '`tabPurchase Order`.`status`']
filters = get_filters(report_filters)
return frappe.get_all('Purchase Order', fields = fields, filters=filters) or []
def get_filters(report_filters):
filters = [['Purchase Order', 'docstatus', '=', 1], ['Purchase Order', 'is_subcontracted', '=', 'Yes'],
['Purchase Order', 'transaction_date', 'between', (report_filters.from_date, report_filters.to_date)]]
for field in ['name', 'company']:
if report_filters.get(field):
filters.append(['Purchase Order', field, '=', report_filters.get(field)])
return filters
def get_supplied_items(orders, report_filters):
if not orders:
return []
fields = ['parent', 'main_item_code', 'rm_item_code', 'required_qty',
'supplied_qty', 'returned_qty', 'total_supplied_qty', 'consumed_qty', 'reference_name']
filters = {'parent': ('in', [d.po_id for d in orders]), 'docstatus': 1}
supplied_items = {}
for row in frappe.get_all('Purchase Order Item Supplied', fields = fields, filters=filters):
new_key = (row.parent, row.reference_name, row.main_item_code)
supplied_items.setdefault(new_key, []).append(row)
return supplied_items
def prepare_subcontracted_data(orders, supplied_items):
po_details = {}
for row in orders:
key = (row.po_id, row.name, row.item_code)
if key not in po_details:
po_details.setdefault(key, frappe._dict({'po_item': row, 'supplied_items': []}))
details = po_details[key]
if supplied_items.get(key):
for supplied_item in supplied_items[key]:
details['supplied_items'].append(supplied_item)
return po_details
def get_subcontracted_data(po_details, data):
for key, details in po_details.items():
res = details.po_item
for index, row in enumerate(details.supplied_items):
if index != 0:
res = {}
res.update(row)
data.append(res)
def get_columns():
return [
{
"label": _("Id"),
"fieldname": "po_id",
"fieldtype": "Link",
"options": "Purchase Order",
"width": 100
},
{
"label": _("Status"),
"fieldname": "status",
"fieldtype": "Data",
"width": 80
},
{
"label": _("Subcontracted Item"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
"width": 140
},
{
"label": _("Qty"),
"fieldname": "qty",
"fieldtype": "Float",
"width": 70
},
{
"label": _("Received"),
"fieldname": "received_qty",
"fieldtype": "Float",
"width": 80
},
{
"label": _("Supplied Item"),
"fieldname": "rm_item_code",
"fieldtype": "Link",
"options": "Item",
"width": 140
},
{
"label": _("Required Qty"),
"fieldname": "required_qty",
"fieldtype": "Float",
"width": 110
},
{
"label": _("Supplied Qty"),
"fieldname": "supplied_qty",
"fieldtype": "Float",
"width": 110
},
{
"label": _("Returned Qty"),
"fieldname": "returned_qty",
"fieldtype": "Float",
"width": 110
},
{
"label": _("Total Supplied"),
"fieldname": "total_supplied_qty",
"fieldtype": "Float",
"width": 120
},
{
"label": _("Consumed Qty"),
"fieldname": "consumed_qty",
"fieldtype": "Float",
"width": 110
},
]