Merge branch 'PawanMeh-fixes_8427' into develop

This commit is contained in:
Nabin Hait 2018-01-05 18:21:40 +05:30
commit 54229ca66e
20 changed files with 507 additions and 57 deletions

View File

@ -169,10 +169,10 @@ class Account(NestedSet):
# Add company abbr if not provided
from erpnext.setup.doctype.company.company import get_name_with_abbr
new_account = get_name_with_abbr(new, self.company)
new_account = get_name_with_number(new_account, self.account_number)
# Validate properties before merging
if merge:
if not merge:
new_account = get_name_with_number(new_account, self.account_number)
else:
# Validate properties before merging
if not frappe.db.exists("Account", new):
throw(_("Account {0} does not exist").format(new))

View File

@ -350,7 +350,6 @@ class PurchaseInvoice(BuyingController):
self.negative_expense_to_be_booked = 0.0
gl_entries = []
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
self.make_tax_gl_entries(gl_entries)
@ -424,7 +423,10 @@ class PurchaseInvoice(BuyingController):
# sub-contracting warehouse
if flt(item.rm_supp_cost):
supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["name"]
supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"]
if not supplier_warehouse_account:
frappe.throw(_("Please set account in Warehouse {0}")
.format(self.supplier_warehouse))
gl_entries.append(self.get_gl_dict({
"account": supplier_warehouse_account,
"against": item.expense_account,

View File

@ -12,6 +12,15 @@ frappe.ui.form.on("Purchase Order", {
'Purchase Invoice': 'Invoice',
'Stock Entry': 'Material to Supplier'
}
frm.set_query("reserve_warehouse", "supplied_items", function() {
return {
filters: {
"company": frm.doc.company,
"is_group": 0
}
}
});
},
onload: function(frm) {
@ -289,7 +298,8 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt
filters: [
['BOM', 'item', '=', d.item_code],
['BOM', 'is_active', '=', '1'],
['BOM', 'docstatus', '=', '1']
['BOM', 'docstatus', '=', '1'],
['BOM', 'company', '=', doc.company]
]
}
}

View File

@ -3071,6 +3071,38 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplied_items_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplied Items",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "supplied_items",
"fieldtype": "Table",
"hidden": 0,
@ -3261,8 +3293,8 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-12-19 14:53:03.986840",
"modified_by": "nabinhait@gmail.com",
"modified": "2018-01-05 14:44:56.132189",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
"owner": "Administrator",

View File

@ -12,7 +12,7 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details
from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty
from frappe.desk.notifications import clear_doctype_notifications
from erpnext.buying.utils import validate_for_items, check_for_closed_status
from erpnext.stock.utils import get_bin
form_grid_templates = {
"items": "templates/form_grid/item_grid.html"
@ -71,8 +71,10 @@ class PurchaseOrder(BuyingController):
def validate_supplier(self):
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
if prevent_po:
standing = frappe.db.get_value("Supplier Scorecard",self.supplier, 'status')
frappe.throw(_("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.").format(self.supplier, standing))
standing = frappe.db.get_value("Supplier Scorecard", self.supplier, 'status')
if standing:
frappe.throw(_("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.")
.format(self.supplier, standing))
warn_po = frappe.db.get_value("Supplier", self.supplier, 'warn_pos')
if warn_po:
@ -184,6 +186,9 @@ class PurchaseOrder(BuyingController):
self.set_status(update=True, status=status)
self.update_requested_qty()
self.update_ordered_qty()
if self.is_subcontracted == "Yes":
self.update_reserved_qty_for_subcontract()
self.notify_update()
clear_doctype_notifications(self)
@ -196,6 +201,8 @@ class PurchaseOrder(BuyingController):
self.update_prevdoc_status()
self.update_requested_qty()
self.update_ordered_qty()
if self.is_subcontracted == "Yes":
self.update_reserved_qty_for_subcontract()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total)
@ -209,6 +216,9 @@ class PurchaseOrder(BuyingController):
if self.has_drop_ship_item():
self.update_delivered_qty_in_sales_order()
if self.is_subcontracted == "Yes":
self.update_reserved_qty_for_subcontract()
self.check_for_closed_status()
frappe.db.set(self,'status','Cancelled')
@ -256,6 +266,12 @@ class PurchaseOrder(BuyingController):
if item.delivered_by_supplier == 1:
item.received_qty = item.qty
def update_reserved_qty_for_subcontract(self):
for d in self.supplied_items:
if d.rm_item_code:
stock_bin = get_bin(d.rm_item_code, d.reserve_warehouse)
stock_bin.update_reserved_qty_for_sub_contracting()
@frappe.whitelist()
def close_or_unclose_purchase_orders(names, status):
if not frappe.has_permission("Purchase Order", "write"):

View File

@ -6,8 +6,8 @@ import unittest
import frappe
import frappe.defaults
from frappe.utils import flt, add_days, nowdate
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_purchase_invoice
from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_stock_entry as make_subcontract_transfer_entry)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_receipt(self):
@ -182,24 +182,129 @@ class TestPurchaseOrder(unittest.TestCase):
pi.insert()
self.assertTrue(pi.get('payment_schedule'))
def test_reserved_qty_subcontract_po(self):
# Make stock available for raw materials
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100",
qty=20, basic_rate=100)
bin1 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
# Submit PO
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
bin2 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10)
# Create stock transfer
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, "_Test FG Item"))
se.to_warehouse = "_Test Warehouse 1 - _TC"
for d in se.get("items"):
if d.item_code == "_Test Item":
d.qty = 6
se.save()
se.submit()
bin3 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# close PO
po.update_status("Closed")
bin4 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Re-open PO
po.update_status("Submitted")
bin5 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# make Purchase Receipt against PO
pr = make_purchase_receipt(po.name)
pr.supplier_warehouse = "_Test Warehouse 1 - _TC"
pr.save()
pr.submit()
bin6 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pr.cancel()
bin7 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Make Purchase Invoice
pi = make_purchase_invoice(po.name)
pi.update_stock = 1
pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.insert()
pi.submit()
bin8 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
# Cancel PR
pi.cancel()
bin9 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Cancel Stock Entry
se.cancel()
bin10 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
# Cancel PO
po.reload()
po.cancel()
bin11 = frappe.db.get_value("Bin",
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
fieldname="reserved_qty_for_sub_contract", as_dict=1)
self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract)
def get_same_items():
return [
{
"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 500,
"schedule_date": add_days(nowdate(), 1)
},
{
"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 4,
"rate": 500,
"schedule_date": add_days(nowdate(), 1)
}
]
{
"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 1,
"rate": 500,
"schedule_date": add_days(nowdate(), 1)
},
{
"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 4,
"rate": 500,
"schedule_date": add_days(nowdate(), 1)
}
]
def create_purchase_order(**args):
po = frappe.new_doc("Purchase Order")
@ -224,6 +329,10 @@ def create_purchase_order(**args):
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"
po.submit()
return po

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
@ -10,16 +11,20 @@
"editable_grid": 1,
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "main_item_code",
"fieldtype": "Data",
"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": "Item Code",
"length": 0,
"no_copy": 0,
@ -29,6 +34,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -36,16 +42,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "rm_item_code",
"fieldtype": "Data",
"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": "Raw Material Item Code",
"length": 0,
"no_copy": 0,
@ -55,6 +65,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -62,16 +73,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "required_qty",
"fieldtype": "Float",
"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": "Supplied Qty",
"length": 0,
"no_copy": 0,
@ -81,6 +96,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -88,16 +104,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "rate",
"fieldtype": "Currency",
"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": "Rate",
"length": 0,
"no_copy": 0,
@ -108,6 +128,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -115,16 +136,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
@ -135,6 +160,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -142,16 +168,49 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bom_detail_no",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "BOM Detail No",
"length": 0,
"no_copy": 0,
@ -161,6 +220,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -168,16 +228,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Name",
"length": 0,
"no_copy": 0,
@ -187,6 +251,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -194,16 +259,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conversion_factor",
"fieldtype": "Float",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Conversion Factor",
"length": 0,
"no_copy": 0,
@ -213,6 +282,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -220,16 +290,20 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Stock Uom",
"length": 0,
"no_copy": 0,
@ -240,6 +314,38 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"fieldname": "reserve_warehouse",
"fieldtype": "Link",
"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": "Reserve Warehouse",
"length": 0,
"no_copy": 0,
"options": "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": 0,
"search_index": 0,
@ -247,17 +353,17 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 1,
"idx": 1,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:05.533063",
"modified": "2018-01-05 14:47:15.400785",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item Supplied",
@ -266,5 +372,7 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
}

