Merge pull request #16925 from rohitwaghchaure/stock_entry_enhancements

feat: stock entry enhancements
This commit is contained in:
rohitwaghchaure 2019-03-26 22:50:54 +05:30 committed by GitHub
commit fa638443bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 4576 additions and 3653 deletions

View File

@ -89,6 +89,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
}, __('Create')); }, __('Create'));
} }
if (doc.docstatus === 1) {
cur_frm.add_custom_button(__('Maintenance Schedule'), function () {
cur_frm.cscript.make_maintenance_schedule();
}, __('Create'));
}
if(!doc.auto_repeat) { if(!doc.auto_repeat) {
cur_frm.add_custom_button(__('Subscription'), function() { cur_frm.add_custom_button(__('Subscription'), function() {
erpnext.utils.make_subscription(doc.doctype, doc.name) erpnext.utils.make_subscription(doc.doctype, doc.name)
@ -118,6 +124,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
} }
}, },
make_maintenance_schedule: function() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
frm: cur_frm
})
},
on_submit: function(doc, dt, dn) { on_submit: function(doc, dt, dn) {
var me = this; var me = this;

View File

@ -1230,6 +1230,22 @@ def get_bank_cash_account(mode_of_payment, company):
"account": account "account": account
} }
@frappe.whitelist()
def make_maintenance_schedule(source_name, target_doc=None):
doclist = get_mapped_doc("Sales Invoice", source_name, {
"Sales Invoice": {
"doctype": "Maintenance Schedule",
"validation": {
"docstatus": ["=", 1]
}
},
"Sales Invoice Item": {
"doctype": "Maintenance Schedule Item",
},
}, target_doc)
return doclist
@frappe.whitelist() @frappe.whitelist()
def make_delivery_note(source_name, target_doc=None): def make_delivery_note(source_name, target_doc=None):
def set_missing_values(source, target): def set_missing_values(source, target):

View File

@ -444,7 +444,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
item_wh = get_item_details(items) item_wh = get_item_details(items)
stock_entry = frappe.new_doc("Stock Entry") stock_entry = frappe.new_doc("Stock Entry")
stock_entry.purpose = "Subcontract" stock_entry.purpose = "Send to Subcontractor"
stock_entry.purchase_order = purchase_order.name stock_entry.purchase_order = purchase_order.name
stock_entry.supplier = purchase_order.supplier stock_entry.supplier = purchase_order.supplier
stock_entry.supplier_name = purchase_order.supplier_name stock_entry.supplier_name = purchase_order.supplier_name
@ -452,6 +452,7 @@ def make_rm_stock_entry(purchase_order, rm_items):
stock_entry.address_display = purchase_order.address_display stock_entry.address_display = purchase_order.address_display
stock_entry.company = purchase_order.company stock_entry.company = purchase_order.company
stock_entry.to_warehouse = purchase_order.supplier_warehouse stock_entry.to_warehouse = purchase_order.supplier_warehouse
stock_entry.set_stock_entry_type()
for item_code in fg_items: for item_code in fg_items:
for rm_item_data in rm_items_list: for rm_item_data in rm_items_list:

View File

