feat: Material Request and Stock Entry Enhancement (#22671)

* feat: send to warehouse

* adding warehouse validation

* fix: review changes

* fix: review changes

* stock entry enhancement

* fix: review changes
This commit is contained in:
Anupam Kumar 2020-08-12 17:55:02 +05:30 committed by GitHub
parent e0cc421e50
commit 674d1b0803
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 177 additions and 140 deletions

View File

@ -718,3 +718,4 @@ erpnext.patches.v13_0.delete_report_requested_items_to_order
erpnext.patches.v12_0.update_item_tax_template_company
erpnext.patches.v13_0.move_branch_code_to_bank_account
erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
erpnext.patches.v13_0.stock_entry_enhancements

View File

@ -19,7 +19,7 @@ def create_stock_entry_types():
for purpose in ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Material Consumption for Manufacture", "Manufacture",
"Repack", "Send to Subcontractor", "Send to Warehouse", "Receive at Warehouse"]:
"Repack", "Send to Subcontractor"]:
ste_type = frappe.get_doc({
'doctype': 'Stock Entry Type',

View File

@ -0,0 +1,27 @@
# Copyright(c) 2020, Frappe Technologies Pvt.Ltd.and Contributors
# License: GNU General Public License v3.See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("stock", "doctype", "stock_entry")
if frappe.db.has_column("Stock Entry", "add_to_transit"):
frappe.db.sql("""
UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer',
add_to_transit = 1 WHERE stock_entry_type = 'Send to Warehouse'
""")
frappe.db.sql("""UPDATE `tabStock Entry` SET
stock_entry_type = 'Material Transfer',
purpose = 'Material Transfer'
WHERE stock_entry_type = 'Receive at Warehouse'
""")
frappe.reload_doc("stock", "doctype", "warehouse_type")
if not frappe.db.exists('Warehouse Type', 'Transit'):
doc = frappe.new_doc('Warehouse Type')
doc.name = 'Transit'
doc.insert()

View File

@ -34,6 +34,16 @@ frappe.ui.form.on("Company", {
frm.set_query("default_buying_terms", function() {
return { filters: { buying: 1 } };
});
frm.set_query("default_in_transit_warehouse", function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
};
});
},
company_name: function(frm) {

View File

@ -25,6 +25,7 @@
"default_selling_terms",
"default_buying_terms",
"default_warehouse_for_sales_return",
"default_in_transit_warehouse",
"column_break_10",
"country",
"create_chart_of_accounts_based_on",
@ -733,6 +734,12 @@
"fieldname": "enable_perpetual_inventory_for_non_stock_items",
"fieldtype": "Check",
"label": "Enable Perpetual Inventory For Non Stock Items"
},
{
"fieldname": "default_in_transit_warehouse",
"fieldtype": "Link",
"label": "Default In Transit Warehouse",
"options": "Warehouse"
}
],
"icon": "fa fa-building",
@ -740,7 +747,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2020-06-24 12:45:31.462195",
"modified": "2020-08-06 00:38:08.311216",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@ -140,7 +140,8 @@ class Company(NestedSet):
{"warehouse_name": _("All Warehouses"), "is_group": 1},
{"warehouse_name": _("Stores"), "is_group": 0},
{"warehouse_name": _("Work In Progress"), "is_group": 0},
{"warehouse_name": _("Finished Goods"), "is_group": 0}]:
{"warehouse_name": _("Finished Goods"), "is_group": 0},
{"warehouse_name": _("Goods In Transit"), "is_group": 0, "warehouse_type": "Transit"}]:
if not frappe.db.exists("Warehouse", "{0} - {1}".format(wh_detail["warehouse_name"], self.abbr)):
warehouse = frappe.get_doc({
@ -149,7 +150,8 @@ class Company(NestedSet):
"is_group": wh_detail["is_group"],
"company": self.name,
"parent_warehouse": "{0} - {1}".format(_("All Warehouses"), self.abbr) \
if not wh_detail["is_group"] else ""
if not wh_detail["is_group"] else "",
"warehouse_type" : wh_detail["warehouse_type"] if "warehouse_type" in wh_detail else None
})
warehouse.flags.ignore_permissions = True
warehouse.flags.ignore_mandatory = True