View File

@ -160,6 +160,11 @@ class BuyingController(StockController):
if item in self.sub_contracted_items and not item.bom:
frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
if self.doctype == "Purchase Order":
for supplied_item in self.get("supplied_items"):
if not supplied_item.reserve_warehouse:
frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code)))
else:
for item in self.get("items"):
if item.bom:
@ -189,8 +194,16 @@ class BuyingController(StockController):
def update_raw_materials_supplied(self, item, raw_material_table):
bom_items = self.get_items_from_bom(item.item_code, item.bom)
raw_materials_cost = 0
items = list(set([d.item_code for d in bom_items]))
item_wh = frappe._dict(frappe.db.sql("""select item_code, default_warehouse
from `tabItem` where name in ({0})""".format(", ".join(["%s"] * len(items))), items))
for bom_item in bom_items:
if self.doctype == "Purchase Order":
reserve_warehouse = bom_item.source_warehouse or item_wh.get(bom_item.item_code)
if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != self.company:
reserve_warehouse = None
# check if exists
exists = 0
for d in self.get(raw_material_table):
@ -210,6 +223,8 @@ class BuyingController(StockController):
rm.rm_item_code = bom_item.item_code
rm.stock_uom = bom_item.stock_uom
rm.required_qty = required_qty
if self.doctype == "Purchase Order" and not rm.reserve_warehouse:
rm.reserve_warehouse = reserve_warehouse
rm.conversion_factor = item.conversion_factor
@ -261,7 +276,7 @@ class BuyingController(StockController):
def get_items_from_bom(self, item_code, bom):
bom_items = frappe.db.sql("""select t2.item_code,
t2.stock_qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit,
t2.rate, t2.stock_uom, t2.name, t2.description
t2.rate, t2.stock_uom, t2.name, t2.description, t2.source_warehouse
from `tabBOM` t1, `tabBOM Item` t2, tabItem t3
where t2.parent = t1.name and t1.item = %s
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
@ -336,7 +351,7 @@ class BuyingController(StockController):
frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code'])))
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
self.update_ordered_qty()
self.update_ordered_and_reserved_qty()
sl_entries = []
stock_items = self.get_stock_items()
@ -378,7 +393,7 @@ class BuyingController(StockController):
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher)
def update_ordered_qty(self):
def update_ordered_and_reserved_qty(self):
po_map = {}
for d in self.get("items"):
if self.doctype=="Purchase Receipt" \
@ -397,6 +412,8 @@ class BuyingController(StockController):
frappe.InvalidStatusError)
po_obj.update_ordered_qty(po_item_rows)
if self.is_subcontracted:
po_obj.update_reserved_qty_for_subcontract()
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
if hasattr(self, 'supplied_items'):

View File

@ -484,4 +484,5 @@ erpnext.patches.v10_0.copy_projects_renamed_fields
erpnext.patches.v10_0.enabled_regional_print_format_based_on_country
erpnext.patches.v10_0.update_asset_calculate_depreciation
erpnext.patches.v10_0.add_guardian_role_for_parent_portal
erpnext.patches.v10_0.set_numeric_ranges_in_template_if_blank
erpnext.patches.v10_0.set_numeric_ranges_in_template_if_blank
erpnext.patches.v10_0.update_reserved_qty_for_purchase_order

View File

@ -0,0 +1,48 @@
import frappe
from frappe import _
from erpnext.stock.utils import get_bin
def execute():
po_item = list(frappe.db.sql(("""
select distinct po.name as poname, poitem.rm_item_code as rm_item_code, po.company
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitem
where po.name = poitem.parent
and po.is_subcontracted = "Yes"
and po.docstatus = 1"""), as_dict=1))
if not po_item:
return
company_warehouse = frappe._dict(frappe.db.sql("""select company, min(name) from `tabWarehouse`
where is_group = 0 group by company"""))
items = list(set([d.rm_item_code for d in po_item]))
item_wh = frappe._dict(frappe.db.sql("""select item_code, default_warehouse
from `tabItem` where name in ({0})""".format(", ".join(["%s"] * len(items))), items))
# Update reserved warehouse
for item in po_item:
reserve_warehouse = get_warehouse(item.rm_item_code, item.company, company_warehouse, item_wh)
update_res_warehouse = frappe.db.sql("""update `tabPurchase Order Item Supplied`
set reserve_warehouse = %s
where parent = %s and rm_item_code = %s
""", (reserve_warehouse, item["poname"], item["rm_item_code"]))
# Update bin
item_wh_bin = frappe.db.sql(("""
select distinct poitemsup.rm_item_code as rm_item_code,
poitemsup.reserve_warehouse as reserve_warehouse
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
where po.name = poitemsup.parent
and po.is_subcontracted = "Yes"
and po.docstatus = 1"""), as_dict=1)
for d in item_wh_bin:
stock_bin = get_bin(d["rm_item_code"], d["reserve_warehouse"])
stock_bin.update_reserved_qty_for_sub_contracting()
def get_warehouse(item_code, company, company_warehouse, item_wh):
reserve_warehouse = item_wh.get(item_code)
if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != company:
reserve_warehouse = None
if not reserve_warehouse:
reserve_warehouse = company_warehouse.get(company)
return reserve_warehouse

View File

@ -10,6 +10,7 @@ def execute():
frappe.reload_doctype("Purchase Receipt")
frappe.reload_doctype("Sales Order Item")
frappe.reload_doctype("Purchase Order Item")
frappe.reload_doctype("Purchase Order Item Supplied")
# sales return
return_entries = list(frappe.db.sql("""
@ -86,6 +87,6 @@ def execute():
""", (order_details[0].purchase_order, order_details[0].po_detail, d.row_id))
pr = frappe.get_doc("Purchase Receipt", d.name)
pr.update_ordered_qty()
pr.update_ordered_and_reserved_qty()
pr.update_prevdoc_status()

View File

@ -89,7 +89,7 @@ erpnext.stock.ItemDashboard = Class.extend({
data.forEach(function(d) {
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production;
d.pending_qty = 0;
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production;
d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
if(d.actual_or_pending > d.actual_qty) {
d.pending_qty = d.actual_or_pending - d.actual_qty;
}

View File

@ -26,13 +26,14 @@ def get_data(item_code=None, warehouse=None, item_group=None,
return frappe.db.sql('''
select
b.item_code, b.warehouse, b.projected_qty, b.reserved_qty,
b.reserved_qty_for_production, b.actual_qty, b.valuation_rate, i.item_name
b.reserved_qty_for_production, b.reserved_qty_for_sub_contract, b.actual_qty, b.valuation_rate, i.item_name
from
tabBin b, tabItem i
where
b.item_code = i.name
and
(b.projected_qty != 0 or b.reserved_qty != 0 or b.reserved_qty_for_production != 0 or b.actual_qty != 0)
(b.projected_qty != 0 or b.reserved_qty != 0 or b.reserved_qty_for_production != 0
or b.reserved_qty_for_sub_contract != 0 or b.actual_qty != 0)
{conditions}
order by
{sort_by} {sort_order}

View File

@ -296,6 +296,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reserved Qty for sub contract",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -463,7 +493,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-06-13 13:06:32.601505",
"modified": "2017-11-22 08:14:30.615638",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",

View File

@ -64,7 +64,7 @@ class Bin(Document):
def set_projected_qty(self):
self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty)
+ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
- flt(self.reserved_qty_for_production))
- flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
def get_first_sle(self):
sle = frappe.db.sql("""
@ -93,6 +93,42 @@ class Bin(Document):
self.db_set('reserved_qty_for_production', flt(self.reserved_qty_for_production))
self.db_set('projected_qty', self.projected_qty)
def update_reserved_qty_for_sub_contracting(self):
#reserved qty
reserved_qty_for_sub_contract = frappe.db.sql('''
select ifnull(sum(itemsup.required_qty),0)
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup
where
itemsup.rm_item_code = %s
and itemsup.parent = po.name
and po.docstatus = 1
and po.is_subcontracted = 'Yes'
and po.status != 'Closed'
and po.per_received < 100
and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0]
#Get Transferred Entries
materials_transferred = frappe.db.sql("""
select
ifnull(sum(transfer_qty),0)
from
`tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
where
se.docstatus=1
and se.purpose='Subcontract'
and ifnull(se.purchase_order, '') !=''
and sed.item_code = %s
and se.name = sed.parent
and se.purchase_order = po.name
and po.docstatus = 1
and po.is_subcontracted = 'Yes'
and po.status != 'Closed'
and po.per_received < 100
""", (self.item_code))[0][0]
self.db_set('reserved_qty_for_sub_contract', (reserved_qty_for_sub_contract - materials_transferred))
self.set_projected_qty()
self.db_set('projected_qty', self.projected_qty)
def update_item_projected_qty(item_code):
'''Set total_projected_qty in Item as sum of projected qty in all warehouses'''

View File

@ -121,8 +121,10 @@ class PurchaseReceipt(BuyingController):
if self.per_billed < 100:
self.update_billing_status()
# Updating stock ledger should always be called after updating prevdoc status,
# because updating ordered qty in bin depends upon updated ordered qty in PO
# because updating ordered qty, reserved_qty_for_subcontract in bin
# depends upon updated ordered qty in PO
self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit

View File

@ -11,6 +11,7 @@ from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError, get
from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor
from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos, get_batch_qty
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.stock.utils import get_bin
import json
class IncorrectValuationRateError(frappe.ValidationError): pass
@ -69,11 +70,15 @@ class StockEntry(StockController):
update_serial_nos_after_submit(self, "items")
self.update_production_order()
self.validate_purchase_order()
if self.purchase_order and self.purpose == "Subcontract":
self.update_purchase_order_supplied_items()
self.make_gl_entries()
def on_cancel(self):
self.update_stock_ledger()
self.update_production_order()
if self.purchase_order and self.purpose == "Subcontract":
self.update_purchase_order_supplied_items()
self.make_gl_entries_on_cancel()
def validate_purpose(self):
@ -581,19 +586,31 @@ class StockEntry(StockController):
frappe.throw(_("Manufacturing Quantity is mandatory"))
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
#Get PO Supplied Items Details
if self.purchase_order and self.purpose == "Subcontract":
#Get PO Supplied Items Details
item_wh = frappe._dict(frappe.db.sql("""
select rm_item_code, reserve_warehouse
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
where po.name = poitemsup.parent
and po.name = %s""",self.purchase_order))
for item in item_dict.values():
if self.pro_doc and not self.pro_doc.skip_transfer:
item["from_warehouse"] = self.pro_doc.wip_warehouse
#Get Reserve Warehouse from PO
item["from_warehouse"] = item_wh.get(item.item_code)
item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else ""
self.add_to_stock_entry_detail(item_dict)
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
for item in scrap_item_dict.values():
if self.pro_doc and self.pro_doc.scrap_warehouse:
item["to_warehouse"] = self.pro_doc.scrap_warehouse
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
if self.purpose != "Subcontract":
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
for item in scrap_item_dict.values():
if self.pro_doc and self.pro_doc.scrap_warehouse:
item["to_warehouse"] = self.pro_doc.scrap_warehouse
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
# fetch the serial_no of the first stock entry for the second stock entry
if self.production_order and self.purpose == "Manufacture":
@ -830,9 +847,23 @@ class StockEntry(StockController):
expiry_date = frappe.db.get_value("Batch", item.batch_no, "expiry_date")
if expiry_date:
if getdate(self.posting_date) > getdate(expiry_date):
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
frappe.throw(_("Batch {0} of Item {1} has expired.")
.format(item.batch_no, item.item_code))
def update_purchase_order_supplied_items(self):
#Get PO Supplied Items Details
item_wh = frappe._dict(frappe.db.sql("""
select rm_item_code, reserve_warehouse
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
where po.name = poitemsup.parent
and po.name = %s""", self.purchase_order))
#Update reserved sub contracted quantity in bin based on Supplied Item Details
for d in self.get("items"):
reserve_warehouse = item_wh.get(d.item_code)
stock_bin = get_bin(d.item_code, reserve_warehouse)
stock_bin.update_reserved_qty_for_sub_contracting()
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, basestring):
@ -843,7 +874,8 @@ def move_sample_to_retention_warehouse(company, items):
stock_entry.purpose = "Material Transfer"
for item in items:
if item.get('sample_quantity') and item.get('batch_no'):
sample_quantity = validate_sample_quantity(item.get('item_code'), item.get('sample_quantity'), item.get('transfer_qty') or item.get('qty'), item.get('batch_no'))
sample_quantity = validate_sample_quantity(item.get('item_code'), item.get('sample_quantity'),
item.get('transfer_qty') or item.get('qty'), item.get('batch_no'))
if sample_quantity:
sample_serial_nos = ''
if item.get('serial_no'):
@ -851,6 +883,7 @@ def move_sample_to_retention_warehouse(company, items):
if serial_nos and len(serial_nos) > item.get('sample_quantity'):
serial_no_list = serial_nos[:-(len(serial_nos)-item.get('sample_quantity'))]
sample_serial_nos = '\n'.join(serial_no_list)
stock_entry.append("items", {
"item_code": item.get('item_code'),
"s_warehouse": item.get('t_warehouse'),

View File

@ -48,6 +48,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
{fieldname: 'projected_qty', label: __('Projected qty')},
{fieldname: 'reserved_qty', label: __('Reserved for sale')},
{fieldname: 'reserved_qty_for_production', label: __('Reserved for manufacturing')},
{fieldname: 'reserved_qty_for_sub_contract', label: __('Reserved for sub contracting')},
{fieldname: 'actual_qty', label: __('Actual qty in stock')},
]
},

View File

@ -16,6 +16,7 @@ def get_columns():
_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100",
_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100",
_("Reserved Qty") + ":Float:100", _("Reserved Qty for Production") + ":Float:100",
_("Reserved for sub contracting") + ":Float:100",
_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100",
_("Shortage Qty") + ":Float:100"]
@ -33,7 +34,8 @@ def get_data(filters):
continue
# item = item_map.setdefault(bin.item_code, get_item(bin.item_code))
company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company"))
company = warehouse_company.setdefault(bin.warehouse,
frappe.db.get_value("Warehouse", bin.warehouse, "company"))
if filters.brand and filters.brand != item.brand:
continue
@ -52,7 +54,8 @@ def get_data(filters):
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
bin.reserved_qty, bin.reserved_qty_for_production, bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract,
bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
return data
@ -71,7 +74,7 @@ def get_bin_list(filters):
warehouse_details.rgt))
bin_list = frappe.db.sql("""select item_code, warehouse, actual_qty, planned_qty, indented_qty,
ordered_qty, reserved_qty, reserved_qty_for_production, projected_qty
ordered_qty, reserved_qty, reserved_qty_for_production, reserved_qty_for_sub_contract, projected_qty
from tabBin bin {conditions} order by item_code, warehouse
""".format(conditions=" where " + " and ".join(conditions) if conditions else ""), as_dict=1)

View File

@ -150,7 +150,7 @@ def update_bin_qty(item_code, warehouse, qty_dict=None):
if mismatch:
bin.projected_qty = (flt(bin.actual_qty) + flt(bin.ordered_qty) +
flt(bin.indented_qty) + flt(bin.planned_qty) - flt(bin.reserved_qty)
- flt(bin.reserved_qty_for_production))
- flt(bin.reserved_qty_for_production)) - flt(bin.reserved_qty_for_sub_contract)
bin.save()