Merge branch 'develop' into project-naming-series-patch
This commit is contained in:
commit
35455d2de6
@ -353,7 +353,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
|||||||
make_purchase_receipt: function() {
|
make_purchase_receipt: function() {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
||||||
frm: cur_frm
|
frm: cur_frm,
|
||||||
|
freeze_message: __("Creating Purchase Receipt ...")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -123,8 +123,8 @@ class PurchaseOrder(BuyingController):
|
|||||||
if self.is_subcontracted == "Yes":
|
if self.is_subcontracted == "Yes":
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
if not item.bom:
|
if not item.bom:
|
||||||
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}"\
|
frappe.throw(_("BOM is not specified for subcontracting item {0} at row {1}")
|
||||||
.format(item.item_code, item.idx)))
|
.format(item.item_code, item.idx))
|
||||||
|
|
||||||
def get_schedule_dates(self):
|
def get_schedule_dates(self):
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
|
@ -6,6 +6,7 @@ import frappe, erpnext
|
|||||||
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
|
||||||
from frappe import _
|
from frappe import _
|
||||||
import frappe.defaults
|
import frappe.defaults
|
||||||
|
from collections import defaultdict
|
||||||
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
|
||||||
from erpnext.controllers.accounts_controller import AccountsController
|
from erpnext.controllers.accounts_controller import AccountsController
|
||||||
@ -23,6 +24,7 @@ class StockController(AccountsController):
|
|||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
self.validate_serialized_batch()
|
self.validate_serialized_batch()
|
||||||
self.validate_customer_provided_item()
|
self.validate_customer_provided_item()
|
||||||
|
self.validate_putaway_capacity()
|
||||||
|
|
||||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||||
if self.docstatus == 2:
|
if self.docstatus == 2:
|
||||||
@ -391,6 +393,58 @@ class StockController(AccountsController):
|
|||||||
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'):
|
||||||
d.allow_zero_valuation_rate = 1
|
d.allow_zero_valuation_rate = 1
|
||||||
|
|
||||||
|
def validate_putaway_capacity(self):
|
||||||
|
# if over receipt is attempted while 'apply putaway rule' is disabled
|
||||||
|
# and if rule was applied on the transaction, validate it.
|
||||||
|
from erpnext.stock.doctype.putaway_rule.putaway_rule import get_available_putaway_capacity
|
||||||
|
valid_doctype = self.doctype in ("Purchase Receipt", "Stock Entry", "Purchase Invoice",
|
||||||
|
"Stock Reconciliation")
|
||||||
|
|
||||||
|
if self.doctype == "Purchase Invoice" and self.get("update_stock") == 0:
|
||||||
|
valid_doctype = False
|
||||||
|
|
||||||
|
if valid_doctype:
|
||||||
|
rule_map = defaultdict(dict)
|
||||||
|
for item in self.get("items"):
|
||||||
|
warehouse_field = "t_warehouse" if self.doctype == "Stock Entry" else "warehouse"
|
||||||
|
rule = frappe.db.get_value("Putaway Rule",
|
||||||
|
{
|
||||||
|
"item_code": item.get("item_code"),
|
||||||
|
"warehouse": item.get(warehouse_field)
|
||||||
|
},
|
||||||
|
["name", "disable"], as_dict=True)
|
||||||
|
if rule:
|
||||||
|
if rule.get("disabled"): continue # dont validate for disabled rule
|
||||||
|
|
||||||
|
if self.doctype == "Stock Reconciliation":
|
||||||
|
stock_qty = flt(item.qty)
|
||||||
|
else:
|
||||||
|
stock_qty = flt(item.transfer_qty) if self.doctype == "Stock Entry" else flt(item.stock_qty)
|
||||||
|
|
||||||
|
rule_name = rule.get("name")
|
||||||
|
if not rule_map[rule_name]:
|
||||||
|
rule_map[rule_name]["warehouse"] = item.get(warehouse_field)
|
||||||
|
rule_map[rule_name]["item"] = item.get("item_code")
|
||||||
|
rule_map[rule_name]["qty_put"] = 0
|
||||||
|
rule_map[rule_name]["capacity"] = get_available_putaway_capacity(rule_name)
|
||||||
|
rule_map[rule_name]["qty_put"] += flt(stock_qty)
|
||||||
|
|
||||||
|
for rule, values in rule_map.items():
|
||||||
|
if flt(values["qty_put"]) > flt(values["capacity"]):
|
||||||
|
message = self.prepare_over_receipt_message(rule, values)
|
||||||
|
frappe.throw(msg=message, title=_("Over Receipt"))
|
||||||
|
|
||||||
|
def prepare_over_receipt_message(self, rule, values):
|
||||||
|
message = _("{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}.") \
|
||||||
|
.format(
|
||||||
|
frappe.bold(values["qty_put"]), frappe.bold(values["item"]),
|
||||||
|
frappe.bold(values["warehouse"]), frappe.bold(values["capacity"])
|
||||||
|
)
|
||||||
|
message += "<br><br>"
|
||||||
|
rule_link = frappe.utils.get_link_to_form("Putaway Rule", rule)
|
||||||
|
message += _(" Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||||
|
return message
|
||||||
|
|
||||||
def repost_future_sle_and_gle(self):
|
def repost_future_sle_and_gle(self):
|
||||||
args = frappe._dict({
|
args = frappe._dict({
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
|
@ -55,6 +55,8 @@
|
|||||||
"js/item-dashboard.min.js": [
|
"js/item-dashboard.min.js": [
|
||||||
"stock/dashboard/item_dashboard.html",
|
"stock/dashboard/item_dashboard.html",
|
||||||
"stock/dashboard/item_dashboard_list.html",
|
"stock/dashboard/item_dashboard_list.html",
|
||||||
"stock/dashboard/item_dashboard.js"
|
"stock/dashboard/item_dashboard.js",
|
||||||
|
"stock/page/warehouse_capacity_summary/warehouse_capacity_summary.html",
|
||||||
|
"stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -516,4 +516,4 @@ erpnext.buying.get_items_from_product_bundle = function(frm) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
@ -2025,3 +2025,35 @@ erpnext.show_serial_batch_selector = function (frm, d, callback, on_close, show_
|
|||||||
}, show_dialog);
|
}, show_dialog);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
erpnext.apply_putaway_rule = (frm, purpose=null) => {
|
||||||
|
if (!frm.doc.company) {
|
||||||
|
frappe.throw({message: __("Please select a Company first."), title: __("Mandatory")});
|
||||||
|
}
|
||||||
|
if (!frm.doc.items.length) return;
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.stock.doctype.putaway_rule.putaway_rule.apply_putaway_rule",
|
||||||
|
args: {
|
||||||
|
doctype: frm.doctype,
|
||||||
|
items: frm.doc.items,
|
||||||
|
company: frm.doc.company,
|
||||||
|
sync: true,
|
||||||
|
purpose: purpose
|
||||||
|
},
|
||||||
|
callback: (result) => {
|
||||||
|
if (!result.exc && result.message) {
|
||||||
|
frm.clear_table("items");
|
||||||
|
|
||||||
|
let items = result.message;
|
||||||
|
items.forEach((row) => {
|
||||||
|
delete row["name"]; // dont overwrite name from server side
|
||||||
|
let child = frm.add_child("items");
|
||||||
|
Object.assign(child, row);
|
||||||
|
frm.script_manager.trigger("qty", child.doctype, child.name);
|
||||||
|
});
|
||||||
|
frm.get_field("items").grid.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -24,6 +24,16 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
handle_move_add($(this), "Add")
|
handle_move_add($(this), "Add")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.content.on('click', '.btn-edit', function() {
|
||||||
|
let item = unescape($(this).attr('data-item'));
|
||||||
|
let warehouse = unescape($(this).attr('data-warehouse'));
|
||||||
|
let company = unescape($(this).attr('data-company'));
|
||||||
|
frappe.db.get_value('Putaway Rule',
|
||||||
|
{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => {
|
||||||
|
frappe.set_route("Form", "Putaway Rule", r.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function handle_move_add(element, action) {
|
function handle_move_add(element, action) {
|
||||||
let item = unescape(element.attr('data-item'));
|
let item = unescape(element.attr('data-item'));
|
||||||
let warehouse = unescape(element.attr('data-warehouse'));
|
let warehouse = unescape(element.attr('data-warehouse'));
|
||||||
@ -59,7 +69,7 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
|
|
||||||
// more
|
// more
|
||||||
this.content.find('.btn-more').on('click', function() {
|
this.content.find('.btn-more').on('click', function() {
|
||||||
me.start += 20;
|
me.start += me.page_length;
|
||||||
me.refresh();
|
me.refresh();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,33 +79,43 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
this.before_refresh();
|
this.before_refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let args = {
|
||||||
|
item_code: this.item_code,
|
||||||
|
warehouse: this.warehouse,
|
||||||
|
parent_warehouse: this.parent_warehouse,
|
||||||
|
item_group: this.item_group,
|
||||||
|
company: this.company,
|
||||||
|
start: this.start,
|
||||||
|
sort_by: this.sort_by,
|
||||||
|
sort_order: this.sort_order
|
||||||
|
};
|
||||||
|
|
||||||
var me = this;
|
var me = this;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'erpnext.stock.dashboard.item_dashboard.get_data',
|
method: this.method,
|
||||||
args: {
|
args: args,
|
||||||
item_code: this.item_code,
|
|
||||||
warehouse: this.warehouse,
|
|
||||||
item_group: this.item_group,
|
|
||||||
start: this.start,
|
|
||||||
sort_by: this.sort_by,
|
|
||||||
sort_order: this.sort_order,
|
|
||||||
},
|
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
me.render(r.message);
|
me.render(r.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function(data) {
|
render: function(data) {
|
||||||
if(this.start===0) {
|
if (this.start===0) {
|
||||||
this.max_count = 0;
|
this.max_count = 0;
|
||||||
this.result.empty();
|
this.result.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
var context = this.get_item_dashboard_data(data, this.max_count, true);
|
let context = "";
|
||||||
|
if (this.page_name === "warehouse-capacity-summary") {
|
||||||
|
context = this.get_capacity_dashboard_data(data);
|
||||||
|
} else {
|
||||||
|
context = this.get_item_dashboard_data(data, this.max_count, true);
|
||||||
|
}
|
||||||
|
|
||||||
this.max_count = this.max_count;
|
this.max_count = this.max_count;
|
||||||
|
|
||||||
// show more button
|
// show more button
|
||||||
if(data && data.length===21) {
|
if (data && data.length===(this.page_length + 1)) {
|
||||||
this.content.find('.more').removeClass('hidden');
|
this.content.find('.more').removeClass('hidden');
|
||||||
|
|
||||||
// remove the last element
|
// remove the last element
|
||||||
@ -106,12 +126,17 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
|
|
||||||
// If not any stock in any warehouses provide a message to end user
|
// If not any stock in any warehouses provide a message to end user
|
||||||
if (context.data.length > 0) {
|
if (context.data.length > 0) {
|
||||||
$(frappe.render_template('item_dashboard_list', context)).appendTo(this.result);
|
this.content.find('.result').css('text-align', 'unset');
|
||||||
|
$(frappe.render_template(this.template, context)).appendTo(this.result);
|
||||||
} else {
|
} else {
|
||||||
var message = __("Currently no stock available in any warehouse");
|
var message = __("No Stock Available Currently");
|
||||||
$(`<span class='text-muted small'> ${message} </span>`).appendTo(this.result);
|
this.content.find('.result').css('text-align', 'center');
|
||||||
|
|
||||||
|
$(`<div class='text-muted' style='margin: 20px 5px; font-weight: lighter;'>
|
||||||
|
${message} </div>`).appendTo(this.result);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
get_item_dashboard_data: function(data, max_count, show_item) {
|
get_item_dashboard_data: function(data, max_count, show_item) {
|
||||||
if(!max_count) max_count = 0;
|
if(!max_count) max_count = 0;
|
||||||
if(!data) data = [];
|
if(!data) data = [];
|
||||||
@ -128,8 +153,8 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
d.total_reserved, max_count);
|
d.total_reserved, max_count);
|
||||||
});
|
});
|
||||||
|
|
||||||
var can_write = 0;
|
let can_write = 0;
|
||||||
if(frappe.boot.user.can_write.indexOf("Stock Entry")>=0){
|
if (frappe.boot.user.can_write.indexOf("Stock Entry") >= 0) {
|
||||||
can_write = 1;
|
can_write = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,9 +163,27 @@ erpnext.stock.ItemDashboard = Class.extend({
|
|||||||
max_count: max_count,
|
max_count: max_count,
|
||||||
can_write:can_write,
|
can_write:can_write,
|
||||||
show_item: show_item || false
|
show_item: show_item || false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
get_capacity_dashboard_data: function(data) {
|
||||||
|
if (!data) data = [];
|
||||||
|
|
||||||
|
data.forEach(function(d) {
|
||||||
|
d.color = d.percent_occupied >=80 ? "#f8814f" : "#2490ef";
|
||||||
|
});
|
||||||
|
|
||||||
|
let can_write = 0;
|
||||||
|
if (frappe.boot.user.can_write.indexOf("Putaway Rule") >= 0) {
|
||||||
|
can_write = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data,
|
||||||
|
can_write: can_write,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
|
erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
|
||||||
var dialog = new frappe.ui.Dialog({
|
var dialog = new frappe.ui.Dialog({
|
||||||
|
69
erpnext/stock/dashboard/warehouse_capacity_dashboard.py
Normal file
69
erpnext/stock/dashboard/warehouse_capacity_dashboard.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.model.db_query import DatabaseQuery
|
||||||
|
from frappe.utils import nowdate
|
||||||
|
from frappe.utils import flt
|
||||||
|
from erpnext.stock.utils import get_stock_balance
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_data(item_code=None, warehouse=None, parent_warehouse=None,
|
||||||
|
company=None, start=0, sort_by="stock_capacity", sort_order="desc"):
|
||||||
|
"""Return data to render the warehouse capacity dashboard."""
|
||||||
|
filters = get_filters(item_code, warehouse, parent_warehouse, company)
|
||||||
|
|
||||||
|
no_permission, filters = get_warehouse_filter_based_on_permissions(filters)
|
||||||
|
if no_permission:
|
||||||
|
return []
|
||||||
|
|
||||||
|
capacity_data = get_warehouse_capacity_data(filters, start)
|
||||||
|
|
||||||
|
asc_desc = -1 if sort_order == "desc" else 1
|
||||||
|
capacity_data = sorted(capacity_data, key = lambda i: (i[sort_by] * asc_desc))
|
||||||
|
|
||||||
|
return capacity_data
|
||||||
|
|
||||||
|
def get_filters(item_code=None, warehouse=None, parent_warehouse=None,
|
||||||
|
company=None):
|
||||||
|
filters = [['disable', '=', 0]]
|
||||||
|
if item_code:
|
||||||
|
filters.append(['item_code', '=', item_code])
|
||||||
|
if warehouse:
|
||||||
|
filters.append(['warehouse', '=', warehouse])
|
||||||
|
if company:
|
||||||
|
filters.append(['company', '=', company])
|
||||||
|
if parent_warehouse:
|
||||||
|
lft, rgt = frappe.db.get_value("Warehouse", parent_warehouse, ["lft", "rgt"])
|
||||||
|
warehouses = frappe.db.sql_list("""
|
||||||
|
select name from `tabWarehouse`
|
||||||
|
where lft >=%s and rgt<=%s
|
||||||
|
""", (lft, rgt))
|
||||||
|
filters.append(['warehouse', 'in', warehouses])
|
||||||
|
return filters
|
||||||
|
|
||||||
|
def get_warehouse_filter_based_on_permissions(filters):
|
||||||
|
try:
|
||||||
|
# check if user has any restrictions based on user permissions on warehouse
|
||||||
|
if DatabaseQuery('Warehouse', user=frappe.session.user).build_match_conditions():
|
||||||
|
filters.append(['warehouse', 'in', [w.name for w in frappe.get_list('Warehouse')]])
|
||||||
|
return False, filters
|
||||||
|
except frappe.PermissionError:
|
||||||
|
# user does not have access on warehouse
|
||||||
|
return True, []
|
||||||
|
|
||||||
|
def get_warehouse_capacity_data(filters, start):
|
||||||
|
capacity_data = frappe.db.get_all('Putaway Rule',
|
||||||
|
fields=['item_code', 'warehouse','stock_capacity', 'company'],
|
||||||
|
filters=filters,
|
||||||
|
limit_start=start,
|
||||||
|
limit_page_length='11'
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in capacity_data:
|
||||||
|
balance_qty = get_stock_balance(entry.item_code, entry.warehouse, nowdate()) or 0
|
||||||
|
entry.update({
|
||||||
|
'actual_qty': balance_qty,
|
||||||
|
'percent_occupied': flt((flt(balance_qty) / flt(entry.stock_capacity)) * 100, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
return capacity_data
|
@ -8,12 +8,12 @@
|
|||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Stock Transactions",
|
"label": "Stock Transactions",
|
||||||
"links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipment\",\n \"name\": \"Shipment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
|
"links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Putaway Rule\",\n \"name\": \"Putaway Rule\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipment\",\n \"name\": \"Shipment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
"label": "Stock Reports",
|
"label": "Stock Reports",
|
||||||
"links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Ledger\",\n \"name\": \"Stock Ledger\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Balance\",\n \"name\": \"Stock Balance\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Projected Qty\",\n \"name\": \"Stock Projected Qty\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Summary\",\n \"name\": \"stock-balance\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Ageing\",\n \"name\": \"Stock Ageing\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item Price Stock\",\n \"name\": \"Item Price Stock\",\n \"type\": \"report\"\n }\n]"
|
"links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Ledger\",\n \"name\": \"Stock Ledger\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Stock Ledger Entry\",\n \"is_query_report\": true,\n \"label\": \"Stock Balance\",\n \"name\": \"Stock Balance\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Projected Qty\",\n \"name\": \"Stock Projected Qty\",\n \"onboard\": 1,\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Summary\",\n \"name\": \"stock-balance\",\n \"type\": \"page\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Stock Ageing\",\n \"name\": \"Stock Ageing\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"doctype\": \"Item\",\n \"is_query_report\": true,\n \"label\": \"Item Price Stock\",\n \"name\": \"Item Price Stock\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Putaway Rule\"\n ],\n \"label\": \"Warehouse Capacity Summary\",\n \"name\": \"warehouse-capacity-summary\",\n \"type\": \"page\"\n }\n]"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hidden": 0,
|
"hidden": 0,
|
||||||
@ -58,7 +58,7 @@
|
|||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": 1,
|
"is_standard": 1,
|
||||||
"label": "Stock",
|
"label": "Stock",
|
||||||
"modified": "2020-12-02 15:47:41.532942",
|
"modified": "2020-12-08 15:47:41.532942",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock",
|
"name": "Stock",
|
||||||
|
@ -384,7 +384,10 @@ $.extend(erpnext.item, {
|
|||||||
<a href="#stock-balance">' + __("Stock Levels") + '</a></h5>');
|
<a href="#stock-balance">' + __("Stock Levels") + '</a></h5>');
|
||||||
erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
|
erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
|
||||||
parent: section,
|
parent: section,
|
||||||
item_code: frm.doc.name
|
item_code: frm.doc.name,
|
||||||
|
page_length: 20,
|
||||||
|
method: 'erpnext.stock.dashboard.item_dashboard.get_data',
|
||||||
|
template: 'item_dashboard_list'
|
||||||
});
|
});
|
||||||
erpnext.item.item_dashboard.refresh();
|
erpnext.item.item_dashboard.refresh();
|
||||||
});
|
});
|
||||||
|
@ -216,6 +216,10 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
apply_putaway_rule: function() {
|
||||||
|
if (this.frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(this.frm);
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// for backward compatibility: combine new and previous states
|
// for backward compatibility: combine new and previous states
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"posting_date",
|
"posting_date",
|
||||||
"posting_time",
|
"posting_time",
|
||||||
"set_posting_time",
|
"set_posting_time",
|
||||||
|
"apply_putaway_rule",
|
||||||
"is_return",
|
"is_return",
|
||||||
"return_against",
|
"return_against",
|
||||||
"section_addresses",
|
"section_addresses",
|
||||||
@ -1106,6 +1107,12 @@
|
|||||||
"label": "Billing Address",
|
"label": "Billing Address",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "apply_putaway_rule",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Apply Putaway Rule"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.__islocal",
|
"depends_on": "eval:!doc.__islocal",
|
||||||
"fieldname": "per_returned",
|
"fieldname": "per_returned",
|
||||||
@ -1120,7 +1127,7 @@
|
|||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-11-30 12:54:23.278500",
|
"modified": "2020-12-08 18:31:32.234503",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
@ -83,6 +83,12 @@ class PurchaseReceipt(BuyingController):
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def before_validate(self):
|
||||||
|
from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
|
||||||
|
|
||||||
|
if self.get("items") and self.apply_putaway_rule and not self.get("is_return"):
|
||||||
|
apply_putaway_rule(self.doctype, self.get("items"), self.company)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_posting_time()
|
self.validate_posting_time()
|
||||||
super(PurchaseReceipt, self).validate()
|
super(PurchaseReceipt, self).validate()
|
||||||
@ -103,6 +109,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if getdate(self.posting_date) > getdate(nowdate()):
|
if getdate(self.posting_date) > getdate(nowdate()):
|
||||||
throw(_("Posting Date cannot be future date"))
|
throw(_("Posting Date cannot be future date"))
|
||||||
|
|
||||||
|
|
||||||
def validate_cwip_accounts(self):
|
def validate_cwip_accounts(self):
|
||||||
for item in self.get('items'):
|
for item in self.get('items'):
|
||||||
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
|
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
|
||||||
@ -408,7 +415,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if warehouse_with_no_account:
|
if warehouse_with_no_account:
|
||||||
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
|
frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
|
||||||
"\n".join(warehouse_with_no_account))
|
"\n".join(warehouse_with_no_account))
|
||||||
|
|
||||||
return process_gl_map(gl_entries)
|
return process_gl_map(gl_entries)
|
||||||
|
|
||||||
def get_asset_gl_entry(self, gl_entries):
|
def get_asset_gl_entry(self, gl_entries):
|
||||||
|
@ -1011,6 +1011,7 @@ def make_purchase_receipt(**args):
|
|||||||
pr.currency = args.currency or "INR"
|
pr.currency = args.currency or "INR"
|
||||||
pr.is_return = args.is_return
|
pr.is_return = args.is_return
|
||||||
pr.return_against = args.return_against
|
pr.return_against = args.return_against
|
||||||
|
pr.apply_putaway_rule = args.apply_putaway_rule
|
||||||
qty = args.qty or 5
|
qty = args.qty or 5
|
||||||
received_qty = args.received_qty or qty
|
received_qty = args.received_qty or qty
|
||||||
rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty)
|
rejected_qty = args.rejected_qty or flt(received_qty) - flt(qty)
|
||||||
@ -1026,6 +1027,7 @@ def make_purchase_receipt(**args):
|
|||||||
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
|
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
|
||||||
"rate": args.rate if args.rate != None else 50,
|
"rate": args.rate if args.rate != None else 50,
|
||||||
"conversion_factor": args.conversion_factor or 1.0,
|
"conversion_factor": args.conversion_factor or 1.0,
|
||||||
|
"stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
"stock_uom": args.stock_uom or "_Test UOM",
|
"stock_uom": args.stock_uom or "_Test UOM",
|
||||||
"uom": uom,
|
"uom": uom,
|
||||||
|
@ -76,6 +76,7 @@
|
|||||||
"purchase_order_item",
|
"purchase_order_item",
|
||||||
"material_request_item",
|
"material_request_item",
|
||||||
"purchase_receipt_item",
|
"purchase_receipt_item",
|
||||||
|
"putaway_rule",
|
||||||
"section_break_45",
|
"section_break_45",
|
||||||
"allow_zero_valuation_rate",
|
"allow_zero_valuation_rate",
|
||||||
"bom",
|
"bom",
|
||||||
@ -839,6 +840,15 @@
|
|||||||
"fieldname": "image_column",
|
"fieldname": "image_column",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "putaway_rule",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Putaway Rule",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Putaway Rule",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tracking_section",
|
"fieldname": "tracking_section",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
@ -866,7 +876,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-07 10:00:38.204294",
|
"modified": "2020-12-09 10:00:38.204294",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt Item",
|
"name": "Purchase Receipt Item",
|
||||||
|
0
erpnext/stock/doctype/putaway_rule/__init__.py
Normal file
0
erpnext/stock/doctype/putaway_rule/__init__.py
Normal file
43
erpnext/stock/doctype/putaway_rule/putaway_rule.js
Normal file
43
erpnext/stock/doctype/putaway_rule/putaway_rule.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Putaway Rule', {
|
||||||
|
setup: function(frm) {
|
||||||
|
frm.set_query("warehouse", function() {
|
||||||
|
return {
|
||||||
|
"filters": {
|
||||||
|
"company": frm.doc.company,
|
||||||
|
"is_group": 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
uom: function(frm) {
|
||||||
|
if (frm.doc.item_code && frm.doc.uom) {
|
||||||
|
return frm.call({
|
||||||
|
method: "erpnext.stock.get_item_details.get_conversion_factor",
|
||||||
|
args: {
|
||||||
|
item_code: frm.doc.item_code,
|
||||||
|
uom: frm.doc.uom
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (!r.exc) {
|
||||||
|
let stock_capacity = flt(frm.doc.capacity) * flt(r.message.conversion_factor);
|
||||||
|
frm.set_value('conversion_factor', r.message.conversion_factor);
|
||||||
|
frm.set_value('stock_capacity', stock_capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
capacity: function(frm) {
|
||||||
|
let stock_capacity = flt(frm.doc.capacity) * flt(frm.doc.conversion_factor);
|
||||||
|
frm.set_value('stock_capacity', stock_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// refresh: function(frm) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
160
erpnext/stock/doctype/putaway_rule/putaway_rule.json
Normal file
160
erpnext/stock/doctype/putaway_rule/putaway_rule.json
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"autoname": "PUT-.####",
|
||||||
|
"creation": "2020-11-09 11:39:46.489501",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"disable",
|
||||||
|
"item_code",
|
||||||
|
"item_name",
|
||||||
|
"warehouse",
|
||||||
|
"priority",
|
||||||
|
"col_break_capacity",
|
||||||
|
"company",
|
||||||
|
"capacity",
|
||||||
|
"uom",
|
||||||
|
"conversion_factor",
|
||||||
|
"stock_uom",
|
||||||
|
"stock_capacity"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "item_code",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Item",
|
||||||
|
"options": "Item",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item_code.item_name",
|
||||||
|
"fieldname": "item_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Item Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "warehouse",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Warehouse",
|
||||||
|
"options": "Warehouse",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "col_break_capacity",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "capacity",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Capacity",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fetch_from": "item_code.stock_uom",
|
||||||
|
"fieldname": "stock_uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Stock UOM",
|
||||||
|
"options": "UOM",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "priority",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Priority"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:!doc.__islocal",
|
||||||
|
"fieldname": "disable",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "uom",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "UOM",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "UOM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "stock_capacity",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Capacity in Stock UOM",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "conversion_factor",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Conversion Factor",
|
||||||
|
"no_copy": 1,
|
||||||
|
"read_only": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-11-25 20:39:19.973437",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "Putaway Rule",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"permlevel": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Stock Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"title_field": "item_code",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
235
erpnext/stock/doctype/putaway_rule/putaway_rule.py
Normal file
235
erpnext/stock/doctype/putaway_rule/putaway_rule.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
from collections import defaultdict
|
||||||
|
from six import string_types
|
||||||
|
from frappe import _
|
||||||
|
from frappe.utils import flt, floor, nowdate, cint
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from erpnext.stock.utils import get_stock_balance
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
|
class PutawayRule(Document):
|
||||||
|
def validate(self):
|
||||||
|
self.validate_duplicate_rule()
|
||||||
|
self.validate_warehouse_and_company()
|
||||||
|
self.validate_capacity()
|
||||||
|
self.validate_priority()
|
||||||
|
self.set_stock_capacity()
|
||||||
|
|
||||||
|
def validate_duplicate_rule(self):
|
||||||
|
existing_rule = frappe.db.exists("Putaway Rule", {"item_code": self.item_code, "warehouse": self.warehouse})
|
||||||
|
if existing_rule and existing_rule != self.name:
|
||||||
|
frappe.throw(_("Putaway Rule already exists for Item {0} in Warehouse {1}.")
|
||||||
|
.format(frappe.bold(self.item_code), frappe.bold(self.warehouse)),
|
||||||
|
title=_("Duplicate"))
|
||||||
|
|
||||||
|
def validate_priority(self):
|
||||||
|
if self.priority < 1:
|
||||||
|
frappe.throw(_("Priority cannot be lesser than 1."), title=_("Invalid Priority"))
|
||||||
|
|
||||||
|
def validate_warehouse_and_company(self):
|
||||||
|
company = frappe.db.get_value("Warehouse", self.warehouse, "company")
|
||||||
|
if company != self.company:
|
||||||
|
frappe.throw(_("Warehouse {0} does not belong to Company {1}.")
|
||||||
|
.format(frappe.bold(self.warehouse), frappe.bold(self.company)),
|
||||||
|
title=_("Invalid Warehouse"))
|
||||||
|
|
||||||
|
def validate_capacity(self):
|
||||||
|
stock_uom = frappe.db.get_value("Item", self.item_code, "stock_uom")
|
||||||
|
balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate())
|
||||||
|
|
||||||
|
if flt(self.stock_capacity) < flt(balance_qty):
|
||||||
|
frappe.throw(_("Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} {2}.")
|
||||||
|
.format(self.item_code, frappe.bold(balance_qty), stock_uom),
|
||||||
|
title=_("Insufficient Capacity"))
|
||||||
|
|
||||||
|
if not self.capacity:
|
||||||
|
frappe.throw(_("Capacity must be greater than 0"), title=_("Invalid"))
|
||||||
|
|
||||||
|
def set_stock_capacity(self):
|
||||||
|
self.stock_capacity = (flt(self.conversion_factor) or 1) * flt(self.capacity)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_available_putaway_capacity(rule):
|
||||||
|
stock_capacity, item_code, warehouse = frappe.db.get_value("Putaway Rule", rule,
|
||||||
|
["stock_capacity", "item_code", "warehouse"])
|
||||||
|
balance_qty = get_stock_balance(item_code, warehouse, nowdate())
|
||||||
|
free_space = flt(stock_capacity) - flt(balance_qty)
|
||||||
|
return free_space if free_space > 0 else 0
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
|
||||||
|
""" Applies Putaway Rule on line items.
|
||||||
|
|
||||||
|
items: List of Purchase Receipt/Stock Entry Items
|
||||||
|
company: Company in the Purchase Receipt/Stock Entry
|
||||||
|
doctype: Doctype to apply rule on
|
||||||
|
purpose: Purpose of Stock Entry
|
||||||
|
sync (optional): Sync with client side only for client side calls
|
||||||
|
"""
|
||||||
|
if isinstance(items, string_types):
|
||||||
|
items = json.loads(items)
|
||||||
|
|
||||||
|
items_not_accomodated, updated_table = [], []
|
||||||
|
item_wise_rules = defaultdict(list)
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
item = frappe._dict(item)
|
||||||
|
|
||||||
|
source_warehouse = item.get("s_warehouse")
|
||||||
|
serial_nos = get_serial_nos(item.get("serial_no"))
|
||||||
|
item.conversion_factor = flt(item.conversion_factor) or 1.0
|
||||||
|
pending_qty, item_code = flt(item.qty), item.item_code
|
||||||
|
pending_stock_qty = flt(item.transfer_qty) if doctype == "Stock Entry" else flt(item.stock_qty)
|
||||||
|
uom_must_be_whole_number = frappe.db.get_value('UOM', item.uom, 'must_be_whole_number')
|
||||||
|
|
||||||
|
if not pending_qty or not item_code:
|
||||||
|
updated_table = add_row(item, pending_qty, source_warehouse or item.warehouse, updated_table)
|
||||||
|
continue
|
||||||
|
|
||||||
|
at_capacity, rules = get_ordered_putaway_rules(item_code, company, source_warehouse=source_warehouse)
|
||||||
|
|
||||||
|
if not rules:
|
||||||
|
warehouse = source_warehouse or item.warehouse
|
||||||
|
if at_capacity:
|
||||||
|
# rules available, but no free space
|
||||||
|
items_not_accomodated.append([item_code, pending_qty])
|
||||||
|
else:
|
||||||
|
updated_table = add_row(item, pending_qty, warehouse, updated_table)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# maintain item/item-warehouse wise rules, to handle if item is entered twice
|
||||||
|
# in the table, due to different price, etc.
|
||||||
|
key = item_code
|
||||||
|
if doctype == "Stock Entry" and purpose == "Material Transfer" and source_warehouse:
|
||||||
|
key = (item_code, source_warehouse)
|
||||||
|
|
||||||
|
if not item_wise_rules[key]:
|
||||||
|
item_wise_rules[key] = rules
|
||||||
|
|
||||||
|
for rule in item_wise_rules[key]:
|
||||||
|
if pending_stock_qty > 0 and rule.free_space:
|
||||||
|
stock_qty_to_allocate = flt(rule.free_space) if pending_stock_qty >= flt(rule.free_space) else pending_stock_qty
|
||||||
|
qty_to_allocate = stock_qty_to_allocate / item.conversion_factor
|
||||||
|
|
||||||
|
if uom_must_be_whole_number:
|
||||||
|
qty_to_allocate = floor(qty_to_allocate)
|
||||||
|
stock_qty_to_allocate = qty_to_allocate * item.conversion_factor
|
||||||
|
|
||||||
|
if not qty_to_allocate: break
|
||||||
|
|
||||||
|
updated_table = add_row(item, qty_to_allocate, rule.warehouse, updated_table,
|
||||||
|
rule.name, serial_nos=serial_nos)
|
||||||
|
|
||||||
|
pending_stock_qty -= stock_qty_to_allocate
|
||||||
|
pending_qty -= qty_to_allocate
|
||||||
|
rule["free_space"] -= stock_qty_to_allocate
|
||||||
|
|
||||||
|
if not pending_stock_qty > 0: break
|
||||||
|
|
||||||
|
# if pending qty after applying all rules, add row without warehouse
|
||||||
|
if pending_stock_qty > 0:
|
||||||
|
items_not_accomodated.append([item.item_code, pending_qty])
|
||||||
|
|
||||||
|
if items_not_accomodated:
|
||||||
|
show_unassigned_items_message(items_not_accomodated)
|
||||||
|
|
||||||
|
items[:] = updated_table if updated_table else items # modify items table
|
||||||
|
|
||||||
|
if sync and json.loads(sync): # sync with client side
|
||||||
|
return items
|
||||||
|
|
||||||
|
def get_ordered_putaway_rules(item_code, company, source_warehouse=None):
|
||||||
|
"""Returns an ordered list of putaway rules to apply on an item."""
|
||||||
|
filters = {
|
||||||
|
"item_code": item_code,
|
||||||
|
"company": company,
|
||||||
|
"disable": 0
|
||||||
|
}
|
||||||
|
if source_warehouse:
|
||||||
|
filters.update({"warehouse": ["!=", source_warehouse]})
|
||||||
|
|
||||||
|
rules = frappe.get_all("Putaway Rule",
|
||||||
|
fields=["name", "item_code", "stock_capacity", "priority", "warehouse"],
|
||||||
|
filters=filters,
|
||||||
|
order_by="priority asc, capacity desc")
|
||||||
|
|
||||||
|
if not rules:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
vacant_rules = []
|
||||||
|
for rule in rules:
|
||||||
|
balance_qty = get_stock_balance(rule.item_code, rule.warehouse, nowdate())
|
||||||
|
free_space = flt(rule.stock_capacity) - flt(balance_qty)
|
||||||
|
if free_space > 0:
|
||||||
|
rule["free_space"] = free_space
|
||||||
|
vacant_rules.append(rule)
|
||||||
|
|
||||||
|
if not vacant_rules:
|
||||||
|
# After iterating through rules, if no rules are left
|
||||||
|
# then there is not enough space left in any rule
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
vacant_rules = sorted(vacant_rules, key = lambda i: (i['priority'], -i['free_space']))
|
||||||
|
|
||||||
|
return False, vacant_rules
|
||||||
|
|
||||||
|
def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=None):
|
||||||
|
new_updated_table_row = copy.deepcopy(item)
|
||||||
|
new_updated_table_row.idx = 1 if not updated_table else cint(updated_table[-1].idx) + 1
|
||||||
|
new_updated_table_row.name = None
|
||||||
|
new_updated_table_row.qty = to_allocate
|
||||||
|
|
||||||
|
if item.doctype == "Stock Entry Detail":
|
||||||
|
new_updated_table_row.t_warehouse = warehouse
|
||||||
|
new_updated_table_row.transfer_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
|
||||||
|
else:
|
||||||
|
new_updated_table_row.stock_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
|
||||||
|
new_updated_table_row.warehouse = warehouse
|
||||||
|
new_updated_table_row.rejected_qty = 0
|
||||||
|
new_updated_table_row.received_qty = to_allocate
|
||||||
|
|
||||||
|
if rule:
|
||||||
|
new_updated_table_row.putaway_rule = rule
|
||||||
|
if serial_nos:
|
||||||
|
new_updated_table_row.serial_no = get_serial_nos_to_allocate(serial_nos, to_allocate)
|
||||||
|
|
||||||
|
updated_table.append(new_updated_table_row)
|
||||||
|
return updated_table
|
||||||
|
|
||||||
|
def show_unassigned_items_message(items_not_accomodated):
|
||||||
|
msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br>"
|
||||||
|
formatted_item_rows = ""
|
||||||
|
|
||||||
|
for entry in items_not_accomodated:
|
||||||
|
item_link = frappe.utils.get_link_to_form("Item", entry[0])
|
||||||
|
formatted_item_rows += """
|
||||||
|
<td>{0}</td>
|
||||||
|
<td>{1}</td>
|
||||||
|
</tr>""".format(item_link, frappe.bold(entry[1]))
|
||||||
|
|
||||||
|
msg += """
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<td>{0}</td>
|
||||||
|
<td>{1}</td>
|
||||||
|
</thead>
|
||||||
|
{2}
|
||||||
|
</table>
|
||||||
|
""".format(_("Item"), _("Unassigned Qty"), formatted_item_rows)
|
||||||
|
|
||||||
|
frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)
|
||||||
|
|
||||||
|
def get_serial_nos_to_allocate(serial_nos, to_allocate):
|
||||||
|
if serial_nos:
|
||||||
|
allocated_serial_nos = serial_nos[0: cint(to_allocate)]
|
||||||
|
serial_nos[:] = serial_nos[cint(to_allocate):] # pop out allocated serial nos and modify list
|
||||||
|
return "\n".join(allocated_serial_nos) if allocated_serial_nos else ""
|
||||||
|
else: return ""
|
18
erpnext/stock/doctype/putaway_rule/putaway_rule_list.js
Normal file
18
erpnext/stock/doctype/putaway_rule/putaway_rule_list.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
frappe.listview_settings['Putaway Rule'] = {
|
||||||
|
add_fields: ["disable"],
|
||||||
|
get_indicator: (doc) => {
|
||||||
|
if (doc.disable) {
|
||||||
|
return [__("Disabled"), "darkgrey", "disable,=,1"];
|
||||||
|
} else {
|
||||||
|
return [__("Active"), "blue", "disable,=,0"];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reports: [
|
||||||
|
{
|
||||||
|
name: 'Warehouse Capacity Summary',
|
||||||
|
report_type: 'Page',
|
||||||
|
route: 'warehouse-capacity-summary'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
389
erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
Normal file
389
erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
import unittest
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
from erpnext.stock.doctype.batch.test_batch import make_new_batch
|
||||||
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
|
||||||
|
class TestPutawayRule(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
if not frappe.db.exists("Item", "_Rice"):
|
||||||
|
make_item("_Rice", {
|
||||||
|
'is_stock_item': 1,
|
||||||
|
'has_batch_no' : 1,
|
||||||
|
'create_new_batch': 1,
|
||||||
|
'stock_uom': 'Kg'
|
||||||
|
})
|
||||||
|
|
||||||
|
if not frappe.db.exists("Warehouse", {"warehouse_name": "Rack 1"}):
|
||||||
|
create_warehouse("Rack 1")
|
||||||
|
if not frappe.db.exists("Warehouse", {"warehouse_name": "Rack 2"}):
|
||||||
|
create_warehouse("Rack 2")
|
||||||
|
|
||||||
|
self.warehouse_1 = frappe.db.get_value("Warehouse", {"warehouse_name": "Rack 1"})
|
||||||
|
self.warehouse_2 = frappe.db.get_value("Warehouse", {"warehouse_name": "Rack 2"})
|
||||||
|
|
||||||
|
if not frappe.db.exists("UOM", "Bag"):
|
||||||
|
new_uom = frappe.new_doc("UOM")
|
||||||
|
new_uom.uom_name = "Bag"
|
||||||
|
new_uom.save()
|
||||||
|
|
||||||
|
def test_putaway_rules_priority(self):
|
||||||
|
"""Test if rule is applied by priority, irrespective of free space."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
|
||||||
|
uom="Kg")
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=300,
|
||||||
|
uom="Kg", priority=2)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1,
|
||||||
|
do_not_submit=1)
|
||||||
|
self.assertEqual(len(pr.items), 2)
|
||||||
|
self.assertEqual(pr.items[0].qty, 200)
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(pr.items[1].qty, 100)
|
||||||
|
self.assertEqual(pr.items[1].warehouse, self.warehouse_2)
|
||||||
|
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rules_with_same_priority(self):
|
||||||
|
"""Test if rule with more free space is applied,
|
||||||
|
among two rules with same priority and capacity."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=500,
|
||||||
|
uom="Kg")
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500,
|
||||||
|
uom="Kg")
|
||||||
|
|
||||||
|
# out of 500 kg capacity, occupy 100 kg in warehouse_1
|
||||||
|
stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=100, basic_rate=50)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=700, apply_putaway_rule=1,
|
||||||
|
do_not_submit=1)
|
||||||
|
self.assertEqual(len(pr.items), 2)
|
||||||
|
self.assertEqual(pr.items[0].qty, 500)
|
||||||
|
# warehouse_2 has 500 kg free space, it is given priority
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(pr.items[1].qty, 200)
|
||||||
|
# warehouse_1 has 400 kg free space, it is given less priority
|
||||||
|
self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
|
||||||
|
|
||||||
|
stock_receipt.cancel()
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rules_with_insufficient_capacity(self):
|
||||||
|
"""Test if qty exceeding capacity, is handled."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=100,
|
||||||
|
uom="Kg")
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=200,
|
||||||
|
uom="Kg")
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=350, apply_putaway_rule=1,
|
||||||
|
do_not_submit=1)
|
||||||
|
self.assertEqual(len(pr.items), 2)
|
||||||
|
self.assertEqual(pr.items[0].qty, 200)
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(pr.items[1].qty, 100)
|
||||||
|
self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
|
||||||
|
# total 300 assigned, 50 unassigned
|
||||||
|
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rules_multi_uom(self):
|
||||||
|
"""Test rules applied on uom other than stock uom."""
|
||||||
|
item = frappe.get_doc("Item", "_Rice")
|
||||||
|
if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Rice", "uom": "Bag"}):
|
||||||
|
item.append("uoms", {
|
||||||
|
"uom": "Bag",
|
||||||
|
"conversion_factor": 1000
|
||||||
|
})
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=3,
|
||||||
|
uom="Bag")
|
||||||
|
self.assertEqual(rule_1.stock_capacity, 3000)
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=4,
|
||||||
|
uom="Bag")
|
||||||
|
self.assertEqual(rule_2.stock_capacity, 4000)
|
||||||
|
|
||||||
|
# populate 'Rack 1' with 1 Bag, making the free space 2 Bags
|
||||||
|
stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=1000, basic_rate=50)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=6, uom="Bag", stock_uom="Kg",
|
||||||
|
conversion_factor=1000, apply_putaway_rule=1, do_not_submit=1)
|
||||||
|
self.assertEqual(len(pr.items), 2)
|
||||||
|
self.assertEqual(pr.items[0].qty, 4)
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(pr.items[1].qty, 2)
|
||||||
|
self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
|
||||||
|
|
||||||
|
stock_receipt.cancel()
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rules_multi_uom_whole_uom(self):
|
||||||
|
"""Test if whole UOMs are handled."""
|
||||||
|
item = frappe.get_doc("Item", "_Rice")
|
||||||
|
if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Rice", "uom": "Bag"}):
|
||||||
|
item.append("uoms", {
|
||||||
|
"uom": "Bag",
|
||||||
|
"conversion_factor": 1000
|
||||||
|
})
|
||||||
|
item.save()
|
||||||
|
|
||||||
|
frappe.db.set_value("UOM", "Bag", "must_be_whole_number", 1)
|
||||||
|
|
||||||
|
# Putaway Rule in different UOM
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=1,
|
||||||
|
uom="Bag")
|
||||||
|
self.assertEqual(rule_1.stock_capacity, 1000)
|
||||||
|
# Putaway Rule in Stock UOM
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500)
|
||||||
|
self.assertEqual(rule_2.stock_capacity, 500)
|
||||||
|
# total capacity is 1500 Kg
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=2, uom="Bag", stock_uom="Kg",
|
||||||
|
conversion_factor=1000, apply_putaway_rule=1, do_not_submit=1)
|
||||||
|
self.assertEqual(len(pr.items), 1)
|
||||||
|
self.assertEqual(pr.items[0].qty, 1)
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
|
||||||
|
# leftover space was for 500 kg (0.5 Bag)
|
||||||
|
# Since Bag is a whole UOM, 1(out of 2) Bag will be unassigned
|
||||||
|
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rules_with_reoccurring_item(self):
|
||||||
|
"""Test rules on same item entered multiple times with different rate."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
|
||||||
|
uom="Kg")
|
||||||
|
# total capacity is 200 Kg
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=100, apply_putaway_rule=1,
|
||||||
|
do_not_submit=1)
|
||||||
|
pr.append("items", {
|
||||||
|
"item_code": "_Rice",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 200,
|
||||||
|
"uom": "Kg",
|
||||||
|
"stock_uom": "Kg",
|
||||||
|
"stock_qty": 200,
|
||||||
|
"received_qty": 200,
|
||||||
|
"rate": 100,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
}) # same item entered again in PR but with different rate
|
||||||
|
pr.save()
|
||||||
|
self.assertEqual(len(pr.items), 2)
|
||||||
|
self.assertEqual(pr.items[0].qty, 100)
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(pr.items[0].putaway_rule, rule_1.name)
|
||||||
|
# same rule applied to second item row
|
||||||
|
# with previous assignment considered
|
||||||
|
self.assertEqual(pr.items[1].qty, 100) # 100 unassigned in second row from 200
|
||||||
|
self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(pr.items[1].putaway_rule, rule_1.name)
|
||||||
|
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
|
||||||
|
def test_validate_over_receipt_in_warehouse(self):
|
||||||
|
"""Test if overreceipt is blocked in the presence of putaway rules."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
|
||||||
|
uom="Kg")
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1,
|
||||||
|
do_not_submit=1)
|
||||||
|
self.assertEqual(len(pr.items), 1)
|
||||||
|
self.assertEqual(pr.items[0].qty, 200) # 100 is unassigned fro 300 Kg
|
||||||
|
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(pr.items[0].putaway_rule, rule_1.name)
|
||||||
|
|
||||||
|
# force overreceipt and disable apply putaway rule in PR
|
||||||
|
pr.items[0].qty = 300
|
||||||
|
pr.items[0].stock_qty = 300
|
||||||
|
pr.apply_putaway_rule = 0
|
||||||
|
self.assertRaises(frappe.ValidationError, pr.save)
|
||||||
|
|
||||||
|
pr.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
|
||||||
|
def test_putaway_rule_on_stock_entry_material_transfer(self):
|
||||||
|
"""Test if source warehouse is considered while applying rules."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
|
||||||
|
uom="Kg") # higher priority
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100,
|
||||||
|
uom="Kg", priority=2)
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(item_code="_Rice", source=self.warehouse_1, qty=200,
|
||||||
|
target="_Test Warehouse - _TC", purpose="Material Transfer",
|
||||||
|
apply_putaway_rule=1, do_not_submit=1)
|
||||||
|
|
||||||
|
stock_entry_item = stock_entry.get("items")[0]
|
||||||
|
|
||||||
|
# since source warehouse is Rack 1, rule 1 (for Rack 1) will be avoided
|
||||||
|
# even though it has more free space and higher priority
|
||||||
|
self.assertEqual(stock_entry_item.t_warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(stock_entry_item.qty, 100) # unassigned 100 out of 200 Kg
|
||||||
|
self.assertEqual(stock_entry_item.putaway_rule, rule_2.name)
|
||||||
|
|
||||||
|
stock_entry.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rule_on_stock_entry_material_transfer_reoccuring_item(self):
|
||||||
|
"""Test if reoccuring item is correctly considered."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=300,
|
||||||
|
uom="Kg")
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=600,
|
||||||
|
uom="Kg", priority=2)
|
||||||
|
|
||||||
|
# create SE with first row having source warehouse as Rack 2
|
||||||
|
stock_entry = make_stock_entry(item_code="_Rice", source=self.warehouse_2, qty=200,
|
||||||
|
target="_Test Warehouse - _TC", purpose="Material Transfer",
|
||||||
|
apply_putaway_rule=1, do_not_submit=1)
|
||||||
|
|
||||||
|
# Add rows with source warehouse as Rack 1
|
||||||
|
stock_entry.extend("items", [
|
||||||
|
{
|
||||||
|
"item_code": "_Rice",
|
||||||
|
"s_warehouse": self.warehouse_1,
|
||||||
|
"t_warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 100,
|
||||||
|
"basic_rate": 50,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"transfer_qty": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"item_code": "_Rice",
|
||||||
|
"s_warehouse": self.warehouse_1,
|
||||||
|
"t_warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 200,
|
||||||
|
"basic_rate": 60,
|
||||||
|
"conversion_factor": 1.0,
|
||||||
|
"transfer_qty": 200
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
stock_entry.save()
|
||||||
|
|
||||||
|
# since source warehouse was Rack 2, exclude rule_2
|
||||||
|
self.assertEqual(stock_entry.items[0].t_warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(stock_entry.items[0].qty, 200)
|
||||||
|
self.assertEqual(stock_entry.items[0].putaway_rule, rule_1.name)
|
||||||
|
|
||||||
|
# since source warehouse was Rack 1, exclude rule_1 even though it has
|
||||||
|
# higher priority
|
||||||
|
self.assertEqual(stock_entry.items[1].t_warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(stock_entry.items[1].qty, 100)
|
||||||
|
self.assertEqual(stock_entry.items[1].putaway_rule, rule_2.name)
|
||||||
|
|
||||||
|
self.assertEqual(stock_entry.items[2].t_warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(stock_entry.items[2].qty, 200)
|
||||||
|
self.assertEqual(stock_entry.items[2].putaway_rule, rule_2.name)
|
||||||
|
|
||||||
|
stock_entry.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rule_on_stock_entry_material_transfer_batch_serial_item(self):
|
||||||
|
"""Test if batch and serial items are split correctly."""
|
||||||
|
if not frappe.db.exists("Item", "Water Bottle"):
|
||||||
|
make_item("Water Bottle", {
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no" : 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"has_serial_no": 1,
|
||||||
|
"serial_no_series": "BOTTL-.####",
|
||||||
|
"stock_uom": "Nos"
|
||||||
|
})
|
||||||
|
|
||||||
|
rule_1 = create_putaway_rule(item_code="Water Bottle", warehouse=self.warehouse_1, capacity=3,
|
||||||
|
uom="Nos")
|
||||||
|
rule_2 = create_putaway_rule(item_code="Water Bottle", warehouse=self.warehouse_2, capacity=2,
|
||||||
|
uom="Nos")
|
||||||
|
|
||||||
|
make_new_batch(batch_id="BOTTL-BATCH-1", item_code="Water Bottle")
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(item_code="Water Bottle", qty=5, do_not_submit=1)
|
||||||
|
pr.items[0].batch_no = "BOTTL-BATCH-1"
|
||||||
|
pr.save()
|
||||||
|
pr.submit()
|
||||||
|
|
||||||
|
serial_nos = frappe.get_list("Serial No", filters={"purchase_document_no": pr.name, "status": "Active"})
|
||||||
|
serial_nos = [d.name for d in serial_nos]
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(item_code="Water Bottle", source="_Test Warehouse - _TC", qty=5,
|
||||||
|
target="Finished Goods - _TC", purpose="Material Transfer",
|
||||||
|
apply_putaway_rule=1, do_not_save=1)
|
||||||
|
stock_entry.items[0].batch_no = "BOTTL-BATCH-1"
|
||||||
|
stock_entry.items[0].serial_no = "\n".join(serial_nos)
|
||||||
|
stock_entry.save()
|
||||||
|
|
||||||
|
self.assertEqual(stock_entry.items[0].t_warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(stock_entry.items[0].qty, 3)
|
||||||
|
self.assertEqual(stock_entry.items[0].putaway_rule, rule_1.name)
|
||||||
|
self.assertEqual(stock_entry.items[0].serial_no, "\n".join(serial_nos[:3]))
|
||||||
|
self.assertEqual(stock_entry.items[0].batch_no, "BOTTL-BATCH-1")
|
||||||
|
|
||||||
|
self.assertEqual(stock_entry.items[1].t_warehouse, self.warehouse_2)
|
||||||
|
self.assertEqual(stock_entry.items[1].qty, 2)
|
||||||
|
self.assertEqual(stock_entry.items[1].putaway_rule, rule_2.name)
|
||||||
|
self.assertEqual(stock_entry.items[1].serial_no, "\n".join(serial_nos[3:]))
|
||||||
|
self.assertEqual(stock_entry.items[1].batch_no, "BOTTL-BATCH-1")
|
||||||
|
|
||||||
|
stock_entry.delete()
|
||||||
|
pr.cancel()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def test_putaway_rule_on_stock_entry_material_receipt(self):
|
||||||
|
"""Test if rules are applied in Stock Entry of type Receipt."""
|
||||||
|
rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
|
||||||
|
uom="Kg") # more capacity
|
||||||
|
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100,
|
||||||
|
uom="Kg")
|
||||||
|
|
||||||
|
stock_entry = make_stock_entry(item_code="_Rice", qty=100,
|
||||||
|
target="_Test Warehouse - _TC", purpose="Material Receipt",
|
||||||
|
apply_putaway_rule=1, do_not_submit=1)
|
||||||
|
|
||||||
|
stock_entry_item = stock_entry.get("items")[0]
|
||||||
|
|
||||||
|
self.assertEqual(stock_entry_item.t_warehouse, self.warehouse_1)
|
||||||
|
self.assertEqual(stock_entry_item.qty, 100)
|
||||||
|
self.assertEqual(stock_entry_item.putaway_rule, rule_1.name)
|
||||||
|
|
||||||
|
stock_entry.delete()
|
||||||
|
rule_1.delete()
|
||||||
|
rule_2.delete()
|
||||||
|
|
||||||
|
def create_putaway_rule(**args):
|
||||||
|
args = frappe._dict(args)
|
||||||
|
putaway = frappe.new_doc("Putaway Rule")
|
||||||
|
|
||||||
|
putaway.disable = args.disable or 0
|
||||||
|
putaway.company = args.company or "_Test Company"
|
||||||
|
putaway.item_code = args.item or args.item_code or "_Test Item"
|
||||||
|
putaway.warehouse = args.warehouse
|
||||||
|
putaway.priority = args.priority or 1
|
||||||
|
putaway.capacity = args.capacity or 1
|
||||||
|
putaway.stock_uom = frappe.db.get_value("Item", putaway.item_code, "stock_uom")
|
||||||
|
putaway.uom = args.uom or putaway.stock_uom
|
||||||
|
putaway.conversion_factor = get_conversion_factor(putaway.item_code, putaway.uom)['conversion_factor']
|
||||||
|
|
||||||
|
if not args.do_not_save:
|
||||||
|
putaway.save()
|
||||||
|
|
||||||
|
return putaway
|
@ -584,8 +584,12 @@ frappe.ui.form.on('Stock Entry', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
apply_putaway_rule: function (frm) {
|
||||||
|
if (frm.doc.apply_putaway_rule) erpnext.apply_putaway_rule(frm, frm.doc.purpose);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Stock Entry Detail', {
|
frappe.ui.form.on('Stock Entry Detail', {
|
||||||
qty: function(frm, cdt, cdn) {
|
qty: function(frm, cdt, cdn) {
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"set_posting_time",
|
"set_posting_time",
|
||||||
"inspection_required",
|
"inspection_required",
|
||||||
"from_bom",
|
"from_bom",
|
||||||
|
"apply_putaway_rule",
|
||||||
"sb1",
|
"sb1",
|
||||||
"bom_no",
|
"bom_no",
|
||||||
"fg_completed_qty",
|
"fg_completed_qty",
|
||||||
@ -640,6 +641,13 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Add to Transit",
|
"label": "Add to Transit",
|
||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:in_list([\"Material Transfer\", \"Material Receipt\"], doc.purpose)",
|
||||||
|
"fieldname": "apply_putaway_rule",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Apply Putaway Rule"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@ -647,7 +655,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-09 12:59:02.508943",
|
"modified": "2020-12-09 14:58:13.267321",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry",
|
"name": "Stock Entry",
|
||||||
|
@ -42,6 +42,14 @@ class StockEntry(StockController):
|
|||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
item.update(get_bin_details(item.item_code, item.s_warehouse))
|
item.update(get_bin_details(item.item_code, item.s_warehouse))
|
||||||
|
|
||||||
|
def before_validate(self):
|
||||||
|
from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
|
||||||
|
apply_rule = self.apply_putaway_rule and (self.purpose in ["Material Transfer", "Material Receipt"])
|
||||||
|
|
||||||
|
if self.get("items") and apply_rule:
|
||||||
|
apply_putaway_rule(self.doctype, self.get("items"), self.company,
|
||||||
|
purpose=self.purpose)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.pro_doc = frappe._dict()
|
self.pro_doc = frappe._dict()
|
||||||
if self.work_order:
|
if self.work_order:
|
||||||
@ -79,6 +87,7 @@ class StockEntry(StockController):
|
|||||||
self.validate_serialized_batch()
|
self.validate_serialized_batch()
|
||||||
self.set_actual_qty()
|
self.set_actual_qty()
|
||||||
self.calculate_rate_and_amount()
|
self.calculate_rate_and_amount()
|
||||||
|
self.validate_putaway_capacity()
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
|
@ -53,6 +53,8 @@ def make_stock_entry(**args):
|
|||||||
args.target = args.to_warehouse
|
args.target = args.to_warehouse
|
||||||
if args.item_code:
|
if args.item_code:
|
||||||
args.item = args.item_code
|
args.item = args.item_code
|
||||||
|
if args.apply_putaway_rule:
|
||||||
|
s.apply_putaway_rule = args.apply_putaway_rule
|
||||||
|
|
||||||
if isinstance(args.qty, string_types):
|
if isinstance(args.qty, string_types):
|
||||||
if '.' in args.qty:
|
if '.' in args.qty:
|
||||||
@ -118,7 +120,8 @@ def make_stock_entry(**args):
|
|||||||
"t_warehouse": args.target,
|
"t_warehouse": args.target,
|
||||||
"qty": args.qty,
|
"qty": args.qty,
|
||||||
"basic_rate": args.rate or args.basic_rate,
|
"basic_rate": args.rate or args.basic_rate,
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": args.conversion_factor or 1.0,
|
||||||
|
"transfer_qty": flt(args.qty) * (flt(args.conversion_factor) or 1.0),
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
'batch_no': args.batch_no,
|
'batch_no': args.batch_no,
|
||||||
'cost_center': args.cost_center,
|
'cost_center': args.cost_center,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2013-03-29 18:22:12",
|
"creation": "2013-03-29 18:22:12",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@ -65,6 +66,7 @@
|
|||||||
"against_stock_entry",
|
"against_stock_entry",
|
||||||
"ste_detail",
|
"ste_detail",
|
||||||
"po_detail",
|
"po_detail",
|
||||||
|
"putaway_rule",
|
||||||
"column_break_51",
|
"column_break_51",
|
||||||
"reference_purchase_receipt",
|
"reference_purchase_receipt",
|
||||||
"quality_inspection"
|
"quality_inspection"
|
||||||
@ -495,6 +497,16 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Set Basic Rate Manually"
|
"label": "Set Basic Rate Manually"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:in_list([\"Material Transfer\", \"Material Receipt\"], parent.purpose)",
|
||||||
|
"fieldname": "putaway_rule",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Putaway Rule",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Putaway Rule",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quantity_section",
|
"fieldname": "quantity_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@ -526,7 +538,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-12-23 17:55:03.384138",
|
"modified": "2020-12-30 15:00:44.489442",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry Detail",
|
"name": "Stock Entry Detail",
|
||||||
|
@ -30,6 +30,7 @@ class StockReconciliation(StockController):
|
|||||||
self.validate_data()
|
self.validate_data()
|
||||||
self.validate_expense_account()
|
self.validate_expense_account()
|
||||||
self.set_total_qty_and_amount()
|
self.set_total_qty_and_amount()
|
||||||
|
self.validate_putaway_capacity()
|
||||||
|
|
||||||
if self._action=="submit":
|
if self._action=="submit":
|
||||||
self.make_batches('warehouse')
|
self.make_batches('warehouse')
|
||||||
|
@ -65,6 +65,9 @@ frappe.pages['stock-balance'].on_page_load = function(wrapper) {
|
|||||||
frappe.require('assets/js/item-dashboard.min.js', function() {
|
frappe.require('assets/js/item-dashboard.min.js', function() {
|
||||||
page.item_dashboard = new erpnext.stock.ItemDashboard({
|
page.item_dashboard = new erpnext.stock.ItemDashboard({
|
||||||
parent: page.main,
|
parent: page.main,
|
||||||
|
page_length: 20,
|
||||||
|
method: 'erpnext.stock.dashboard.item_dashboard.get_data',
|
||||||
|
template: 'item_dashboard_list'
|
||||||
})
|
})
|
||||||
|
|
||||||
page.item_dashboard.before_refresh = function() {
|
page.item_dashboard.before_refresh = function() {
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
{% for d in data %}
|
||||||
|
<div class="dashboard-list-item" style="padding: 7px 15px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2 small" style="margin-top: 8px;">
|
||||||
|
<a data-type="warehouse" data-name="{{ d.warehouse }}">{{ d.warehouse }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 small" style="margin-top: 8px; ">
|
||||||
|
<a data-type="item" data-name="{{ d.item_code }}">{{ d.item_code }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 small" style="margin-top: 8px; ">
|
||||||
|
{{ d.stock_capacity }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 small" style="margin-top: 8px; ">
|
||||||
|
{{ d.actual_qty }}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 small">
|
||||||
|
<div class="progress" title="Occupied Qty: {{ d.actual_qty }}" style="margin-bottom: 4px; height: 7px; margin-top: 14px;">
|
||||||
|
<div class="progress-bar" role="progressbar"
|
||||||
|
aria-valuenow="{{ d.percent_occupied }}"
|
||||||
|
aria-valuemin="0" aria-valuemax="100"
|
||||||
|
style="width:{{ d.percent_occupied }}%;
|
||||||
|
background-color: {{ d.color }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 small" style="margin-top: 8px;">
|
||||||
|
{{ d.percent_occupied }}%
|
||||||
|
</div>
|
||||||
|
{% if can_write %}
|
||||||
|
<div class="col-sm-1 text-right" style="margin-top: 2px;">
|
||||||
|
<button class="btn btn-default btn-xs btn-edit"
|
||||||
|
style="margin-top: 4px;margin-bottom: 4px;"
|
||||||
|
data-warehouse="{{ d.warehouse }}"
|
||||||
|
data-item="{{ escape(d.item_code) }}"
|
||||||
|
data-company="{{ escape(d.company) }}">{{ __("Edit Capacity") }}</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
@ -0,0 +1,120 @@
|
|||||||
|
frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) {
|
||||||
|
var page = frappe.ui.make_app_page({
|
||||||
|
parent: wrapper,
|
||||||
|
title: 'Warehouse Capacity Summary',
|
||||||
|
single_column: true
|
||||||
|
});
|
||||||
|
page.set_secondary_action('Refresh', () => page.capacity_dashboard.refresh(), 'octicon octicon-sync');
|
||||||
|
page.start = 0;
|
||||||
|
|
||||||
|
page.company_field = page.add_field({
|
||||||
|
fieldname: 'company',
|
||||||
|
label: __('Company'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Company',
|
||||||
|
reqd: 1,
|
||||||
|
default: frappe.defaults.get_default("company"),
|
||||||
|
change: function() {
|
||||||
|
page.capacity_dashboard.start = 0;
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.warehouse_field = page.add_field({
|
||||||
|
fieldname: 'warehouse',
|
||||||
|
label: __('Warehouse'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Warehouse',
|
||||||
|
change: function() {
|
||||||
|
page.capacity_dashboard.start = 0;
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.item_field = page.add_field({
|
||||||
|
fieldname: 'item_code',
|
||||||
|
label: __('Item'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Item',
|
||||||
|
change: function() {
|
||||||
|
page.capacity_dashboard.start = 0;
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.parent_warehouse_field = page.add_field({
|
||||||
|
fieldname: 'parent_warehouse',
|
||||||
|
label: __('Parent Warehouse'),
|
||||||
|
fieldtype: 'Link',
|
||||||
|
options: 'Warehouse',
|
||||||
|
get_query: function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"is_group": 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
change: function() {
|
||||||
|
page.capacity_dashboard.start = 0;
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.sort_selector = new frappe.ui.SortSelector({
|
||||||
|
parent: page.wrapper.find('.page-form'),
|
||||||
|
args: {
|
||||||
|
sort_by: 'stock_capacity',
|
||||||
|
sort_order: 'desc',
|
||||||
|
options: [
|
||||||
|
{fieldname: 'stock_capacity', label: __('Capacity (Stock UOM)')},
|
||||||
|
{fieldname: 'percent_occupied', label: __('% Occupied')},
|
||||||
|
{fieldname: 'actual_qty', label: __('Balance Qty (Stock ')}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
change: function(sort_by, sort_order) {
|
||||||
|
page.capacity_dashboard.sort_by = sort_by;
|
||||||
|
page.capacity_dashboard.sort_order = sort_order;
|
||||||
|
page.capacity_dashboard.start = 0;
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.require('assets/js/item-dashboard.min.js', function() {
|
||||||
|
$(frappe.render_template('warehouse_capacity_summary_header')).appendTo(page.main);
|
||||||
|
|
||||||
|
page.capacity_dashboard = new erpnext.stock.ItemDashboard({
|
||||||
|
page_name: "warehouse-capacity-summary",
|
||||||
|
page_length: 10,
|
||||||
|
parent: page.main,
|
||||||
|
sort_by: 'stock_capacity',
|
||||||
|
sort_order: 'desc',
|
||||||
|
method: 'erpnext.stock.dashboard.warehouse_capacity_dashboard.get_data',
|
||||||
|
template: 'warehouse_capacity_summary'
|
||||||
|
});
|
||||||
|
|
||||||
|
page.capacity_dashboard.before_refresh = function() {
|
||||||
|
this.item_code = page.item_field.get_value();
|
||||||
|
this.warehouse = page.warehouse_field.get_value();
|
||||||
|
this.parent_warehouse = page.parent_warehouse_field.get_value();
|
||||||
|
this.company = page.company_field.get_value();
|
||||||
|
};
|
||||||
|
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
|
||||||
|
let setup_click = function(doctype) {
|
||||||
|
page.main.on('click', 'a[data-type="'+ doctype.toLowerCase() +'"]', function() {
|
||||||
|
var name = $(this).attr('data-name');
|
||||||
|
var field = page[doctype.toLowerCase() + '_field'];
|
||||||
|
if (field.get_value()===name) {
|
||||||
|
frappe.set_route('Form', doctype, name);
|
||||||
|
} else {
|
||||||
|
field.set_input(name);
|
||||||
|
page.capacity_dashboard.refresh();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setup_click('Item');
|
||||||
|
setup_click('Warehouse');
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"content": null,
|
||||||
|
"creation": "2020-11-25 12:07:54.056208",
|
||||||
|
"docstatus": 0,
|
||||||
|
"doctype": "Page",
|
||||||
|
"idx": 0,
|
||||||
|
"modified": "2020-11-25 11:07:54.056208",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Stock",
|
||||||
|
"name": "warehouse-capacity-summary",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"page_name": "Warehouse Capacity Summary",
|
||||||
|
"roles": [
|
||||||
|
{
|
||||||
|
"role": "Stock User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "Stock Manager"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"script": null,
|
||||||
|
"standard": "Yes",
|
||||||
|
"style": null,
|
||||||
|
"system_page": 0,
|
||||||
|
"title": "Warehouse Capacity Summary"
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<div class="dashboard-list-item" style="padding: 12px 15px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
|
||||||
|
Warehouse
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
|
||||||
|
Item
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-1 small text-muted" style="margin-top: 8px;">
|
||||||
|
Stock Capacity
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
|
||||||
|
Balance Stock Qty
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2 small text-muted" style="margin-top: 8px;">
|
||||||
|
% Occupied
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user