View File

@ -95,8 +95,6 @@ def install(country=None):
{'doctype': 'Stock Entry Type', 'name': 'Send to Subcontractor', 'purpose': 'Send to Subcontractor'},
{'doctype': 'Stock Entry Type', 'name': 'Material Transfer for Manufacture', 'purpose': 'Material Transfer for Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Material Consumption for Manufacture', 'purpose': 'Material Consumption for Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Send to Warehouse', 'purpose': 'Send to Warehouse'},
{'doctype': 'Stock Entry Type', 'name': 'Receive at Warehouse', 'purpose': 'Receive at Warehouse'},
# Designation
{'doctype': 'Designation', 'designation_name': _('CEO')},
@ -244,7 +242,10 @@ def install(country=None):
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")},
# Warehouse Type
{'doctype': 'Warehouse Type', 'name': 'Transit'},
]
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types

View File

@ -11,6 +11,7 @@
"naming_series",
"title",
"material_request_type",
"transfer_status",
"customer",
"column_break_2",
"schedule_date",
@ -303,13 +304,22 @@
"fieldtype": "Link",
"label": "Set From Warehouse",
"options": "Warehouse"
},
{
"allow_on_submit": 1,
"depends_on": "eval:doc.add_to_transit == 1",
"fieldname": "transfer_status",
"fieldtype": "Select",
"label": "Transfer Status",
"options": "\nNot Started\nIn Transit\nCompleted",
"read_only": 1
}
],
"icon": "fa fa-ticket",
"idx": 70,
"is_submittable": 1,
"links": [],
"modified": "2020-05-01 20:21:09.990867",
"modified": "2020-08-10 13:27:54.891058",
"modified_by": "Administrator",
"module": "Stock",
"name": "Material Request",

View File

