feat: Putaway
diff --git a/erpnext/stock/doctype/putaway_rule/__init__.py b/erpnext/stock/doctype/putaway_rule/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/__init__.py
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.js b/erpnext/stock/doctype/putaway_rule/putaway_rule.js
new file mode 100644
index 0000000..ae08e82
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.js
@@ -0,0 +1,18 @@
+// 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
+ }
+ };
+ });
+ }
+ // refresh: function(frm) {
+
+ // }
+});
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.json b/erpnext/stock/doctype/putaway_rule/putaway_rule.json
new file mode 100644
index 0000000..6a132c7
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.json
@@ -0,0 +1,111 @@
+{
+ "actions": [],
+ "autoname": "format:{item_code}-{warehouse}",
+ "creation": "2020-11-09 11:39:46.489501",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "disable",
+ "item_code",
+ "item_name",
+ "warehouse",
+ "col_break_capacity",
+ "company",
+ "capacity",
+ "priority",
+ "stock_uom"
+ ],
+ "fields": [
+ {
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "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
+ },
+ {
+ "default": "item_code.stock_uom",
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "read_only": 1
+ },
+ {
+ "default": "1",
+ "fieldname": "priority",
+ "fieldtype": "Int",
+ "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"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-11-10 17:06:27.151335",
+ "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": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
new file mode 100644
index 0000000..9f02833
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -0,0 +1,93 @@
+# -*- 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
+from frappe import _
+from frappe.utils import flt
+from frappe.model.document import Document
+
+class PutawayRule(Document):
+ def validate(self):
+ self.validate_duplicate_rule()
+ self.validate_warehouse_and_company()
+ self.validate_capacity()
+ self.validate_priority()
+
+ 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):
+ # check if capacity is lesser than current balance in warehouse
+ pass
+
+@frappe.whitelist()
+def get_ordered_putaway_rules(item_code, company, qty):
+ """Returns an ordered list of putaway rules to apply on an item."""
+
+ # get enabled putaway rules for this item code in this company that have pending capacity
+ # order the rules by priority first
+ # if same priority, order by pending capacity (capacity - get how much stock is in the warehouse)
+ # return this list
+ # [{'name': "something", "free space": 20}, {'name': "something", "free space": 10}]
+
+@frappe.whitelist()
+def apply_putaway_rule(items, company):
+ """ Applies Putaway Rule on line items.
+
+ items: List of line items in a Purchase Receipt
+ company: Company in Purchase Receipt
+ """
+ items_not_accomodated = []
+ for item in items:
+ item_qty = item.qty
+ at_capacity, rules = get_ordered_putaway_rules(item.item_code, company, item_qty)
+
+ if not rules:
+ if at_capacity:
+ items_not_accomodated.append([item.item_code, item_qty])
+ continue
+
+ item_row_updated = False
+ for rule in rules:
+ while item_qty > 0:
+ if not item_row_updated:
+ # update pre-existing row
+ item.qty = rule.qty
+ item.warehouse = rule.warehouse
+ item_row_updated = True
+ else:
+ # add rows for split quantity
+ added_row = copy.deepcopy(item)
+ added_row.qty = rule.qty
+ added_row.warehouse = rule.warehouse
+ items.append(added_row)
+
+ item_qty -= flt(rule.qty)
+
+ # if pending qty after applying rules, add row without warehouse
+ if item_qty > 0:
+ added_row = copy.deepcopy(item)
+ added_row.qty = item_qty
+ added_row.warehouse = ''
+ items.append(added_row)
+ items_not_accomodated.append([item.item_code, item_qty])
+
+ # need to check pricing rule, item tax impact
\ No newline at end of file
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule_list.js b/erpnext/stock/doctype/putaway_rule/putaway_rule_list.js
new file mode 100644
index 0000000..bb1654c
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule_list.js
@@ -0,0 +1,10 @@
+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"];
+ };
+ }
+};
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
new file mode 100644
index 0000000..e262217
--- /dev/null
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestPutawayRule(unittest.TestCase):
+ pass