Fixed Stock Entry Test Cases frappe/frappe#478
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index b5d46a2..9a28033 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -19,12 +19,12 @@
class IncorrectValuationRateError(frappe.ValidationError): pass
class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass
-
+
from erpnext.controllers.stock_controller import StockController
class StockEntry(StockController):
- fname = 'mtn_details'
-
+ fname = 'mtn_details'
+
def validate(self):
self.validate_posting_time()
self.validate_purpose()
@@ -44,7 +44,7 @@
self.validate_with_material_request()
self.validate_fiscal_year()
self.set_total_amount()
-
+
def on_submit(self):
self.update_stock_ledger()
@@ -57,32 +57,32 @@
self.update_stock_ledger()
self.update_production_order()
self.make_cancel_gl_entries()
-
+
def validate_fiscal_year(self):
from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.posting_date, self.fiscal_year,
self.meta.get_label("posting_date"))
-
+
def validate_purpose(self):
- valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
+ valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"]
if self.purpose not in valid_purposes:
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
raise_exception=True)
-
+
def validate_item(self):
stock_items = self.get_stock_items()
for item in self.get("mtn_details"):
if item.item_code not in stock_items:
msgprint(_("""Only Stock Items are allowed for Stock Entry"""),
raise_exception=True)
-
+
def validate_warehouse(self, pro_obj):
"""perform various (sometimes conditional) validations on warehouse"""
-
+
source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return"]
target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return"]
-
+
validate_for_manufacture_repack = any([d.bom_no for d in self.get("mtn_details")])
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
@@ -101,11 +101,11 @@
if not (d.s_warehouse or d.t_warehouse):
msgprint(_("Atleast one warehouse is mandatory"), raise_exception=1)
-
+
if self.purpose in source_mandatory and not d.s_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1)
-
+
if self.purpose in target_mandatory and not d.t_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1)
@@ -114,39 +114,39 @@
if validate_for_manufacture_repack:
if d.bom_no:
d.s_warehouse = None
-
+
if not d.t_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1)
-
+
elif pro_obj and cstr(d.t_warehouse) != pro_obj.fg_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Target Warehouse") + _(" should be same as that in ")
+ _("Production Order"), raise_exception=1)
-
+
else:
d.t_warehouse = None
if not d.s_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1)
-
+
if cstr(d.s_warehouse) == cstr(d.t_warehouse):
- msgprint(_("Source and Target Warehouse cannot be same"),
+ msgprint(_("Source and Target Warehouse cannot be same"),
raise_exception=1)
-
+
def validate_production_order(self, pro_obj=None):
if not pro_obj:
if self.production_order:
pro_obj = frappe.get_doc('Production Order', self.production_order)
else:
return
-
+
if self.purpose == "Manufacture/Repack":
# check for double entry
self.check_duplicate_entry_for_production_order()
elif self.purpose != "Material Transfer":
self.production_order = None
-
+
def check_duplicate_entry_for_production_order(self):
other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", {
"production_order": self.production_order,
@@ -154,24 +154,24 @@
"docstatus": ["!=", 2],
"name": ["!=", self.name]
}, "name")]
-
+
if other_ste:
- production_item, qty = frappe.db.get_value("Production Order",
+ production_item, qty = frappe.db.get_value("Production Order",
self.production_order, ["production_item", "qty"])
args = other_ste + [production_item]
fg_qty_already_entered = frappe.db.sql("""select sum(actual_qty)
- from `tabStock Entry Detail`
- where parent in (%s)
- and item_code = %s
+ from `tabStock Entry Detail`
+ where parent in (%s)
+ and item_code = %s
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
-
+
if fg_qty_already_entered >= qty:
- frappe.throw(_("Stock Entries already created for Production Order ")
+ frappe.throw(_("Stock Entries already created for Production Order ")
+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
def set_total_amount(self):
self.total_amount = sum([flt(item.amount) for item in self.get("mtn_details")])
-
+
def get_stock_and_rate(self):
"""get stock and incoming rate on posting date"""
for d in self.get('mtn_details'):
@@ -186,21 +186,21 @@
})
# get actual stock at source warehouse
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
-
+
# get incoming rate
if not flt(d.incoming_rate):
d.incoming_rate = self.get_incoming_rate(args)
-
+
d.amount = flt(d.transfer_qty) * flt(d.incoming_rate)
-
+
def get_incoming_rate(self, args):
incoming_rate = 0
if self.purpose == "Sales Return" and \
(self.delivery_note_no or self.sales_invoice_no):
- sle = frappe.db.sql("""select name, posting_date, posting_time,
- actual_qty, stock_value, warehouse from `tabStock Ledger Entry`
- where voucher_type = %s and voucher_no = %s and
- item_code = %s limit 1""",
+ sle = frappe.db.sql("""select name, posting_date, posting_time,
+ actual_qty, stock_value, warehouse from `tabStock Ledger Entry`
+ where voucher_type = %s and voucher_no = %s and
+ item_code = %s limit 1""",
((self.delivery_note_no and "Delivery Note" or "Sales Invoice"),
self.delivery_note_no or self.sales_invoice_no, args.item_code), as_dict=1)
if sle:
@@ -215,14 +215,14 @@
flt(sle[0].actual_qty)
else:
incoming_rate = get_incoming_rate(args)
-
+
return incoming_rate
-
+
def validate_incoming_rate(self):
for d in self.get('mtn_details'):
if d.t_warehouse:
self.validate_value("incoming_rate", ">", 0, d, raise_exception=IncorrectValuationRateError)
-
+
def validate_bom(self):
for d in self.get('mtn_details'):
if d.bom_no and not frappe.db.sql("""select name from `tabBOM`
@@ -231,72 +231,72 @@
msgprint(_("Item") + " %s: " % cstr(d.item_code)
+ _("does not belong to BOM: ") + cstr(d.bom_no)
+ _(" or the BOM is cancelled or inactive"), raise_exception=1)
-
+
def validate_finished_goods(self):
"""validation: finished good quantity should be same as manufacturing quantity"""
for d in self.get('mtn_details'):
if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty):
- msgprint(_("Row #") + " %s: " % d.idx
+ msgprint(_("Row #") + " %s: " % d.idx
+ _("Quantity should be equal to Manufacturing Quantity. To fetch items again, click on 'Get Items' button or update the Quantity manually."), raise_exception=1)
-
+
def validate_return_reference_doc(self):
"""validate item with reference doc"""
ref = get_return_doc_and_details(self)
-
+
if ref.doc:
# validate docstatus
if ref.doc.docstatus != 1:
- frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '
+ frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '
+ _("Status should be Submitted"), raise_exception=frappe.InvalidStatusError)
-
+
# update stock check
if ref.doc.doctype == "Sales Invoice" and cint(ref.doc.update_stock) != 1:
- frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '
- + _("Update Stock should be checked."),
+ frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '
+ + _("Update Stock should be checked."),
raise_exception=NotUpdateStockError)
-
+
# posting date check
- ref_posting_datetime = "%s %s" % (cstr(ref.doc.posting_date),
+ ref_posting_datetime = "%s %s" % (cstr(ref.doc.posting_date),
cstr(ref.doc.posting_time) or "00:00:00")
- this_posting_datetime = "%s %s" % (cstr(self.posting_date),
+ this_posting_datetime = "%s %s" % (cstr(self.posting_date),
cstr(self.posting_time))
if this_posting_datetime < ref_posting_datetime:
from frappe.utils.dateutils import datetime_in_user_format
frappe.msgprint(_("Posting Date Time cannot be before")
+ ": " + datetime_in_user_format(ref_posting_datetime),
raise_exception=True)
-
+
stock_items = get_stock_items_for_return(ref.doc, ref.parentfields)
already_returned_item_qty = self.get_already_returned_item_qty(ref.fieldname)
-
+
for item in self.get("mtn_details"):
# validate if item exists in the ref doc and that it is a stock item
if item.item_code not in stock_items:
msgprint(_("Item") + ': "' + item.item_code + _("\" does not exist in ") +
- ref.doc.doctype + ": " + ref.doc.name,
+ ref.doc.doctype + ": " + ref.doc.name,
raise_exception=frappe.DoesNotExistError)
-
+
# validate quantity <= ref item's qty - qty already returned
ref_item = ref.doc.getone({"item_code": item.item_code})
returnable_qty = ref_item.qty - flt(already_returned_item_qty.get(item.item_code))
if not returnable_qty:
frappe.throw("{item}: {item_code} {returned}".format(
- item=_("Item"), item_code=item.item_code,
- returned=_("already returned though some other documents")),
+ item=_("Item"), item_code=item.item_code,
+ returned=_("already returned though some other documents")),
StockOverReturnError)
elif item.transfer_qty > returnable_qty:
frappe.throw("{item}: {item_code}, {returned}: {qty}".format(
item=_("Item"), item_code=item.item_code,
returned=_("Max Returnable Qty"), qty=returnable_qty), StockOverReturnError)
-
+
def get_already_returned_item_qty(self, ref_fieldname):
return dict(frappe.db.sql("""select item_code, sum(transfer_qty) as qty
from `tabStock Entry Detail` where parent in (
select name from `tabStock Entry` where `%s`=%s and docstatus=1)
group by item_code""" % (ref_fieldname, "%s"), (self.get(ref_fieldname),)))
-
+
def update_stock_ledger(self):
- sl_entries = []
+ sl_entries = []
for d in self.get('mtn_details'):
if cstr(d.s_warehouse) and self.docstatus == 1:
sl_entries.append(self.get_sl_entries(d, {
@@ -304,57 +304,57 @@
"actual_qty": -flt(d.transfer_qty),
"incoming_rate": 0
}))
-
+
if cstr(d.t_warehouse):
sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.t_warehouse),
"actual_qty": flt(d.transfer_qty),
"incoming_rate": flt(d.incoming_rate)
}))
-
- # On cancellation, make stock ledger entry for
+
+ # On cancellation, make stock ledger entry for
# target warehouse first, to update serial no values properly
-
+
if cstr(d.s_warehouse) and self.docstatus == 2:
sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.s_warehouse),
"actual_qty": -flt(d.transfer_qty),
"incoming_rate": 0
}))
-
+
self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No')
def update_production_order(self):
def _validate_production_order(pro_doc):
if flt(pro_doc.docstatus) != 1:
- frappe.throw(_("Production Order must be submitted") + ": " +
+ frappe.throw(_("Production Order must be submitted") + ": " +
self.production_order)
-
+
if pro_doc.status == 'Stopped':
- msgprint(_("Transaction not allowed against stopped Production Order") + ": " +
+ msgprint(_("Transaction not allowed against stopped Production Order") + ": " +
self.production_order)
-
+
if self.production_order:
pro_doc = frappe.get_doc("Production Order", self.production_order)
_validate_production_order(pro_doc)
self.update_produced_qty(pro_doc)
if self.purpose == "Manufacture/Repack":
self.update_planned_qty(pro_doc)
-
+
def update_produced_qty(self, pro_doc):
if self.purpose == "Manufacture/Repack":
produced_qty = flt(pro_doc.produced_qty) + \
(self.docstatus==1 and 1 or -1 ) * flt(self.fg_completed_qty)
-
+
if produced_qty > flt(pro_doc.qty):
frappe.throw(_("Production Order") + ": " + self.production_order + "\n" +
- _("Total Manufactured Qty can not be greater than Planned qty to manufacture")
+ _("Total Manufactured Qty can not be greater than Planned qty to manufacture")
+ "(%s/%s)" % (produced_qty, flt(pro_doc.qty)), StockOverProductionError)
-
+
status = 'Completed' if flt(produced_qty) >= flt(pro_doc.qty) else 'In Process'
- frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s
+ frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s
where name=%s""", (status, produced_qty, self.production_order))
-
+
def update_planned_qty(self, pro_doc):
from erpnext.stock.utils import update_bin
update_bin({
@@ -363,16 +363,16 @@
"posting_date": self.posting_date,
"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty)
})
-
+
def get_item_details(self, arg):
arg = json.loads(arg)
- item = frappe.db.sql("""select stock_uom, description, item_name,
- expense_account, buying_cost_center from `tabItem`
- where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'
+ item = frappe.db.sql("""select stock_uom, description, item_name,
+ expense_account, buying_cost_center from `tabItem`
+ where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'
or end_of_life > now())""", (arg.get('item_code')), as_dict = 1)
- if not item:
+ if not item:
msgprint("Item is not active", raise_exception=1)
-
+
ret = {
'uom' : item and item[0]['stock_uom'] or '',
'stock_uom' : item and item[0]['stock_uom'] or '',
@@ -394,7 +394,7 @@
def get_uom_details(self, arg = ''):
arg, ret = eval(arg), {}
- uom = frappe.db.sql("""select conversion_factor from `tabUOM Conversion Detail`
+ uom = frappe.db.sql("""select conversion_factor from `tabUOM Conversion Detail`
where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1)
if not uom or not flt(uom[0].conversion_factor):
msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'],
@@ -406,7 +406,7 @@
'transfer_qty' : flt(arg['qty']) * flt(uom[0]['conversion_factor']),
}
return ret
-
+
def get_warehouse_details(self, args):
args = json.loads(args)
ret = {}
@@ -416,14 +416,14 @@
"posting_time": self.posting_time,
})
args = frappe._dict(args)
-
+
ret = {
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
"incoming_rate" : self.get_incoming_rate(args)
}
return ret
-
- def get_items(self):
+
+ def get_items(self):
pro_obj = None
if self.production_order:
# common validations
@@ -434,7 +434,7 @@
else:
# invalid production order
self.production_order = None
-
+
if self.bom_no:
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack",
"Subcontract"]:
@@ -451,10 +451,10 @@
# add raw materials to Stock Entry Detail table
idx = self.add_to_stock_entry_detail(item_dict)
-
+
# add finished good item to Stock Entry Detail table -- along with bom_no
if self.production_order and self.purpose == "Manufacture/Repack":
- item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name",
+ item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name",
"description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1)
self.add_to_stock_entry_detail({
cstr(pro_obj.production_item): {
@@ -468,14 +468,14 @@
"cost_center": item.buying_cost_center,
}
}, bom_no=pro_obj.bom_no, idx=idx)
-
+
elif self.purpose in ["Material Receipt", "Manufacture/Repack"]:
if self.purpose=="Material Receipt":
self.from_warehouse = ""
-
- item = frappe.db.sql("""select name, item_name, description,
- stock_uom, expense_account, buying_cost_center from `tabItem`
- where name=(select item from tabBOM where name=%s)""",
+
+ item = frappe.db.sql("""select name, item_name, description,
+ stock_uom, expense_account, buying_cost_center from `tabItem`
+ where name=(select item from tabBOM where name=%s)""",
self.bom_no, as_dict=1)
self.add_to_stock_entry_detail({
item[0]["name"] : {
@@ -488,20 +488,20 @@
"cost_center": item[0].buying_cost_center,
}
}, bom_no=self.bom_no, idx=idx)
-
+
self.get_stock_and_rate()
-
+
def get_bom_raw_materials(self, qty):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
-
+
# item dict = { item_code: {qty, description, stock_uom} }
item_dict = get_bom_items_as_dict(self.bom_no, qty=qty, fetch_exploded = self.use_multi_level_bom)
-
+
for item in item_dict.values():
item.from_warehouse = item.default_warehouse
-
+
return item_dict
-
+
def get_pending_raw_materials(self, pro_obj):
"""
issue (item quantity) that is pending to issue or desire to transfer,
@@ -509,10 +509,10 @@
"""
item_dict = self.get_bom_raw_materials(1)
issued_item_qty = self.get_issued_qty()
-
+
max_qty = flt(pro_obj.qty)
only_pending_fetched = []
-
+
for item in item_dict:
pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0)
desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"]
@@ -522,17 +522,17 @@
item_dict[item]["qty"] = pending_to_issue
if pending_to_issue:
only_pending_fetched.append(item)
-
+
# delete items with 0 qty
for item in item_dict.keys():
if not item_dict[item]["qty"]:
del item_dict[item]
-
+
# show some message
if not len(item_dict):
frappe.msgprint(_("""All items have already been transferred \
for this Production Order."""))
-
+
elif only_pending_fetched:
frappe.msgprint(_("""Only quantities pending to be transferred \
were fetched for the following items:\n""" + "\n".join(only_pending_fetched)))
@@ -548,7 +548,7 @@
group by t1.item_code""", self.production_order)
for t in result:
issued_item_qty[t[0]] = flt(t[1])
-
+
return issued_item_qty
def add_to_stock_entry_detail(self, item_dict, bom_no=None, idx=None):
@@ -569,79 +569,79 @@
se_child.qty = flt(item_dict[d]["qty"])
se_child.expense_account = item_dict[d]["expense_account"] or expense_account
se_child.cost_center = item_dict[d]["cost_center"] or cost_center
-
+
# in stock uom
se_child.transfer_qty = flt(item_dict[d]["qty"])
se_child.conversion_factor = 1.00
-
+
# to be assigned for finished item
se_child.bom_no = bom_no
# increment idx by 1
idx += 1
return idx
-
+
def validate_with_material_request(self):
for item in self.get("mtn_details"):
if item.material_request:
- mreq_item = frappe.db.get_value("Material Request Item",
+ mreq_item = frappe.db.get_value("Material Request Item",
{"name": item.material_request_item, "parent": item.material_request},
["item_code", "warehouse", "idx"], as_dict=True)
if mreq_item.item_code != item.item_code or mreq_item.warehouse != item.t_warehouse:
msgprint(_("Row #") + (" %d: " % item.idx) + _("does not match")
+ " " + _("Row #") + (" %d %s " % (mreq_item.idx, _("of")))
- + _("Material Request") + (" - %s" % item.material_request),
+ + _("Material Request") + (" - %s" % item.material_request),
raise_exception=frappe.MappingMismatchError)
-
-@frappe.whitelist()
+
+@frappe.whitelist()
def get_party_details(ref_dt, ref_dn):
if ref_dt in ["Delivery Note", "Sales Invoice"]:
- res = frappe.db.get_value(ref_dt, ref_dn,
+ res = frappe.db.get_value(ref_dt, ref_dn,
["customer", "customer_name", "address_display as customer_address"], as_dict=1)
else:
- res = frappe.db.get_value(ref_dt, ref_dn,
+ res = frappe.db.get_value(ref_dt, ref_dn,
["supplier", "supplier_name", "address_display as supplier_address"], as_dict=1)
return res or {}
-
+
@frappe.whitelist()
def get_production_order_details(production_order):
- result = frappe.db.sql("""select bom_no,
- ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom,
+ result = frappe.db.sql("""select bom_no,
+ ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom,
wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1)
return result and result[0] or {}
-
+
def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if doctype == "Sales Invoice":
conditions = "and update_stock=1"
-
+
return frappe.db.sql("""select name, customer, customer_name
from `tab%s` where docstatus = 1
- and (`%s` like %%(txt)s
+ and (`%s` like %%(txt)s
or `customer` like %%(txt)s) %s %s
order by name, customer, customer_name
- limit %s""" % (doctype, searchfield, conditions,
- get_match_cond(doctype), "%(start)s, %(page_len)s"),
- {"txt": "%%%s%%" % txt, "start": start, "page_len": page_len},
+ limit %s""" % (doctype, searchfield, conditions,
+ get_match_cond(doctype), "%(start)s, %(page_len)s"),
+ {"txt": "%%%s%%" % txt, "start": start, "page_len": page_len},
as_list=True)
-
+
def query_purchase_return_doc(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name, supplier, supplier_name
from `tab%s` where docstatus = 1
- and (`%s` like %%(txt)s
+ and (`%s` like %%(txt)s
or `supplier` like %%(txt)s) %s
order by name, supplier, supplier_name
- limit %s""" % (doctype, searchfield, get_match_cond(doctype),
- "%(start)s, %(page_len)s"), {"txt": "%%%s%%" % txt, "start":
+ limit %s""" % (doctype, searchfield, get_match_cond(doctype),
+ "%(start)s, %(page_len)s"), {"txt": "%%%s%%" % txt, "start":
start, "page_len": page_len}, as_list=True)
-
+
def query_return_item(doctype, txt, searchfield, start, page_len, filters):
txt = txt.replace("%", "")
ref = get_return_doc_and_details(filters)
-
+
stock_items = get_stock_items_for_return(ref.doc, ref.parentfields)
-
+
result = []
for item in ref.doc.get_all_children():
if getattr(item, "item_code", None) in stock_items:
@@ -649,8 +649,8 @@
item.description = cstr(item.description)
if (txt in item.item_code) or (txt in item.item_name) or (txt in item.description):
val = [
- item.item_code,
- (len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name,
+ item.item_code,
+ (len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name,
(len(item.description) > 40) and (item.description[:40] + "...") or \
item.description
]
@@ -662,45 +662,45 @@
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("posting_date"):
filters["posting_date"] = nowdate()
-
+
batch_nos = None
args = {
- 'item_code': filters['item_code'],
- 's_warehouse': filters['s_warehouse'],
- 'posting_date': filters['posting_date'],
- 'txt': "%%%s%%" % txt,
- 'mcond':get_match_cond(doctype),
- "start": start,
+ 'item_code': filters['item_code'],
+ 's_warehouse': filters['s_warehouse'],
+ 'posting_date': filters['posting_date'],
+ 'txt': "%%%s%%" % txt,
+ 'mcond':get_match_cond(doctype),
+ "start": start,
"page_len": page_len
}
-
+
if filters.get("s_warehouse"):
- batch_nos = frappe.db.sql("""select batch_no
- from `tabStock Ledger Entry` sle
- where item_code = '%(item_code)s'
+ batch_nos = frappe.db.sql("""select batch_no
+ from `tabStock Ledger Entry` sle
+ where item_code = '%(item_code)s'
and warehouse = '%(s_warehouse)s'
- and batch_no like '%(txt)s'
- and exists(select * from `tabBatch`
- where name = sle.batch_no
- and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
+ and batch_no like '%(txt)s'
+ and exists(select * from `tabBatch`
+ where name = sle.batch_no
+ and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
or expiry_date = '')
- and docstatus != 2)
+ and docstatus != 2)
%(mcond)s
- group by batch_no having sum(actual_qty) > 0
- order by batch_no desc
- limit %(start)s, %(page_len)s """
+ group by batch_no having sum(actual_qty) > 0
+ order by batch_no desc
+ limit %(start)s, %(page_len)s """
% args)
-
+
if batch_nos:
return batch_nos
else:
- return frappe.db.sql("""select name from `tabBatch`
+ return frappe.db.sql("""select name from `tabBatch`
where item = '%(item_code)s'
and docstatus < 2
- and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
+ and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
or expiry_date = '' or expiry_date = "0000-00-00")
%(mcond)s
- order by name desc
+ order by name desc
limit %(start)s, %(page_len)s
""" % args)
@@ -708,19 +708,19 @@
"""return item codes filtered from doc, which are stock items"""
if isinstance(parentfields, basestring):
parentfields = [parentfields]
-
- all_items = list(set([d.item_code for d in
+
+ all_items = list(set([d.item_code for d in
ref_doc.get_all_children() if d.get("item_code")]))
stock_items = frappe.db.sql_list("""select name from `tabItem`
where is_stock_item='Yes' and name in (%s)""" % (", ".join(["%s"] * len(all_items))),
tuple(all_items))
return stock_items
-
+
def get_return_doc_and_details(args):
ref = frappe._dict()
-
- # get ref_doc
+
+ # get ref_doc
if args.get("purpose") in return_map:
for fieldname, val in return_map[args.get("purpose")].items():
if args.get(fieldname):
@@ -728,9 +728,9 @@
ref.doc = frappe.get_doc(val[0], args.get(fieldname))
ref.parentfields = val[1]
break
-
+
return ref
-
+
return_map = {
"Sales Return": {
# [Ref DocType, [Item tables' parentfields]]
@@ -747,29 +747,28 @@
se = frappe.get_doc("Stock Entry", stock_entry)
if not se.purpose in ["Sales Return", "Purchase Return"]:
return
-
+
ref = get_return_doc_and_details(se)
-
+
if ref.doc.doctype == "Delivery Note":
result = make_return_jv_from_delivery_note(se, ref)
elif ref.doc.doctype == "Sales Invoice":
result = make_return_jv_from_sales_invoice(se, ref)
elif ref.doc.doctype == "Purchase Receipt":
result = make_return_jv_from_purchase_receipt(se, ref)
-
+
# create jv doc and fetch balance for each unique row item
- jv_list = [{
- "__islocal": 1,
- "doctype": "Journal Voucher",
+ jv = frappe.new_doc("Journal Voucher")
+ jv.update({
"posting_date": se.posting_date,
"voucher_type": se.purpose == "Sales Return" and "Credit Note" or "Debit Note",
"fiscal_year": se.fiscal_year,
"company": se.company
- }]
-
+ })
+
from erpnext.accounts.utils import get_balance_on
for r in result:
- jv_list.append({
+ jv.append("entries", {
"__islocal": 1,
"doctype": "Journal Voucher Detail",
"parentfield": "entries",
@@ -779,139 +778,140 @@
"balance": get_balance_on(r.get("account"), se.posting_date) \
if r.get("account") else 0
})
-
- return jv_list
-
+
+ return jv
+
def make_return_jv_from_sales_invoice(se, ref):
# customer account entry
parent = {
"account": ref.doc.debit_to,
"against_invoice": ref.doc.name,
}
-
+
# income account entries
children = []
for se_item in se.get("mtn_details"):
# find item in ref.doc
ref_item = ref.doc.get({"item_code": se_item.item_code})[0]
-
+
account = get_sales_account_from_item(ref.doc, ref_item)
-
+
if account not in children:
children.append(account)
-
+
return [parent] + [{"account": account} for account in children]
-
+
def get_sales_account_from_item(doc, ref_item):
account = None
- if not ref_item.income_account:
+ if not getattr(ref_item, "income_account", None):
if ref_item.parent_item:
- parent_item = doc.get({"item_code": ref_item.parent_item})[0]
+ parent_item = doc.get(doc.fname, {"item_code": ref_item.parent_item})[0]
account = parent_item.income_account
else:
account = ref_item.income_account
-
+
return account
-
+
def make_return_jv_from_delivery_note(se, ref):
invoices_against_delivery = get_invoice_list("Sales Invoice Item", "delivery_note",
ref.doc.name)
-
+
if not invoices_against_delivery:
- sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if d.against_sales_order]
-
+ sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if getattr(d, "against_sales_order", None)]
+
if sales_orders_against_delivery:
invoices_against_delivery = get_invoice_list("Sales Invoice Item", "sales_order",
sales_orders_against_delivery)
-
+
if not invoices_against_delivery:
return []
-
+
packing_item_parent_map = dict([[d.item_code, d.parent_item] for d in ref.doc.get(ref.parentfields[1])])
-
+
parent = {}
children = []
-
+
for se_item in se.get("mtn_details"):
for sales_invoice in invoices_against_delivery:
si = frappe.get_doc("Sales Invoice", sales_invoice)
-
+
if se_item.item_code in packing_item_parent_map:
ref_item = si.get({"item_code": packing_item_parent_map[se_item.item_code]})
else:
ref_item = si.get({"item_code": se_item.item_code})
-
+
if not ref_item:
continue
-
+
ref_item = ref_item[0]
-
+
account = get_sales_account_from_item(si, ref_item)
-
+
if account not in children:
children.append(account)
-
+
if not parent:
parent = {"account": si.debit_to}
break
-
+
if len(invoices_against_delivery) == 1:
parent["against_invoice"] = invoices_against_delivery[0]
-
+
result = [parent] + [{"account": account} for account in children]
-
+
return result
-
+
def get_invoice_list(doctype, link_field, value):
if isinstance(value, basestring):
value = [value]
-
+
return frappe.db.sql_list("""select distinct parent from `tab%s`
where docstatus = 1 and `%s` in (%s)""" % (doctype, link_field,
", ".join(["%s"]*len(value))), tuple(value))
-
+
def make_return_jv_from_purchase_receipt(se, ref):
invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_receipt",
ref.doc.name)
-
+
if not invoice_against_receipt:
- purchase_orders_against_receipt = [d.prevdoc_docname for d in
- ref.get({"prevdoc_doctype": "Purchase Order"}) if d.prevdoc_docname]
-
+ purchase_orders_against_receipt = [d.prevdoc_docname for d in
+ ref.doc.get(ref.doc.fname, {"prevdoc_doctype": "Purchase Order"})
+ if getattr(d, "prevdoc_docname", None)]
+
if purchase_orders_against_receipt:
invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_order",
purchase_orders_against_receipt)
-
+
if not invoice_against_receipt:
return []
-
+
parent = {}
children = []
-
+
for se_item in se.get("mtn_details"):
for purchase_invoice in invoice_against_receipt:
pi = frappe.get_doc("Purchase Invoice", purchase_invoice)
ref_item = pi.get({"item_code": se_item.item_code})
-
+
if not ref_item:
continue
-
+
ref_item = ref_item[0]
-
+
account = ref_item.expense_account
-
+
if account not in children:
children.append(account)
-
+
if not parent:
parent = {"account": pi.credit_to}
break
-
+
if len(invoice_against_receipt) == 1:
parent["against_voucher"] = invoice_against_receipt[0]
-
+
result = [parent] + [{"account": account} for account in children]
-
- return result
\ No newline at end of file
+
+ return result
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 0aa0433..f8cdeb7 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -3,6 +3,7 @@
from __future__ import unicode_literals
import frappe, unittest
+import frappe.defaults
from frappe.utils import flt, getdate
from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
@@ -31,7 +32,6 @@
st2.submit()
from erpnext.stock.utils import reorder_item
-
reorder_item()
mr_name = frappe.db.sql("""select parent from `tabMaterial Request Item`
@@ -39,8 +39,6 @@
self.assertTrue(mr_name)
- frappe.db.set_default("company", self.old_default_company)
-
def test_material_receipt_gl_entry(self):
self._clear_stock_account_balance()
set_perpetual_inventory()
@@ -367,14 +365,14 @@
def _test_sales_return_jv(self, se):
from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv
- jv_list = make_return_jv(se.name)
+ jv = make_return_jv(se.name)
- self.assertEqual(len(jv_list), 3)
- self.assertEqual(jv_list[0].get("voucher_type"), "Credit Note")
- self.assertEqual(jv_list[0].get("posting_date"), se.posting_date)
- self.assertEqual(jv_list[1].get("account"), "_Test Customer - _TC")
- self.assertEqual(jv_list[2].get("account"), "Sales - _TC")
- self.assertTrue(jv_list[1].get("against_invoice"))
+ self.assertEqual(len(jv.get("entries")), 2)
+ self.assertEqual(jv.get("voucher_type"), "Credit Note")
+ self.assertEqual(jv.get("posting_date"), se.posting_date)
+ self.assertEqual(jv.get("entries")[0].get("account"), "_Test Customer - _TC")
+ self.assertEqual(jv.get("entries")[1].get("account"), "Sales - _TC")
+ self.assertTrue(jv.get("entries")[0].get("against_invoice"))
def test_make_return_jv_for_sales_invoice_non_packing_item(self):
self._clear_stock_account_balance()
@@ -527,14 +525,14 @@
def _test_purchase_return_jv(self, se):
from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv
- jv_list = make_return_jv(se.name)
+ jv = make_return_jv(se.name)
- self.assertEqual(len(jv_list), 3)
- self.assertEqual(jv_list[0].get("voucher_type"), "Debit Note")
- self.assertEqual(jv_list[0].get("posting_date"), se.posting_date)
- self.assertEqual(jv_list[1].get("account"), "_Test Supplier - _TC")
- self.assertEqual(jv_list[2].get("account"), "_Test Account Cost for Goods Sold - _TC")
- self.assertTrue(jv_list[1].get("against_voucher"))
+ self.assertEqual(len(jv.get("entries")), 2)
+ self.assertEqual(jv.get("voucher_type"), "Debit Note")
+ self.assertEqual(jv.get("posting_date"), se.posting_date)
+ self.assertEqual(jv.get("entries")[0].get("account"), "_Test Supplier - _TC")
+ self.assertEqual(jv.get("entries")[1].get("account"), "_Test Account Cost for Goods Sold - _TC")
+ self.assertTrue(jv.get("entries")[0].get("against_voucher"))
def test_make_return_jv_for_purchase_receipt(self):
self._clear_stock_account_balance()
@@ -774,10 +772,9 @@
# permission tests
def test_warehouse_user(self):
- import frappe.defaults
set_perpetual_inventory(0)
- frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", "Restriction")
+ frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com", "Restriction")
frappe.defaults.add_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", "Restriction")
frappe.get_doc("User", "test@example.com")\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
@@ -797,15 +794,17 @@
st1.insert()
st1.submit()
- frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", parenttype="Restriction")
- frappe.defaults.clear_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", parenttype="Restriction")
+ frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC",
+ "test@example.com", parenttype="Restriction")
+ frappe.defaults.clear_default("Warehouse", "_Test Warehouse 2 - _TC1",
+ "test2@example.com", parenttype="Restriction")
def test_freeze_stocks (self):
self._clear_stock_account_balance()
frappe.db.set_value('Stock Settings', None,'stock_auth_role', '')
# test freeze_stocks_upto
- date_newer_than_test_records = add_days(getdate(test_records[0][0]['posting_date']), 5)
+ date_newer_than_test_records = add_days(getdate(test_records[0]['posting_date']), 5)
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", date_newer_than_test_records)
se = frappe.copy_doc(test_records[0]).insert()
self.assertRaises (StockFreezeError, se.submit)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 17d4282..17d683f 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -19,15 +19,15 @@
self.validate_item()
validate_warehouse_company(self.warehouse, self.company)
self.scrub_posting_time()
-
+
from erpnext.accounts.utils import validate_fiscal_year
- validate_fiscal_year(self.posting_date, self.fiscal_year,
+ validate_fiscal_year(self.posting_date, self.fiscal_year,
self.meta.get_label("posting_date"))
def on_submit(self):
self.check_stock_frozen_date()
self.actual_amt_check()
-
+
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
process_serial_no(self)
@@ -58,7 +58,7 @@
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
elif k == 'warehouse':
if not frappe.db.exists("Warehouse", self.get(k)):
- msgprint("Warehouse: '%s' does not exist in the system. Please check." %
+ msgprint("Warehouse: '%s' does not exist in the system. Please check." %
self.get(k), raise_exception = 1)
def validate_item(self):
@@ -76,9 +76,9 @@
frappe.throw("Batch number is mandatory for Item '%s'" % self.item_code)
# check if batch belongs to item
- if not frappe.db.get_value("Batch",
+ if not frappe.db.get_value("Batch",
{"item": self.item_code, "name": self.batch_no}):
- frappe.throw("'%s' is not a valid Batch Number for Item '%s'" %
+ frappe.throw("'%s' is not a valid Batch Number for Item '%s'" %
(self.batch_no, self.item_code))
if not self.stock_uom:
@@ -108,4 +108,4 @@
where Key_name="posting_sort_index" """):
frappe.db.commit()
frappe.db.sql("""alter table `tabStock Ledger Entry`
- add index posting_sort_index(posting_date, posting_time, name)""")
\ No newline at end of file
+ add index posting_sort_index(posting_date, posting_time, name)""")
diff --git a/erpnext/stock/doctype/warehouse/test_records.json b/erpnext/stock/doctype/warehouse/test_records.json
index 05d30d6..e0941af 100644
--- a/erpnext/stock/doctype/warehouse/test_records.json
+++ b/erpnext/stock/doctype/warehouse/test_records.json
@@ -1,25 +1,25 @@
[
{
- "company": "_Test Company",
- "create_account_under": "Stock Assets - _TC",
- "doctype": "Warehouse",
+ "company": "_Test Company",
+ "create_account_under": "Stock Assets - _TC",
+ "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse"
- },
+ },
{
- "company": "_Test Company",
- "create_account_under": "Fixed Assets - _TC",
- "doctype": "Warehouse",
+ "company": "_Test Company",
+ "create_account_under": "Fixed Assets - _TC",
+ "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse 1"
- },
+ },
{
- "company": "_Test Company 1",
- "create_account_under": "Stock Assets - _TC",
- "doctype": "Warehouse",
+ "company": "_Test Company 1",
+ "create_account_under": "Stock Assets - _TC",
+ "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse 2"
- },
+ },
{
- "company": "_Test Company",
- "doctype": "Warehouse",
+ "company": "_Test Company",
+ "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse No Account"
}
-]
\ No newline at end of file
+]
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 8a19bb1..34558fb 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -169,9 +169,13 @@
def get_sle_after_datetime(args, for_update=False):
"""get Stock Ledger Entries after a particular datetime, for reposting"""
# NOTE: using for update of
- return get_stock_ledger_entries(args,
- ["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"],
- "asc", for_update=for_update)
+ conditions = ["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"]
+
+ # Excluding name: Workaround for MariaDB timestamp() floating microsecond issue
+ if args.get("name"):
+ conditions.append("name!=%(name)s")
+
+ return get_stock_ledger_entries(args, conditions, "asc", for_update=for_update)
def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, for_update=False):
"""get stock ledger entries filtered by specific posting datetime conditions"""
@@ -180,7 +184,7 @@
if not args.get("posting_time"):
args["posting_time"] = "00:00"
- return frappe.db.sql("""select * from `tabStock Ledger Entry`
+ return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry`
where item_code = %%(item_code)s
and warehouse = %%(warehouse)s
and ifnull(is_cancelled, 'No')='No'
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 0345a7e..bce94f3 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -9,34 +9,34 @@
from frappe.utils.email_lib import sendmail
class InvalidWarehouseCompany(frappe.ValidationError): pass
-
+
def get_stock_balance_on(warehouse, posting_date=None):
if not posting_date: posting_date = nowdate()
-
+
stock_ledger_entries = frappe.db.sql("""
- SELECT
+ SELECT
item_code, stock_value
- FROM
+ FROM
`tabStock Ledger Entry`
- WHERE
+ WHERE
warehouse=%s AND posting_date <= %s
ORDER BY timestamp(posting_date, posting_time) DESC, name DESC
""", (warehouse, posting_date), as_dict=1)
-
+
sle_map = {}
for sle in stock_ledger_entries:
sle_map.setdefault(sle.item_code, flt(sle.stock_value))
-
+
return sum(sle_map.values())
-
+
def get_latest_stock_balance():
bin_map = {}
- for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value
+ for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value
FROM tabBin""", as_dict=1):
bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value))
-
+
return bin_map
-
+
def get_bin(item_code, warehouse):
bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin:
@@ -58,18 +58,18 @@
bin.update_stock(args)
return bin
else:
- msgprint("[Stock Update] Ignored %s since it is not a stock item"
+ msgprint("[Stock Update] Ignored %s since it is not a stock item"
% args.get("item_code"))
def get_incoming_rate(args):
"""Get Incoming Rate based on valuation method"""
from erpnext.stock.stock_ledger import get_previous_sle
-
+
in_rate = 0
if args.get("serial_no"):
in_rate = get_avg_purchase_rate(args.get("serial_no"))
elif args.get("bom_no"):
- result = frappe.db.sql("""select ifnull(total_cost, 0) / ifnull(quantity, 1)
+ result = frappe.db.sql("""select ifnull(total_cost, 0) / ifnull(quantity, 1)
from `tabBOM` where name = %s and docstatus=1 and is_active=1""", args.get("bom_no"))
in_rate = result and flt(result[0][0]) or 0
else:
@@ -84,12 +84,12 @@
elif valuation_method == 'Moving Average':
in_rate = previous_sle.get('valuation_rate') or 0
return in_rate
-
+
def get_avg_purchase_rate(serial_nos):
"""get average value of serial numbers"""
-
+
serial_nos = get_valid_serial_nos(serial_nos)
- return flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) from `tabSerial No`
+ return flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) from `tabSerial No`
where name in (%s)""" % ", ".join(["%s"] * len(serial_nos)),
tuple(serial_nos))[0][0])
@@ -99,11 +99,11 @@
if not val_method:
val_method = get_global_default('valuation_method') or "FIFO"
return val_method
-
+
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
if qty >= 0:
- total = sum(f[0] for f in previous_stock_queue)
+ total = sum(f[0] for f in previous_stock_queue)
return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0
else:
outgoing_cost = 0
@@ -123,12 +123,12 @@
qty_to_pop = 0
# if queue gets blank and qty_to_pop remaining, get average rate of full queue
return outgoing_cost / abs(qty) - qty_to_pop
-
+
def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
"""split serial nos, validate and return list of valid serial nos"""
# TODO: remove duplicates in client side
serial_nos = cstr(sr_nos).strip().replace(',', '\n').split('\n')
-
+
valid_serial_nos = []
for val in serial_nos:
if val:
@@ -137,12 +137,12 @@
msgprint("You have entered duplicate serial no: '%s'" % val, raise_exception=1)
else:
valid_serial_nos.append(val)
-
+
if qty and len(valid_serial_nos) != abs(qty):
msgprint("Please enter serial nos for "
+ cstr(abs(qty)) + " quantity against item code: " + item_code,
raise_exception=1)
-
+
return valid_serial_nos
def validate_warehouse_company(warehouse, company):
@@ -151,48 +151,48 @@
frappe.msgprint(_("Warehouse does not belong to company.") + " (" + \
warehouse + ", " + company +")", raise_exception=InvalidWarehouseCompany)
-def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,
+def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,
stock_ledger_entries, item_sales_bom):
# sales bom item
buying_amount = 0.0
for bom_item in item_sales_bom[item_code]:
if bom_item.get("parent_detail_docname")==voucher_detail_no:
- buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,
+ buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,
stock_ledger_entries.get((bom_item.item_code, warehouse), []))
return buying_amount
-
+
def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries):
# IMP NOTE
- # stock_ledger_entries should already be filtered by item_code and warehouse and
+ # stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc
for i, sle in enumerate(stock_ledger_entries):
if sle.voucher_type == voucher_type and sle.voucher_no == voucher_no and \
sle.voucher_detail_no == item_row:
previous_stock_value = len(stock_ledger_entries) > i+1 and \
flt(stock_ledger_entries[i+1].stock_value) or 0.0
- buying_amount = previous_stock_value - flt(sle.stock_value)
-
+ buying_amount = previous_stock_value - flt(sle.stock_value)
+
return buying_amount
return 0.0
-
+
def reorder_item():
""" Reorder item if stock reaches reorder level"""
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:
material_requests = {}
bin_list = 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
+ 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, '')='' or end_of_life > now()))""", as_dict=True)
+ (ifnull(end_of_life, '')='' or end_of_life > curdate()))""", as_dict=True)
for bin in bin_list:
#check if re-order is required
- item_reorder = frappe.db.get("Item Reorder",
+ item_reorder = frappe.db.get("Item Reorder",
{"parent": bin.item_code, "warehouse": bin.warehouse})
if item_reorder:
reorder_level = item_reorder.warehouse_reorder_level
@@ -202,15 +202,15 @@
reorder_level, reorder_qty = frappe.db.get_value("Item", bin.item_code,
["re_order_level", "re_order_qty"])
material_request_type = "Purchase"
-
+
if flt(reorder_level) and flt(bin.projected_qty) < flt(reorder_level):
if flt(reorder_level) - flt(bin.projected_qty) > flt(reorder_qty):
reorder_qty = flt(reorder_level) - flt(bin.projected_qty)
-
+
company = frappe.db.get_value("Warehouse", bin.warehouse, "company") or \
frappe.defaults.get_defaults()["company"] or \
frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
-
+
material_requests.setdefault(material_request_type, frappe._dict()).setdefault(
company, []).append(frappe._dict({
"item_code": bin.item_code,
@@ -218,7 +218,7 @@
"reorder_qty": reorder_qty
})
)
-
+
create_material_request(material_requests)
def create_material_request(material_requests):
@@ -234,21 +234,19 @@
items = material_requests[request_type][company]
if not items:
continue
-
- mr = [{
- "doctype": "Material Request",
+
+ 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:
item = frappe.get_doc("Item", d.item_code)
- mr.append({
+ mr.append("indent_details", {
"doctype": "Material Request Item",
- "parenttype": "Material Request",
- "parentfield": "indent_details",
"item_code": d.item_code,
"schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
"uom": item.stock_uom,
@@ -259,11 +257,10 @@
"qty": d.reorder_qty,
"brand": item.brand,
})
-
- mr_doc = frappe.get_doc(mr)
- mr_doc.insert()
- mr_doc.submit()
- mr_list.append(mr_doc)
+
+ mr.insert()
+ mr.submit()
+ mr_list.append(mr)
except:
if frappe.local.message_log:
@@ -274,24 +271,24 @@
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,
+ 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)
-
+
def send_email_notification(mr_list):
""" Notify user about auto creation of indent"""
-
- email_list = frappe.db.sql_list("""select distinct r.parent
+
+ 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 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:
@@ -302,13 +299,13 @@
cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>"
msg += "</table>"
sendmail(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"
msg = """Dear System Manager,
An error occured for certain Items while creating Material Requests based on Re-order level.
-
+
Please rectify these issues:
---