@ -1,8 +1,16 @@
frappe.listview_settings['Material Request'] = {
add_fields: ["material_request_type", "status", "per_ordered", "per_received"],
add_fields: ["material_request_type", "status", "per_ordered", "per_received", "transfer_status"],
get_indicator: function(doc) {
if(doc.status=="Stopped") {
return [__("Stopped"), "red", "status,=,Stopped"];
} else if(doc.transfer_status && doc.docstatus != 2) {
if (doc.transfer_status == "Not Started") {
return [__("Not Started"), "orange"];
} else if (doc.transfer_status == "In Transit") {
return [__("In Transit"), "yellow"];
} else if (doc.transfer_status == "Completed") {
return [__("Completed"), "green"];
}
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) == 0) {
return [__("Pending"), "orange", "per_ordered,=,0"];
} else if(doc.docstatus==1 && flt(doc.per_ordered, 2) < 100) {

View File

@ -19,7 +19,6 @@ frappe.ui.form.on('Stock Entry', {
filters: [
['Stock Entry', 'docstatus', '=', 1],
['Stock Entry', 'per_transferred', '<','100'],
['Stock Entry', 'purpose', '=', 'Send to Warehouse']
]
}
});
@ -171,9 +170,9 @@ frappe.ui.form.on('Stock Entry', {
}
}
if (frm.doc.docstatus === 1 && frm.doc.purpose == 'Send to Warehouse') {
if (frm.doc.per_transferred < 100) {
frm.add_custom_button(__('Receive at Warehouse Entry'), function() {
if (frm.doc.docstatus === 1) {
if (frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer' && frm.doc.per_transferred < 100) {
frm.add_custom_button('End Transit', function() {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
frm: frm
@ -266,6 +265,7 @@ frappe.ui.form.on('Stock Entry', {
stock_entry_type: function(frm){
frm.remove_custom_button('Bill of Materials', "Get items from");
frm.events.show_bom_custom_button(frm);
frm.trigger('add_to_transit');
},
purpose: function(frm) {
@ -532,6 +532,26 @@ frappe.ui.form.on('Stock Entry', {
target_warehouse_address: function(frm) {
erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false);
},
add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
frm.set_value('stock_entry_type', 'Material Transfer');
frm.fields_dict.to_warehouse.get_query = function() {
return {
filters:{
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
};
};
frappe.db.get_value('Company', frm.doc.company, 'default_in_transit_warehouse', (r) => {
if (r.default_in_transit_warehouse) {
frm.set_value('to_warehouse', r.default_in_transit_warehouse);
}
});
}
}
})
@ -754,6 +774,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}
erpnext.hide_company();
erpnext.utils.add_item(this.frm);
this.frm.trigger('add_to_transit');
},
scan_barcode: function() {
@ -919,8 +940,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
doc.purpose!='Material Issue');
this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", doc.purpose!='Material Issue');
this.frm.toggle_reqd("outgoing_stock_entry",
doc.purpose == 'Receive at Warehouse' ? 1: 0);
},
supplier: function(doc) {

View File

@ -13,6 +13,7 @@
"stock_entry_type",
"outgoing_stock_entry",
"purpose",
"add_to_transit",
"work_order",
"purchase_order",
"delivery_note_no",
@ -116,11 +117,12 @@
"reqd": 1
},
{
"depends_on": "eval:doc.purpose == 'Receive at Warehouse'",
"depends_on": "eval:doc.purpose == 'Material Transfer'",
"fieldname": "outgoing_stock_entry",
"fieldtype": "Link",
"label": "Stock Entry (Outward GIT)",
"options": "Stock Entry"
"options": "Stock Entry",
"read_only": 1
},
{
"bold": 1,
@ -132,7 +134,7 @@
"label": "Purpose",
"oldfieldname": "purpose",
"oldfieldtype": "Select",
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse",
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"read_only": 1
},
{
@ -630,13 +632,21 @@
{
"fieldname": "print_settings_col_break",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval: doc.purpose=='Material Transfer' && !doc.outgoing_stock_entry",
"fieldname": "add_to_transit",
"fieldtype": "Check",
"label": "Add to Transit",
"no_copy": 1
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2020-04-23 12:56:52.881752",
"modified": "2020-08-11 19:10:07.954981",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@ -96,6 +96,11 @@ class StockEntry(StockController):
self.update_quality_inspection()
if self.work_order and self.purpose == "Manufacture":
self.update_so_in_serial_number()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('In Transit')
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
self.set_material_request_transfer_status('Completed')
def on_cancel(self):
@ -116,6 +121,11 @@ class StockEntry(StockController):
self.update_quality_inspection()
self.delete_auto_created_batches()
if self.purpose == 'Material Transfer' and self.add_to_transit:
self.set_material_request_transfer_status('Not Started')
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
self.set_material_request_transfer_status('In Transit')
def set_job_card_data(self):
if self.job_card and not self.work_order:
data = frappe.db.get_value('Job Card',
@ -133,7 +143,7 @@ class StockEntry(StockController):
def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
"Material Consumption for Manufacture"]
if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
@ -259,10 +269,10 @@ class StockEntry(StockController):
"""perform various (sometimes conditional) validations on warehouse"""
source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
"Material Consumption for Manufacture"]
target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor",
"Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
"Material Transfer for Manufacture"]
validate_for_manufacture = any([d.bom_no for d in self.get("items")])
@ -810,7 +820,7 @@ class StockEntry(StockController):
def set_items_for_stock_in(self):
self.items = []
if self.outgoing_stock_entry and self.purpose == 'Receive at Warehouse':
if self.outgoing_stock_entry and self.purpose == 'Material Transfer':
doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry)
if doc.per_transferred == 100:
@ -1211,13 +1221,25 @@ class StockEntry(StockController):
def validate_with_material_request(self):
for item in self.get("items"):
if item.material_request:
material_request = item.material_request or None
material_request_item = item.material_request_item or None
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
parent_se = frappe.get_value("Stock Entry Detail", item.ste_detail, ['material_request','material_request_item'],as_dict=True)
if parent_se:
material_request = parent_se.material_request
material_request_item = parent_se.material_request_item
if material_request:
mreq_item = frappe.db.get_value("Material Request Item",
{"name": item.material_request_item, "parent": item.material_request},
{"name": material_request_item, "parent": material_request},
["item_code", "warehouse", "idx"], as_dict=True)
if mreq_item.item_code != item.item_code or \
mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse):
frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx),
if mreq_item.item_code != item.item_code:
frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx),
frappe.MappingMismatchError)
elif self.purpose == "Material Transfer" and self.add_to_transit:
continue
elif mreq_item.warehouse != (item.s_warehouse if self.purpose == "Material Issue" else item.t_warehouse):
frappe.throw(_("Warehouse for row {0} does not match Material Request").format(item.idx),
frappe.MappingMismatchError)
def validate_batch(self):
@ -1285,7 +1307,7 @@ class StockEntry(StockController):
to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
def update_transferred_qty(self):
if self.purpose == 'Receive at Warehouse':
if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
stock_entries = {}
stock_entries_child_list = []
for d in self.items:
@ -1343,6 +1365,20 @@ class StockEntry(StockController):
'reference_type': reference_type,
'reference_name': reference_name
})
def set_material_request_transfer_status(self, status):
material_requests = []
if self.outgoing_stock_entry:
parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, 'add_to_transit')
for item in self.items:
material_request = item.material_request or None
if self.purpose == "Material Transfer" and material_request not in material_requests:
if self.outgoing_stock_entry and parent_se:
material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, 'material_request')
if material_request and material_request not in material_requests:
material_requests.append(material_request)
frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
@ -1382,12 +1418,19 @@ def move_sample_to_retention_warehouse(company, items):
@frappe.whitelist()
def make_stock_in_entry(source_name, target_doc=None):
def set_missing_values(source, target):
target.purpose = 'Receive at Warehouse'
target.set_stock_entry_type()
def update_item(source_doc, target_doc, source_parent):
target_doc.t_warehouse = ''
if source_doc.material_request_item and source_doc.material_request :
add_to_transit = frappe.db.get_value('Stock Entry', source_name, 'add_to_transit')
if add_to_transit:
warehouse = frappe.get_value('Material Request Item', source_doc.material_request_item, 'warehouse')
target_doc.t_warehouse = warehouse
target_doc.s_warehouse = source_doc.t_warehouse
target_doc.qty = source_doc.qty - source_doc.transferred_qty

