Reserve for subcontracting (#13195)
* [fix] #8427 * review comments changes * Validation for reserved warhouse * code improvements * alignment * test case * message changes * default warehouse / remove validation / change sql * fix * patch * Fixed merge conflict * Fixes and cleanups of reserve qty for subcontracting * set from_warehouse only if purchase_order and purpose found (#12398) * [HotFix] Validation issue for subcontract stock entry (#12127) * [Fix] Validation issue for subcontract stock entry * Update stock_entry.py * Fixes and cleanups of reserve qty for subcontracting * patch fixed * Reload bin in patch * [fix] set source warehouse in stock entry for manufacture * [fix] #8540 * code alignment * code alignment * Move target warehouse validation to submit * validation code improvement * code changes for single stock entry * validation fix * call make_rm_stock_entry * remove old stock entry method/rewrite test case * Don't set bom_no against raw materials while trasferring items for sub-contracting * minor fix
This commit is contained in:
parent
58797481f0
commit
2c7a6e6b43
@ -169,10 +169,10 @@ class Account(NestedSet):
|
|||||||
# Add company abbr if not provided
|
# Add company abbr if not provided
|
||||||
from erpnext.setup.doctype.company.company import get_name_with_abbr
|
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_abbr(new, self.company)
|
||||||
new_account = get_name_with_number(new_account, self.account_number)
|
if not merge:
|
||||||
|
new_account = get_name_with_number(new_account, self.account_number)
|
||||||
# Validate properties before merging
|
else:
|
||||||
if merge:
|
# Validate properties before merging
|
||||||
if not frappe.db.exists("Account", new):
|
if not frappe.db.exists("Account", new):
|
||||||
throw(_("Account {0} does not exist").format(new))
|
throw(_("Account {0} does not exist").format(new))
|
||||||
|
|
||||||
|
@ -350,7 +350,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.negative_expense_to_be_booked = 0.0
|
self.negative_expense_to_be_booked = 0.0
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
|
|
||||||
|
|
||||||
self.make_supplier_gl_entry(gl_entries)
|
self.make_supplier_gl_entry(gl_entries)
|
||||||
self.make_item_gl_entries(gl_entries)
|
self.make_item_gl_entries(gl_entries)
|
||||||
self.make_tax_gl_entries(gl_entries)
|
self.make_tax_gl_entries(gl_entries)
|
||||||
@ -424,7 +423,10 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
# sub-contracting warehouse
|
# sub-contracting warehouse
|
||||||
if flt(item.rm_supp_cost):
|
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({
|
gl_entries.append(self.get_gl_dict({
|
||||||
"account": supplier_warehouse_account,
|
"account": supplier_warehouse_account,
|
||||||
"against": item.expense_account,
|
"against": item.expense_account,
|
||||||
|
@ -15,6 +15,15 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
|
|
||||||
frm.set_indicator_formatter('item_code',
|
frm.set_indicator_formatter('item_code',
|
||||||
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
|
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
|
||||||
|
|
||||||
|
frm.set_query("reserve_warehouse", "supplied_items", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"company": frm.doc.company,
|
||||||
|
"is_group": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload: function(frm) {
|
onload: function(frm) {
|
||||||
@ -134,23 +143,124 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
|||||||
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
|
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
|
||||||
var me = this;
|
var me = this;
|
||||||
|
|
||||||
if(items.length===1) {
|
if(items.length >= 1){
|
||||||
me._make_stock_entry(items[0]);
|
me.raw_material_data = [];
|
||||||
return;
|
me.show_dialog = 1;
|
||||||
|
let title = "";
|
||||||
|
let fields = [
|
||||||
|
{fieldtype:'Section Break', label: __('Raw Materials')},
|
||||||
|
{fieldname: 'sub_con_rm_items', fieldtype: 'Table',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
fieldtype:'Data',
|
||||||
|
fieldname:'item_code',
|
||||||
|
label: __('Item'),
|
||||||
|
read_only:1,
|
||||||
|
in_list_view:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype:'Data',
|
||||||
|
fieldname:'rm_item_code',
|
||||||
|
label: __('Raw Material'),
|
||||||
|
read_only:1,
|
||||||
|
in_list_view:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype:'Float',
|
||||||
|
read_only:1,
|
||||||
|
fieldname:'qty',
|
||||||
|
label: __('Quantity'),
|
||||||
|
read_only:1,
|
||||||
|
in_list_view:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype:'Data',
|
||||||
|
read_only:1,
|
||||||
|
fieldname:'warehouse',
|
||||||
|
label: __('Reserve Warehouse'),
|
||||||
|
in_list_view:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype:'Float',
|
||||||
|
read_only:1,
|
||||||
|
fieldname:'rate',
|
||||||
|
label: __('Rate'),
|
||||||
|
hidden:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype:'Float',
|
||||||
|
read_only:1,
|
||||||
|
fieldname:'amount',
|
||||||
|
label: __('Amount'),
|
||||||
|
hidden:1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype:'Link',
|
||||||
|
read_only:1,
|
||||||
|
fieldname:'uom',
|
||||||
|
label: __('UOM'),
|
||||||
|
hidden:1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
data: me.raw_material_data,
|
||||||
|
get_data: function() {
|
||||||
|
return me.raw_material_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
me.dialog = new frappe.ui.Dialog({
|
||||||
|
title: title, fields: fields
|
||||||
|
});
|
||||||
|
|
||||||
|
if (me.frm.doc['supplied_items']) {
|
||||||
|
me.frm.doc['supplied_items'].forEach((item, index) => {
|
||||||
|
if (item.rm_item_code && item.main_item_code) {
|
||||||
|
me.raw_material_data.push ({
|
||||||
|
'name':index,
|
||||||
|
'item_code': item.main_item_code,
|
||||||
|
'rm_item_code': item.rm_item_code,
|
||||||
|
'item_name': item.rm_item_code,
|
||||||
|
'qty': item.required_qty,
|
||||||
|
'warehouse':item.reserve_warehouse,
|
||||||
|
'rate':item.rate,
|
||||||
|
'amount':item.amount,
|
||||||
|
'stock_uom':item.stock_uom
|
||||||
|
});
|
||||||
|
me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
frappe.prompt({fieldname:"item", options: items, fieldtype:"Select",
|
|
||||||
label: __("Select Item for Transfer"), reqd: 1}, function(data) {
|
me.dialog.show()
|
||||||
me._make_stock_entry(data.item);
|
this.dialog.set_primary_action(__('Transfer'), function() {
|
||||||
}, __("Select Item"), __("Make"));
|
me.values = me.dialog.get_values();
|
||||||
|
if(me.values) {
|
||||||
|
me.values.sub_con_rm_items.map((row,i) => {
|
||||||
|
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
|
||||||
|
frappe.throw(__("Item Code, warehouse, quantity are required on row" + (i+1)));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
|
||||||
|
me.dialog.hide()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
me.dialog.get_close_btn().on('click', () => {
|
||||||
|
me.dialog.hide();
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_make_stock_entry: function(item) {
|
_make_rm_stock_entry: function(rm_items) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method:"erpnext.buying.doctype.purchase_order.purchase_order.make_stock_entry",
|
method:"erpnext.buying.doctype.purchase_order.purchase_order.make_rm_stock_entry",
|
||||||
args: {
|
args: {
|
||||||
purchase_order: cur_frm.doc.name,
|
purchase_order: cur_frm.doc.name,
|
||||||
item_code: item
|
rm_items: rm_items
|
||||||
},
|
}
|
||||||
|
,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
var doclist = frappe.model.sync(r.message);
|
var doclist = frappe.model.sync(r.message);
|
||||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||||
@ -284,7 +394,8 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt
|
|||||||
filters: [
|
filters: [
|
||||||
['BOM', 'item', '=', d.item_code],
|
['BOM', 'item', '=', d.item_code],
|
||||||
['BOM', 'is_active', '=', '1'],
|
['BOM', 'is_active', '=', '1'],
|
||||||
['BOM', 'docstatus', '=', '1']
|
['BOM', 'docstatus', '=', '1'],
|
||||||
|
['BOM', 'company', '=', doc.company]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3133,6 +3133,38 @@
|
|||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
"columns": 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",
|
"fieldname": "supplied_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
|
@ -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 erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty
|
||||||
from frappe.desk.notifications import clear_doctype_notifications
|
from frappe.desk.notifications import clear_doctype_notifications
|
||||||
from erpnext.buying.utils import validate_for_items, check_for_closed_status
|
from erpnext.buying.utils import validate_for_items, check_for_closed_status
|
||||||
|
from erpnext.stock.utils import get_bin
|
||||||
|
|
||||||
form_grid_templates = {
|
form_grid_templates = {
|
||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
@ -80,8 +80,10 @@ class PurchaseOrder(BuyingController):
|
|||||||
def validate_supplier(self):
|
def validate_supplier(self):
|
||||||
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
|
prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos')
|
||||||
if prevent_po:
|
if prevent_po:
|
||||||
standing = frappe.db.get_value("Supplier Scorecard",self.supplier, 'status')
|
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))
|
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')
|
warn_po = frappe.db.get_value("Supplier", self.supplier, 'warn_pos')
|
||||||
if warn_po:
|
if warn_po:
|
||||||
@ -192,6 +194,9 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.set_status(update=True, status=status)
|
self.set_status(update=True, status=status)
|
||||||
self.update_requested_qty()
|
self.update_requested_qty()
|
||||||
self.update_ordered_qty()
|
self.update_ordered_qty()
|
||||||
|
if self.is_subcontracted == "Yes":
|
||||||
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
|
||||||
self.notify_update()
|
self.notify_update()
|
||||||
clear_doctype_notifications(self)
|
clear_doctype_notifications(self)
|
||||||
|
|
||||||
@ -204,6 +209,8 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.update_prevdoc_status()
|
self.update_prevdoc_status()
|
||||||
self.update_requested_qty()
|
self.update_requested_qty()
|
||||||
self.update_ordered_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,
|
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
|
||||||
self.company, self.base_grand_total)
|
self.company, self.base_grand_total)
|
||||||
@ -217,6 +224,9 @@ class PurchaseOrder(BuyingController):
|
|||||||
if self.has_drop_ship_item():
|
if self.has_drop_ship_item():
|
||||||
self.update_delivered_qty_in_sales_order()
|
self.update_delivered_qty_in_sales_order()
|
||||||
|
|
||||||
|
if self.is_subcontracted == "Yes":
|
||||||
|
self.update_reserved_qty_for_subcontract()
|
||||||
|
|
||||||
self.check_for_closed_status()
|
self.check_for_closed_status()
|
||||||
|
|
||||||
frappe.db.set(self,'status','Cancelled')
|
frappe.db.set(self,'status','Cancelled')
|
||||||
@ -268,6 +278,12 @@ class PurchaseOrder(BuyingController):
|
|||||||
if item.delivered_by_supplier == 1:
|
if item.delivered_by_supplier == 1:
|
||||||
item.received_qty = item.qty
|
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()
|
||||||
|
|
||||||
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor= 1.0):
|
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor= 1.0):
|
||||||
"""get last purchase rate for an item"""
|
"""get last purchase rate for an item"""
|
||||||
if cint(frappe.db.get_single_value("Buying Settings", "disable_fetch_last_purchase_rate")): return
|
if cint(frappe.db.get_single_value("Buying Settings", "disable_fetch_last_purchase_rate")): return
|
||||||
@ -388,23 +404,52 @@ def make_purchase_invoice(source_name, target_doc=None):
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_stock_entry(purchase_order, item_code):
|
def make_rm_stock_entry(purchase_order, rm_items):
|
||||||
purchase_order = frappe.get_doc("Purchase Order", purchase_order)
|
if isinstance(rm_items, basestring):
|
||||||
|
rm_items_list = json.loads(rm_items)
|
||||||
|
else:
|
||||||
|
frappe.throw(_("No Items available for transfer"))
|
||||||
|
|
||||||
stock_entry = frappe.new_doc("Stock Entry")
|
if rm_items_list:
|
||||||
stock_entry.purpose = "Subcontract"
|
fg_items = list(set(d["item_code"] for d in rm_items_list))
|
||||||
stock_entry.purchase_order = purchase_order.name
|
else:
|
||||||
stock_entry.supplier = purchase_order.supplier
|
frappe.throw(_("No Items selected for transfer"))
|
||||||
stock_entry.supplier_name = purchase_order.supplier_name
|
|
||||||
stock_entry.supplier_address = purchase_order.supplier_address
|
if purchase_order:
|
||||||
stock_entry.address_display = purchase_order.address_display
|
purchase_order = frappe.get_doc("Purchase Order", purchase_order)
|
||||||
stock_entry.company = purchase_order.company
|
|
||||||
stock_entry.from_bom = 1
|
if fg_items:
|
||||||
po_item = [d for d in purchase_order.items if d.item_code == item_code][0]
|
items = tuple(set(d["rm_item_code"] for d in rm_items_list))
|
||||||
stock_entry.fg_completed_qty = po_item.qty
|
item_wh = frappe._dict(frappe.db.sql("""
|
||||||
stock_entry.bom_no = po_item.bom
|
select item_code, description
|
||||||
stock_entry.get_items()
|
from `tabItem` where name in ({0})
|
||||||
return stock_entry.as_dict()
|
""".format(", ".join(["%s"] * len(items))), items))
|
||||||
|
|
||||||
|
stock_entry = frappe.new_doc("Stock Entry")
|
||||||
|
stock_entry.purpose = "Subcontract"
|
||||||
|
stock_entry.purchase_order = purchase_order.name
|
||||||
|
stock_entry.supplier = purchase_order.supplier
|
||||||
|
stock_entry.supplier_name = purchase_order.supplier_name
|
||||||
|
stock_entry.supplier_address = purchase_order.supplier_address
|
||||||
|
stock_entry.address_display = purchase_order.address_display
|
||||||
|
stock_entry.company = purchase_order.company
|
||||||
|
for item_code in fg_items:
|
||||||
|
for rm_item_data in rm_items_list:
|
||||||
|
if rm_item_data["item_code"] == item_code:
|
||||||
|
items_dict = {
|
||||||
|
rm_item_data["rm_item_code"]: {
|
||||||
|
"item_name":rm_item_data["item_name"],
|
||||||
|
"description":item_wh.get(rm_item_data["rm_item_code"]),
|
||||||
|
'qty':rm_item_data["qty"],
|
||||||
|
'from_warehouse':rm_item_data["warehouse"],
|
||||||
|
'stock_uom':rm_item_data["stock_uom"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stock_entry.add_to_stock_entry_detail(items_dict)
|
||||||
|
return stock_entry.as_dict()
|
||||||
|
else:
|
||||||
|
frappe.throw(_("No Items selected for transfer"))
|
||||||
|
return purchase_order.name
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_status(status, name):
|
def update_status(status, name):
|
||||||
|
@ -6,8 +6,9 @@ import unittest
|
|||||||
import frappe
|
import frappe
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
from frappe.utils import flt, add_days, nowdate
|
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_rm_stock_entry as make_subcontract_transfer_entry)
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
import json
|
||||||
|
|
||||||
class TestPurchaseOrder(unittest.TestCase):
|
class TestPurchaseOrder(unittest.TestCase):
|
||||||
def test_make_purchase_receipt(self):
|
def test_make_purchase_receipt(self):
|
||||||
@ -182,24 +183,129 @@ class TestPurchaseOrder(unittest.TestCase):
|
|||||||
pi.insert()
|
pi.insert()
|
||||||
self.assertTrue(pi.get('payment_schedule'))
|
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
|
||||||
|
rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
|
||||||
|
"qty":6,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":600,"stock_uom":"Nos"}]
|
||||||
|
rm_item_string = json.dumps(rm_item)
|
||||||
|
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||||
|
se.to_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
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():
|
def get_same_items():
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"item_code": "_Test FG Item",
|
"item_code": "_Test FG Item",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
"rate": 500,
|
"rate": 500,
|
||||||
"schedule_date": add_days(nowdate(), 1)
|
"schedule_date": add_days(nowdate(), 1)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"item_code": "_Test FG Item",
|
"item_code": "_Test FG Item",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"qty": 4,
|
"qty": 4,
|
||||||
"rate": 500,
|
"rate": 500,
|
||||||
"schedule_date": add_days(nowdate(), 1)
|
"schedule_date": add_days(nowdate(), 1)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def create_purchase_order(**args):
|
def create_purchase_order(**args):
|
||||||
po = frappe.new_doc("Purchase Order")
|
po = frappe.new_doc("Purchase Order")
|
||||||
@ -224,6 +330,10 @@ def create_purchase_order(**args):
|
|||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
po.insert()
|
po.insert()
|
||||||
if not args.do_not_submit:
|
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()
|
po.submit()
|
||||||
|
|
||||||
return po
|
return po
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"allow_copy": 0,
|
"allow_copy": 0,
|
||||||
|
"allow_guest_to_view": 0,
|
||||||
"allow_import": 0,
|
"allow_import": 0,
|
||||||
"allow_rename": 0,
|
"allow_rename": 0,
|
||||||
"beta": 0,
|
"beta": 0,
|
||||||
@ -10,16 +11,20 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "main_item_code",
|
"fieldname": "main_item_code",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Item Code",
|
"label": "Item Code",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -29,6 +34,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -36,16 +42,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "rm_item_code",
|
"fieldname": "rm_item_code",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Raw Material Item Code",
|
"label": "Raw Material Item Code",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -55,6 +65,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -62,16 +73,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "required_qty",
|
"fieldname": "required_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Supplied Qty",
|
"label": "Supplied Qty",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -81,6 +96,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -88,16 +104,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 2,
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -108,6 +128,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -115,16 +136,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -135,6 +160,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -142,16 +168,49 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 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",
|
"fieldname": "bom_detail_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_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",
|
"label": "BOM Detail No",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -161,6 +220,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -168,16 +228,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "reference_name",
|
"fieldname": "reference_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_global_search": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Reference Name",
|
"label": "Reference Name",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -187,6 +251,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -194,16 +259,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "conversion_factor",
|
"fieldname": "conversion_factor",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Conversion Factor",
|
"label": "Conversion Factor",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -213,6 +282,7 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"read_only": 1,
|
||||||
|
"remember_last_selected_value": 0,
|
||||||
"report_hide": 0,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -220,16 +290,20 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
"bold": 0,
|
"bold": 0,
|
||||||
"collapsible": 0,
|
"collapsible": 0,
|
||||||
|
"columns": 0,
|
||||||
"fieldname": "stock_uom",
|
"fieldname": "stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"ignore_user_permissions": 0,
|
"ignore_user_permissions": 0,
|
||||||
"ignore_xss_filter": 0,
|
"ignore_xss_filter": 0,
|
||||||
"in_filter": 0,
|
"in_filter": 0,
|
||||||
|
"in_global_search": 0,
|
||||||
"in_list_view": 0,
|
"in_list_view": 0,
|
||||||
|
"in_standard_filter": 0,
|
||||||
"label": "Stock Uom",
|
"label": "Stock Uom",
|
||||||
"length": 0,
|
"length": 0,
|
||||||
"no_copy": 0,
|
"no_copy": 0,
|
||||||
@ -240,6 +314,38 @@
|
|||||||
"print_hide": 0,
|
"print_hide": 0,
|
||||||
"print_hide_if_no_value": 0,
|
"print_hide_if_no_value": 0,
|
||||||
"read_only": 1,
|
"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,
|
"report_hide": 0,
|
||||||
"reqd": 0,
|
"reqd": 0,
|
||||||
"search_index": 0,
|
"search_index": 0,
|
||||||
@ -247,17 +353,17 @@
|
|||||||
"unique": 0
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"has_web_view": 0,
|
||||||
"hide_heading": 0,
|
"hide_heading": 0,
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_view": 0,
|
"image_view": 0,
|
||||||
"in_create": 0,
|
"in_create": 0,
|
||||||
"in_dialog": 0,
|
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2016-07-11 03:28:05.533063",
|
"modified": "2018-01-05 14:47:15.400785",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item Supplied",
|
"name": "Purchase Order Item Supplied",
|
||||||
@ -266,5 +372,7 @@
|
|||||||
"quick_entry": 0,
|
"quick_entry": 0,
|
||||||
"read_only": 0,
|
"read_only": 0,
|
||||||
"read_only_onload": 0,
|
"read_only_onload": 0,
|
||||||
|
"show_name_in_global_search": 0,
|
||||||
|
"track_changes": 0,
|
||||||
"track_seen": 0
|
"track_seen": 0
|
||||||
}
|
}
|
@ -163,6 +163,11 @@ class BuyingController(StockController):
|
|||||||
if item in self.sub_contracted_items and not item.bom:
|
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))
|
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:
|
else:
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if item.bom:
|
if item.bom:
|
||||||
@ -192,8 +197,16 @@ class BuyingController(StockController):
|
|||||||
def update_raw_materials_supplied(self, item, raw_material_table):
|
def update_raw_materials_supplied(self, item, raw_material_table):
|
||||||
bom_items = self.get_items_from_bom(item.item_code, item.bom)
|
bom_items = self.get_items_from_bom(item.item_code, item.bom)
|
||||||
raw_materials_cost = 0
|
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:
|
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
|
# check if exists
|
||||||
exists = 0
|
exists = 0
|
||||||
for d in self.get(raw_material_table):
|
for d in self.get(raw_material_table):
|
||||||
@ -213,6 +226,8 @@ class BuyingController(StockController):
|
|||||||
rm.rm_item_code = bom_item.item_code
|
rm.rm_item_code = bom_item.item_code
|
||||||
rm.stock_uom = bom_item.stock_uom
|
rm.stock_uom = bom_item.stock_uom
|
||||||
rm.required_qty = required_qty
|
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
|
rm.conversion_factor = item.conversion_factor
|
||||||
|
|
||||||
@ -264,7 +279,7 @@ class BuyingController(StockController):
|
|||||||
def get_items_from_bom(self, item_code, bom):
|
def get_items_from_bom(self, item_code, bom):
|
||||||
bom_items = frappe.db.sql("""select t2.item_code,
|
bom_items = frappe.db.sql("""select t2.item_code,
|
||||||
t2.stock_qty / ifnull(t1.quantity, 1) as qty_consumed_per_unit,
|
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
|
from `tabBOM` t1, `tabBOM Item` t2, tabItem t3
|
||||||
where t2.parent = t1.name and t1.item = %s
|
where t2.parent = t1.name and t1.item = %s
|
||||||
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
|
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
|
||||||
@ -339,7 +354,7 @@ class BuyingController(StockController):
|
|||||||
frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code'])))
|
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):
|
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 = []
|
sl_entries = []
|
||||||
stock_items = self.get_stock_items()
|
stock_items = self.get_stock_items()
|
||||||
@ -381,7 +396,7 @@ class BuyingController(StockController):
|
|||||||
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
|
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
|
||||||
via_landed_cost_voucher=via_landed_cost_voucher)
|
via_landed_cost_voucher=via_landed_cost_voucher)
|
||||||
|
|
||||||
def update_ordered_qty(self):
|
def update_ordered_and_reserved_qty(self):
|
||||||
po_map = {}
|
po_map = {}
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
if self.doctype=="Purchase Receipt" \
|
if self.doctype=="Purchase Receipt" \
|
||||||
@ -400,6 +415,8 @@ class BuyingController(StockController):
|
|||||||
frappe.InvalidStatusError)
|
frappe.InvalidStatusError)
|
||||||
|
|
||||||
po_obj.update_ordered_qty(po_item_rows)
|
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):
|
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
|
||||||
if hasattr(self, 'supplied_items'):
|
if hasattr(self, 'supplied_items'):
|
||||||
|
@ -597,9 +597,16 @@ def validate_bom_no(item, bom_no):
|
|||||||
if bom.docstatus != 1:
|
if bom.docstatus != 1:
|
||||||
if not getattr(frappe.flags, "in_test", False):
|
if not getattr(frappe.flags, "in_test", False):
|
||||||
frappe.throw(_("BOM {0} must be submitted").format(bom_no))
|
frappe.throw(_("BOM {0} must be submitted").format(bom_no))
|
||||||
if item and not (bom.item.lower() == item.lower() or \
|
if item:
|
||||||
bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower()):
|
rm_item_exists = False
|
||||||
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
|
for d in bom.items:
|
||||||
|
if (d.item_code.lower() == item.lower()):
|
||||||
|
rm_item_exists = True
|
||||||
|
if bom.item.lower() == item.lower() or \
|
||||||
|
bom.item.lower() == cstr(frappe.db.get_value("Item", item, "variant_of")).lower():
|
||||||
|
rm_item_exists = True
|
||||||
|
if not rm_item_exists:
|
||||||
|
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_children(doctype, parent=None, is_root=False, **filters):
|
def get_children(doctype, parent=None, is_root=False, **filters):
|
||||||
|
@ -493,4 +493,5 @@ erpnext.patches.v10_0.set_b2c_limit
|
|||||||
erpnext.patches.v10_0.update_status_for_multiple_source_in_po
|
erpnext.patches.v10_0.update_status_for_multiple_source_in_po
|
||||||
erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry
|
erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry
|
||||||
erpnext.patches.v10_0.update_territory_and_customer_group
|
erpnext.patches.v10_0.update_territory_and_customer_group
|
||||||
erpnext.patches.v10_0.update_warehouse_address_details
|
erpnext.patches.v10_0.update_warehouse_address_details
|
||||||
|
erpnext.patches.v10_0.update_reserved_qty_for_purchase_order
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import frappe
|
||||||
|
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
|
||||||
|
|
||||||
|
frappe.reload_doc("stock", "doctype", "bin")
|
||||||
|
frappe.reload_doc("buying", "doctype", "purchase_order_item_supplied")
|
||||||
|
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)
|
||||||
|
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
|
@ -10,6 +10,7 @@ def execute():
|
|||||||
frappe.reload_doctype("Purchase Receipt")
|
frappe.reload_doctype("Purchase Receipt")
|
||||||
frappe.reload_doctype("Sales Order Item")
|
frappe.reload_doctype("Sales Order Item")
|
||||||
frappe.reload_doctype("Purchase Order Item")
|
frappe.reload_doctype("Purchase Order Item")
|
||||||
|
frappe.reload_doctype("Purchase Order Item Supplied")
|
||||||
|
|
||||||
# sales return
|
# sales return
|
||||||
return_entries = list(frappe.db.sql("""
|
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))
|
""", (order_details[0].purchase_order, order_details[0].po_detail, d.row_id))
|
||||||
|
|
||||||
pr = frappe.get_doc("Purchase Receipt", d.name)
|
pr = frappe.get_doc("Purchase Receipt", d.name)
|
||||||
pr.update_ordered_qty()
|
pr.update_ordered_and_reserved_qty()
|
||||||
pr.update_prevdoc_status()
|
pr.update_prevdoc_status()
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
data.forEach(function(d) {
|
data.forEach(function(d) {
|
||||||
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production;
|
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production;
|
||||||
d.pending_qty = 0;
|
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) {
|
if(d.actual_or_pending > d.actual_qty) {
|
||||||
d.pending_qty = d.actual_or_pending - d.actual_qty;
|
d.pending_qty = d.actual_or_pending - d.actual_qty;
|
||||||
}
|
}
|
||||||
|
@ -26,13 +26,14 @@ def get_data(item_code=None, warehouse=None, item_group=None,
|
|||||||
return frappe.db.sql('''
|
return frappe.db.sql('''
|
||||||
select
|
select
|
||||||
b.item_code, b.warehouse, b.projected_qty, b.reserved_qty,
|
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
|
from
|
||||||
tabBin b, tabItem i
|
tabBin b, tabItem i
|
||||||
where
|
where
|
||||||
b.item_code = i.name
|
b.item_code = i.name
|
||||||
and
|
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}
|
{conditions}
|
||||||
order by
|
order by
|
||||||
{sort_by} {sort_order}
|
{sort_by} {sort_order}
|
||||||
|
@ -296,6 +296,36 @@
|
|||||||
"set_only_once": 0,
|
"set_only_once": 0,
|
||||||
"unique": 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_bulk_edit": 0,
|
||||||
"allow_on_submit": 0,
|
"allow_on_submit": 0,
|
||||||
@ -463,7 +493,7 @@
|
|||||||
"issingle": 0,
|
"issingle": 0,
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
"max_attachments": 0,
|
"max_attachments": 0,
|
||||||
"modified": "2017-06-13 13:06:32.601505",
|
"modified": "2017-11-22 08:14:30.615638",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Bin",
|
"name": "Bin",
|
||||||
|
@ -61,7 +61,7 @@ class Bin(Document):
|
|||||||
def set_projected_qty(self):
|
def set_projected_qty(self):
|
||||||
self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty)
|
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.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):
|
def get_first_sle(self):
|
||||||
sle = frappe.db.sql("""
|
sle = frappe.db.sql("""
|
||||||
@ -90,6 +90,42 @@ class Bin(Document):
|
|||||||
self.db_set('reserved_qty_for_production', flt(self.reserved_qty_for_production))
|
self.db_set('reserved_qty_for_production', flt(self.reserved_qty_for_production))
|
||||||
self.db_set('projected_qty', self.projected_qty)
|
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 on_doctype_update():
|
def on_doctype_update():
|
||||||
frappe.db.add_index("Bin", ["item_code", "warehouse"])
|
frappe.db.add_index("Bin", ["item_code", "warehouse"])
|
||||||
|
@ -122,7 +122,8 @@ class PurchaseReceipt(BuyingController):
|
|||||||
self.update_billing_status()
|
self.update_billing_status()
|
||||||
|
|
||||||
# Updating stock ledger should always be called after updating prevdoc 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()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||||
|
@ -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.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.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.manufacturing.doctype.bom.bom import validate_bom_no
|
||||||
|
from erpnext.stock.utils import get_bin
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class IncorrectValuationRateError(frappe.ValidationError): pass
|
class IncorrectValuationRateError(frappe.ValidationError): pass
|
||||||
@ -63,17 +64,22 @@ class StockEntry(StockController):
|
|||||||
self.calculate_rate_and_amount(update_finished_item_rate=False)
|
self.calculate_rate_and_amount(update_finished_item_rate=False)
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
|
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||||
update_serial_nos_after_submit(self, "items")
|
update_serial_nos_after_submit(self, "items")
|
||||||
self.update_production_order()
|
self.update_production_order()
|
||||||
self.validate_purchase_order()
|
self.validate_purchase_order()
|
||||||
|
if self.purchase_order and self.purpose == "Subcontract":
|
||||||
|
self.update_purchase_order_supplied_items()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.update_production_order()
|
self.update_production_order()
|
||||||
|
if self.purchase_order and self.purpose == "Subcontract":
|
||||||
|
self.update_purchase_order_supplied_items()
|
||||||
self.make_gl_entries_on_cancel()
|
self.make_gl_entries_on_cancel()
|
||||||
|
|
||||||
def validate_purpose(self):
|
def validate_purpose(self):
|
||||||
@ -403,7 +409,7 @@ class StockEntry(StockController):
|
|||||||
"""validation: finished good quantity should be same as manufacturing quantity"""
|
"""validation: finished good quantity should be same as manufacturing quantity"""
|
||||||
items_with_target_warehouse = []
|
items_with_target_warehouse = []
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if 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 != "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):
|
||||||
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))
|
||||||
|
|
||||||
@ -582,19 +588,32 @@ class StockEntry(StockController):
|
|||||||
frappe.throw(_("Manufacturing Quantity is mandatory"))
|
frappe.throw(_("Manufacturing Quantity is mandatory"))
|
||||||
|
|
||||||
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
|
||||||
|
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():
|
for item in item_dict.values():
|
||||||
if self.pro_doc and not self.pro_doc.skip_transfer:
|
if self.pro_doc and 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
|
||||||
|
if self.purchase_order and self.purpose=="Subcontract":
|
||||||
|
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=="Subcontract" else ""
|
||||||
|
|
||||||
self.add_to_stock_entry_detail(item_dict)
|
self.add_to_stock_entry_detail(item_dict)
|
||||||
|
|
||||||
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
|
if self.purpose != "Subcontract":
|
||||||
for item in scrap_item_dict.values():
|
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
|
||||||
if self.pro_doc and self.pro_doc.scrap_warehouse:
|
for item in scrap_item_dict.values():
|
||||||
item["to_warehouse"] = self.pro_doc.scrap_warehouse
|
if self.pro_doc and self.pro_doc.scrap_warehouse:
|
||||||
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
|
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
|
# fetch the serial_no of the first stock entry for the second stock entry
|
||||||
if self.production_order and self.purpose == "Manufacture":
|
if self.production_order and self.purpose == "Manufacture":
|
||||||
@ -782,7 +801,7 @@ class StockEntry(StockController):
|
|||||||
def add_to_stock_entry_detail(self, item_dict, bom_no=None):
|
def add_to_stock_entry_detail(self, item_dict, bom_no=None):
|
||||||
expense_account, cost_center = frappe.db.get_values("Company", self.company, \
|
expense_account, cost_center = frappe.db.get_values("Company", self.company, \
|
||||||
["default_expense_account", "cost_center"])[0]
|
["default_expense_account", "cost_center"])[0]
|
||||||
|
|
||||||
for d in item_dict:
|
for d in item_dict:
|
||||||
stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
|
stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
|
||||||
|
|
||||||
@ -833,7 +852,20 @@ class StockEntry(StockController):
|
|||||||
if getdate(self.posting_date) > getdate(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()
|
@frappe.whitelist()
|
||||||
def move_sample_to_retention_warehouse(company, items):
|
def move_sample_to_retention_warehouse(company, items):
|
||||||
if isinstance(items, basestring):
|
if isinstance(items, basestring):
|
||||||
|
@ -562,9 +562,8 @@ class TestStockEntry(unittest.TestCase):
|
|||||||
|
|
||||||
rm_cost = 0
|
rm_cost = 0
|
||||||
for d in stock_entry.get("items"):
|
for d in stock_entry.get("items"):
|
||||||
if d.s_warehouse:
|
if d.item_code != "_Test FG Item 2":
|
||||||
rm_cost += flt(d.amount)
|
rm_cost += flt(d.amount)
|
||||||
|
|
||||||
fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount
|
fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount
|
||||||
self.assertEqual(fg_cost,
|
self.assertEqual(fg_cost,
|
||||||
flt(rm_cost + bom_operation_cost + production_order.additional_operating_cost, 2))
|
flt(rm_cost + bom_operation_cost + production_order.additional_operating_cost, 2))
|
||||||
|
@ -48,6 +48,7 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
|
|||||||
{fieldname: 'projected_qty', label: __('Projected qty')},
|
{fieldname: 'projected_qty', label: __('Projected qty')},
|
||||||
{fieldname: 'reserved_qty', label: __('Reserved for sale')},
|
{fieldname: 'reserved_qty', label: __('Reserved for sale')},
|
||||||
{fieldname: 'reserved_qty_for_production', label: __('Reserved for manufacturing')},
|
{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')},
|
{fieldname: 'actual_qty', label: __('Actual qty in stock')},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -16,6 +16,7 @@ def get_columns():
|
|||||||
_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100",
|
_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100",
|
||||||
_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100",
|
_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100",
|
||||||
_("Reserved Qty") + ":Float:100", _("Reserved Qty for Production") + ":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",
|
_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100",
|
||||||
_("Shortage Qty") + ":Float:100"]
|
_("Shortage Qty") + ":Float:100"]
|
||||||
|
|
||||||
@ -33,7 +34,8 @@ def get_data(filters):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# item = item_map.setdefault(bin.item_code, get_item(bin.item_code))
|
# 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:
|
if filters.brand and filters.brand != item.brand:
|
||||||
continue
|
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,
|
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,
|
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
|
return data
|
||||||
|
|
||||||
@ -71,7 +74,7 @@ def get_bin_list(filters):
|
|||||||
warehouse_details.rgt))
|
warehouse_details.rgt))
|
||||||
|
|
||||||
bin_list = frappe.db.sql("""select item_code, warehouse, actual_qty, planned_qty, indented_qty,
|
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
|
from tabBin bin {conditions} order by item_code, warehouse
|
||||||
""".format(conditions=" where " + " and ".join(conditions) if conditions else ""), as_dict=1)
|
""".format(conditions=" where " + " and ".join(conditions) if conditions else ""), as_dict=1)
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ def update_bin_qty(item_code, warehouse, qty_dict=None):
|
|||||||
if mismatch:
|
if mismatch:
|
||||||
bin.projected_qty = (flt(bin.actual_qty) + flt(bin.ordered_qty) +
|
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.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()
|
bin.save()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user