[fixes] tests and moved reorder_item to separate module
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index f9ac629..6ef3c43 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -42,7 +42,7 @@
scheduler_events = {
"daily": [
"erpnext.controllers.recurring_document.create_recurring_documents",
- "erpnext.stock.utils.reorder_item",
+ "erpnext.stock.reorder_item.reorder_item",
"erpnext.setup.doctype.email_digest.email_digest.send",
"erpnext.support.doctype.support_ticket.support_ticket.auto_close_tickets"
],
diff --git a/erpnext/manufacturing/doctype/production_order/test_production_order.py b/erpnext/manufacturing/doctype/production_order/test_production_order.py
index 8a0dbe1..96fe6e4 100644
--- a/erpnext/manufacturing/doctype/production_order/test_production_order.py
+++ b/erpnext/manufacturing/doctype/production_order/test_production_order.py
@@ -20,8 +20,10 @@
pro_doc.submit()
# add raw materials to stores
- test_stock_entry.make_stock_entry("_Test Item", None, "Stores - _TC", 100, 100)
- test_stock_entry.make_stock_entry("_Test Item Home Desktop 100", None, "Stores - _TC", 100, 100)
+ test_stock_entry.make_stock_entry(item_code="_Test Item",
+ target="Stores - _TC", qty=100, incoming_rate=100)
+ test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+ target="Stores - _TC", qty=100, incoming_rate=100)
# from stores to wip
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Material Transfer", 4))
@@ -46,8 +48,10 @@
from erpnext.manufacturing.doctype.production_order.production_order import StockOverProductionError
pro_doc = self.test_planned_qty()
- test_stock_entry.make_stock_entry("_Test Item", None, "_Test Warehouse - _TC", 100, 100)
- test_stock_entry.make_stock_entry("_Test Item Home Desktop 100", None, "_Test Warehouse - _TC", 100, 100)
+ test_stock_entry.make_stock_entry(item_code="_Test Item",
+ target="_Test Warehouse - _TC", qty=100, incoming_rate=100)
+ test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+ target="_Test Warehouse - _TC", qty=100, incoming_rate=100)
s = frappe.get_doc(make_stock_entry(pro_doc.name, "Manufacture", 7))
s.insert()
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index eb0d7c9..7f5271e 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -9,7 +9,7 @@
"income_account": "Sales - _TC",
"inspection_required": "No",
"is_asset_item": "No",
- "is_pro_applicable": "Yes",
+ "is_pro_applicable": "No",
"is_purchase_item": "Yes",
"is_sales_item": "Yes",
"is_service_item": "No",
@@ -20,9 +20,7 @@
"item_name": "_Test Item",
"item_reorder": [
{
- "doctype": "Item Reorder",
"material_request_type": "Purchase",
- "parentfield": "item_reorder",
"warehouse": "_Test Warehouse - _TC",
"warehouse_reorder_level": 20,
"warehouse_reorder_qty": 20
@@ -105,7 +103,7 @@
"item_code": "_Test Item Home Desktop 200",
"item_group": "_Test Item Group Desktops",
"item_name": "_Test Item Home Desktop 200",
- "stock_uom": "_Test UOM"
+ "stock_uom": "_Test UOM 1"
},
{
"description": "_Test Sales BOM Item 5",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 5562e5a..bf7b2eb 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -230,7 +230,7 @@
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
- "serial_no": d.serial_no
+ "serial_no": d.serial_no,
})
# get actual stock at source warehouse
@@ -243,7 +243,7 @@
self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty))
# get incoming rate
- if not d.bom_no:
+ if not d.t_warehouse:
if not flt(d.incoming_rate) or d.s_warehouse or self.purpose == "Sales Return" or force:
incoming_rate = flt(self.get_incoming_rate(args), self.precision("incoming_rate", d))
if incoming_rate > 0:
@@ -253,7 +253,7 @@
raw_material_cost += flt(d.amount)
# set incoming rate for fg item
- if self.purpose in ["Manufacture", "Repack"]:
+ if self.purpose in ("Manufacture", "Repack"):
number_of_fg_items = len([t.t_warehouse for t in self.get("mtn_details") if t.t_warehouse])
for d in self.get("mtn_details"):
if d.bom_no or (d.t_warehouse and number_of_fg_items == 1):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 6d8dc8b..95e4a89 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -10,7 +10,6 @@
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
class TestStockEntry(unittest.TestCase):
-
def tearDown(self):
frappe.set_user("Administrator")
set_perpetual_inventory(0)
@@ -18,60 +17,34 @@
frappe.db.set_default("company", self.old_default_company)
def test_auto_material_request(self):
- frappe.db.sql("""delete from `tabMaterial Request Item`""")
- frappe.db.sql("""delete from `tabMaterial Request`""")
- self._clear_stock_account_balance()
-
- frappe.db.set_value("Stock Settings", None, "auto_indent", 1)
-
- st1 = frappe.copy_doc(test_records[0])
- st1.insert()
- st1.submit()
- st2 = frappe.copy_doc(test_records[1])
- st2.insert()
- st2.submit()
-
- from erpnext.stock.utils import reorder_item
- reorder_item()
-
- mr_name = frappe.db.sql("""select parent from `tabMaterial Request Item`
- where item_code='_Test Item'""")
-
- frappe.db.set_value("Stock Settings", None, "auto_indent", 0)
-
- self.assertTrue(mr_name)
+ self._test_auto_material_request("_Test Item")
def test_auto_material_request_for_variant(self):
- item_code = "_Test Variant Item-S"
+ self._test_auto_material_request("_Test Variant Item-S")
+
+ def _test_auto_material_request(self, item_code):
item = frappe.get_doc("Item", item_code)
- template = frappe.get_doc("Item", item.variant_of)
+
+ if item.variant_of:
+ template = frappe.get_doc("Item", item.variant_of)
+ else:
+ template = item
warehouse = "_Test Warehouse - _TC"
# stock entry reqd for auto-reorder
- se = frappe.new_doc("Stock Entry")
- se.purpose = "Material Receipt"
- se.company = "_Test Company"
- se.append("mtn_details", {
- "item_code": item_code,
- "t_warehouse": "_Test Warehouse - _TC",
- "qty": 1,
- "incoming_rate": 1
- })
- se.insert()
- se.submit()
+ make_stock_entry(item_code=item_code, target="_Test Warehouse 1 - _TC", qty=1)
frappe.db.set_value("Stock Settings", None, "auto_indent", 1)
projected_qty = frappe.db.get_value("Bin", {"item_code": item_code,
"warehouse": warehouse}, "projected_qty") or 0
-
# update re-level qty so that it is more than projected_qty
if projected_qty > template.item_reorder[0].warehouse_reorder_level:
template.item_reorder[0].warehouse_reorder_level += projected_qty
template.save()
- from erpnext.stock.utils import reorder_item
+ from erpnext.stock.reorder_item import reorder_item
mr_list = reorder_item()
frappe.db.set_value("Stock Settings", None, "auto_indent", 0)
@@ -897,6 +870,7 @@
"total_fixed_cost": 1000
})
stock_entry.get_items()
+
fg_rate = [d.amount for d in stock_entry.get("mtn_details") if d.item_code=="_Test FG Item 2"][0]
self.assertEqual(fg_rate, 1200.00)
fg_rate = [d.amount for d in stock_entry.get("mtn_details") if d.item_code=="_Test Item"][0]
@@ -939,21 +913,27 @@
se.submit()
return se
-def make_stock_entry(item, source, target, qty, incoming_rate=None):
+def make_stock_entry(**args):
s = frappe.new_doc("Stock Entry")
- if source and target:
- s.purpose = "Material Transfer"
- elif source:
- s.purpose = "Material Issue"
- else:
- s.purpose = "Material Receipt"
- s.company = "_Test Company"
+ args = frappe._dict(args)
+ if args.posting_date:
+ s.posting_date = args.posting_date
+ if args.posting_time:
+ s.posting_time = args.posting_time
+ if not args.purpose:
+ if args.source and args.target:
+ s.purpose = "Material Transfer"
+ elif args.source:
+ s.purpose = "Material Issue"
+ else:
+ s.purpose = "Material Receipt"
+ s.company = args.company or "_Test Company"
s.append("mtn_details", {
- "item_code": item,
- "s_warehouse": source,
- "t_warehouse": target,
- "qty": qty,
- "incoming_rate": incoming_rate,
+ "item_code": args.item,
+ "s_warehouse": args.from_warehouse or args.source,
+ "t_warehouse": args.to_warehouse or args.target,
+ "qty": args.qty,
+ "incoming_rate": args.incoming_rate,
"conversion_factor": 1.0
})
s.insert()
diff --git a/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py b/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py
index a14c21e..864a2c6 100644
--- a/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py
+++ b/erpnext/stock/doctype/stock_uom_replace_utility/stock_uom_replace_utility.py
@@ -33,7 +33,7 @@
item_doc.stock_uom = self.new_stock_uom
item_doc.save()
- frappe.msgprint(_("Stock UOM updatd for Item {0}").format(self.item_code))
+ frappe.msgprint(_("Stock UOM updated for Item {0}").format(self.item_code))
def update_bin(self):
# update bin
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
new file mode 100644
index 0000000..3afbb4b
--- /dev/null
+++ b/erpnext/stock/reorder_item.py
@@ -0,0 +1,196 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe import _
+from frappe.utils import flt, cstr, nowdate, add_days, cint
+from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
+
+def reorder_item():
+ """ Reorder item if stock reaches reorder level"""
+ # if initial setup not completed, return
+ if not frappe.db.sql("select name from `tabFiscal Year` limit 1"):
+ return
+
+ if getattr(frappe.local, "auto_indent", None) is None:
+ frappe.local.auto_indent = cint(frappe.db.get_value('Stock Settings', None, 'auto_indent'))
+
+ if frappe.local.auto_indent:
+ return _reorder_item()
+
+def _reorder_item():
+ material_requests = {"Purchase": {}, "Transfer": {}}
+
+ item_warehouse_projected_qty = get_item_warehouse_projected_qty()
+
+ warehouse_company = frappe._dict(frappe.db.sql("""select name, company
+ from `tabWarehouse`"""))
+ default_company = (frappe.defaults.get_defaults().get("company") or
+ frappe.db.sql("""select name from tabCompany limit 1""")[0][0])
+
+ def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type):
+ if warehouse not in item_warehouse_projected_qty[item_code]:
+ # likely a disabled warehouse or a warehouse where BIN does not exist
+ return
+
+ reorder_level = flt(reorder_level)
+ reorder_qty = flt(reorder_qty)
+ projected_qty = item_warehouse_projected_qty[item_code][warehouse]
+
+ if reorder_level and projected_qty < reorder_level:
+ deficiency = reorder_level - projected_qty
+ if deficiency > reorder_qty:
+ reorder_qty = deficiency
+
+ company = warehouse_company.get(warehouse) or default_company
+
+ material_requests[material_request_type].setdefault(company, []).append({
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "reorder_qty": reorder_qty
+ })
+
+ for item_code in item_warehouse_projected_qty:
+ item = frappe.get_doc("Item", item_code)
+
+ if item.variant_of and not item.get("item_reorder"):
+ item.update_template_tables()
+
+ if item.get("item_reorder"):
+ for d in item.get("item_reorder"):
+ add_to_material_request(item_code, d.warehouse, d.warehouse_reorder_level,
+ d.warehouse_reorder_qty, d.material_request_type)
+
+ else:
+ # raise for default warehouse
+ add_to_material_request(item_code, item.default_warehouse, item.re_order_level, item.re_order_qty, "Purchase")
+
+ if material_requests:
+ return create_material_request(material_requests)
+
+def get_item_warehouse_projected_qty():
+ item_warehouse_projected_qty = {}
+
+ for item_code, warehouse, projected_qty in frappe.db.sql("""select item_code, warehouse, projected_qty
+ from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != ''
+ and exists (select name from `tabItem`
+ where `tabItem`.name = `tabBin`.item_code and
+ is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and
+ (ifnull(end_of_life, '0000-00-00')='0000-00-00' or end_of_life > %s))
+ and exists (select name from `tabWarehouse`
+ where `tabWarehouse`.name = `tabBin`.warehouse
+ and ifnull(disabled, 0)=0)""", nowdate()):
+
+ item_warehouse_projected_qty.setdefault(item_code, {})[warehouse] = flt(projected_qty)
+
+ return item_warehouse_projected_qty
+
+def create_material_request(material_requests):
+ """ Create indent on reaching reorder level """
+ mr_list = []
+ defaults = frappe.defaults.get_defaults()
+ exceptions_list = []
+
+ def _log_exception():
+ if frappe.local.message_log:
+ exceptions_list.extend(frappe.local.message_log)
+ frappe.local.message_log = []
+ else:
+ exceptions_list.append(frappe.get_traceback())
+
+ try:
+ current_fiscal_year = get_fiscal_year(nowdate())[0] or defaults.fiscal_year
+
+ except FiscalYearError:
+ _log_exception()
+ notify_errors(exceptions_list)
+ return
+
+ for request_type in material_requests:
+ for company in material_requests[request_type]:
+ try:
+ items = material_requests[request_type][company]
+ if not items:
+ continue
+
+ mr = frappe.new_doc("Material Request")
+ mr.update({
+ "company": company,
+ "fiscal_year": current_fiscal_year,
+ "transaction_date": nowdate(),
+ "material_request_type": request_type
+ })
+
+ for d in items:
+ d = frappe._dict(d)
+ item = frappe.get_doc("Item", d.item_code)
+ mr.append("indent_details", {
+ "doctype": "Material Request Item",
+ "item_code": d.item_code,
+ "schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
+ "uom": item.stock_uom,
+ "warehouse": d.warehouse,
+ "item_name": item.item_name,
+ "description": item.description,
+ "item_group": item.item_group,
+ "qty": d.reorder_qty,
+ "brand": item.brand,
+ })
+
+ mr.insert()
+ mr.submit()
+ mr_list.append(mr)
+
+ except:
+ _log_exception()
+
+ if mr_list:
+ if getattr(frappe.local, "reorder_email_notify", None) is None:
+ frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None,
+ 'reorder_email_notify'))
+
+ if(frappe.local.reorder_email_notify):
+ send_email_notification(mr_list)
+
+ if exceptions_list:
+ notify_errors(exceptions_list)
+
+ return mr_list
+
+def send_email_notification(mr_list):
+ """ Notify user about auto creation of indent"""
+
+ email_list = frappe.db.sql_list("""select distinct r.parent
+ from tabUserRole r, tabUser p
+ where p.name = r.parent and p.enabled = 1 and p.docstatus < 2
+ and r.role in ('Purchase Manager','Material Manager')
+ and p.name not in ('Administrator', 'All', 'Guest')""")
+
+ msg="""<h3>Following Material Requests has been raised automatically \
+ based on item reorder level:</h3>"""
+ for mr in mr_list:
+ msg += "<p><b><u>" + mr.name + """</u></b></p><table class='table table-bordered'><tr>
+ <th>Item Code</th><th>Warehouse</th><th>Qty</th><th>UOM</th></tr>"""
+ for item in mr.get("indent_details"):
+ msg += "<tr><td>" + item.item_code + "</td><td>" + item.warehouse + "</td><td>" + \
+ cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>"
+ msg += "</table>"
+ frappe.sendmail(recipients=email_list, subject='Auto Material Request Generation Notification', msg = msg)
+
+def notify_errors(exceptions_list):
+ subject = "[Important] [ERPNext] Auto Reorder Errors"
+ content = """Dear System Manager,
+
+An error occured for certain Items while creating Material Requests based on Re-order level.
+
+Please rectify these issues:
+---
+<pre>
+%s
+</pre>
+---
+Regards,
+Administrator""" % ("\n\n".join(exceptions_list),)
+
+ from frappe.email import sendmail_to_system_managers
+ sendmail_to_system_managers(subject, content)
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 0a4be40..092f580 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -4,24 +4,30 @@
import frappe
from frappe import _
import json
-from frappe.utils import flt, cstr, nowdate, add_days, cint
+from frappe.utils import flt, cstr, nowdate, nowtime
from frappe.defaults import get_global_default
-from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
class InvalidWarehouseCompany(frappe.ValidationError): pass
-def get_stock_balance_on(warehouse, posting_date=None):
+def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
if not posting_date: posting_date = nowdate()
+ values, condition = [posting_date], ""
+
+ if warehouse:
+ values.append(warehouse)
+ condition += " AND warehouse = %s"
+
+ if item_code:
+ values.append(item_code)
+ condition.append(" AND item_code = %s")
+
stock_ledger_entries = frappe.db.sql("""
- SELECT
- item_code, stock_value
- FROM
- `tabStock Ledger Entry`
- WHERE
- warehouse=%s AND posting_date <= %s
+ SELECT item_code, stock_value
+ FROM `tabStock Ledger Entry`
+ WHERE posting_date <= %s {0}
ORDER BY timestamp(posting_date, posting_time) DESC, name DESC
- """, (warehouse, posting_date), as_dict=1)
+ """.format(condition), values, as_dict=1)
sle_map = {}
for sle in stock_ledger_entries:
@@ -29,6 +35,20 @@
return sum(sle_map.values())
+def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None):
+ if not posting_date: posting_date = nowdate()
+ if not posting_time: posting_time = nowtime()
+ last_entry = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry`
+ where item_code=%s and warehouse=%s
+ and timestamp(posting_date, posting_time) < timestamp(%s, %s)
+ order by timestamp(posting_date, posting_time) limit 1""",
+ (item_code, warehouse, posting_date, posting_time))
+
+ if last_entry:
+ return last_entry[0][0]
+ else:
+ return 0.0
+
def get_latest_stock_balance():
bin_map = {}
for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value
@@ -181,192 +201,3 @@
return 0.0
-
-def reorder_item():
- """ Reorder item if stock reaches reorder level"""
- # if initial setup not completed, return
- if not frappe.db.sql("select name from `tabFiscal Year` limit 1"):
- return
-
- if getattr(frappe.local, "auto_indent", None) is None:
- frappe.local.auto_indent = cint(frappe.db.get_value('Stock Settings', None, 'auto_indent'))
-
- if frappe.local.auto_indent:
- return _reorder_item()
-
-def _reorder_item():
- material_requests = {"Purchase": {}, "Transfer": {}}
-
- item_warehouse_projected_qty = get_item_warehouse_projected_qty()
-
- warehouse_company = frappe._dict(frappe.db.sql("""select name, company
- from `tabWarehouse`"""))
- default_company = (frappe.defaults.get_defaults().get("company") or
- frappe.db.sql("""select name from tabCompany limit 1""")[0][0])
-
- def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type):
- if warehouse not in item_warehouse_projected_qty[item_code]:
- # likely a disabled warehouse or a warehouse where BIN does not exist
- return
-
- reorder_level = flt(reorder_level)
- reorder_qty = flt(reorder_qty)
- projected_qty = item_warehouse_projected_qty[item_code][warehouse]
-
- if reorder_level and projected_qty < reorder_level:
- deficiency = reorder_level - projected_qty
- if deficiency > reorder_qty:
- reorder_qty = deficiency
-
- company = warehouse_company.get(warehouse) or default_company
-
- material_requests[material_request_type].setdefault(company, []).append({
- "item_code": item_code,
- "warehouse": warehouse,
- "reorder_qty": reorder_qty
- })
-
- for item_code in item_warehouse_projected_qty:
- item = frappe.get_doc("Item", item_code)
-
- if item.variant_of and not item.get("item_reorder"):
- item.update_template_tables()
-
- if item.get("item_reorder"):
- for d in item.get("item_reorder"):
- add_to_material_request(item_code, d.warehouse, d.warehouse_reorder_level,
- d.warehouse_reorder_qty, d.material_request_type)
-
- else:
- # raise for default warehouse
- add_to_material_request(item_code, item.default_warehouse, item.re_order_level, item.re_order_qty, "Purchase")
-
- if material_requests:
- return create_material_request(material_requests)
-
-def get_item_warehouse_projected_qty():
- item_warehouse_projected_qty = {}
-
- for item_code, warehouse, projected_qty in frappe.db.sql("""select item_code, warehouse, projected_qty
- from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != ''
- and exists (select name from `tabItem`
- where `tabItem`.name = `tabBin`.item_code and
- is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and
- (ifnull(end_of_life, '0000-00-00')='0000-00-00' or end_of_life > %s))
- and exists (select name from `tabWarehouse`
- where `tabWarehouse`.name = `tabBin`.warehouse
- and ifnull(disabled, 0)=0)""", nowdate()):
-
- item_warehouse_projected_qty.setdefault(item_code, {})[warehouse] = flt(projected_qty)
-
- return item_warehouse_projected_qty
-
-def create_material_request(material_requests):
- """ Create indent on reaching reorder level """
- mr_list = []
- defaults = frappe.defaults.get_defaults()
- exceptions_list = []
-
- def _log_exception():
- if frappe.local.message_log:
- exceptions_list.extend(frappe.local.message_log)
- frappe.local.message_log = []
- else:
- exceptions_list.append(frappe.get_traceback())
-
- try:
- current_fiscal_year = get_fiscal_year(nowdate())[0] or defaults.fiscal_year
-
- except FiscalYearError:
- _log_exception()
- notify_errors(exceptions_list)
- return
-
- for request_type in material_requests:
- for company in material_requests[request_type]:
- try:
- items = material_requests[request_type][company]
- if not items:
- continue
-
- mr = frappe.new_doc("Material Request")
- mr.update({
- "company": company,
- "fiscal_year": current_fiscal_year,
- "transaction_date": nowdate(),
- "material_request_type": request_type
- })
-
- for d in items:
- d = frappe._dict(d)
- item = frappe.get_doc("Item", d.item_code)
- mr.append("indent_details", {
- "doctype": "Material Request Item",
- "item_code": d.item_code,
- "schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
- "uom": item.stock_uom,
- "warehouse": d.warehouse,
- "item_name": item.item_name,
- "description": item.description,
- "item_group": item.item_group,
- "qty": d.reorder_qty,
- "brand": item.brand,
- })
-
- mr.insert()
- mr.submit()
- mr_list.append(mr)
-
- except:
- _log_exception()
-
- if mr_list:
- if getattr(frappe.local, "reorder_email_notify", None) is None:
- frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None,
- 'reorder_email_notify'))
-
- if(frappe.local.reorder_email_notify):
- send_email_notification(mr_list)
-
- if exceptions_list:
- notify_errors(exceptions_list)
-
- return mr_list
-
-def send_email_notification(mr_list):
- """ Notify user about auto creation of indent"""
-
- email_list = frappe.db.sql_list("""select distinct r.parent
- from tabUserRole r, tabUser p
- where p.name = r.parent and p.enabled = 1 and p.docstatus < 2
- and r.role in ('Purchase Manager','Material Manager')
- and p.name not in ('Administrator', 'All', 'Guest')""")
-
- msg="""<h3>Following Material Requests has been raised automatically \
- based on item reorder level:</h3>"""
- for mr in mr_list:
- msg += "<p><b><u>" + mr.name + """</u></b></p><table class='table table-bordered'><tr>
- <th>Item Code</th><th>Warehouse</th><th>Qty</th><th>UOM</th></tr>"""
- for item in mr.get("indent_details"):
- msg += "<tr><td>" + item.item_code + "</td><td>" + item.warehouse + "</td><td>" + \
- cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>"
- msg += "</table>"
- frappe.sendmail(recipients=email_list, subject='Auto Material Request Generation Notification', msg = msg)
-
-def notify_errors(exceptions_list):
- subject = "[Important] [ERPNext] Error(s) while creating Material Requests based on Re-order Levels"
- content = """Dear System Manager,
-
-An error occured for certain Items while creating Material Requests based on Re-order level.
-
-Please rectify these issues:
----
-<pre>
-%s
-</pre>
----
-Regards,
-Administrator""" % ("\n\n".join(exceptions_list),)
-
- from frappe.email import sendmail_to_system_managers
- sendmail_to_system_managers(subject, content)