View File

@ -737,34 +737,6 @@ class TestStockEntry(unittest.TestCase):
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0)
def test_goods_in_transit(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
warehouse = "_Test Warehouse FG 1 - _TC"
if not frappe.db.exists('Warehouse', warehouse):
create_warehouse("_Test Warehouse FG 1")
outward_entry = make_stock_entry(item_code="_Test Item",
purpose="Send to Warehouse",
source="_Test Warehouse - _TC",
target="_Test Warehouse 1 - _TC", qty=50, basic_rate=100)
inward_entry1 = make_stock_in_entry(outward_entry.name)
inward_entry1.items[0].t_warehouse = warehouse
inward_entry1.items[0].qty = 25
inward_entry1.submit()
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 50)
inward_entry2 = make_stock_in_entry(outward_entry.name)
inward_entry2.items[0].t_warehouse = warehouse
inward_entry2.items[0].qty = 25
inward_entry2.submit()
doc = frappe.get_doc('Stock Entry', outward_entry.name)
self.assertEqual(doc.per_transferred, 100)
def test_gle_for_opening_stock_entry(self):
mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company="_Test Company with perpetual inventory",qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)

View File

@ -1,156 +1,83 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"actions": [],
"autoname": "Prompt",
"beta": 0,
"creation": "2019-03-13 16:23:46.636769",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"purpose"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Material Issue",
"fetch_if_empty": 0,
"fieldname": "purpose",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Purpose",
"length": 0,
"no_copy": 0,
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor\nSend to Warehouse\nReceive at Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"options": "\nMaterial Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
"set_only_once": 1
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-03-26 12:02:42.144377",
"links": [],
"modified": "2020-08-10 23:24:37.160817",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Type",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Manufacturing Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Stock User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
"track_changes": 1
}