@ -729,7 +729,7 @@ def get_subcontracted_raw_materials_from_se(purchase_orders):
sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
from `tabStock Entry` se,`tabStock Entry Detail` sed from `tabStock Entry` se,`tabStock Entry Detail` sed
where where
se.name = sed.parent and se.docstatus=1 and se.purpose='Subcontract' se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != '' and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
group by sed.item_code, sed.t_warehouse group by sed.item_code, sed.t_warehouse
""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1) """ % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)

View File

@ -568,6 +568,7 @@ def make_stock_entry(production_order_id, purpose, qty=None):
stock_entry.bom_no = production_order.bom_no stock_entry.bom_no = production_order.bom_no
stock_entry.use_multi_level_bom = production_order.use_multi_level_bom stock_entry.use_multi_level_bom = production_order.use_multi_level_bom
stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty))
stock_entry.set_stock_entry_type()
if purpose=="Material Transfer for Manufacture": if purpose=="Material Transfer for Manufacture":
stock_entry.to_warehouse = wip_warehouse stock_entry.to_warehouse = wip_warehouse

View File

@ -625,6 +625,7 @@ def make_stock_entry(work_order_id, purpose, qty=None):
additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty) additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty)
stock_entry.set("additional_costs", additional_costs) stock_entry.set("additional_costs", additional_costs)
stock_entry.set_stock_entry_type()
stock_entry.get_items() stock_entry.get_items()
return stock_entry.as_dict() return stock_entry.as_dict()

View File

@ -591,4 +591,5 @@ erpnext.patches.v12_0.add_item_name_in_work_orders
erpnext.patches.v12_0.update_pricing_rule_fields erpnext.patches.v12_0.update_pricing_rule_fields
erpnext.patches.v11_1.make_job_card_time_logs erpnext.patches.v11_1.make_job_card_time_logs
erpnext.patches.v12_0.rename_pricing_rule_child_doctypes erpnext.patches.v12_0.rename_pricing_rule_child_doctypes
erpnext.patches.v12_0.move_target_distribution_from_parent_to_child #wmnfb erpnext.patches.v12_0.move_target_distribution_from_parent_to_child
erpnext.patches.v12_0.stock_entry_enhancements

View File

@ -7,13 +7,13 @@ import frappe
def execute(): def execute():
frappe.reload_doc('buying', 'doctype', 'buying_settings') frappe.reload_doc('buying', 'doctype', 'buying_settings')
frappe.db.set_value('Buying Settings', None, 'backflush_raw_materials_of_subcontract_based_on', 'BOM') frappe.db.set_value('Buying Settings', None, 'backflush_raw_materials_of_subcontract_based_on', 'BOM')
frappe.reload_doc('stock', 'doctype', 'stock_entry_detail') frappe.reload_doc('stock', 'doctype', 'stock_entry_detail')
frappe.db.sql(""" update `tabStock Entry Detail` as sed, frappe.db.sql(""" update `tabStock Entry Detail` as sed,
`tabStock Entry` as se, `tabPurchase Order Item Supplied` as pois `tabStock Entry` as se, `tabPurchase Order Item Supplied` as pois
set set
sed.subcontracted_item = pois.main_item_code sed.subcontracted_item = pois.main_item_code
where where
se.purpose = 'Subcontract' and sed.parent = se.name se.purpose = 'Send to Subcontractor' and sed.parent = se.name
and pois.rm_item_code = sed.item_code and se.docstatus = 1 and pois.rm_item_code = sed.item_code and se.docstatus = 1
and pois.parenttype = 'Purchase Order'""") and pois.parenttype = 'Purchase Order'""")

View File

@ -0,0 +1,52 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
create_stock_entry_types()
company = frappe.db.get_value("Company", {'country': 'India'}, 'name')
if company:
add_gst_hsn_code_field()
def create_stock_entry_types():
frappe.reload_doc('stock', 'doctype', 'stock_entry_type')
frappe.reload_doc('stock', 'doctype', 'stock_entry')
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"]:
ste_type = frappe.get_doc({
'doctype': 'Stock Entry Type',
'name': purpose,
'purpose': purpose
})
try:
ste_type.insert()
except frappe.DuplicateEntryError:
pass
frappe.db.sql(" UPDATE `tabStock Entry` set purpose = 'Send to Subcontractor' where purpose = 'Subcontract'")
frappe.db.sql(" UPDATE `tabStock Entry` set stock_entry_type = purpose ")
def add_gst_hsn_code_field():
custom_fields = {
'Stock Entry Detail': [dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', fetch_from='item_code.gst_hsn_code',
insert_after='description', allow_on_submit=1, print_hide=0)]
}
create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch, update=True)
frappe.db.sql(""" update `tabStock Entry Detail`, `tabItem`
SET
`tabStock Entry Detail`.gst_hsn_code = `tabItem`.gst_hsn_code
Where
`tabItem`.name = `tabStock Entry Detail`.item_code and `tabItem`.gst_hsn_code is not null
""")

View File

@ -244,4 +244,4 @@
"track_changes": 1, "track_changes": 1,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@ -82,6 +82,19 @@ def install(country=None):
{'doctype': 'Employment Type', 'employee_type_name': _('Intern')}, {'doctype': 'Employment Type', 'employee_type_name': _('Intern')},
{'doctype': 'Employment Type', 'employee_type_name': _('Apprentice')}, {'doctype': 'Employment Type', 'employee_type_name': _('Apprentice')},
# Stock Entry Type
{'doctype': 'Stock Entry Type', 'name': 'Material Issue', 'purpose': 'Material Issue'},
{'doctype': 'Stock Entry Type', 'name': 'Material Receipt', 'purpose': 'Material Receipt'},
{'doctype': 'Stock Entry Type', 'name': 'Material Transfer', 'purpose': 'Material Transfer'},
{'doctype': 'Stock Entry Type', 'name': 'Manufacture', 'purpose': 'Manufacture'},
{'doctype': 'Stock Entry Type', 'name': 'Repack', 'purpose': 'Repack'},
{'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 # Designation
{'doctype': 'Designation', 'designation_name': _('CEO')}, {'doctype': 'Designation', 'designation_name': _('CEO')},
{'doctype': 'Designation', 'designation_name': _('Manager')}, {'doctype': 'Designation', 'designation_name': _('Manager')},

View File

@ -202,6 +202,7 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None):
), ),
] ]
)) ))
stock_entry.set_stock_entry_type()
stock_entry.insert() stock_entry.insert()
stock_entry.submit() stock_entry.submit()

View File

@ -67,7 +67,10 @@ class TestBatch(unittest.TestCase):
rate = 10 rate = 10
) )
] ]
)).insert() ))
stock_entry.set_stock_entry_type()
stock_entry.insert()
stock_entry.submit() stock_entry.submit()
self.assertTrue(stock_entry.items[0].batch_no) self.assertTrue(stock_entry.items[0].batch_no)
@ -136,7 +139,10 @@ class TestBatch(unittest.TestCase):
s_warehouse=receipt.items[0].warehouse, s_warehouse=receipt.items[0].warehouse,
) )
] ]
)).insert() ))
stock_entry.set_stock_entry_type()
stock_entry.insert()
stock_entry.submit() stock_entry.submit()
# assert same batch is selected # assert same batch is selected
@ -193,7 +199,10 @@ class TestBatch(unittest.TestCase):
allow_zero_valuation_rate = 1 allow_zero_valuation_rate = 1
) )
] ]
)).insert() ))
stock_entry.set_stock_entry_type()
stock_entry.insert()
stock_entry.submit() stock_entry.submit()
def test_batch_name_with_naming_series(self): def test_batch_name_with_naming_series(self):

View File

@ -106,7 +106,7 @@ class Bin(Document):
`tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
where where
se.docstatus=1 se.docstatus=1
and se.purpose='Subcontract' and se.purpose='Send to Subcontractor'
and ifnull(se.purchase_order, '') !='' and ifnull(se.purchase_order, '') !=''
and (sed.item_code = %(item)s or sed.original_item = %(item)s) and (sed.item_code = %(item)s or sed.original_item = %(item)s)
and se.name = sed.parent and se.name = sed.parent

View File

@ -426,6 +426,7 @@ def make_stock_entry(source_name, target_doc=None):
target.purpose = "Material Receipt" target.purpose = "Material Receipt"
target.run_method("calculate_rate_and_amount") target.run_method("calculate_rate_and_amount")
target.set_stock_entry_type()
target.set_job_card_data() target.set_job_card_data()
doclist = get_mapped_doc("Material Request", source_name, { doclist = get_mapped_doc("Material Request", source_name, {

View File

@ -96,6 +96,8 @@ class TestMaterialRequest(unittest.TestCase):
} }
] ]
}) })
se.set_stock_entry_type()
se.insert() se.insert()
se.submit() se.submit()
@ -388,6 +390,7 @@ class TestMaterialRequest(unittest.TestCase):
# check for stopped status of Material Request # check for stopped status of Material Request
se = frappe.copy_doc(se_doc) se = frappe.copy_doc(se_doc)
se.set_stock_entry_type()
se.insert() se.insert()
mr.update_status('Stopped') mr.update_status('Stopped')
self.assertRaises(frappe.InvalidStatusError, se.submit) self.assertRaises(frappe.InvalidStatusError, se.submit)
@ -395,6 +398,7 @@ class TestMaterialRequest(unittest.TestCase):
mr.update_status('Submitted') mr.update_status('Submitted')
se = frappe.copy_doc(se_doc) se = frappe.copy_doc(se_doc)
se.set_stock_entry_type()
se.insert() se.insert()
se.submit() se.submit()
@ -528,7 +532,7 @@ class TestMaterialRequest(unittest.TestCase):
#testing bin requested qty after issuing stock against material request #testing bin requested qty after issuing stock against material request
self.assertEqual(_get_requested_qty(), existing_requested_qty) self.assertEqual(_get_requested_qty(), existing_requested_qty)
def test_material_request_type_manufacture(self): def test_material_request_type_manufacture(self):
mr = frappe.copy_doc(test_records[1]).insert() mr = frappe.copy_doc(test_records[1]).insert()
mr = frappe.get_doc("Material Request", mr.name) mr = frappe.get_doc("Material Request", mr.name)
@ -541,20 +545,20 @@ class TestMaterialRequest(unittest.TestCase):
po = frappe.get_doc("Work Order", prod_order[0]) po = frappe.get_doc("Work Order", prod_order[0])
po.wip_warehouse = "_Test Warehouse 1 - _TC" po.wip_warehouse = "_Test Warehouse 1 - _TC"
po.submit() po.submit()
mr = frappe.get_doc("Material Request", mr.name) mr = frappe.get_doc("Material Request", mr.name)
self.assertEqual(completed_qty + po.qty, mr.items[0].ordered_qty) self.assertEqual(completed_qty + po.qty, mr.items[0].ordered_qty)
new_requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \ new_requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \
item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0] item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
self.assertEqual(requested_qty - po.qty, new_requested_qty) self.assertEqual(requested_qty - po.qty, new_requested_qty)
po.cancel() po.cancel()
mr = frappe.get_doc("Material Request", mr.name) mr = frappe.get_doc("Material Request", mr.name)
self.assertEqual(completed_qty, mr.items[0].ordered_qty) self.assertEqual(completed_qty, mr.items[0].ordered_qty)
new_requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \ new_requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \
item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0] item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
self.assertEqual(requested_qty, new_requested_qty) self.assertEqual(requested_qty, new_requested_qty)

View File

@ -14,6 +14,16 @@ frappe.ui.form.on('Stock Entry', {
} }
}); });
frm.set_query('outgoing_stock_entry', function() {
return {
filters: [
['Stock Entry', 'docstatus', '=', 1],
['Stock Entry', 'per_transferred', '<','100'],
['Stock Entry', 'purpose', '=', 'Send to Warehouse']
]
}
});
frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => { frappe.db.get_value('Stock Settings', {name: 'Stock Settings'}, 'sample_retention_warehouse', (r) => {
if (r.sample_retention_warehouse) { if (r.sample_retention_warehouse) {
var filters = [ var filters = [
@ -39,7 +49,7 @@ frappe.ui.form.on('Stock Entry', {
if(!item.item_code) { if(!item.item_code) {
frappe.throw(__("Please enter Item Code to get Batch Number")); frappe.throw(__("Please enter Item Code to get Batch Number"));
} else { } else {
if (in_list(["Material Transfer for Manufacture", "Manufacture", "Repack", "Subcontract"], doc.purpose)) { if (in_list(["Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"], doc.purpose)) {
var filters = { var filters = {
'item_code': item.item_code, 'item_code': item.item_code,
'posting_date': frm.doc.posting_date || frappe.datetime.nowdate() 'posting_date': frm.doc.posting_date || frappe.datetime.nowdate()
@ -92,6 +102,16 @@ frappe.ui.form.on('Stock Entry', {
}); });
}, },
outgoing_stock_entry: function(frm) {
frappe.call({
doc: frm.doc,
method: "set_items_for_stock_in",
callback: function() {
refresh_field('items');
}
});
},
refresh: function(frm) { refresh: function(frm) {
if(!frm.doc.docstatus) { if(!frm.doc.docstatus) {
frm.trigger('validate_purpose_consumption'); frm.trigger('validate_purpose_consumption');
@ -139,6 +159,28 @@ 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() {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
frm: frm
})
});
}
if (frm.doc.per_transferred > 0) {
frm.add_custom_button(__('Received Stock Entries'), function() {
frappe.route_options = {
'outgoing_stock_entry': frm.doc.name,
'docstatus': ['!=', 2]
};
frappe.set_route('List', 'Stock Entry');
}, __("View"));
}
}
if (frm.doc.docstatus===0) { if (frm.doc.docstatus===0) {
frm.add_custom_button(__('Purchase Invoice'), function() { frm.add_custom_button(__('Purchase Invoice'), function() {
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
@ -154,6 +196,23 @@ frappe.ui.form.on('Stock Entry', {
} }
}) })
}, __("Get items from")); }, __("Get items from"));
frm.add_custom_button(__('Material Request'), function() {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.material_request.material_request.make_stock_entry",
source_doctype: "Material Request",
target: frm,
date_field: "schedule_date",
setters: {
company: frm.doc.company,
},
get_query_filters: {
docstatus: 1,
material_request_type: "Material Transfer",
status: ['!=', 'Transferred']
}
})
}, __("Get items from"));
} }
if (frm.doc.docstatus===0 && frm.doc.purpose == "Material Issue") { if (frm.doc.docstatus===0 && frm.doc.purpose == "Material Issue") {
frm.add_custom_button(__('Expired Batches'), function() { frm.add_custom_button(__('Expired Batches'), function() {
@ -457,6 +516,7 @@ frappe.ui.form.on('Stock Entry Detail', {
'voucher_no' : d.name, 'voucher_no' : d.name,
'allow_zero_valuation': 1, 'allow_zero_valuation': 1,
}; };
return frappe.call({ return frappe.call({
doc: frm.doc, doc: frm.doc,
method: "get_item_details", method: "get_item_details",
@ -721,8 +781,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse; if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
}, },
source_mandatory: ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"], source_mandatory: ["Material Issue", "Material Transfer", "Send to Subcontractor",
target_mandatory: ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"], "Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"],
target_mandatory: ["Material Receipt", "Material Transfer", "Send to Subcontractor",
"Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"],
from_warehouse: function(doc) { from_warehouse: function(doc) {
var me = this; var me = this;
@ -769,7 +831,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
this.frm.cscript.toggle_enable_bom(); this.frm.cscript.toggle_enable_bom();
if (doc.purpose == 'Subcontract') { if (doc.purpose == 'Send to Subcontractor') {
doc.customer = doc.customer_name = doc.customer_address = doc.customer = doc.customer_name = doc.customer_address =
doc.delivery_note_no = doc.sales_invoice_no = null; doc.delivery_note_no = doc.sales_invoice_no = null;
} else { } else {
@ -787,6 +849,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
doc.purpose!='Material Issue'); doc.purpose!='Material Issue');
this.frm.fields_dict["items"].grid.set_column_disp("additional_cost", 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) { supplier: function(doc) {

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@ from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos, get_b
from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, add_additional_cost from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, add_additional_cost
from erpnext.stock.utils import get_bin from erpnext.stock.utils import get_bin
from frappe.model.mapper import get_mapped_doc
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit, get_serial_nos
import json import json
@ -61,6 +62,7 @@ class StockEntry(StockController):
self.validate_inspection() self.validate_inspection()
self.validate_fg_completed_qty() self.validate_fg_completed_qty()
self.set_job_card_data() self.set_job_card_data()
self.set_purpose_for_stock_entry()
if not self.from_bom: if not self.from_bom:
self.fg_completed_qty = 0.0 self.fg_completed_qty = 0.0
@ -81,17 +83,18 @@ class StockEntry(StockController):
update_serial_nos_after_submit(self, "items") update_serial_nos_after_submit(self, "items")
self.update_work_order() self.update_work_order()
self.validate_purchase_order() self.validate_purchase_order()
if self.purchase_order and self.purpose == "Subcontract": if self.purchase_order and self.purpose == "Send to Subcontractor":
self.update_purchase_order_supplied_items() self.update_purchase_order_supplied_items()
self.make_gl_entries() self.make_gl_entries()
self.update_cost_in_project() self.update_cost_in_project()
self.validate_reserved_serial_no_consumption() self.validate_reserved_serial_no_consumption()
self.update_transferred_qty()
if self.work_order and self.purpose == "Manufacture": if self.work_order and self.purpose == "Manufacture":
self.update_so_in_serial_number() self.update_so_in_serial_number()
def on_cancel(self): def on_cancel(self):
if self.purchase_order and self.purpose == "Subcontract": if self.purchase_order and self.purpose == "Send to Subcontractor":
self.update_purchase_order_supplied_items() self.update_purchase_order_supplied_items()
if self.work_order and self.purpose == "Material Consumption for Manufacture": if self.work_order and self.purpose == "Material Consumption for Manufacture":
@ -102,6 +105,7 @@ class StockEntry(StockController):
self.update_stock_ledger() self.update_stock_ledger()
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()
self.update_cost_in_project() self.update_cost_in_project()
self.update_transferred_qty()
def set_job_card_data(self): def set_job_card_data(self):
if self.job_card and not self.work_order: if self.job_card and not self.work_order:
@ -118,8 +122,10 @@ class StockEntry(StockController):
frappe.throw(_("Cannot cancel transaction for Completed Work Order.")) frappe.throw(_("Cannot cancel transaction for Completed Work Order."))
def validate_purpose(self): def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", "Material Transfer for Manufacture", valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Manufacture", "Repack", "Subcontract", "Material Consumption for Manufacture"] "Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor",
"Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
if self.purpose not in valid_purposes: if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes))) frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
@ -221,9 +227,11 @@ class StockEntry(StockController):
def validate_warehouse(self): def validate_warehouse(self):
"""perform various (sometimes conditional) validations on warehouse""" """perform various (sometimes conditional) validations on warehouse"""
source_mandatory = ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture", source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture",
"Material Consumption for Manufacture"] "Material Consumption for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture",]
target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor",
"Material Transfer for Manufacture", "Send to Warehouse", "Receive at Warehouse"]
validate_for_manufacture_repack = any([d.bom_no for d in self.get("items")]) validate_for_manufacture_repack = any([d.bom_no for d in self.get("items")])
@ -478,13 +486,23 @@ class StockEntry(StockController):
if self.purpose not in ['Manufacture', 'Repack']: if self.purpose not in ['Manufacture', 'Repack']:
self.total_amount = sum([flt(item.amount) for item in self.get("items")]) self.total_amount = sum([flt(item.amount) for item in self.get("items")])
def set_stock_entry_type(self):
if self.purpose:
self.stock_entry_type = frappe.get_cached_value('Stock Entry Type',
{'purpose': self.purpose}, 'name')
def set_purpose_for_stock_entry(self):
if self.stock_entry_type and not self.purpose:
self.purpose = frappe.get_cached_value('Stock Entry Type',
self.stock_entry_type, 'purpose')
def validate_purchase_order(self): def validate_purchase_order(self):
"""Throw exception if more raw material is transferred against Purchase Order than in """Throw exception if more raw material is transferred against Purchase Order than in
the raw materials supplied table""" the raw materials supplied table"""
backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings", backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings",
"backflush_raw_materials_of_subcontract_based_on") "backflush_raw_materials_of_subcontract_based_on")
if (self.purpose == "Subcontract" and self.purchase_order and if (self.purpose == "Send to Subcontractor" and self.purchase_order and
backflush_raw_materials_based_on == 'BOM'): backflush_raw_materials_based_on == 'BOM'):
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order) purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
for se_item in self.items: for se_item in self.items:
@ -520,7 +538,7 @@ class StockEntry(StockController):
"overproduction_percentage_for_work_order")) "overproduction_percentage_for_work_order"))
for d in self.get('items'): for d in self.get('items'):
if self.purpose != "Subcontract" and d.bom_no and flt(d.transfer_qty) > flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse): if self.purpose != "Send to Subcontractor" and d.bom_no and flt(d.transfer_qty) > flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \ frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
format(d.idx, d.transfer_qty, self.fg_completed_qty)) format(d.idx, d.transfer_qty, self.fg_completed_qty))
@ -684,6 +702,30 @@ class StockEntry(StockController):
return ret return ret
def set_items_for_stock_in(self):
self.items = []
if self.outgoing_stock_entry and self.purpose == 'Receive at Warehouse':
doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry)
if doc.per_transferred == 100:
frappe.throw(_("Goods are already received against the outward entry {0}")
.format(doc.name))
for d in doc.items:
self.append('items', {
's_warehouse': d.t_warehouse,
'item_code': d.item_code,
'qty': d.qty,
'uom': d.uom,
'against_stock_entry': d.parent,
'ste_detail': d.name,
'stock_uom': d.stock_uom,
'conversion_factor': d.conversion_factor,
'serial_no': d.serial_no,
'batch_no': d.batch_no
})
def get_items(self): def get_items(self):
self.set('items', []) self.set('items', [])
self.validate_work_order() self.validate_work_order()
@ -696,7 +738,7 @@ class StockEntry(StockController):
if self.bom_no: if self.bom_no:
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack", if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
"Subcontract", "Material Transfer for Manufacture", "Material Consumption for Manufacture"]: "Send to Subcontractor", "Material Transfer for Manufacture", "Material Consumption for Manufacture"]:
if self.work_order and self.purpose == "Material Transfer for Manufacture": if self.work_order and self.purpose == "Material Transfer for Manufacture":
item_dict = self.get_pending_raw_materials() item_dict = self.get_pending_raw_materials()
@ -722,7 +764,7 @@ class StockEntry(StockController):
item_dict = self.get_bom_raw_materials(self.fg_completed_qty) item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
#Get PO Supplied Items Details #Get PO Supplied Items Details
if self.purchase_order and self.purpose == "Subcontract": if self.purchase_order and self.purpose == "Send to Subcontractor":
#Get PO Supplied Items Details #Get PO Supplied Items Details
item_wh = frappe._dict(frappe.db.sql(""" item_wh = frappe._dict(frappe.db.sql("""
select rm_item_code, reserve_warehouse select rm_item_code, reserve_warehouse
@ -734,13 +776,13 @@ class StockEntry(StockController):
if self.pro_doc and (cint(self.pro_doc.from_wip_warehouse) or not self.pro_doc.skip_transfer): if self.pro_doc and (cint(self.pro_doc.from_wip_warehouse) or not self.pro_doc.skip_transfer):
item["from_warehouse"] = self.pro_doc.wip_warehouse item["from_warehouse"] = self.pro_doc.wip_warehouse
#Get Reserve Warehouse from PO #Get Reserve Warehouse from PO
if self.purchase_order and self.purpose=="Subcontract": if self.purchase_order and self.purpose=="Send to Subcontractor":
item["from_warehouse"] = item_wh.get(item.item_code) item["from_warehouse"] = item_wh.get(item.item_code)
item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else "" item["to_warehouse"] = self.to_warehouse if self.purpose=="Send to Subcontractor" else ""
self.add_to_stock_entry_detail(item_dict) self.add_to_stock_entry_detail(item_dict)
if self.purpose != "Subcontract": if self.purpose != "Send to Subcontractor":
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty) scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
for item in itervalues(scrap_item_dict): for item in itervalues(scrap_item_dict):
if self.pro_doc and self.pro_doc.scrap_warehouse: if self.pro_doc and self.pro_doc.scrap_warehouse:
@ -1074,7 +1116,7 @@ class StockEntry(StockController):
frappe.MappingMismatchError) frappe.MappingMismatchError)
def validate_batch(self): def validate_batch(self):
if self.purpose in ["Material Transfer for Manufacture", "Manufacture", "Repack", "Subcontract", "Material Issue"]: if self.purpose in ["Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor", "Material Issue"]:
for item in self.get("items"): for item in self.get("items"):
if item.batch_no: if item.batch_no:
disabled = frappe.db.get_value("Batch", item.batch_no, "disabled") disabled = frappe.db.get_value("Batch", item.batch_no, "disabled")
@ -1130,6 +1172,52 @@ class StockEntry(StockController):
frappe.throw(_("Item {0} (Serial No: {1}) cannot be consumed as is reserverd\ frappe.throw(_("Item {0} (Serial No: {1}) cannot be consumed as is reserverd\
to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order)) to fullfill Sales Order {2}.").format(item.item_code, sr, sales_order))
def update_transferred_qty(self):
if self.purpose == 'Receive at Warehouse':
stock_entries = {}
stock_entries_child_list = []
for d in self.items:
if not (d.against_stock_entry and d.ste_detail):
continue
stock_entries_child_list.append(d.ste_detail)
transferred_qty = frappe.get_all("Stock Entry Detail", fields = ["sum(qty) as qty"],
filters = { 'against_stock_entry': d.against_stock_entry,
'ste_detail': d.ste_detail,'docstatus': 1})
stock_entries[(d.against_stock_entry, d.ste_detail)] = (transferred_qty[0].qty
if transferred_qty and transferred_qty[0] else 0.0) or 0.0
if not stock_entries: return None
cond = ''
for data, transferred_qty in stock_entries.items():
cond += """ WHEN (parent = %s and name = %s) THEN %s
""" %(frappe.db.escape(data[0]), frappe.db.escape(data[1]), transferred_qty)
if cond and stock_entries_child_list:
frappe.db.sql(""" UPDATE `tabStock Entry Detail`
SET
transferred_qty = CASE {cond} END
WHERE
name in ({ste_details}) """.format(cond=cond,
ste_details = ','.join(['%s'] * len(stock_entries_child_list))),
tuple(stock_entries_child_list))
args = {
'source_dt': 'Stock Entry Detail',
'target_field': 'transferred_qty',
'target_ref_field': 'qty',
'target_dt': 'Stock Entry Detail',
'join_field': 'ste_detail',
'target_parent_dt': 'Stock Entry',
'target_parent_field': 'per_transferred',
'source_field': 'qty',
'percent_join_field': 'against_stock_entry'
}
self._update_percent_field_in_targets(args, update_modified=True)
@frappe.whitelist() @frappe.whitelist()
def move_sample_to_retention_warehouse(company, items): def move_sample_to_retention_warehouse(company, items):
if isinstance(items, string_types): if isinstance(items, string_types):
@ -1138,6 +1226,7 @@ def move_sample_to_retention_warehouse(company, items):
stock_entry = frappe.new_doc("Stock Entry") stock_entry = frappe.new_doc("Stock Entry")
stock_entry.company = company stock_entry.company = company
stock_entry.purpose = "Material Transfer" stock_entry.purpose = "Material Transfer"
stock_entry.set_stock_entry_type()
for item in items: for item in items:
if item.get('sample_quantity') and item.get('batch_no'): if item.get('sample_quantity') and item.get('batch_no'):
sample_quantity = validate_sample_quantity(item.get('item_code'), item.get('sample_quantity'), sample_quantity = validate_sample_quantity(item.get('item_code'), item.get('sample_quantity'),
@ -1165,6 +1254,42 @@ def move_sample_to_retention_warehouse(company, items):
if stock_entry.get('items'): if stock_entry.get('items'):
return stock_entry.as_dict() return stock_entry.as_dict()
@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 = ''
target_doc.s_warehouse = source_doc.t_warehouse
target_doc.qty = source_doc.qty - source_doc.transferred_qty
doclist = get_mapped_doc("Stock Entry", source_name, {
"Stock Entry": {
"doctype": "Stock Entry",
"field_map": {
"name": "outgoing_stock_entry"
},
"validation": {
"docstatus": ["=", 1]
}
},
"Stock Entry Detail": {
"doctype": "Stock Entry Detail",
"field_map": {
"name": "ste_detail",
"parent": "against_stock_entry",
"serial_no": "serial_no",
"batch_no": "batch_no"
},
"postprocess": update_item,
"condition": lambda doc: flt(doc.qty) - flt(doc.transferred_qty) > 0.01
},
}, target_doc, set_missing_values)
return doclist
@frappe.whitelist() @frappe.whitelist()
def get_work_order_details(work_order): def get_work_order_details(work_order):
work_order = frappe.get_doc("Work Order", work_order) work_order = frappe.get_doc("Work Order", work_order)
@ -1224,7 +1349,7 @@ def get_used_alternative_items(purchase_order=None, work_order=None):
cond = "" cond = ""
if purchase_order: if purchase_order:
cond = "and ste.purpose = 'Subcontract' and ste.purchase_order = '{0}'".format(purchase_order) cond = "and ste.purpose = 'Send to Subcontractor' and ste.purchase_order = '{0}'".format(purchase_order)
elif work_order: elif work_order:
cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(work_order) cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(work_order)

View File

@ -1,6 +1,22 @@
frappe.listview_settings['Stock Entry'] = { frappe.listview_settings['Stock Entry'] = {
add_fields: ["`tabStock Entry`.`from_warehouse`", "`tabStock Entry`.`to_warehouse`", add_fields: ["`tabStock Entry`.`from_warehouse`", "`tabStock Entry`.`to_warehouse`",
"`tabStock Entry`.`purpose`", "`tabStock Entry`.`work_order`", "`tabStock Entry`.`bom_no`"], "`tabStock Entry`.`purpose`", "`tabStock Entry`.`work_order`", "`tabStock Entry`.`bom_no`"],
get_indicator: function (doc) {
if (doc.docstatus === 0) {
return [__("Draft"), "red", "docstatus,=,0"];
} else if (doc.purpose === 'Send to Warehouse' && doc.per_transferred < 100) {
// not delivered & overdue
return [__("Goods In Transit"), "grey", "per_transferred,<,100"];
} else if (doc.purpose === 'Send to Warehouse' && doc.per_transferred === 100) {
return [__("Goods Transferred"), "green", "per_transferred,=,100"];
} else if (doc.docstatus === 2) {
return [__("Canceled"), "red", "docstatus,=,2"];
} else {
return [__("Submitted"), "blue", "docstatus,=,1"];
}
},
column_render: { column_render: {
"from_warehouse": function(doc) { "from_warehouse": function(doc) {
var html = ""; var html = "";

View File

@ -124,6 +124,7 @@ def make_stock_entry(**args):
'expense_account': args.expense_account 'expense_account': args.expense_account
}) })
s.set_stock_entry_type()
if not args.do_not_save: if not args.do_not_save:
s.insert() s.insert()
if not args.do_not_submit: if not args.do_not_submit:

View File

@ -1,109 +1,107 @@
[ [
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Stock Entry", "doctype": "Stock Entry",
"items": [ "stock_entry_type": "Material Receipt",
{ "purpose": "Material Receipt",
"conversion_factor": 1.0, "items": [
"cost_center": "_Test Cost Center - _TC", {
"doctype": "Stock Entry Detail", "conversion_factor": 1.0,
"expense_account": "Stock Adjustment - _TC", "cost_center": "_Test Cost Center - _TC",
"basic_rate": 100, "doctype": "Stock Entry Detail",
"item_code": "_Test Item", "expense_account": "Stock Adjustment - _TC",
"parentfield": "items", "basic_rate": 100,
"qty": 50.0, "item_code": "_Test Item",
"stock_uom": "_Test UOM", "parentfield": "items",
"t_warehouse": "_Test Warehouse - _TC", "qty": 50.0,
"transfer_qty": 50.0, "stock_uom": "_Test UOM",
"uom": "_Test UOM" "t_warehouse": "_Test Warehouse - _TC",
} "transfer_qty": 50.0,
], "uom": "_Test UOM"
"purpose": "Material Receipt" }
}, ]
},
{
{ "company": "_Test Company",
"company": "_Test Company", "doctype": "Stock Entry",
"doctype": "Stock Entry", "posting_date": "2013-01-25",
"items": [ "purpose": "Material Issue",
{ "stock_entry_type": "Material Issue",
"conversion_factor": 1.0, "items": [
"cost_center": "_Test Cost Center - _TC", {
"doctype": "Stock Entry Detail", "conversion_factor": 1.0,
"expense_account": "Stock Adjustment - _TC", "cost_center": "_Test Cost Center - _TC",
"basic_rate": 100, "doctype": "Stock Entry Detail",
"item_code": "_Test Item", "expense_account": "Stock Adjustment - _TC",
"parentfield": "items", "basic_rate": 100,
"qty": 40.0, "item_code": "_Test Item",
"s_warehouse": "_Test Warehouse - _TC", "parentfield": "items",
"stock_uom": "_Test UOM", "qty": 40.0,
"transfer_qty": 40.0, "s_warehouse": "_Test Warehouse - _TC",
"uom": "_Test UOM" "stock_uom": "_Test UOM",
} "transfer_qty": 40.0,
], "uom": "_Test UOM"
"posting_date": "2013-01-25", }
"purpose": "Material Issue" ]
}, },
{
"company": "_Test Company",
{ "doctype": "Stock Entry",
"company": "_Test Company", "posting_date": "2013-01-25",
"doctype": "Stock Entry", "purpose": "Material Transfer",
"items": [ "stock_entry_type": "Material Transfer",
{ "items": [
"conversion_factor": 1.0, {
"cost_center": "_Test Cost Center - _TC", "conversion_factor": 1.0,
"doctype": "Stock Entry Detail", "cost_center": "_Test Cost Center - _TC",
"expense_account": "Stock Adjustment - _TC", "doctype": "Stock Entry Detail",
"basic_rate": 100, "expense_account": "Stock Adjustment - _TC",
"item_code": "_Test Item", "basic_rate": 100,
"parentfield": "items", "item_code": "_Test Item",
"qty": 45.0, "parentfield": "items",
"s_warehouse": "_Test Warehouse - _TC", "qty": 45.0,
"stock_uom": "_Test UOM", "s_warehouse": "_Test Warehouse - _TC",
"t_warehouse": "_Test Warehouse 1 - _TC", "stock_uom": "_Test UOM",
"transfer_qty": 45.0, "t_warehouse": "_Test Warehouse 1 - _TC",
"uom": "_Test UOM" "transfer_qty": 45.0,
} "uom": "_Test UOM"
], }
"posting_date": "2013-01-25", ]
"purpose": "Material Transfer" },
}, {
"company": "_Test Company",
"doctype": "Stock Entry",
{ "purpose": "Repack",
"company": "_Test Company", "stock_entry_type": "Repack",
"doctype": "Stock Entry", "items": [
"items": [ {
{ "conversion_factor": 1.0,
"conversion_factor": 1.0, "cost_center": "_Test Cost Center - _TC",
"cost_center": "_Test Cost Center - _TC", "doctype": "Stock Entry Detail",
"doctype": "Stock Entry Detail", "expense_account": "Stock Adjustment - _TC",
"expense_account": "Stock Adjustment - _TC", "basic_rate": 100,
"basic_rate": 100, "item_code": "_Test Item",
"item_code": "_Test Item", "parentfield": "items",
"parentfield": "items", "qty": 50.0,
"qty": 50.0, "s_warehouse": "_Test Warehouse - _TC",
"s_warehouse": "_Test Warehouse - _TC", "stock_uom": "_Test UOM",
"stock_uom": "_Test UOM", "transfer_qty": 50.0,
"transfer_qty": 50.0, "uom": "_Test UOM"
"uom": "_Test UOM" },
}, {
{ "conversion_factor": 1.0,
"conversion_factor": 1.0, "cost_center": "_Test Cost Center - _TC",
"cost_center": "_Test Cost Center - _TC", "doctype": "Stock Entry Detail",
"doctype": "Stock Entry Detail", "expense_account": "Stock Adjustment - _TC",
"expense_account": "Stock Adjustment - _TC", "basic_rate": 5000,
"basic_rate": 5000, "item_code": "_Test Item Home Desktop 100",
"item_code": "_Test Item Home Desktop 100", "parentfield": "items",
"parentfield": "items", "qty": 1,
"qty": 1, "stock_uom": "_Test UOM",
"stock_uom": "_Test UOM", "t_warehouse": "_Test Warehouse - _TC",
"t_warehouse": "_Test Warehouse - _TC", "transfer_qty": 1,
"transfer_qty": 1, "uom": "_Test UOM"
"uom": "_Test UOM" }
} ]
], }
"purpose": "Repack"
}
] ]

View File

@ -15,7 +15,7 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make_item_variant, create_item from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make_item_variant, create_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse, make_stock_in_entry
from six import iteritems from six import iteritems
@ -239,6 +239,7 @@ class TestStockEntry(unittest.TestCase):
repack = frappe.copy_doc(test_records[3]) repack = frappe.copy_doc(test_records[3])
repack.posting_date = nowdate() repack.posting_date = nowdate()
repack.posting_time = nowtime() repack.posting_time = nowtime()
repack.set_stock_entry_type()
repack.insert() repack.insert()
repack.submit() repack.submit()
@ -272,6 +273,8 @@ class TestStockEntry(unittest.TestCase):
"amount": 200 "amount": 200
}, },
]) ])
repack.set_stock_entry_type()
repack.insert() repack.insert()
repack.submit() repack.submit()
@ -327,6 +330,7 @@ class TestStockEntry(unittest.TestCase):
def test_serial_no_not_reqd(self): def test_serial_no_not_reqd(self):
se = frappe.copy_doc(test_records[0]) se = frappe.copy_doc(test_records[0])
se.get("items")[0].serial_no = "ABCD" se.get("items")[0].serial_no = "ABCD"
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoNotRequiredError, se.submit) self.assertRaises(SerialNoNotRequiredError, se.submit)
@ -335,6 +339,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].item_code = "_Test Serialized Item" se.get("items")[0].item_code = "_Test Serialized Item"
se.get("items")[0].qty = 2 se.get("items")[0].qty = 2
se.get("items")[0].transfer_qty = 2 se.get("items")[0].transfer_qty = 2
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoRequiredError, se.submit) self.assertRaises(SerialNoRequiredError, se.submit)
@ -344,6 +349,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].qty = 2 se.get("items")[0].qty = 2
se.get("items")[0].serial_no = "ABCD\nEFGH\nXYZ" se.get("items")[0].serial_no = "ABCD\nEFGH\nXYZ"
se.get("items")[0].transfer_qty = 2 se.get("items")[0].transfer_qty = 2
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoQtyError, se.submit) self.assertRaises(SerialNoQtyError, se.submit)
@ -353,6 +359,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].qty = 2 se.get("items")[0].qty = 2
se.get("items")[0].serial_no = "ABCD" se.get("items")[0].serial_no = "ABCD"
se.get("items")[0].transfer_qty = 2 se.get("items")[0].transfer_qty = 2
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoQtyError, se.submit) self.assertRaises(SerialNoQtyError, se.submit)
@ -362,6 +369,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].qty = 2 se.get("items")[0].qty = 2
se.get("items")[0].serial_no = "ABCD\nEFGH" se.get("items")[0].serial_no = "ABCD\nEFGH"
se.get("items")[0].transfer_qty = 2 se.get("items")[0].transfer_qty = 2
se.set_stock_entry_type()
se.insert() se.insert()
se.submit() se.submit()
@ -382,6 +390,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].t_warehouse = None se.get("items")[0].t_warehouse = None
se.get("items")[0].serial_no = "ABCD\nEFGH" se.get("items")[0].serial_no = "ABCD\nEFGH"
se.get("items")[0].transfer_qty = 2 se.get("items")[0].transfer_qty = 2
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoNotExistsError, se.submit) self.assertRaises(SerialNoNotExistsError, se.submit)
@ -394,6 +403,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].qty = 1 se.get("items")[0].qty = 1
se.get("items")[0].serial_no = serial_nos[0] se.get("items")[0].serial_no = serial_nos[0]
se.get("items")[0].transfer_qty = 1 se.get("items")[0].transfer_qty = 1
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoDuplicateError, se.submit) self.assertRaises(SerialNoDuplicateError, se.submit)
@ -420,6 +430,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].serial_no = serial_nos[0] se.get("items")[0].serial_no = serial_nos[0]
se.get("items")[0].s_warehouse = "_Test Warehouse - _TC" se.get("items")[0].s_warehouse = "_Test Warehouse - _TC"
se.get("items")[0].t_warehouse = "_Test Warehouse 1 - _TC" se.get("items")[0].t_warehouse = "_Test Warehouse 1 - _TC"
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoItemError, se.submit) self.assertRaises(SerialNoItemError, se.submit)
@ -435,6 +446,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].serial_no = serial_no se.get("items")[0].serial_no = serial_no
se.get("items")[0].s_warehouse = "_Test Warehouse - _TC" se.get("items")[0].s_warehouse = "_Test Warehouse - _TC"
se.get("items")[0].t_warehouse = "_Test Warehouse 1 - _TC" se.get("items")[0].t_warehouse = "_Test Warehouse 1 - _TC"
se.set_stock_entry_type()
se.insert() se.insert()
se.submit() se.submit()
self.assertTrue(frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC") self.assertTrue(frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC")
@ -456,6 +468,7 @@ class TestStockEntry(unittest.TestCase):
se.get("items")[0].serial_no = serial_nos[0] se.get("items")[0].serial_no = serial_nos[0]
se.get("items")[0].s_warehouse = "_Test Warehouse 1 - _TC" se.get("items")[0].s_warehouse = "_Test Warehouse 1 - _TC"
se.get("items")[0].t_warehouse = "_Test Warehouse - _TC" se.get("items")[0].t_warehouse = "_Test Warehouse - _TC"
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(SerialNoWarehouseError, se.submit) self.assertRaises(SerialNoWarehouseError, se.submit)
@ -476,6 +489,7 @@ class TestStockEntry(unittest.TestCase):
from erpnext.stock.utils import InvalidWarehouseCompany from erpnext.stock.utils import InvalidWarehouseCompany
st1 = frappe.copy_doc(test_records[0]) st1 = frappe.copy_doc(test_records[0])
st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1" st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1"
st1.set_stock_entry_type()
st1.insert() st1.insert()
self.assertRaises(InvalidWarehouseCompany, st1.submit) self.assertRaises(InvalidWarehouseCompany, st1.submit)
@ -506,6 +520,7 @@ class TestStockEntry(unittest.TestCase):
st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1" st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1"
st1.get("items")[0].expense_account = "Stock Adjustment - _TC1" st1.get("items")[0].expense_account = "Stock Adjustment - _TC1"
st1.get("items")[0].cost_center = "Main - _TC1" st1.get("items")[0].cost_center = "Main - _TC1"
st1.set_stock_entry_type()
st1.insert() st1.insert()
st1.submit() st1.submit()
@ -529,6 +544,7 @@ class TestStockEntry(unittest.TestCase):
se = frappe.copy_doc(test_records[0]) se = frappe.copy_doc(test_records[0])
se.set_posting_time = 1 se.set_posting_time = 1
se.posting_date = add_days(nowdate(), -15) se.posting_date = add_days(nowdate(), -15)
se.set_stock_entry_type()
se.insert() se.insert()
self.assertRaises(StockFreezeError, se.submit) self.assertRaises(StockFreezeError, se.submit)
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0) frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", 0)
@ -644,6 +660,7 @@ class TestStockEntry(unittest.TestCase):
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"sample_quantity": 4 "sample_quantity": 4
}) })
receipt_entry.set_stock_entry_type()
receipt_entry.insert() receipt_entry.insert()
receipt_entry.submit() receipt_entry.submit()
@ -660,6 +677,7 @@ class TestStockEntry(unittest.TestCase):
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"batch_no": receipt_entry.get("items")[0].batch_no "batch_no": receipt_entry.get("items")[0].batch_no
}) })
retention_entry.set_stock_entry_type()
retention_entry.insert() retention_entry.insert()
retention_entry.submit() retention_entry.submit()
@ -726,6 +744,33 @@ class TestStockEntry(unittest.TestCase):
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1) self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0) 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 make_serialized_item(item_code=None, serial_no=None, target_warehouse=None): def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
se = frappe.copy_doc(test_records[0]) se = frappe.copy_doc(test_records[0])
@ -737,6 +782,7 @@ def make_serialized_item(item_code=None, serial_no=None, target_warehouse=None):
if target_warehouse: if target_warehouse:
se.get("items")[0].t_warehouse = target_warehouse se.get("items")[0].t_warehouse = target_warehouse
se.set_stock_entry_type()
se.insert() se.insert()
se.submit() se.submit()
return se return se

View File

@ -6,7 +6,7 @@ QUnit.test("test material Transfer to manufacture", function(assert) {
frappe.run_serially([ frappe.run_serially([
() => { () => {
return frappe.tests.make('Stock Entry', [ return frappe.tests.make('Stock Entry', [
{purpose:'Subcontract'}, {purpose:'Send to Subcontractor'},
{from_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, {from_warehouse:'Work In Progress - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
{to_warehouse:'Finished Goods - '+frappe.get_abbr(frappe.defaults.get_default('Company'))}, {to_warehouse:'Finished Goods - '+frappe.get_abbr(frappe.defaults.get_default('Company'))},
{items: [ {items: [

View File

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Stock Entry Type', {
// refresh: function(frm) {
// }
});

View File

@ -0,0 +1,156 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"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",
"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,
"reqd": 1,
"search_index": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}
],
"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",
"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
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class StockEntryType(Document):
pass

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
class TestStockEntryType(unittest.TestCase):
pass

View File

@ -59,7 +59,7 @@ def get_consumed_items(condition):
sum(se_item.transfer_qty) as 'consume_qty' sum(se_item.transfer_qty) as 'consume_qty'
from `tabStock Entry` se, `tabStock Entry Detail` se_item from `tabStock Entry` se, `tabStock Entry Detail` se_item
where se.name = se_item.parent and se.docstatus = 1 where se.name = se_item.parent and se.docstatus = 1
and (ifnull(se_item.t_warehouse, '') = '' or se.purpose = 'Subcontract') %s and (ifnull(se_item.t_warehouse, '') = '' or se.purpose = 'Send to Subcontractor') %s
group by se_item.item_code""" % (condition), as_dict=1) group by se_item.item_code""" % (condition), as_dict=1)
cn_items_map = {} cn_items_map = {}