Fixed rest of the test cases frappe/frapp#478

This commit is contained in:
Anand Doshi 2014-04-09 19:20:01 +05:30
parent d29465029d
commit 9fd50bcfb6
23 changed files with 705 additions and 656 deletions

View File

@ -17,17 +17,20 @@ from erpnext.accounts.party import get_party_account, get_due_date
class PurchaseInvoice(BuyingController): class PurchaseInvoice(BuyingController):
tname = 'Purchase Invoice Item' tname = 'Purchase Invoice Item'
fname = 'entries' fname = 'entries'
status_updater = [{
'source_dt': 'Purchase Invoice Item', def __init__(self, arg1, arg2=None):
'target_dt': 'Purchase Order Item', super(PurchaseInvoice, self).__init__(arg1, arg2)
'join_field': 'po_detail', self.status_updater = [{
'target_field': 'billed_amt', 'source_dt': 'Purchase Invoice Item',
'target_parent_dt': 'Purchase Order', 'target_dt': 'Purchase Order Item',
'target_parent_field': 'per_billed', 'join_field': 'po_detail',
'target_ref_field': 'amount', 'target_field': 'billed_amt',
'source_field': 'amount', 'target_parent_dt': 'Purchase Order',
'percent_join_field': 'purchase_order', 'target_parent_field': 'per_billed',
}] 'target_ref_field': 'amount',
'source_field': 'amount',
'percent_join_field': 'purchase_order',
}]
def validate(self): def validate(self):
if not self.is_opening: if not self.is_opening:

View File

@ -20,20 +20,23 @@ from erpnext.controllers.selling_controller import SellingController
class SalesInvoice(SellingController): class SalesInvoice(SellingController):
tname = 'Sales Invoice Item' tname = 'Sales Invoice Item'
fname = 'entries' fname = 'entries'
status_updater = [{
'source_dt': 'Sales Invoice Item', def __init__(self, arg1, arg2=None):
'target_field': 'billed_amt', super(SalesInvoice, self).__init__(arg1, arg2)
'target_ref_field': 'amount', self.status_updater = [{
'target_dt': 'Sales Order Item', 'source_dt': 'Sales Invoice Item',
'join_field': 'so_detail', 'target_field': 'billed_amt',
'target_parent_dt': 'Sales Order', 'target_ref_field': 'amount',
'target_parent_field': 'per_billed', 'target_dt': 'Sales Order Item',
'source_field': 'amount', 'join_field': 'so_detail',
'join_field': 'so_detail', 'target_parent_dt': 'Sales Order',
'percent_join_field': 'sales_order', 'target_parent_field': 'per_billed',
'status_field': 'billing_status', 'source_field': 'amount',
'keyword': 'Billed' 'join_field': 'so_detail',
}] 'percent_join_field': 'sales_order',
'status_field': 'billing_status',
'keyword': 'Billed'
}]
def validate(self): def validate(self):
super(SalesInvoice, self).validate() super(SalesInvoice, self).validate()

View File

@ -11,13 +11,13 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
class PurchaseCommon(BuyingController): class PurchaseCommon(BuyingController):
def update_last_purchase_rate(self, obj, is_submit): def update_last_purchase_rate(self, obj, is_submit):
"""updates last_purchase_rate in item table for each item""" """updates last_purchase_rate in item table for each item"""
import frappe.utils import frappe.utils
this_purchase_date = frappe.utils.getdate(obj.get('posting_date') or obj.get('transaction_date')) this_purchase_date = frappe.utils.getdate(obj.get('posting_date') or obj.get('transaction_date'))
for d in obj.get(obj.fname): for d in obj.get(obj.fname):
# get last purchase details # get last purchase details
last_purchase_details = get_last_purchase_details(d.item_code, obj.name) last_purchase_details = get_last_purchase_details(d.item_code, obj.name)
@ -33,19 +33,19 @@ class PurchaseCommon(BuyingController):
if flt(d.conversion_factor): if flt(d.conversion_factor):
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor) last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
else: else:
frappe.throw(_("Row ") + cstr(d.idx) + ": " + frappe.throw(_("Row ") + cstr(d.idx) + ": " +
_("UOM Conversion Factor is mandatory")) _("UOM Conversion Factor is mandatory"))
# update last purchsae rate # update last purchsae rate
if last_purchase_rate: if last_purchase_rate:
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""", frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
(flt(last_purchase_rate), d.item_code)) (flt(last_purchase_rate), d.item_code))
def get_last_purchase_rate(self, obj): def get_last_purchase_rate(self, obj):
"""get last purchase rates for all items""" """get last purchase rates for all items"""
doc_name = obj.name doc_name = obj.name
conversion_rate = flt(obj.get('conversion_rate')) or 1.0 conversion_rate = flt(obj.get('conversion_rate')) or 1.0
for d in obj.get(obj.fname): for d in obj.get(obj.fname):
if d.item_code: if d.item_code:
last_purchase_details = get_last_purchase_details(d.item_code, doc_name) last_purchase_details = get_last_purchase_details(d.item_code, doc_name)
@ -59,113 +59,113 @@ class PurchaseCommon(BuyingController):
else: else:
# if no last purchase found, reset all values to 0 # if no last purchase found, reset all values to 0
d.base_price_list_rate = d.base_rate = d.price_list_rate = d.rate = d.discount_percentage = 0 d.base_price_list_rate = d.base_rate = d.price_list_rate = d.rate = d.discount_percentage = 0
item_last_purchase_rate = frappe.db.get_value("Item", item_last_purchase_rate = frappe.db.get_value("Item",
d.item_code, "last_purchase_rate") d.item_code, "last_purchase_rate")
if item_last_purchase_rate: if item_last_purchase_rate:
d.base_price_list_rate = d.base_rate = d.price_list_rate \ d.base_price_list_rate = d.base_rate = d.price_list_rate \
= d.rate = item_last_purchase_rate = d.rate = item_last_purchase_rate
def validate_for_items(self, obj): def validate_for_items(self, obj):
check_list, chk_dupl_itm=[],[] check_list, chk_dupl_itm=[],[]
for d in obj.get(obj.fname): for d in obj.get(obj.fname):
# validation for valid qty # validation for valid qty
if flt(d.qty) < 0 or (d.parenttype != 'Purchase Receipt' and not flt(d.qty)): if flt(d.qty) < 0 or (d.parenttype != 'Purchase Receipt' and not flt(d.qty)):
frappe.throw("Please enter valid qty for item %s" % cstr(d.item_code)) frappe.throw("Please enter valid qty for item %s" % cstr(d.item_code))
# udpate with latest quantities # udpate with latest quantities
bin = frappe.db.sql("""select projected_qty from `tabBin` where bin = frappe.db.sql("""select projected_qty from `tabBin` where
item_code = %s and warehouse = %s""", (d.item_code, d.warehouse), as_dict=1) item_code = %s and warehouse = %s""", (d.item_code, d.warehouse), as_dict=1)
f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0} f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0}
if d.doctype == 'Purchase Receipt Item': if d.doctype == 'Purchase Receipt Item':
f_lst.pop('received_qty') f_lst.pop('received_qty')
for x in f_lst : for x in f_lst :
if d.meta.get_field(x): if d.meta.get_field(x):
d.set(x, f_lst[x]) d.set(x, f_lst[x])
item = frappe.db.sql("""select is_stock_item, is_purchase_item, item = frappe.db.sql("""select is_stock_item, is_purchase_item,
is_sub_contracted_item, end_of_life from `tabItem` where name=%s""", d.item_code) is_sub_contracted_item, end_of_life from `tabItem` where name=%s""", d.item_code)
if not item: if not item:
frappe.throw("Item %s does not exist in Item Master." % cstr(d.item_code)) frappe.throw("Item %s does not exist in Item Master." % cstr(d.item_code))
from erpnext.stock.doctype.item.item import validate_end_of_life from erpnext.stock.doctype.item.item import validate_end_of_life
validate_end_of_life(d.item_code, item[0][3]) validate_end_of_life(d.item_code, item[0][3])
# validate stock item # validate stock item
if item[0][0]=='Yes' and d.qty and not d.warehouse: if item[0][0]=='Yes' and d.qty and not d.warehouse:
frappe.throw("Warehouse is mandatory for %s, since it is a stock item" % d.item_code) frappe.throw("Warehouse is mandatory for %s, since it is a stock item" % d.item_code)
# validate purchase item # validate purchase item
if item[0][1] != 'Yes' and item[0][2] != 'Yes': if item[0][1] != 'Yes' and item[0][2] != 'Yes':
frappe.throw("Item %s is not a purchase item or sub-contracted item. Please check" % (d.item_code)) frappe.throw("Item %s is not a purchase item or sub-contracted item. Please check" % (d.item_code))
# list criteria that should not repeat if item is stock item # list criteria that should not repeat if item is stock item
e = [getattr(d, "schedule_date", None), d.item_code, d.description, d.warehouse, d.uom, e = [getattr(d, "schedule_date", None), d.item_code, d.description, d.warehouse, d.uom,
d.meta.get_field('prevdoc_docname') and d.prevdoc_docname or d.meta.get_field('sales_order_no') and d.sales_order_no or '', d.meta.get_field('prevdoc_docname') and d.prevdoc_docname or d.meta.get_field('sales_order_no') and d.sales_order_no or '',
d.meta.get_field('prevdoc_detail_docname') and d.prevdoc_detail_docname or '', d.meta.get_field('prevdoc_detail_docname') and d.prevdoc_detail_docname or '',
d.meta.get_field('batch_no') and d.batch_no or ''] d.meta.get_field('batch_no') and d.batch_no or '']
# if is not stock item # if is not stock item
f = [getattr(d, "schedule_date", None), d.item_code, d.description] f = [getattr(d, "schedule_date", None), d.item_code, d.description]
ch = frappe.db.sql("""select is_stock_item from `tabItem` where name = %s""", d.item_code) ch = frappe.db.sql("""select is_stock_item from `tabItem` where name = %s""", d.item_code)
if ch and ch[0][0] == 'Yes': if ch and ch[0][0] == 'Yes':
# check for same items # check for same items
if e in check_list: if e in check_list:
frappe.throw("""Item %s has been entered more than once with same description, schedule date, warehouse and uom.\n frappe.throw("""Item %s has been entered more than once with same description, schedule date, warehouse and uom.\n
Please change any of the field value to enter the item twice""" % d.item_code) Please change any of the field value to enter the item twice""" % d.item_code)
else: else:
check_list.append(e) check_list.append(e)
elif ch and ch[0][0] == 'No': elif ch and ch[0][0] == 'No':
# check for same items # check for same items
if f in chk_dupl_itm: if f in chk_dupl_itm:
frappe.throw("""Item %s has been entered more than once with same description, schedule date.\n frappe.throw("""Item %s has been entered more than once with same description, schedule date.\n
Please change any of the field value to enter the item twice.""" % d.item_code) Please change any of the field value to enter the item twice.""" % d.item_code)
else: else:
chk_dupl_itm.append(f) chk_dupl_itm.append(f)
def get_qty(self, curr_doctype, ref_tab_fname, ref_tab_dn, ref_doc_tname, transaction, curr_parent_name): def get_qty(self, curr_doctype, ref_tab_fname, ref_tab_dn, ref_doc_tname, transaction, curr_parent_name):
# Get total Quantities of current doctype (eg. PR) except for qty of this transaction # Get total Quantities of current doctype (eg. PR) except for qty of this transaction
#------------------------------ #------------------------------
# please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same . # please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same .
# i.e. in PO uom is NOS then in PR uom should be NOS # i.e. in PO uom is NOS then in PR uom should be NOS
# but if in Material Request uom KG it can change in PO # but if in Material Request uom KG it can change in PO
get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty' get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty'
qty = frappe.db.sql("""select sum(%s) from `tab%s` where %s = %s and qty = frappe.db.sql("""select sum(%s) from `tab%s` where %s = %s and
docstatus = 1 and parent != %s""" % (get_qty, curr_doctype, ref_tab_fname, '%s', '%s'), docstatus = 1 and parent != %s""" % (get_qty, curr_doctype, ref_tab_fname, '%s', '%s'),
(ref_tab_dn, curr_parent_name)) (ref_tab_dn, curr_parent_name))
qty = qty and flt(qty[0][0]) or 0 qty = qty and flt(qty[0][0]) or 0
# get total qty of ref doctype # get total qty of ref doctype
#-------------------- #--------------------
max_qty = frappe.db.sql("""select qty from `tab%s` where name = %s max_qty = frappe.db.sql("""select qty from `tab%s` where name = %s
and docstatus = 1""" % (ref_doc_tname, '%s'), ref_tab_dn) and docstatus = 1""" % (ref_doc_tname, '%s'), ref_tab_dn)
max_qty = max_qty and flt(max_qty[0][0]) or 0 max_qty = max_qty and flt(max_qty[0][0]) or 0
return cstr(qty)+'~~~'+cstr(max_qty) return cstr(qty)+'~~~'+cstr(max_qty)
def check_for_stopped_status(self, doctype, docname): def check_for_stopped_status(self, doctype, docname):
stopped = frappe.db.sql("""select name from `tab%s` where name = %s and stopped = frappe.db.sql("""select name from `tab%s` where name = %s and
status = 'Stopped'""" % (doctype, '%s'), docname) status = 'Stopped'""" % (doctype, '%s'), docname)
if stopped: if stopped:
frappe.throw("One cannot do any transaction against %s : %s, it's status is 'Stopped'" % frappe.throw("One cannot do any transaction against %s : %s, it's status is 'Stopped'" %
(doctype, docname)) (doctype, docname), exc=frappe.InvalidStatusError)
def check_docstatus(self, check, doctype, docname, detail_doctype = ''): def check_docstatus(self, check, doctype, docname, detail_doctype = ''):
if check == 'Next': if check == 'Next':
submitted = frappe.db.sql("""select t1.name from `tab%s` t1,`tab%s` t2 submitted = frappe.db.sql("""select t1.name from `tab%s` t1,`tab%s` t2
where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""" where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1"""
% (doctype, detail_doctype, '%s'), docname) % (doctype, detail_doctype, '%s'), docname)
if submitted: if submitted:
frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0]) frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0])
+ _("has already been submitted.")) + _("has already been submitted."))
if check == 'Previous': if check == 'Previous':
submitted = frappe.db.sql("""select name from `tab%s` submitted = frappe.db.sql("""select name from `tab%s`
where docstatus = 1 and name = %s""" % (doctype, '%s'), docname) where docstatus = 1 and name = %s""" % (doctype, '%s'), docname)
if not submitted: if not submitted:
frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0]) + _("not submitted")) frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0]) + _("not submitted"))

View File

@ -3,27 +3,27 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cstr, flt from frappe.utils import cstr, flt
from frappe import msgprint from frappe import msgprint
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
class PurchaseOrder(BuyingController): class PurchaseOrder(BuyingController):
tname = 'Purchase Order Item' tname = 'Purchase Order Item'
fname = 'po_details' fname = 'po_details'
status_updater = [{
'source_dt': 'Purchase Order Item', def __init__(self, arg1, arg2=None):
'target_dt': 'Material Request Item', super(PurchaseOrder, self).__init__(arg1, arg2)
'join_field': 'prevdoc_detail_docname', self.status_updater = [{
'target_field': 'ordered_qty', 'source_dt': 'Purchase Order Item',
'target_parent_dt': 'Material Request', 'target_dt': 'Material Request Item',
'target_parent_field': 'per_ordered', 'join_field': 'prevdoc_detail_docname',
'target_ref_field': 'qty', 'target_field': 'ordered_qty',
'source_field': 'qty', 'target_parent_dt': 'Material Request',
'percent_join_field': 'prevdoc_docname', 'target_parent_field': 'per_ordered',
}] 'target_ref_field': 'qty',
'source_field': 'qty',
'percent_join_field': 'prevdoc_docname',
}]
def validate(self): def validate(self):
super(PurchaseOrder, self).validate() super(PurchaseOrder, self).validate()
@ -156,7 +156,7 @@ class PurchaseOrder(BuyingController):
frappe.db.set(self,'status','Submitted') frappe.db.set(self,'status','Submitted')
def on_cancel(self): def on_cancel(self):
pc_obj = frappe.get_doc(dt = 'Purchase Common') pc_obj = frappe.get_doc('Purchase Common')
self.check_for_stopped_status(pc_obj) self.check_for_stopped_status(pc_obj)
# Check if Purchase Receipt has been submitted against current Purchase Order # Check if Purchase Receipt has been submitted against current Purchase Order

View File

@ -64,11 +64,11 @@ class StatusUpdater(DocListController):
def update_prevdoc_status(self): def update_prevdoc_status(self):
self.update_qty() self.update_qty()
self.validate_qty() self.validate_qty()
def set_status(self, update=False): def set_status(self, update=False):
if self.get("__islocal"): if self.get("__islocal"):
return return
if self.doctype in status_map: if self.doctype in status_map:
sl = status_map[self.doctype][:] sl = status_map[self.doctype][:]
sl.reverse() sl.reverse()
@ -83,15 +83,15 @@ class StatusUpdater(DocListController):
elif getattr(self, s[1])(): elif getattr(self, s[1])():
self.status = s[0] self.status = s[0]
break break
if update: if update:
frappe.db.set_value(self.doctype, self.name, "status", self.status) frappe.db.set_value(self.doctype, self.name, "status", self.status)
def on_communication(self): def on_communication(self):
self.communication_set = True self.communication_set = True
self.set_status(update=True) self.set_status(update=True)
del self.communication_set del self.communication_set
def communication_received(self): def communication_received(self):
if getattr(self, "communication_set", False): if getattr(self, "communication_set", False):
last_comm = self.get("communications") last_comm = self.get("communications")
@ -103,14 +103,14 @@ class StatusUpdater(DocListController):
last_comm = self.get("communications") last_comm = self.get("communications")
if last_comm: if last_comm:
return last_comm[-1].sent_or_received == "Sent" return last_comm[-1].sent_or_received == "Sent"
def validate_qty(self): def validate_qty(self):
""" """
Validates qty at row level Validates qty at row level
""" """
self.tolerance = {} self.tolerance = {}
self.global_tolerance = None self.global_tolerance = None
for args in self.status_updater: for args in self.status_updater:
# get unique transactions to update # get unique transactions to update
for d in self.get_all_children(): for d in self.get_all_children():
@ -118,10 +118,10 @@ class StatusUpdater(DocListController):
args['name'] = d.get(args['join_field']) args['name'] = d.get(args['join_field'])
# get all qty where qty > target_field # get all qty where qty > target_field
item = frappe.db.sql("""select item_code, `{target_ref_field}`, item = frappe.db.sql("""select item_code, `{target_ref_field}`,
`{target_field}`, parenttype, parent from `tab{target_dt}` `{target_field}`, parenttype, parent from `tab{target_dt}`
where `{target_ref_field}` < `{target_field}` where `{target_ref_field}` < `{target_field}`
and name=%s and docstatus=1""".format(**args), and name=%s and docstatus=1""".format(**args),
args['name'], as_dict=1) args['name'], as_dict=1)
if item: if item:
item = item[0] item = item[0]
@ -142,38 +142,37 @@ class StatusUpdater(DocListController):
is <b>""" % item + cstr(item[args['target_ref_field']]) + is <b>""" % item + cstr(item[args['target_ref_field']]) +
"""</b>.<br>You must reduce the %(target_ref_field)s by \ """</b>.<br>You must reduce the %(target_ref_field)s by \
%(reduce_by)s""" % item, raise_exception=1) %(reduce_by)s""" % item, raise_exception=1)
else: else:
self.check_overflow_with_tolerance(item, args) self.check_overflow_with_tolerance(item, args)
def check_overflow_with_tolerance(self, item, args): def check_overflow_with_tolerance(self, item, args):
""" """
Checks if there is overflow condering a relaxation tolerance Checks if there is overflow condering a relaxation tolerance
""" """
# check if overflow is within tolerance # check if overflow is within tolerance
tolerance, self.tolerance, self.global_tolerance = get_tolerance_for(item['item_code'], tolerance, self.tolerance, self.global_tolerance = get_tolerance_for(item['item_code'],
self.tolerance, self.global_tolerance) self.tolerance, self.global_tolerance)
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) / overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
item[args['target_ref_field']]) * 100 item[args['target_ref_field']]) * 100
if overflow_percent - tolerance > 0.01: if overflow_percent - tolerance > 0.01:
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+tolerance)/100) item['max_allowed'] = flt(item[args['target_ref_field']] * (100+tolerance)/100)
item['reduce_by'] = item[args['target_field']] - item['max_allowed'] item['reduce_by'] = item[args['target_field']] - item['max_allowed']
msgprint(""" msgprint("""
Row #%(idx)s: Max %(target_ref_field)s allowed for <b>Item %(item_code)s</b> \ Row #%(idx)s: Max %(target_ref_field)s allowed for <b>Item %(item_code)s</b> \
against <b>%(parenttype)s %(parent)s</b> is <b>%(max_allowed)s</b>. against <b>%(parenttype)s %(parent)s</b> is <b>%(max_allowed)s</b>.
If you want to increase your overflow tolerance, please increase tolerance %% in \ If you want to increase your overflow tolerance, please increase tolerance %% in \
Global Defaults or Item master. Global Defaults or Item master.
Or, you must reduce the %(target_ref_field)s by %(reduce_by)s Or, you must reduce the %(target_ref_field)s by %(reduce_by)s
Also, please check if the order item has already been billed in the Sales Order""" % Also, please check if the order item has already been billed in the Sales Order""" %
item, raise_exception=1) item, raise_exception=1)
def update_qty(self, change_modified=True): def update_qty(self, change_modified=True):
""" """
@ -185,103 +184,103 @@ class StatusUpdater(DocListController):
args['cond'] = ' or parent="%s"' % self.name.replace('"', '\"') args['cond'] = ' or parent="%s"' % self.name.replace('"', '\"')
else: else:
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"') args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
args['modified_cond'] = '' args['modified_cond'] = ''
if change_modified: if change_modified:
args['modified_cond'] = ', modified = now()' args['modified_cond'] = ', modified = now()'
# update quantities in child table # update quantities in child table
for d in self.get_all_children(): for d in self.get_all_children():
if d.doctype == args['source_dt']: if d.doctype == args['source_dt']:
# updates qty in the child table # updates qty in the child table
args['detail_id'] = d.get(args['join_field']) args['detail_id'] = d.get(args['join_field'])
args['second_source_condition'] = "" args['second_source_condition'] = ""
if args.get('second_source_dt') and args.get('second_source_field') \ if args.get('second_source_dt') and args.get('second_source_field') \
and args.get('second_join_field'): and args.get('second_join_field'):
args['second_source_condition'] = """ + (select sum(%(second_source_field)s) args['second_source_condition'] = """ + (select sum(%(second_source_field)s)
from `tab%(second_source_dt)s` from `tab%(second_source_dt)s`
where `%(second_join_field)s`="%(detail_id)s" where `%(second_join_field)s`="%(detail_id)s"
and (docstatus=1))""" % args and (docstatus=1))""" % args
if args['detail_id']: if args['detail_id']:
frappe.db.sql("""update `tab%(target_dt)s` frappe.db.sql("""update `tab%(target_dt)s`
set %(target_field)s = (select sum(%(source_field)s) set %(target_field)s = (select sum(%(source_field)s)
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s" from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
and (docstatus=1 %(cond)s)) %(second_source_condition)s and (docstatus=1 %(cond)s)) %(second_source_condition)s
where name='%(detail_id)s'""" % args) where name='%(detail_id)s'""" % args)
# get unique transactions to update # get unique transactions to update
for name in set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])]): for name in set([d.get(args['percent_join_field']) for d in self.get_all_children(args['source_dt'])]):
if name: if name:
args['name'] = name args['name'] = name
# update percent complete in the parent table # update percent complete in the parent table
frappe.db.sql("""update `tab%(target_parent_dt)s` frappe.db.sql("""update `tab%(target_parent_dt)s`
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s > set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
ifnull(%(target_field)s, 0), %(target_field)s, ifnull(%(target_field)s, 0), %(target_field)s,
%(target_ref_field)s))/sum(%(target_ref_field)s)*100 %(target_ref_field)s))/sum(%(target_ref_field)s)*100
from `tab%(target_dt)s` where parent="%(name)s") %(modified_cond)s from `tab%(target_dt)s` where parent="%(name)s") %(modified_cond)s
where name='%(name)s'""" % args) where name='%(name)s'""" % args)
# update field # update field
if args.get('status_field'): if args.get('status_field'):
frappe.db.sql("""update `tab%(target_parent_dt)s` frappe.db.sql("""update `tab%(target_parent_dt)s`
set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001, set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001,
'Not %(keyword)s', if(%(target_parent_field)s>=99.99, 'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
'Fully %(keyword)s', 'Partly %(keyword)s')) 'Fully %(keyword)s', 'Partly %(keyword)s'))
where name='%(name)s'""" % args) where name='%(name)s'""" % args)
def update_billing_status_for_zero_amount_refdoc(self, ref_dt): def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = ref_dt.lower().replace(" ", "_") ref_fieldname = ref_dt.lower().replace(" ", "_")
zero_amount_refdoc = [] zero_amount_refdoc = []
all_zero_amount_refdoc = frappe.db.sql_list("""select name from `tab%s` all_zero_amount_refdoc = frappe.db.sql_list("""select name from `tab%s`
where docstatus=1 and net_total = 0""" % ref_dt) where docstatus=1 and net_total = 0""" % ref_dt)
for item in self.get("entries"): for item in self.get("entries"):
if item.get(ref_fieldname) \ if item.get(ref_fieldname) \
and item.get(ref_fieldname) in all_zero_amount_refdoc \ and item.get(ref_fieldname) in all_zero_amount_refdoc \
and item.get(ref_fieldname) not in zero_amount_refdoc: and item.get(ref_fieldname) not in zero_amount_refdoc:
zero_amount_refdoc.append(item.get(ref_fieldname)) zero_amount_refdoc.append(item.get(ref_fieldname))
if zero_amount_refdoc: if zero_amount_refdoc:
self.update_biling_status(zero_amount_refdoc, ref_dt, ref_fieldname) self.update_biling_status(zero_amount_refdoc, ref_dt, ref_fieldname)
def update_biling_status(self, zero_amount_refdoc, ref_dt, ref_fieldname): def update_biling_status(self, zero_amount_refdoc, ref_dt, ref_fieldname):
for ref_dn in zero_amount_refdoc: for ref_dn in zero_amount_refdoc:
ref_doc_qty = flt(frappe.db.sql("""select sum(ifnull(qty, 0)) from `tab%s Item` ref_doc_qty = flt(frappe.db.sql("""select sum(ifnull(qty, 0)) from `tab%s Item`
where parent=%s""" % (ref_dt, '%s'), (ref_dn))[0][0]) where parent=%s""" % (ref_dt, '%s'), (ref_dn))[0][0])
billed_qty = flt(frappe.db.sql("""select sum(ifnull(qty, 0)) billed_qty = flt(frappe.db.sql("""select sum(ifnull(qty, 0))
from `tab%s Item` where %s=%s and docstatus=1""" % from `tab%s Item` where %s=%s and docstatus=1""" %
(self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0]) (self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0])
per_billed = ((ref_doc_qty if billed_qty > ref_doc_qty else billed_qty)\ per_billed = ((ref_doc_qty if billed_qty > ref_doc_qty else billed_qty)\
/ ref_doc_qty)*100 / ref_doc_qty)*100
frappe.db.set_value(ref_dt, ref_dn, "per_billed", per_billed) frappe.db.set_value(ref_dt, ref_dn, "per_billed", per_billed)
if frappe.get_meta(ref_dt).get_field("billing_status"): if frappe.get_meta(ref_dt).get_field("billing_status"):
if per_billed < 0.001: billing_status = "Not Billed" if per_billed < 0.001: billing_status = "Not Billed"
elif per_billed >= 99.99: billing_status = "Fully Billed" elif per_billed >= 99.99: billing_status = "Fully Billed"
else: billing_status = "Partly Billed" else: billing_status = "Partly Billed"
frappe.db.set_value(ref_dt, ref_dn, "billing_status", billing_status) frappe.db.set_value(ref_dt, ref_dn, "billing_status", billing_status)
def get_tolerance_for(item_code, item_tolerance={}, global_tolerance=None): def get_tolerance_for(item_code, item_tolerance={}, global_tolerance=None):
""" """
Returns the tolerance for the item, if not set, returns global tolerance Returns the tolerance for the item, if not set, returns global tolerance
""" """
if item_tolerance.get(item_code): if item_tolerance.get(item_code):
return item_tolerance[item_code], item_tolerance, global_tolerance return item_tolerance[item_code], item_tolerance, global_tolerance
tolerance = flt(frappe.db.get_value('Item',item_code,'tolerance') or 0) tolerance = flt(frappe.db.get_value('Item',item_code,'tolerance') or 0)
if not tolerance: if not tolerance:
if global_tolerance == None: if global_tolerance == None:
global_tolerance = flt(frappe.db.get_value('Global Defaults', None, global_tolerance = flt(frappe.db.get_value('Global Defaults', None,
'tolerance')) 'tolerance'))
tolerance = global_tolerance tolerance = global_tolerance
item_tolerance[item_code] = tolerance item_tolerance[item_code] = tolerance
return tolerance, item_tolerance, global_tolerance return tolerance, item_tolerance, global_tolerance

View File

@ -1,16 +1,16 @@
# ERPNext - web based ERP (http://erpnext.com) # ERPNext - web based ERP (http://erpnext.com)
# Copyright (C) 2012 Web Notes Technologies Pvt Ltd # Copyright (C) 2012 Web Notes Technologies Pvt Ltd
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
@ -22,35 +22,35 @@ feed_dict = {
# Project # Project
'Project': ['[%(status)s]', '#000080'], 'Project': ['[%(status)s]', '#000080'],
'Task': ['[%(status)s] %(subject)s', '#000080'], 'Task': ['[%(status)s] %(subject)s', '#000080'],
# Sales # Sales
'Lead': ['%(lead_name)s', '#000080'], 'Lead': ['%(lead_name)s', '#000080'],
'Quotation': ['[%(status)s] To %(customer_name)s worth %(currency)s %(grand_total_export)s', '#4169E1'], 'Quotation': ['[%(status)s] To %(customer_name)s worth %(currency)s %(grand_total_export)s', '#4169E1'],
'Sales Order': ['[%(status)s] To %(customer_name)s worth %(currency)s %(grand_total_export)s', '#4169E1'], 'Sales Order': ['[%(status)s] To %(customer_name)s worth %(currency)s %(grand_total_export)s', '#4169E1'],
# Purchase # Purchase
'Supplier': ['%(supplier_name)s, %(supplier_type)s', '#6495ED'], 'Supplier': ['%(supplier_name)s, %(supplier_type)s', '#6495ED'],
'Purchase Order': ['[%(status)s] %(name)s To %(supplier_name)s for %(currency)s %(grand_total_import)s', '#4169E1'], 'Purchase Order': ['[%(status)s] %(name)s To %(supplier_name)s for %(currency)s %(grand_total_import)s', '#4169E1'],
# Stock # Stock
'Delivery Note': ['[%(status)s] To %(customer_name)s', '#4169E1'], 'Delivery Note': ['[%(status)s] To %(customer_name)s', '#4169E1'],
'Purchase Receipt': ['[%(status)s] From %(supplier)s', '#4169E1'], 'Purchase Receipt': ['[%(status)s] From %(supplier)s', '#4169E1'],
# Accounts # Accounts
'Journal Voucher': ['[%(voucher_type)s] %(name)s', '#4169E1'], 'Journal Voucher': ['[%(voucher_type)s] %(name)s', '#4169E1'],
'Purchase Invoice': ['To %(supplier_name)s for %(currency)s %(grand_total_import)s', '#4169E1'], 'Purchase Invoice': ['To %(supplier_name)s for %(currency)s %(grand_total_import)s', '#4169E1'],
'Sales Invoice': ['To %(customer_name)s for %(currency)s %(grand_total_export)s', '#4169E1'], 'Sales Invoice': ['To %(customer_name)s for %(currency)s %(grand_total_export)s', '#4169E1'],
# HR # HR
'Expense Claim': ['[%(approval_status)s] %(name)s by %(employee_name)s', '#4169E1'], 'Expense Claim': ['[%(approval_status)s] %(name)s by %(employee_name)s', '#4169E1'],
'Salary Slip': ['%(employee_name)s for %(month)s %(fiscal_year)s', '#4169E1'], 'Salary Slip': ['%(employee_name)s for %(month)s %(fiscal_year)s', '#4169E1'],
'Leave Transaction': ['%(leave_type)s for %(employee)s', '#4169E1'], 'Leave Transaction': ['%(leave_type)s for %(employee)s', '#4169E1'],
# Support # Support
'Customer Issue': ['[%(status)s] %(description)s by %(customer_name)s', '#000080'], 'Customer Issue': ['[%(status)s] %(description)s by %(customer_name)s', '#000080'],
'Maintenance Visit': ['To %(customer_name)s', '#4169E1'], 'Maintenance Visit': ['To %(customer_name)s', '#4169E1'],
'Support Ticket': ["[%(status)s] %(subject)s", '#000080'], 'Support Ticket': ["[%(status)s] %(subject)s", '#000080'],
# Website # Website
'Web Page': ['%(title)s', '#000080'], 'Web Page': ['%(title)s', '#000080'],
'Blog': ['%(title)s', '#000080'] 'Blog': ['%(title)s', '#000080']
@ -63,12 +63,12 @@ def make_feed(feedtype, doctype, name, owner, subject, color):
if feedtype in ('Login', 'Comment', 'Assignment'): if feedtype in ('Login', 'Comment', 'Assignment'):
# delete old login, comment feed # delete old login, comment feed
frappe.db.sql("""delete from tabFeed where frappe.db.sql("""delete from tabFeed where
datediff(curdate(), creation) > 7 and doc_type in ('Comment', 'Login', 'Assignment')""") datediff(curdate(), creation) > 7 and doc_type in ('Comment', 'Login', 'Assignment')""")
else: else:
# one feed per item # one feed per item
frappe.db.sql("""delete from tabFeed frappe.db.sql("""delete from tabFeed
where doc_type=%s and doc_name=%s where doc_type=%s and doc_name=%s
and ifnull(feed_type,'') != 'Comment'""", (doctype, name)) and ifnull(feed_type,'') != 'Comment'""", (doctype, name))
f = frappe.new_doc('Feed') f = frappe.new_doc('Feed')
@ -79,9 +79,9 @@ def make_feed(feedtype, doctype, name, owner, subject, color):
f.subject = subject f.subject = subject
f.color = color f.color = color
f.full_name = get_fullname(owner) f.full_name = get_fullname(owner)
f.save() f.save(ignore_permissions=True)
def update_feed(doc, method=None): def update_feed(doc, method=None):
"adds a new feed" "adds a new feed"
if method in ['on_update', 'on_submit']: if method in ['on_update', 'on_submit']:
subject, color = feed_dict.get(doc.doctype, [None, None]) subject, color = feed_dict.get(doc.doctype, [None, None])

View File

@ -7,70 +7,73 @@ import frappe
from frappe.utils import cstr, getdate from frappe.utils import cstr, getdate
from frappe import msgprint from frappe import msgprint
from erpnext.stock.utils import get_valid_serial_nos from erpnext.stock.utils import get_valid_serial_nos
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
class InstallationNote(TransactionBase): class InstallationNote(TransactionBase):
tname = 'Installation Note Item' tname = 'Installation Note Item'
fname = 'installed_item_details' fname = 'installed_item_details'
status_updater = [{
'source_dt': 'Installation Note Item', def __init__(self, arg1, arg2=None):
'target_dt': 'Delivery Note Item', super(InstallationNote, self).__init__(arg1, arg2)
'target_field': 'installed_qty', self.status_updater = [{
'target_ref_field': 'qty', 'source_dt': 'Installation Note Item',
'join_field': 'prevdoc_detail_docname', 'target_dt': 'Delivery Note Item',
'target_parent_dt': 'Delivery Note', 'target_field': 'installed_qty',
'target_parent_field': 'per_installed', 'target_ref_field': 'qty',
'source_field': 'qty', 'join_field': 'prevdoc_detail_docname',
'percent_join_field': 'prevdoc_docname', 'target_parent_dt': 'Delivery Note',
'status_field': 'installation_status', 'target_parent_field': 'per_installed',
'keyword': 'Installed' 'source_field': 'qty',
}] 'percent_join_field': 'prevdoc_docname',
'status_field': 'installation_status',
'keyword': 'Installed'
}]
def validate(self): def validate(self):
self.validate_fiscal_year() self.validate_fiscal_year()
self.validate_installation_date() self.validate_installation_date()
self.check_item_table() self.check_item_table()
from erpnext.controllers.selling_controller import check_active_sales_items from erpnext.controllers.selling_controller import check_active_sales_items
check_active_sales_items(self) check_active_sales_items(self)
def validate_fiscal_year(self): def validate_fiscal_year(self):
from erpnext.accounts.utils import validate_fiscal_year from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.inst_date, self.fiscal_year, "Installation Date") validate_fiscal_year(self.inst_date, self.fiscal_year, "Installation Date")
def is_serial_no_added(self, item_code, serial_no): def is_serial_no_added(self, item_code, serial_no):
ar_required = frappe.db.get_value("Item", item_code, "has_serial_no") ar_required = frappe.db.get_value("Item", item_code, "has_serial_no")
if ar_required == 'Yes' and not serial_no: if ar_required == 'Yes' and not serial_no:
msgprint("Serial No is mandatory for item: " + item_code, raise_exception=1) msgprint("Serial No is mandatory for item: " + item_code, raise_exception=1)
elif ar_required != 'Yes' and cstr(serial_no).strip(): elif ar_required != 'Yes' and cstr(serial_no).strip():
msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :" + msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :" +
item_code, raise_exception=1) item_code, raise_exception=1)
def is_serial_no_exist(self, item_code, serial_no): def is_serial_no_exist(self, item_code, serial_no):
for x in serial_no: for x in serial_no:
if not frappe.db.exists("Serial No", x): if not frappe.db.exists("Serial No", x):
msgprint("Serial No " + x + " does not exist in the system", raise_exception=1) msgprint("Serial No " + x + " does not exist in the system", raise_exception=1)
def is_serial_no_installed(self,cur_s_no,item_code): def is_serial_no_installed(self,cur_s_no,item_code):
for x in cur_s_no: for x in cur_s_no:
status = frappe.db.sql("select status from `tabSerial No` where name = %s", x) status = frappe.db.sql("select status from `tabSerial No` where name = %s", x)
status = status and status[0][0] or '' status = status and status[0][0] or ''
if status == 'Installed': if status == 'Installed':
msgprint("Item "+item_code+" with serial no. " + x + " already installed", msgprint("Item "+item_code+" with serial no. " + x + " already installed",
raise_exception=1) raise_exception=1)
def get_prevdoc_serial_no(self, prevdoc_detail_docname): def get_prevdoc_serial_no(self, prevdoc_detail_docname):
serial_nos = frappe.db.get_value("Delivery Note Item", serial_nos = frappe.db.get_value("Delivery Note Item",
prevdoc_detail_docname, "serial_no") prevdoc_detail_docname, "serial_no")
return get_valid_serial_nos(serial_nos) return get_valid_serial_nos(serial_nos)
def is_serial_no_match(self, cur_s_no, prevdoc_s_no, prevdoc_docname): def is_serial_no_match(self, cur_s_no, prevdoc_s_no, prevdoc_docname):
for sr in cur_s_no: for sr in cur_s_no:
if sr not in prevdoc_s_no: if sr not in prevdoc_s_no:
msgprint("Serial No. " + sr + " is not matching with the Delivery Note " + msgprint("Serial No. " + sr + " is not matching with the Delivery Note " +
prevdoc_docname, raise_exception = 1) prevdoc_docname, raise_exception = 1)
def validate_serial_no(self): def validate_serial_no(self):
@ -80,33 +83,33 @@ class InstallationNote(TransactionBase):
if d.serial_no: if d.serial_no:
sr_list = get_valid_serial_nos(d.serial_no, d.qty, d.item_code) sr_list = get_valid_serial_nos(d.serial_no, d.qty, d.item_code)
self.is_serial_no_exist(d.item_code, sr_list) self.is_serial_no_exist(d.item_code, sr_list)
prevdoc_s_no = self.get_prevdoc_serial_no(d.prevdoc_detail_docname) prevdoc_s_no = self.get_prevdoc_serial_no(d.prevdoc_detail_docname)
if prevdoc_s_no: if prevdoc_s_no:
self.is_serial_no_match(sr_list, prevdoc_s_no, d.prevdoc_docname) self.is_serial_no_match(sr_list, prevdoc_s_no, d.prevdoc_docname)
self.is_serial_no_installed(sr_list, d.item_code) self.is_serial_no_installed(sr_list, d.item_code)
def validate_installation_date(self): def validate_installation_date(self):
for d in self.get('installed_item_details'): for d in self.get('installed_item_details'):
if d.prevdoc_docname: if d.prevdoc_docname:
d_date = frappe.db.get_value("Delivery Note", d.prevdoc_docname, "posting_date") d_date = frappe.db.get_value("Delivery Note", d.prevdoc_docname, "posting_date")
if d_date > getdate(self.inst_date): if d_date > getdate(self.inst_date):
msgprint("Installation Date can not be before Delivery Date " + cstr(d_date) + msgprint("Installation Date can not be before Delivery Date " + cstr(d_date) +
" for item "+d.item_code, raise_exception=1) " for item "+d.item_code, raise_exception=1)
def check_item_table(self): def check_item_table(self):
if not(self.get('installed_item_details')): if not(self.get('installed_item_details')):
msgprint("Please fetch items from Delivery Note selected", raise_exception=1) msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
def on_update(self): def on_update(self):
frappe.db.set(self, 'status', 'Draft') frappe.db.set(self, 'status', 'Draft')
def on_submit(self): def on_submit(self):
self.validate_serial_no() self.validate_serial_no()
self.update_prevdoc_status() self.update_prevdoc_status()
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
def on_cancel(self): def on_cancel(self):
for d in self.get('installed_item_details'): for d in self.get('installed_item_details'):
if d.serial_no: if d.serial_no:

View File

@ -21,9 +21,9 @@ class TestQuotation(unittest.TestCase):
sales_order = make_sales_order(quotation.name) sales_order = make_sales_order(quotation.name)
self.assertEquals(sales_order.doctype, "Sales Order") self.assertEquals(sales_order.doctype, "Sales Order")
self.assertEquals(len(sales_order.get("sales_order_details")), 2) self.assertEquals(len(sales_order.get("sales_order_details")), 1)
self.assertEquals(sales_order.get("sales_order_details")[0]["doctype"], "Sales Order Item") self.assertEquals(sales_order.get("sales_order_details")[0].doctype, "Sales Order Item")
self.assertEquals(sales_order.get("sales_order_details")[0]["prevdoc_docname"], quotation.name) self.assertEquals(sales_order.get("sales_order_details")[0].prevdoc_docname, quotation.name)
self.assertEquals(sales_order.customer, "_Test Customer") self.assertEquals(sales_order.customer, "_Test Customer")
sales_order.delivery_date = "2014-01-01" sales_order.delivery_date = "2014-01-01"

View File

@ -281,10 +281,10 @@ def make_material_request(source_name, target_doc=None):
@frappe.whitelist() @frappe.whitelist()
def make_delivery_note(source_name, target_doc=None): def make_delivery_note(source_name, target_doc=None):
def update_item(obj, target, source_parent): def update_item(source, target, source_parent):
target.base_amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.base_rate) target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
target.amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.rate) target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
target.qty = flt(obj.qty) - flt(obj.delivered_qty) target.qty = flt(source.qty) - flt(source.delivered_qty)
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {
@ -326,10 +326,10 @@ def make_sales_invoice(source_name, target_doc=None):
doc.is_pos = 0 doc.is_pos = 0
doc.run_method("onload_post_render") doc.run_method("onload_post_render")
def update_item(obj, target, source_parent): def update_item(source, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt) target.amount = flt(source.amount) - flt(source.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate) target.base_amount = target.amount * flt(source_parent.conversion_rate)
target.qty = obj.rate and target.amount / flt(obj.rate) or obj.qty target.qty = source.rate and target.amount / flt(source.rate) or obj.qty
doclist = get_mapped_doc("Sales Order", source_name, { doclist = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {

View File

@ -106,7 +106,7 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_so(self): def test_reserved_qty_for_so(self):
# reset bin # reset bin
so_item = test_records[0]["sales_order_details"][0] so_item = test_records[0]["sales_order_details"][0].copy()
self.delete_bin(so_item["item_code"], so_item["warehouse"]) self.delete_bin(so_item["item_code"], so_item["warehouse"])
# submit # submit
@ -120,7 +120,7 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_partial_delivery(self): def test_reserved_qty_for_partial_delivery(self):
# reset bin # reset bin
so_item = test_records[0]["sales_order_details"][0] so_item = test_records[0]["sales_order_details"][0].copy()
self.delete_bin(so_item["item_code"], so_item["warehouse"]) self.delete_bin(so_item["item_code"], so_item["warehouse"])
# submit so # submit so
@ -150,7 +150,7 @@ class TestSalesOrder(unittest.TestCase):
def test_reserved_qty_for_over_delivery(self): def test_reserved_qty_for_over_delivery(self):
# reset bin # reset bin
so_item = test_records[0]["sales_order_details"][0] so_item = test_records[0]["sales_order_details"][0].copy()
self.delete_bin(so_item["item_code"], so_item["warehouse"]) self.delete_bin(so_item["item_code"], so_item["warehouse"])
# submit so # submit so

View File

@ -5,22 +5,33 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.utils.nestedset import NestedSet from frappe.utils.nestedset import NestedSet
from frappe.website.website_generator import WebsiteGenerator
class ItemGroup(NestedSet): class ItemGroup(NestedSet, WebsiteGenerator):
nsm_parent_field = 'parent_item_group' nsm_parent_field = 'parent_item_group'
def autoname(self):
self.name = self.item_group_name
def validate(self): def validate(self):
if not self.parent_website_route: if not self.parent_website_route:
self.parent_website_route = frappe.get_website_route("Item Group", self.parent_website_route = frappe.get_website_route("Item Group",
self.parent_item_group) self.parent_item_group)
def on_update(self): def on_update(self):
NestedSet.on_update(self) NestedSet.on_update(self)
WebsiteGenerator.on_update(self)
self.validate_name_with_item() self.validate_name_with_item()
self.validate_one_root() self.validate_one_root()
def after_rename(self, olddn, newdn, merge=False):
NestedSet.after_rename(self, olddn, newdn, merge)
WebsiteGenerator.after_rename(self, olddn, newdn, merge)
def on_trash(self):
NestedSet.on_trash(self)
WebsiteGenerator.on_trash(self)
def validate_name_with_item(self): def validate_name_with_item(self):
if frappe.db.exists("Item", self.name): if frappe.db.exists("Item", self.name):
frappe.msgprint("An item exists with same name (%s), please change the \ frappe.msgprint("An item exists with same name (%s), please change the \

View File

@ -13,15 +13,14 @@ class TestItem(unittest.TestCase):
def test_basic_tree(self, records=None): def test_basic_tree(self, records=None):
min_lft = 1 min_lft = 1
max_rgt = frappe.db.sql("select max(rgt) from `tabItem Group`")[0][0] max_rgt = frappe.db.sql("select max(rgt) from `tabItem Group`")[0][0]
if not records: if not records:
records = test_records[2:] records = test_records[2:]
for item_group in records: for item_group in records:
item_group = item_group[0] lft, rgt, parent_item_group = frappe.db.get_value("Item Group", item_group["item_group_name"],
lft, rgt, parent_item_group = frappe.db.get_value("Item Group", item_group["item_group_name"],
["lft", "rgt", "parent_item_group"]) ["lft", "rgt", "parent_item_group"])
if parent_item_group: if parent_item_group:
parent_lft, parent_rgt = frappe.db.get_value("Item Group", parent_item_group, parent_lft, parent_rgt = frappe.db.get_value("Item Group", parent_item_group,
["lft", "rgt"]) ["lft", "rgt"])
@ -29,7 +28,7 @@ class TestItem(unittest.TestCase):
# root # root
parent_lft = min_lft - 1 parent_lft = min_lft - 1
parent_rgt = max_rgt + 1 parent_rgt = max_rgt + 1
self.assertTrue(lft) self.assertTrue(lft)
self.assertTrue(rgt) self.assertTrue(rgt)
self.assertTrue(lft < rgt) self.assertTrue(lft < rgt)
@ -38,69 +37,69 @@ class TestItem(unittest.TestCase):
self.assertTrue(rgt < parent_rgt) self.assertTrue(rgt < parent_rgt)
self.assertTrue(lft >= min_lft) self.assertTrue(lft >= min_lft)
self.assertTrue(rgt <= max_rgt) self.assertTrue(rgt <= max_rgt)
no_of_children = self.get_no_of_children(item_group["item_group_name"]) no_of_children = self.get_no_of_children(item_group["item_group_name"])
self.assertTrue(rgt == (lft + 1 + (2 * no_of_children))) self.assertTrue(rgt == (lft + 1 + (2 * no_of_children)))
no_of_children = self.get_no_of_children(parent_item_group) no_of_children = self.get_no_of_children(parent_item_group)
self.assertTrue(parent_rgt == (parent_lft + 1 + (2 * no_of_children))) self.assertTrue(parent_rgt == (parent_lft + 1 + (2 * no_of_children)))
def get_no_of_children(self, item_group): def get_no_of_children(self, item_group):
def get_no_of_children(item_groups, no_of_children): def get_no_of_children(item_groups, no_of_children):
children = [] children = []
for ig in item_groups: for ig in item_groups:
children += frappe.db.sql_list("""select name from `tabItem Group` children += frappe.db.sql_list("""select name from `tabItem Group`
where ifnull(parent_item_group, '')=%s""", ig or '') where ifnull(parent_item_group, '')=%s""", ig or '')
if len(children): if len(children):
return get_no_of_children(children, no_of_children + len(children)) return get_no_of_children(children, no_of_children + len(children))
else: else:
return no_of_children return no_of_children
return get_no_of_children([item_group], 0) return get_no_of_children([item_group], 0)
def test_recursion(self): def test_recursion(self):
group_b = frappe.get_doc("Item Group", "_Test Item Group B") group_b = frappe.get_doc("Item Group", "_Test Item Group B")
group_b.parent_item_group = "_Test Item Group B - 3" group_b.parent_item_group = "_Test Item Group B - 3"
self.assertRaises(NestedSetRecursionError, group_b.save) self.assertRaises(NestedSetRecursionError, group_b.save)
# cleanup # cleanup
group_b.parent_item_group = "All Item Groups" group_b.parent_item_group = "All Item Groups"
group_b.save() group_b.save()
def test_rebuild_tree(self): def test_rebuild_tree(self):
rebuild_tree("Item Group", "parent_item_group") rebuild_tree("Item Group", "parent_item_group")
self.test_basic_tree() self.test_basic_tree()
def move_it_back(self): def move_it_back(self):
group_b = frappe.get_doc("Item Group", "_Test Item Group B") group_b = frappe.get_doc("Item Group", "_Test Item Group B")
group_b.parent_item_group = "All Item Groups" group_b.parent_item_group = "All Item Groups"
group_b.save() group_b.save()
self.test_basic_tree() self.test_basic_tree()
def test_move_group_into_another(self): def test_move_group_into_another(self):
# before move # before move
old_lft, old_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"]) old_lft, old_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
# put B under C # put B under C
group_b = frappe.get_doc("Item Group", "_Test Item Group B") group_b = frappe.get_doc("Item Group", "_Test Item Group B")
lft, rgt = group_b.lft, group_b.rgt lft, rgt = group_b.lft, group_b.rgt
group_b.parent_item_group = "_Test Item Group C" group_b.parent_item_group = "_Test Item Group C"
group_b.save() group_b.save()
self.test_basic_tree() self.test_basic_tree()
# after move # after move
new_lft, new_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"]) new_lft, new_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
# lft should reduce # lft should reduce
self.assertEquals(old_lft - new_lft, rgt - lft + 1) self.assertEquals(old_lft - new_lft, rgt - lft + 1)
# adjacent siblings, hence rgt diff will be 0 # adjacent siblings, hence rgt diff will be 0
self.assertEquals(new_rgt - old_rgt, 0) self.assertEquals(new_rgt - old_rgt, 0)
self.move_it_back() self.move_it_back()
def test_move_group_into_root(self): def test_move_group_into_root(self):
group_b = frappe.get_doc("Item Group", "_Test Item Group B") group_b = frappe.get_doc("Item Group", "_Test Item Group B")
group_b.parent_item_group = "" group_b.parent_item_group = ""
@ -108,101 +107,101 @@ class TestItem(unittest.TestCase):
# trick! works because it hasn't been rolled back :D # trick! works because it hasn't been rolled back :D
self.test_basic_tree() self.test_basic_tree()
self.move_it_back() self.move_it_back()
def print_tree(self): def print_tree(self):
import json import json
print json.dumps(frappe.db.sql("select name, lft, rgt from `tabItem Group` order by lft"), indent=1) print json.dumps(frappe.db.sql("select name, lft, rgt from `tabItem Group` order by lft"), indent=1)
def test_move_leaf_into_another_group(self): def test_move_leaf_into_another_group(self):
# before move # before move
old_lft, old_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"]) old_lft, old_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
group_b_3 = frappe.get_doc("Item Group", "_Test Item Group B - 3") group_b_3 = frappe.get_doc("Item Group", "_Test Item Group B - 3")
lft, rgt = group_b_3.lft, group_b_3.rgt lft, rgt = group_b_3.lft, group_b_3.rgt
# child of right sibling is moved into it # child of right sibling is moved into it
group_b_3.parent_item_group = "_Test Item Group C" group_b_3.parent_item_group = "_Test Item Group C"
group_b_3.save() group_b_3.save()
self.test_basic_tree() self.test_basic_tree()
new_lft, new_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"]) new_lft, new_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
# lft should remain the same # lft should remain the same
self.assertEquals(old_lft - new_lft, 0) self.assertEquals(old_lft - new_lft, 0)
# rgt should increase # rgt should increase
self.assertEquals(new_rgt - old_rgt, rgt - lft + 1) self.assertEquals(new_rgt - old_rgt, rgt - lft + 1)
# move it back # move it back
group_b_3 = frappe.get_doc("Item Group", "_Test Item Group B - 3") group_b_3 = frappe.get_doc("Item Group", "_Test Item Group B - 3")
group_b_3.parent_item_group = "_Test Item Group B" group_b_3.parent_item_group = "_Test Item Group B"
group_b_3.save() group_b_3.save()
self.test_basic_tree() self.test_basic_tree()
def test_delete_leaf(self): def test_delete_leaf(self):
# for checking later # for checking later
parent_item_group = frappe.db.get_value("Item Group", "_Test Item Group B - 3", "parent_item_group") parent_item_group = frappe.db.get_value("Item Group", "_Test Item Group B - 3", "parent_item_group")
rgt = frappe.db.get_value("Item Group", parent_item_group, "rgt") rgt = frappe.db.get_value("Item Group", parent_item_group, "rgt")
ancestors = get_ancestors_of("Item Group", "_Test Item Group B - 3") ancestors = get_ancestors_of("Item Group", "_Test Item Group B - 3")
ancestors = frappe.db.sql("""select name, rgt from `tabItem Group` ancestors = frappe.db.sql("""select name, rgt from `tabItem Group`
where name in ({})""".format(", ".join(["%s"]*len(ancestors))), tuple(ancestors), as_dict=True) where name in ({})""".format(", ".join(["%s"]*len(ancestors))), tuple(ancestors), as_dict=True)
frappe.delete_doc("Item Group", "_Test Item Group B - 3") frappe.delete_doc("Item Group", "_Test Item Group B - 3")
records_to_test = test_records[2:] records_to_test = test_records[2:]
del records_to_test[4] del records_to_test[4]
self.test_basic_tree(records=records_to_test) self.test_basic_tree(records=records_to_test)
# rgt of each ancestor would reduce by 2 # rgt of each ancestor would reduce by 2
for item_group in ancestors: for item_group in ancestors:
new_lft, new_rgt = frappe.db.get_value("Item Group", item_group.name, ["lft", "rgt"]) new_lft, new_rgt = frappe.db.get_value("Item Group", item_group.name, ["lft", "rgt"])
self.assertEquals(new_rgt, item_group.rgt - 2) self.assertEquals(new_rgt, item_group.rgt - 2)
# insert it back # insert it back
frappe.copy_doc(test_records[6]).insert() frappe.copy_doc(test_records[6]).insert()
self.test_basic_tree() self.test_basic_tree()
def test_delete_group(self): def test_delete_group(self):
# cannot delete group with child, but can delete leaf # cannot delete group with child, but can delete leaf
self.assertRaises(NestedSetChildExistsError, frappe.delete_doc, "Item Group", "_Test Item Group B") self.assertRaises(NestedSetChildExistsError, frappe.delete_doc, "Item Group", "_Test Item Group B")
def test_merge_groups(self): def test_merge_groups(self):
frappe.rename_doc("Item Group", "_Test Item Group B", "_Test Item Group C", merge=True) frappe.rename_doc("Item Group", "_Test Item Group B", "_Test Item Group C", merge=True)
records_to_test = test_records[2:] records_to_test = test_records[2:]
del records_to_test[1] del records_to_test[1]
self.test_basic_tree(records=records_to_test) self.test_basic_tree(records=records_to_test)
# insert Group B back # insert Group B back
frappe.copy_doc(test_records[3]).insert() frappe.copy_doc(test_records[3]).insert()
self.test_basic_tree() self.test_basic_tree()
# move its children back # move its children back
for name in frappe.db.sql_list("""select name from `tabItem Group` for name in frappe.db.sql_list("""select name from `tabItem Group`
where parent_item_group='_Test Item Group C'"""): where parent_item_group='_Test Item Group C'"""):
doc = frappe.get_doc("Item Group", name) doc = frappe.get_doc("Item Group", name)
doc.parent_item_group = "_Test Item Group B" doc.parent_item_group = "_Test Item Group B"
doc.save() doc.save()
self.test_basic_tree() self.test_basic_tree()
def test_merge_leaves(self): def test_merge_leaves(self):
frappe.rename_doc("Item Group", "_Test Item Group B - 2", "_Test Item Group B - 1", merge=True) frappe.rename_doc("Item Group", "_Test Item Group B - 2", "_Test Item Group B - 1", merge=True)
records_to_test = test_records[2:] records_to_test = test_records[2:]
del records_to_test[3] del records_to_test[3]
self.test_basic_tree(records=records_to_test) self.test_basic_tree(records=records_to_test)
# insert Group B - 2back # insert Group B - 2back
frappe.copy_doc(test_records[5]).insert() frappe.copy_doc(test_records[5]).insert()
self.test_basic_tree() self.test_basic_tree()
def test_merge_leaf_into_group(self): def test_merge_leaf_into_group(self):
self.assertRaises(NestedSetInvalidMergeError, frappe.rename_doc, "Item Group", "_Test Item Group B - 3", self.assertRaises(NestedSetInvalidMergeError, frappe.rename_doc, "Item Group", "_Test Item Group B - 3",
"_Test Item Group B", merge=True) "_Test Item Group B", merge=True)
def test_merge_group_into_leaf(self): def test_merge_group_into_leaf(self):
self.assertRaises(NestedSetInvalidMergeError, frappe.rename_doc, "Item Group", "_Test Item Group B", self.assertRaises(NestedSetInvalidMergeError, frappe.rename_doc, "Item Group", "_Test Item Group B",
"_Test Item Group B - 3", merge=True) "_Test Item Group B - 3", merge=True)

View File

@ -4,9 +4,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cint, cstr, filter_strip_join from frappe.utils import cint, cstr, filter_strip_join
from frappe.model.document import Document from frappe.website.website_generator import WebsiteGenerator
class SalesPartner(WebsiteGenerator):
def autoname(self):
self.name = self.partner_name
class SalesPartner(Document):
def validate(self): def validate(self):
if self.partner_website and not self.partner_website.startswith("http"): if self.partner_website and not self.partner_website.startswith("http"):
self.partner_website = "http://" + self.partner_website self.partner_website = "http://" + self.partner_website
@ -14,8 +17,8 @@ class SalesPartner(Document):
def get_contacts(self, nm): def get_contacts(self, nm):
if nm: if nm:
return frappe.db.convert_to_lists(frappe.db.sql(""" return frappe.db.convert_to_lists(frappe.db.sql("""
select name, CONCAT(IFNULL(first_name,''), select name, CONCAT(IFNULL(first_name,''),
' ',IFNULL(last_name,'')),contact_no,email_id ' ',IFNULL(last_name,'')),contact_no,email_id
from `tabContact` where sales_partner = %s""", nm)) from `tabContact` where sales_partner = %s""", nm))
else: else:
return '' return ''

View File

@ -15,19 +15,22 @@ from erpnext.controllers.selling_controller import SellingController
class DeliveryNote(SellingController): class DeliveryNote(SellingController):
tname = 'Delivery Note Item' tname = 'Delivery Note Item'
fname = 'delivery_note_details' fname = 'delivery_note_details'
status_updater = [{
'source_dt': 'Delivery Note Item', def __init__(self, arg1, arg2=None):
'target_dt': 'Sales Order Item', super(DeliveryNote, self).__init__(arg1, arg2)
'join_field': 'prevdoc_detail_docname', self.status_updater = [{
'target_field': 'delivered_qty', 'source_dt': 'Delivery Note Item',
'target_parent_dt': 'Sales Order', 'target_dt': 'Sales Order Item',
'target_parent_field': 'per_delivered', 'join_field': 'prevdoc_detail_docname',
'target_ref_field': 'qty', 'target_field': 'delivered_qty',
'source_field': 'qty', 'target_parent_dt': 'Sales Order',
'percent_join_field': 'against_sales_order', 'target_parent_field': 'per_delivered',
'status_field': 'delivery_status', 'target_ref_field': 'qty',
'keyword': 'Delivered' 'source_field': 'qty',
}] 'percent_join_field': 'against_sales_order',
'status_field': 'delivery_status',
'keyword': 'Delivered'
}]
def onload(self): def onload(self):
billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item` billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item`

View File

@ -11,76 +11,76 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_
def _insert_purchase_receipt(item_code=None): def _insert_purchase_receipt(item_code=None):
if not item_code: if not item_code:
item_code = pr_test_records[0][1]["item_code"] item_code = pr_test_records[0]["purchase_receipt_details"][0]["item_code"]
pr = frappe.copy_doc(pr_test_records[0]) pr = frappe.copy_doc(pr_test_records[0])
pr.get("purchase_receipt_details")[0].item_code = item_code pr.get("purchase_receipt_details")[0].item_code = item_code
pr.insert() pr.insert()
pr.submit() pr.submit()
class TestDeliveryNote(unittest.TestCase): class TestDeliveryNote(unittest.TestCase):
def test_over_billing_against_dn(self): def test_over_billing_against_dn(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
_insert_purchase_receipt() _insert_purchase_receipt()
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
_insert_purchase_receipt() _insert_purchase_receipt()
dn = frappe.copy_doc(test_records[0]).insert() dn = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_sales_invoice, self.assertRaises(frappe.ValidationError, make_sales_invoice,
dn.name) dn.name)
dn = frappe.get_doc("Delivery Note", dn.name) dn = frappe.get_doc("Delivery Note", dn.name)
dn.submit() dn.submit()
si = make_sales_invoice(dn.name) si = make_sales_invoice(dn.name)
self.assertEquals(len(si), len(dn)) self.assertEquals(len(si.get("entries")), len(dn.get("delivery_note_details")))
# modify amount # modify amount
si[1]["rate"] = 200 si.get("entries")[0].rate = 200
self.assertRaises(frappe.ValidationError, frappe.get_doc(si).insert) self.assertRaises(frappe.ValidationError, frappe.get_doc(si).insert)
def test_delivery_note_no_gl_entry(self): def test_delivery_note_no_gl_entry(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory(0) set_perpetual_inventory(0)
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 0) self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 0)
_insert_purchase_receipt() _insert_purchase_receipt()
dn = frappe.copy_doc(test_records[0]) dn = frappe.copy_doc(test_records[0])
dn.insert() dn.insert()
dn.submit() dn.submit()
stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": dn.name, {"voucher_type": "Delivery Note", "voucher_no": dn.name,
"item_code": "_Test Item"}, ["stock_value", "stock_value_difference"]) "item_code": "_Test Item"}, ["stock_value", "stock_value_difference"])
self.assertEqual(stock_value, 0) self.assertEqual(stock_value, 0)
self.assertEqual(stock_value_difference, -375) self.assertEqual(stock_value_difference, -375)
self.assertFalse(get_gl_entries("Delivery Note", dn.name)) self.assertFalse(get_gl_entries("Delivery Note", dn.name))
def test_delivery_note_gl_entry(self): def test_delivery_note_gl_entry(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1) self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1)
frappe.db.set_value("Item", "_Test Item", "valuation_method", "FIFO") frappe.db.set_value("Item", "_Test Item", "valuation_method", "FIFO")
_insert_purchase_receipt() _insert_purchase_receipt()
dn = frappe.copy_doc(test_records[0]) dn = frappe.copy_doc(test_records[0])
dn.get("delivery_note_details")[0].expense_account = "Cost of Goods Sold - _TC" dn.get("delivery_note_details")[0].expense_account = "Cost of Goods Sold - _TC"
dn.get("delivery_note_details")[0].cost_center = "Main - _TC" dn.get("delivery_note_details")[0].cost_center = "Main - _TC"
stock_in_hand_account = frappe.db.get_value("Account", stock_in_hand_account = frappe.db.get_value("Account",
{"master_name": dn.get("delivery_note_details")[0].warehouse}) {"master_name": dn.get("delivery_note_details")[0].warehouse})
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
prev_bal = get_balance_on(stock_in_hand_account, dn.posting_date) prev_bal = get_balance_on(stock_in_hand_account, dn.posting_date)
dn.insert() dn.insert()
dn.submit() dn.submit()
gl_entries = get_gl_entries("Delivery Note", dn.name) gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_values = { expected_values = {
@ -89,20 +89,20 @@ class TestDeliveryNote(unittest.TestCase):
} }
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account)) self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
# check stock in hand balance # check stock in hand balance
bal = get_balance_on(stock_in_hand_account, dn.posting_date) bal = get_balance_on(stock_in_hand_account, dn.posting_date)
self.assertEquals(bal, prev_bal - 375.0) self.assertEquals(bal, prev_bal - 375.0)
# back dated purchase receipt # back dated purchase receipt
pr = frappe.copy_doc(pr_test_records[0]) pr = frappe.copy_doc(pr_test_records[0])
pr.posting_date = "2013-01-01" pr.posting_date = "2013-01-01"
pr.get("purchase_receipt_details")[0].rate = 100 pr.get("purchase_receipt_details")[0].rate = 100
pr.get("purchase_receipt_details")[0].base_amount = 100 pr.get("purchase_receipt_details")[0].base_amount = 100
pr.insert() pr.insert()
pr.submit() pr.submit()
gl_entries = get_gl_entries("Delivery Note", dn.name) gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_values = { expected_values = {
@ -111,71 +111,71 @@ class TestDeliveryNote(unittest.TestCase):
} }
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account)) self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
dn.cancel() dn.cancel()
self.assertFalse(get_gl_entries("Delivery Note", dn.name)) self.assertFalse(get_gl_entries("Delivery Note", dn.name))
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_delivery_note_gl_entry_packing_item(self): def test_delivery_note_gl_entry_packing_item(self):
self.clear_stock_account_balance() self.clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
_insert_purchase_receipt() _insert_purchase_receipt()
_insert_purchase_receipt("_Test Item Home Desktop 100") _insert_purchase_receipt("_Test Item Home Desktop 100")
dn = frappe.copy_doc(test_records[0]) dn = frappe.copy_doc(test_records[0])
dn.get("delivery_note_details")[0].item_code = "_Test Sales BOM Item" dn.get("delivery_note_details")[0].item_code = "_Test Sales BOM Item"
dn.get("delivery_note_details")[0].qty = 1 dn.get("delivery_note_details")[0].qty = 1
stock_in_hand_account = frappe.db.get_value("Account", stock_in_hand_account = frappe.db.get_value("Account",
{"master_name": dn.get("delivery_note_details")[0].warehouse}) {"master_name": dn.get("delivery_note_details")[0].warehouse})
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
prev_bal = get_balance_on(stock_in_hand_account, dn.posting_date) prev_bal = get_balance_on(stock_in_hand_account, dn.posting_date)
dn.insert() dn.insert()
dn.submit() dn.submit()
gl_entries = get_gl_entries("Delivery Note", dn.name) gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
expected_values = { expected_values = {
stock_in_hand_account: [0.0, 525], stock_in_hand_account: [0.0, 525],
"Cost of Goods Sold - _TC": [525.0, 0.0] "Cost of Goods Sold - _TC": [525.0, 0.0]
} }
for i, gle in enumerate(gl_entries): for i, gle in enumerate(gl_entries):
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account)) self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
# check stock in hand balance # check stock in hand balance
bal = get_balance_on(stock_in_hand_account, dn.posting_date) bal = get_balance_on(stock_in_hand_account, dn.posting_date)
self.assertEquals(bal, prev_bal - 525.0) self.assertEquals(bal, prev_bal - 525.0)
dn.cancel() dn.cancel()
self.assertFalse(get_gl_entries("Delivery Note", dn.name)) self.assertFalse(get_gl_entries("Delivery Note", dn.name))
set_perpetual_inventory(0) set_perpetual_inventory(0)
def test_serialized(self): def test_serialized(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
se = make_serialized_item() se = make_serialized_item()
serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no) serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no)
dn = frappe.copy_doc(test_records[0]) dn = frappe.copy_doc(test_records[0])
dn.get("delivery_note_details")[0].item_code = "_Test Serialized Item With Series" dn.get("delivery_note_details")[0].item_code = "_Test Serialized Item With Series"
dn.get("delivery_note_details")[0].qty = 1 dn.get("delivery_note_details")[0].qty = 1
dn.get("delivery_note_details")[0].serial_no = serial_nos[0] dn.get("delivery_note_details")[0].serial_no = serial_nos[0]
dn.insert() dn.insert()
dn.submit() dn.submit()
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Delivered") self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Delivered")
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse")) self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no"), dn.name) "delivery_document_no"), dn.name)
return dn return dn
def test_serialized_cancel(self): def test_serialized_cancel(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
dn = self.test_serialized() dn = self.test_serialized()
@ -185,20 +185,20 @@ class TestDeliveryNote(unittest.TestCase):
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Available") self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "status"), "Available")
self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC") self.assertEquals(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0],
"delivery_document_no")) "delivery_document_no"))
def test_serialize_status(self): def test_serialize_status(self):
from erpnext.stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos from erpnext.stock.doctype.serial_no.serial_no import SerialNoStatusError, get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
se = make_serialized_item() se = make_serialized_item()
serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no) serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no)
sr = frappe.get_doc("Serial No", serial_nos[0]) sr = frappe.get_doc("Serial No", serial_nos[0])
sr.status = "Not Available" sr.status = "Not Available"
sr.save() sr.save()
dn = frappe.copy_doc(test_records[0]) dn = frappe.copy_doc(test_records[0])
dn.get("delivery_note_details")[0].item_code = "_Test Serialized Item With Series" dn.get("delivery_note_details")[0].item_code = "_Test Serialized Item With Series"
dn.get("delivery_note_details")[0].qty = 1 dn.get("delivery_note_details")[0].qty = 1
@ -206,7 +206,7 @@ class TestDeliveryNote(unittest.TestCase):
dn.insert() dn.insert()
self.assertRaises(SerialNoStatusError, dn.submit) self.assertRaises(SerialNoStatusError, dn.submit)
def clear_stock_account_balance(self): def clear_stock_account_balance(self):
frappe.db.sql("""delete from `tabBin`""") frappe.db.sql("""delete from `tabBin`""")
frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from `tabStock Ledger Entry`")
@ -214,4 +214,4 @@ class TestDeliveryNote(unittest.TestCase):
test_dependencies = ["Sales BOM"] test_dependencies = ["Sales BOM"]
test_records = frappe.get_test_records('Delivery Note') test_records = frappe.get_test_records('Delivery Note')

View File

@ -3,32 +3,29 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import cstr, flt, getdate, now_datetime, formatdate
from frappe import msgprint, _ from frappe import msgprint, _
from frappe.utils import cstr, flt, getdate, now_datetime, formatdate
from frappe.model.controller import DocListController from frappe.website.website_generator import WebsiteGenerator
class WarehouseNotSet(Exception): pass class WarehouseNotSet(Exception): pass
class Item(DocListController): class Item(WebsiteGenerator):
def onload(self): def onload(self):
self.set("__sle_exists", self.check_if_sle_exists()) self.set("__sle_exists", self.check_if_sle_exists())
def autoname(self): def autoname(self):
if frappe.db.get_default("item_naming_by")=="Naming Series": if frappe.db.get_default("item_naming_by")=="Naming Series":
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
self.item_code = make_autoname(self.naming_series+'.#####') self.item_code = make_autoname(self.naming_series+'.#####')
elif not self.item_code: elif not self.item_code:
msgprint(_("Item Code (item_code) is mandatory because Item naming is not sequential."), raise_exception=1) msgprint(_("Item Code (item_code) is mandatory because Item naming is not sequential."), raise_exception=1)
self.name = self.item_code self.name = self.item_code
def validate(self): def validate(self):
if not self.stock_uom: if not self.stock_uom:
msgprint(_("Please enter Default Unit of Measure"), raise_exception=1) msgprint(_("Please enter Default Unit of Measure"), raise_exception=1)
self.check_warehouse_is_set_for_stock_item() self.check_warehouse_is_set_for_stock_item()
self.check_stock_uom_with_bin() self.check_stock_uom_with_bin()
self.add_default_uom_in_conversion_factor_table() self.add_default_uom_in_conversion_factor_table()
@ -40,14 +37,15 @@ class Item(DocListController):
self.validate_barcode() self.validate_barcode()
self.cant_change() self.cant_change()
self.validate_item_type_for_reorder() self.validate_item_type_for_reorder()
if not self.parent_website_route: if not self.parent_website_route:
self.parent_website_route = frappe.get_website_route("Item Group", self.item_group) self.parent_website_route = frappe.get_website_route("Item Group", self.item_group)
if self.name: if self.name:
self.old_page_name = frappe.db.get_value('Item', self.name, 'page_name') self.old_page_name = frappe.db.get_value('Item', self.name, 'page_name')
def on_update(self): def on_update(self):
super(Item, self).on_update()
self.validate_name_with_item_group() self.validate_name_with_item_group()
self.update_item_price() self.update_item_price()
@ -55,46 +53,46 @@ class Item(DocListController):
if self.is_stock_item=="Yes" and not self.default_warehouse: if self.is_stock_item=="Yes" and not self.default_warehouse:
frappe.msgprint(_("Default Warehouse is mandatory for Stock Item."), frappe.msgprint(_("Default Warehouse is mandatory for Stock Item."),
raise_exception=WarehouseNotSet) raise_exception=WarehouseNotSet)
def add_default_uom_in_conversion_factor_table(self): def add_default_uom_in_conversion_factor_table(self):
uom_conv_list = [d.uom for d in self.get("uom_conversion_details")] uom_conv_list = [d.uom for d in self.get("uom_conversion_details")]
if self.stock_uom not in uom_conv_list: if self.stock_uom not in uom_conv_list:
ch = self.append('uom_conversion_details', {}) ch = self.append('uom_conversion_details', {})
ch.uom = self.stock_uom ch.uom = self.stock_uom
ch.conversion_factor = 1 ch.conversion_factor = 1
to_remove = [] to_remove = []
for d in self.get("uom_conversion_details"): for d in self.get("uom_conversion_details"):
if d.conversion_factor == 1 and d.uom != self.stock_uom: if d.conversion_factor == 1 and d.uom != self.stock_uom:
to_remove.append(d) to_remove.append(d)
[self.remove(d) for d in to_remove] [self.remove(d) for d in to_remove]
def check_stock_uom_with_bin(self): def check_stock_uom_with_bin(self):
if not self.get("__islocal"): if not self.get("__islocal"):
matched=True matched=True
ref_uom = frappe.db.get_value("Stock Ledger Entry", ref_uom = frappe.db.get_value("Stock Ledger Entry",
{"item_code": self.name}, "stock_uom") {"item_code": self.name}, "stock_uom")
if ref_uom: if ref_uom:
if cstr(ref_uom) != cstr(self.stock_uom): if cstr(ref_uom) != cstr(self.stock_uom):
matched = False matched = False
else: else:
bin_list = frappe.db.sql("select * from tabBin where item_code=%s", bin_list = frappe.db.sql("select * from tabBin where item_code=%s",
self.item_code, as_dict=1) self.item_code, as_dict=1)
for bin in bin_list: for bin in bin_list:
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \ if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(self.stock_uom): or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(self.stock_uom):
matched = False matched = False
break break
if matched and bin_list: if matched and bin_list:
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""",
(self.stock_uom, self.name)) (self.stock_uom, self.name))
if not matched: if not matched:
frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.")) frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module."))
def validate_conversion_factor(self): def validate_conversion_factor(self):
check_list = [] check_list = []
for d in self.get('uom_conversion_details'): for d in self.get('uom_conversion_details'):
@ -105,12 +103,12 @@ class Item(DocListController):
check_list.append(cstr(d.uom)) check_list.append(cstr(d.uom))
if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1: if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1:
msgprint(_("""Conversion Factor of UOM: %s should be equal to 1. As UOM: %s is Stock UOM of Item: %s.""" % msgprint(_("""Conversion Factor of UOM: %s should be equal to 1. As UOM: %s is Stock UOM of Item: %s.""" %
(d.uom, d.uom, self.name)), raise_exception=1) (d.uom, d.uom, self.name)), raise_exception=1)
elif d.uom and cstr(d.uom)!= self.stock_uom and flt(d.conversion_factor) == 1: elif d.uom and cstr(d.uom)!= self.stock_uom and flt(d.conversion_factor) == 1:
msgprint(_("""Conversion Factor of UOM: %s should not be equal to 1. As UOM: %s is not Stock UOM of Item: %s""" % msgprint(_("""Conversion Factor of UOM: %s should not be equal to 1. As UOM: %s is not Stock UOM of Item: %s""" %
(d.uom, d.uom, self.name)), raise_exception=1) (d.uom, d.uom, self.name)), raise_exception=1)
def validate_item_type(self): def validate_item_type(self):
if cstr(self.is_manufactured_item) == "No": if cstr(self.is_manufactured_item) == "No":
self.is_pro_applicable = "No" self.is_pro_applicable = "No"
@ -121,25 +119,25 @@ class Item(DocListController):
if self.has_serial_no == 'Yes' and self.is_stock_item == 'No': if self.has_serial_no == 'Yes' and self.is_stock_item == 'No':
msgprint("'Has Serial No' can not be 'Yes' for non-stock item", raise_exception=1) msgprint("'Has Serial No' can not be 'Yes' for non-stock item", raise_exception=1)
def check_for_active_boms(self): def check_for_active_boms(self):
if self.is_purchase_item != "Yes": if self.is_purchase_item != "Yes":
bom_mat = frappe.db.sql("""select distinct t1.parent bom_mat = frappe.db.sql("""select distinct t1.parent
from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent
and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1 and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1
and t2.docstatus = 1 and t1.docstatus =1 """, self.name) and t2.docstatus = 1 and t1.docstatus =1 """, self.name)
if bom_mat and bom_mat[0][0]: if bom_mat and bom_mat[0][0]:
frappe.throw(_("Item must be a purchase item, \ frappe.throw(_("Item must be a purchase item, \
as it is present in one or many Active BOMs")) as it is present in one or many Active BOMs"))
if self.is_manufactured_item != "Yes": if self.is_manufactured_item != "Yes":
bom = frappe.db.sql("""select name from `tabBOM` where item = %s bom = frappe.db.sql("""select name from `tabBOM` where item = %s
and is_active = 1""", (self.name,)) and is_active = 1""", (self.name,))
if bom and bom[0][0]: if bom and bom[0][0]:
frappe.throw(_("""Allow Bill of Materials should be 'Yes'. Because one or many \ frappe.throw(_("""Allow Bill of Materials should be 'Yes'. Because one or many \
active BOMs present for this item""")) active BOMs present for this item"""))
def fill_customer_code(self): def fill_customer_code(self):
""" Append all the customer codes and insert into "customer_code" field of item table """ """ Append all the customer codes and insert into "customer_code" field of item table """
cust_code=[] cust_code=[]
@ -153,7 +151,7 @@ class Item(DocListController):
for d in self.get('item_tax'): for d in self.get('item_tax'):
if d.tax_type: if d.tax_type:
account_type = frappe.db.get_value("Account", d.tax_type, "account_type") account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']: if account_type not in ['Tax', 'Chargeable', 'Income Account', 'Expense Account']:
msgprint("'%s' is not Tax / Chargeable / Income / Expense Account" % d.tax_type, raise_exception=1) msgprint("'%s' is not Tax / Chargeable / Income / Expense Account" % d.tax_type, raise_exception=1)
else: else:
@ -161,34 +159,34 @@ class Item(DocListController):
msgprint("Rate is entered twice for: '%s'" % d.tax_type, raise_exception=1) msgprint("Rate is entered twice for: '%s'" % d.tax_type, raise_exception=1)
else: else:
check_list.append(d.tax_type) check_list.append(d.tax_type)
def validate_barcode(self): def validate_barcode(self):
if self.barcode: if self.barcode:
duplicate = frappe.db.sql("""select name from tabItem where barcode = %s duplicate = frappe.db.sql("""select name from tabItem where barcode = %s
and name != %s""", (self.barcode, self.name)) and name != %s""", (self.barcode, self.name))
if duplicate: if duplicate:
msgprint("Barcode: %s already used in item: %s" % msgprint("Barcode: %s already used in item: %s" %
(self.barcode, cstr(duplicate[0][0])), raise_exception = 1) (self.barcode, cstr(duplicate[0][0])), raise_exception = 1)
def cant_change(self): def cant_change(self):
if not self.get("__islocal"): if not self.get("__islocal"):
vals = frappe.db.get_value("Item", self.name, vals = frappe.db.get_value("Item", self.name,
["has_serial_no", "is_stock_item", "valuation_method"], as_dict=True) ["has_serial_no", "is_stock_item", "valuation_method"], as_dict=True)
if vals and ((self.is_stock_item == "No" and vals.is_stock_item == "Yes") or if vals and ((self.is_stock_item == "No" and vals.is_stock_item == "Yes") or
vals.has_serial_no != self.has_serial_no or vals.has_serial_no != self.has_serial_no or
cstr(vals.valuation_method) != cstr(self.valuation_method)): cstr(vals.valuation_method) != cstr(self.valuation_method)):
if self.check_if_sle_exists() == "exists": if self.check_if_sle_exists() == "exists":
frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Is Stock Item' and 'Valuation Method'")) frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Is Stock Item' and 'Valuation Method'"))
def validate_item_type_for_reorder(self): def validate_item_type_for_reorder(self):
if self.re_order_level or len(self.get("item_reorder", {"material_request_type": "Purchase"})): if self.re_order_level or len(self.get("item_reorder", {"material_request_type": "Purchase"})):
if not self.is_purchase_item: if not self.is_purchase_item:
frappe.msgprint(_("""To set reorder level, item must be Purchase Item"""), frappe.msgprint(_("""To set reorder level, item must be Purchase Item"""),
raise_exception=1) raise_exception=1)
def check_if_sle_exists(self): def check_if_sle_exists(self):
sle = frappe.db.sql("""select name from `tabStock Ledger Entry` sle = frappe.db.sql("""select name from `tabStock Ledger Entry`
where item_code = %s""", self.name) where item_code = %s""", self.name)
return sle and 'exists' or 'not exists' return sle and 'exists' or 'not exists'
@ -196,11 +194,11 @@ class Item(DocListController):
# causes problem with tree build # causes problem with tree build
if frappe.db.exists("Item Group", self.name): if frappe.db.exists("Item Group", self.name):
frappe.msgprint("An item group exists with same name (%s), \ frappe.msgprint("An item group exists with same name (%s), \
please change the item name or rename the item group" % please change the item name or rename the item group" %
self.name, raise_exception=1) self.name, raise_exception=1)
def update_item_price(self): def update_item_price(self):
frappe.db.sql("""update `tabItem Price` set item_name=%s, frappe.db.sql("""update `tabItem Price` set item_name=%s,
item_description=%s, modified=NOW() where item_code=%s""", item_description=%s, modified=NOW() where item_code=%s""",
(self.item_name, self.description, self.name)) (self.item_name, self.description, self.name))
@ -209,9 +207,9 @@ class Item(DocListController):
page_name_from = self.name page_name_from = self.name
else: else:
page_name_from = self.name + " " + self.item_name page_name_from = self.name + " " + self.item_name
return page_name_from return page_name_from
def get_tax_rate(self, tax_type): def get_tax_rate(self, tax_type):
return { "tax_rate": frappe.db.get_value("Account", tax_type, "tax_rate") } return { "tax_rate": frappe.db.get_value("Account", tax_type, "tax_rate") }
@ -223,8 +221,9 @@ class Item(DocListController):
'description' : file and file[0]['description'] or '' 'description' : file and file[0]['description'] or ''
} }
return ret return ret
def on_trash(self): def on_trash(self):
super(Item, self).on_trash()
frappe.db.sql("""delete from tabBin where item_code=%s""", self.item_code) frappe.db.sql("""delete from tabBin where item_code=%s""", self.item_code)
def before_rename(self, olddn, newdn, merge=False): def before_rename(self, olddn, newdn, merge=False):
@ -232,7 +231,7 @@ class Item(DocListController):
# Validate properties before merging # Validate properties before merging
if not frappe.db.exists("Item", newdn): if not frappe.db.exists("Item", newdn):
frappe.throw(_("Item ") + newdn +_(" does not exists")) frappe.throw(_("Item ") + newdn +_(" does not exists"))
field_list = ["stock_uom", "is_stock_item", "has_serial_no", "has_batch_no"] field_list = ["stock_uom", "is_stock_item", "has_serial_no", "has_batch_no"]
new_properties = [cstr(d) for d in frappe.db.get_value("Item", newdn, field_list)] new_properties = [cstr(d) for d in frappe.db.get_value("Item", newdn, field_list)]
if new_properties != [cstr(self.get(fld)) for fld in field_list]: if new_properties != [cstr(self.get(fld)) for fld in field_list]:
@ -242,32 +241,33 @@ class Item(DocListController):
frappe.db.sql("delete from `tabBin` where item_code=%s", olddn) frappe.db.sql("delete from `tabBin` where item_code=%s", olddn)
def after_rename(self, olddn, newdn, merge): def after_rename(self, olddn, newdn, merge):
super(Item, self).after_rename(olddn, newdn, merge)
frappe.db.set_value("Item", newdn, "item_code", newdn) frappe.db.set_value("Item", newdn, "item_code", newdn)
if merge: if merge:
self.set_last_purchase_rate(newdn) self.set_last_purchase_rate(newdn)
self.recalculate_bin_qty(newdn) self.recalculate_bin_qty(newdn)
def set_last_purchase_rate(self, newdn): def set_last_purchase_rate(self, newdn):
last_purchase_rate = get_last_purchase_details(newdn).get("base_rate", 0) last_purchase_rate = get_last_purchase_details(newdn).get("base_rate", 0)
frappe.db.set_value("Item", newdn, "last_purchase_rate", last_purchase_rate) frappe.db.set_value("Item", newdn, "last_purchase_rate", last_purchase_rate)
def recalculate_bin_qty(self, newdn): def recalculate_bin_qty(self, newdn):
from erpnext.utilities.repost_stock import repost_stock from erpnext.utilities.repost_stock import repost_stock
frappe.db.auto_commit_on_many_writes = 1 frappe.db.auto_commit_on_many_writes = 1
frappe.db.set_default("allow_negative_stock", 1) frappe.db.set_default("allow_negative_stock", 1)
for warehouse in frappe.db.sql("select name from `tabWarehouse`"): for warehouse in frappe.db.sql("select name from `tabWarehouse`"):
repost_stock(newdn, warehouse[0]) repost_stock(newdn, warehouse[0])
frappe.db.set_default("allow_negative_stock", frappe.db.set_default("allow_negative_stock",
frappe.db.get_value("Stock Settings", None, "allow_negative_stock")) frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
frappe.db.auto_commit_on_many_writes = 0 frappe.db.auto_commit_on_many_writes = 0
def validate_end_of_life(item_code, end_of_life=None, verbose=1): def validate_end_of_life(item_code, end_of_life=None, verbose=1):
if not end_of_life: if not end_of_life:
end_of_life = frappe.db.get_value("Item", item_code, "end_of_life") end_of_life = frappe.db.get_value("Item", item_code, "end_of_life")
if end_of_life and getdate(end_of_life) <= now_datetime().date(): if end_of_life and getdate(end_of_life) <= now_datetime().date():
msg = (_("Item") + " %(item_code)s: " + _("reached its end of life on") + \ msg = (_("Item") + " %(item_code)s: " + _("reached its end of life on") + \
" %(date)s. " + _("Please check") + ": %(end_of_life_label)s " + \ " %(date)s. " + _("Please check") + ": %(end_of_life_label)s " + \
@ -276,29 +276,29 @@ def validate_end_of_life(item_code, end_of_life=None, verbose=1):
"date": formatdate(end_of_life), "date": formatdate(end_of_life),
"end_of_life_label": frappe.get_meta("Item").get_label("end_of_life") "end_of_life_label": frappe.get_meta("Item").get_label("end_of_life")
} }
_msgprint(msg, verbose) _msgprint(msg, verbose)
def validate_is_stock_item(item_code, is_stock_item=None, verbose=1): def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
if not is_stock_item: if not is_stock_item:
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item") is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
if is_stock_item != "Yes": if is_stock_item != "Yes":
msg = (_("Item") + " %(item_code)s: " + _("is not a Stock Item")) % { msg = (_("Item") + " %(item_code)s: " + _("is not a Stock Item")) % {
"item_code": item_code, "item_code": item_code,
} }
_msgprint(msg, verbose) _msgprint(msg, verbose)
def validate_cancelled_item(item_code, docstatus=None, verbose=1): def validate_cancelled_item(item_code, docstatus=None, verbose=1):
if docstatus is None: if docstatus is None:
docstatus = frappe.db.get_value("Item", item_code, "docstatus") docstatus = frappe.db.get_value("Item", item_code, "docstatus")
if docstatus == 2: if docstatus == 2:
msg = (_("Item") + " %(item_code)s: " + _("is a cancelled Item")) % { msg = (_("Item") + " %(item_code)s: " + _("is a cancelled Item")) % {
"item_code": item_code, "item_code": item_code,
} }
_msgprint(msg, verbose) _msgprint(msg, verbose)
def _msgprint(msg, verbose): def _msgprint(msg, verbose):
@ -306,22 +306,22 @@ def _msgprint(msg, verbose):
msgprint(msg, raise_exception=True) msgprint(msg, raise_exception=True)
else: else:
raise frappe.ValidationError, msg raise frappe.ValidationError, msg
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0): def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"""returns last purchase details in stock uom""" """returns last purchase details in stock uom"""
# get last purchase order item details # get last purchase order item details
last_purchase_order = frappe.db.sql("""\ last_purchase_order = frappe.db.sql("""\
select po.name, po.transaction_date, po.conversion_rate, select po.name, po.transaction_date, po.conversion_rate,
po_item.conversion_factor, po_item.base_price_list_rate, po_item.conversion_factor, po_item.base_price_list_rate,
po_item.discount_percentage, po_item.base_rate po_item.discount_percentage, po_item.base_rate
from `tabPurchase Order` po, `tabPurchase Order Item` po_item from `tabPurchase Order` po, `tabPurchase Order Item` po_item
where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and
po.name = po_item.parent po.name = po_item.parent
order by po.transaction_date desc, po.name desc order by po.transaction_date desc, po.name desc
limit 1""", (item_code, cstr(doc_name)), as_dict=1) limit 1""", (item_code, cstr(doc_name)), as_dict=1)
# get last purchase receipt item details # get last purchase receipt item details
last_purchase_receipt = frappe.db.sql("""\ last_purchase_receipt = frappe.db.sql("""\
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate, select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage, pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage,
@ -342,16 +342,16 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
# use purchase order # use purchase order
last_purchase = last_purchase_order[0] last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date purchase_date = purchase_order_date
elif (purchase_receipt_date > purchase_order_date) or \ elif (purchase_receipt_date > purchase_order_date) or \
(last_purchase_receipt and not last_purchase_order): (last_purchase_receipt and not last_purchase_order):
# use purchase receipt # use purchase receipt
last_purchase = last_purchase_receipt[0] last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date purchase_date = purchase_receipt_date
else: else:
return frappe._dict() return frappe._dict()
conversion_factor = flt(last_purchase.conversion_factor) conversion_factor = flt(last_purchase.conversion_factor)
out = frappe._dict({ out = frappe._dict({
"base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor, "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
@ -366,5 +366,5 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"rate": out.base_rate / conversion_rate, "rate": out.base_rate / conversion_rate,
"base_rate": out.base_rate "base_rate": out.base_rate
}) })
return out return out

View File

@ -169,11 +169,10 @@ def update_completed_qty(doc, method):
for mr_name, mr_items in material_request_map.items(): for mr_name, mr_items in material_request_map.items():
mr_obj = frappe.get_doc("Material Request", mr_name) mr_obj = frappe.get_doc("Material Request", mr_name)
mr_doctype = frappe.get_meta("Material Request")
if mr_obj.status in ["Stopped", "Cancelled"]: if mr_obj.status in ["Stopped", "Cancelled"]:
frappe.throw(_("Material Request") + ": %s, " % mr_obj.name frappe.throw(_("Material Request") + ": %s, " % mr_obj.name
+ _(mr_doctype.get_label("status")) + " = %s. " % _(mr_obj.status) + _(mr_obj.meta.get_label("status")) + " = %s. " % _(mr_obj.status)
+ _("Cannot continue."), exc=frappe.InvalidStatusError) + _("Cannot continue."), exc=frappe.InvalidStatusError)
_update_requested_qty(doc, mr_obj, mr_items) _update_requested_qty(doc, mr_obj, mr_items)

View File

@ -24,7 +24,7 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit() mr.submit()
po = make_purchase_order(mr.name) po = make_purchase_order(mr.name)
self.assertEquals(po["doctype"], "Purchase Order") self.assertEquals(po.doctype, "Purchase Order")
self.assertEquals(len(po.get("po_details")), len(mr.get("indent_details"))) self.assertEquals(len(po.get("po_details")), len(mr.get("indent_details")))
def test_make_supplier_quotation(self): def test_make_supplier_quotation(self):
@ -38,7 +38,7 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit() mr.submit()
sq = make_supplier_quotation(mr.name) sq = make_supplier_quotation(mr.name)
self.assertEquals(sq["doctype"], "Supplier Quotation") self.assertEquals(sq.doctype, "Supplier Quotation")
self.assertEquals(len(sq.get("quotation_items")), len(mr.get("indent_details"))) self.assertEquals(len(sq.get("quotation_items")), len(mr.get("indent_details")))
@ -55,14 +55,9 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit() mr.submit()
se = make_stock_entry(mr.name) se = make_stock_entry(mr.name)
self.assertEquals(se["doctype"], "Stock Entry") self.assertEquals(se.doctype, "Stock Entry")
self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details"))) self.assertEquals(len(se.get("mtn_details")), len(mr.get("indent_details")))
def _test_expected(self, doc, expected_values):
for i, expected in enumerate(expected_values):
for fieldname, val in expected.items():
self.assertEquals(val, doc.get(fieldname))
def _test_requested_qty(self, qty1, qty2): def _test_requested_qty(self, qty1, qty2):
self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100", self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
"warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty1) "warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty1)
@ -116,28 +111,34 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit() mr.submit()
# check if per complete is None # check if per complete is None
self._test_expected(mr, [{"per_ordered": None}, {"ordered_qty": None}, {"ordered_qty": None}]) self.assertEquals(mr.per_ordered, None)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) self._test_requested_qty(54.0, 3.0)
# map a purchase order # map a purchase order
from erpnext.stock.doctype.material_request.material_request import make_purchase_order from erpnext.stock.doctype.material_request.material_request import make_purchase_order
po_doc = make_purchase_order(mr.name) po_doc = make_purchase_order(mr.name)
po_doc["supplier"] = "_Test Supplier" po_doc.supplier = "_Test Supplier"
po_doc["transaction_date"] = "2013-07-07" po_doc.transaction_date = "2013-07-07"
po_doc.get("po_details")[0]["qty"] = 27.0 po_doc.get("po_details")[0].qty = 27.0
po_doc.get("po_details")[1]["qty"] = 1.5 po_doc.get("po_details")[1].qty = 1.5
po_doc.get("po_details")[0]["schedule_date"] = "2013-07-09" po_doc.get("po_details")[0].schedule_date = "2013-07-09"
po_doc.get("po_details")[1]["schedule_date"] = "2013-07-09" po_doc.get("po_details")[1].schedule_date = "2013-07-09"
# check for stopped status of Material Request # check for stopped status of Material Request
po = frappe.copy_doc(po_doc) po = frappe.copy_doc(po_doc)
po.insert() po.insert()
po.load_from_db()
mr.update_status('Stopped') mr.update_status('Stopped')
self.assertRaises(frappe.ValidationError, po.submit) self.assertRaises(frappe.InvalidStatusError, po.submit)
self.assertRaises(frappe.ValidationError, po.cancel) frappe.db.set(po, "docstatus", 1)
self.assertRaises(frappe.InvalidStatusError, po.cancel)
# resubmit and check for per complete
mr.load_from_db()
mr.update_status('Submitted') mr.update_status('Submitted')
po = frappe.copy_doc(po_doc) po = frappe.copy_doc(po_doc)
po.insert() po.insert()
@ -145,13 +146,18 @@ class TestMaterialRequest(unittest.TestCase):
# check if per complete is as expected # check if per complete is as expected
mr.load_from_db() mr.load_from_db()
self._test_expected(mr, [{"per_ordered": 50}, {"ordered_qty": 27.0}, {"ordered_qty": 1.5}]) self.assertEquals(mr.per_ordered, 50)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5)
self._test_requested_qty(27.0, 1.5) self._test_requested_qty(27.0, 1.5)
po.cancel() po.cancel()
# check if per complete is as expected # check if per complete is as expected
mr.load_from_db() mr.load_from_db()
self._test_expected(mr, [{"per_ordered": None}, {"ordered_qty": None}, {"ordered_qty": None}]) self.assertEquals(mr.per_ordered, None)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, None)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, None)
self._test_requested_qty(54.0, 3.0) self._test_requested_qty(54.0, 3.0)
def test_completed_qty_for_transfer(self): def test_completed_qty_for_transfer(self):
@ -165,7 +171,9 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit() mr.submit()
# check if per complete is None # check if per complete is None
self._test_expected(mr, [{"per_ordered": None}, {"ordered_qty": None}, {"ordered_qty": None}]) self.assertEquals(mr.per_ordered, None)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) self._test_requested_qty(54.0, 3.0)
@ -198,8 +206,12 @@ class TestMaterialRequest(unittest.TestCase):
se = frappe.copy_doc(se_doc) se = frappe.copy_doc(se_doc)
se.insert() se.insert()
mr.update_status('Stopped') mr.update_status('Stopped')
self.assertRaises(frappe.ValidationError, se.submit) self.assertRaises(frappe.InvalidStatusError, se.submit)
self.assertRaises(frappe.ValidationError, se.cancel)
mr.update_status('Submitted')
se.submit()
mr.update_status('Stopped')
self.assertRaises(frappe.InvalidStatusError, se.cancel)
mr.update_status('Submitted') mr.update_status('Submitted')
se = frappe.copy_doc(se_doc) se = frappe.copy_doc(se_doc)
@ -208,13 +220,19 @@ class TestMaterialRequest(unittest.TestCase):
# check if per complete is as expected # check if per complete is as expected
mr.load_from_db() mr.load_from_db()
self._test_expected(mr, [{"per_ordered": 50}, {"ordered_qty": 27.0}, {"ordered_qty": 1.5}]) self.assertEquals(mr.per_ordered, 50)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 27.0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 1.5)
self._test_requested_qty(27.0, 1.5) self._test_requested_qty(27.0, 1.5)
# check if per complete is as expected for Stock Entry cancelled # check if per complete is as expected for Stock Entry cancelled
se.cancel() se.cancel()
mr.load_from_db() mr.load_from_db()
self._test_expected(mr, [{"per_ordered": 0}, {"ordered_qty": 0}, {"ordered_qty": 0}]) self.assertEquals(mr.per_ordered, 0)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) self._test_requested_qty(54.0, 3.0)
def test_completed_qty_for_over_transfer(self): def test_completed_qty_for_over_transfer(self):
@ -228,7 +246,9 @@ class TestMaterialRequest(unittest.TestCase):
mr.submit() mr.submit()
# check if per complete is None # check if per complete is None
self._test_expected(mr, [{"per_ordered": None}, {"ordered_qty": None}, {"ordered_qty": None}]) self.assertEquals(mr.per_ordered, None)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) self._test_requested_qty(54.0, 3.0)
@ -261,8 +281,8 @@ class TestMaterialRequest(unittest.TestCase):
se = frappe.copy_doc(se_doc) se = frappe.copy_doc(se_doc)
se.insert() se.insert()
mr.update_status('Stopped') mr.update_status('Stopped')
self.assertRaises(frappe.ValidationError, se.submit) self.assertRaises(frappe.InvalidStatusError, se.submit)
self.assertRaises(frappe.ValidationError, se.cancel) self.assertRaises(frappe.InvalidStatusError, se.cancel)
mr.update_status('Submitted') mr.update_status('Submitted')
se = frappe.copy_doc(se_doc) se = frappe.copy_doc(se_doc)
@ -271,13 +291,19 @@ class TestMaterialRequest(unittest.TestCase):
# check if per complete is as expected # check if per complete is as expected
mr.load_from_db() mr.load_from_db()
self._test_expected(mr, [{"per_ordered": 100}, {"ordered_qty": 60.0}, {"ordered_qty": 3.0}])
self.assertEquals(mr.per_ordered, 100)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 60.0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 3.0)
self._test_requested_qty(0.0, 0.0) self._test_requested_qty(0.0, 0.0)
# check if per complete is as expected for Stock Entry cancelled # check if per complete is as expected for Stock Entry cancelled
se.cancel() se.cancel()
mr.load_from_db() mr.load_from_db()
self._test_expected(mr, [{"per_ordered": 0}, {"ordered_qty": 0}, {"ordered_qty": 0}]) self.assertEquals(mr.per_ordered, 0)
self.assertEquals(mr.get("indent_details")[0].ordered_qty, 0)
self.assertEquals(mr.get("indent_details")[1].ordered_qty, 0)
self._test_requested_qty(54.0, 3.0) self._test_requested_qty(54.0, 3.0)
def test_incorrect_mapping_of_stock_entry(self): def test_incorrect_mapping_of_stock_entry(self):

View File

@ -14,19 +14,21 @@ from erpnext.controllers.buying_controller import BuyingController
class PurchaseReceipt(BuyingController): class PurchaseReceipt(BuyingController):
tname = 'Purchase Receipt Item' tname = 'Purchase Receipt Item'
fname = 'purchase_receipt_details' fname = 'purchase_receipt_details'
count = 0
status_updater = [{ def __init__(self, arg1, arg2=None):
'source_dt': 'Purchase Receipt Item', super(PurchaseReceipt, self).__init__(arg1, arg2)
'target_dt': 'Purchase Order Item', self.status_updater = [{
'join_field': 'prevdoc_detail_docname', 'source_dt': 'Purchase Receipt Item',
'target_field': 'received_qty', 'target_dt': 'Purchase Order Item',
'target_parent_dt': 'Purchase Order', 'join_field': 'prevdoc_detail_docname',
'target_parent_field': 'per_received', 'target_field': 'received_qty',
'target_ref_field': 'qty', 'target_parent_dt': 'Purchase Order',
'source_field': 'qty', 'target_parent_field': 'per_received',
'percent_join_field': 'prevdoc_docname', 'target_ref_field': 'qty',
}] 'source_field': 'qty',
'percent_join_field': 'prevdoc_docname',
}]
def onload(self): def onload(self):
billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabPurchase Invoice Item` billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabPurchase Invoice Item`
where purchase_receipt=%s""", self.name) where purchase_receipt=%s""", self.name)
@ -36,7 +38,7 @@ class PurchaseReceipt(BuyingController):
def validate(self): def validate(self):
super(PurchaseReceipt, self).validate() super(PurchaseReceipt, self).validate()
self.po_required() self.po_required()
if not self.status: if not self.status:
@ -60,7 +62,7 @@ class PurchaseReceipt(BuyingController):
# sub-contracting # sub-contracting
self.validate_for_subcontracting() self.validate_for_subcontracting()
self.update_raw_materials_supplied("pr_raw_material_details") self.update_raw_materials_supplied("pr_raw_material_details")
self.update_valuation_rate("purchase_receipt_details") self.update_valuation_rate("purchase_receipt_details")
def validate_rejected_warehouse(self): def validate_rejected_warehouse(self):
@ -68,7 +70,7 @@ class PurchaseReceipt(BuyingController):
if flt(d.rejected_qty) and not d.rejected_warehouse: if flt(d.rejected_qty) and not d.rejected_warehouse:
d.rejected_warehouse = self.rejected_warehouse d.rejected_warehouse = self.rejected_warehouse
if not d.rejected_warehouse: if not d.rejected_warehouse:
frappe.throw(_("Rejected Warehouse is mandatory against regected item")) frappe.throw(_("Rejected Warehouse is mandatory against regected item"))
# validate accepted and rejected qty # validate accepted and rejected qty
def validate_accepted_rejected_qty(self): def validate_accepted_rejected_qty(self):
@ -99,7 +101,7 @@ class PurchaseReceipt(BuyingController):
if exists: if exists:
frappe.msgprint("Another Purchase Receipt using the same Challan No. already exists.\ frappe.msgprint("Another Purchase Receipt using the same Challan No. already exists.\
Please enter a valid Challan No.", raise_exception=1) Please enter a valid Challan No.", raise_exception=1)
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(PurchaseReceipt, self).validate_with_previous_doc(self.tname, { super(PurchaseReceipt, self).validate_with_previous_doc(self.tname, {
"Purchase Order": { "Purchase Order": {
@ -112,7 +114,7 @@ class PurchaseReceipt(BuyingController):
"is_child_table": True "is_child_table": True
} }
}) })
if cint(frappe.defaults.get_global_default('maintain_same_rate')): if cint(frappe.defaults.get_global_default('maintain_same_rate')):
super(PurchaseReceipt, self).validate_with_previous_doc(self.tname, { super(PurchaseReceipt, self).validate_with_previous_doc(self.tname, {
"Purchase Order Item": { "Purchase Order Item": {
@ -121,7 +123,7 @@ class PurchaseReceipt(BuyingController):
"is_child_table": True "is_child_table": True
} }
}) })
def po_required(self): def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
@ -133,18 +135,18 @@ class PurchaseReceipt(BuyingController):
def update_stock(self): def update_stock(self):
sl_entries = [] sl_entries = []
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
for d in self.get('purchase_receipt_details'): for d in self.get('purchase_receipt_details'):
if d.item_code in stock_items and d.warehouse: if d.item_code in stock_items and d.warehouse:
pr_qty = flt(d.qty) * flt(d.conversion_factor) pr_qty = flt(d.qty) * flt(d.conversion_factor)
if pr_qty: if pr_qty:
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"actual_qty": flt(pr_qty), "actual_qty": flt(pr_qty),
"serial_no": cstr(d.serial_no).strip(), "serial_no": cstr(d.serial_no).strip(),
"incoming_rate": d.valuation_rate "incoming_rate": d.valuation_rate
})) }))
if flt(d.rejected_qty) > 0: if flt(d.rejected_qty) > 0:
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"warehouse": d.rejected_warehouse, "warehouse": d.rejected_warehouse,
@ -152,28 +154,28 @@ class PurchaseReceipt(BuyingController):
"serial_no": cstr(d.rejected_serial_no).strip(), "serial_no": cstr(d.rejected_serial_no).strip(),
"incoming_rate": d.valuation_rate "incoming_rate": d.valuation_rate
})) }))
self.bk_flush_supp_wh(sl_entries) self.bk_flush_supp_wh(sl_entries)
self.make_sl_entries(sl_entries) self.make_sl_entries(sl_entries)
def update_ordered_qty(self): def update_ordered_qty(self):
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
for d in self.get("purchase_receipt_details"): for d in self.get("purchase_receipt_details"):
if d.item_code in stock_items and d.warehouse \ if d.item_code in stock_items and d.warehouse \
and cstr(d.prevdoc_doctype) == 'Purchase Order': and cstr(d.prevdoc_doctype) == 'Purchase Order':
already_received_qty = self.get_already_received_qty(d.prevdoc_docname, already_received_qty = self.get_already_received_qty(d.prevdoc_docname,
d.prevdoc_detail_docname) d.prevdoc_detail_docname)
po_qty, ordered_warehouse = self.get_po_qty_and_warehouse(d.prevdoc_detail_docname) po_qty, ordered_warehouse = self.get_po_qty_and_warehouse(d.prevdoc_detail_docname)
if not ordered_warehouse: if not ordered_warehouse:
frappe.throw(_("Warehouse is missing in Purchase Order")) frappe.throw(_("Warehouse is missing in Purchase Order"))
if already_received_qty + d.qty > po_qty: if already_received_qty + d.qty > po_qty:
ordered_qty = - (po_qty - already_received_qty) * flt(d.conversion_factor) ordered_qty = - (po_qty - already_received_qty) * flt(d.conversion_factor)
else: else:
ordered_qty = - flt(d.qty) * flt(d.conversion_factor) ordered_qty = - flt(d.qty) * flt(d.conversion_factor)
update_bin({ update_bin({
"item_code": d.item_code, "item_code": d.item_code,
"warehouse": ordered_warehouse, "warehouse": ordered_warehouse,
@ -182,20 +184,20 @@ class PurchaseReceipt(BuyingController):
}) })
def get_already_received_qty(self, po, po_detail): def get_already_received_qty(self, po, po_detail):
qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item` qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
where prevdoc_detail_docname = %s and docstatus = 1 where prevdoc_detail_docname = %s and docstatus = 1
and prevdoc_doctype='Purchase Order' and prevdoc_docname=%s and prevdoc_doctype='Purchase Order' and prevdoc_docname=%s
and parent != %s""", (po_detail, po, self.name)) and parent != %s""", (po_detail, po, self.name))
return qty and flt(qty[0][0]) or 0.0 return qty and flt(qty[0][0]) or 0.0
def get_po_qty_and_warehouse(self, po_detail): def get_po_qty_and_warehouse(self, po_detail):
po_qty, po_warehouse = frappe.db.get_value("Purchase Order Item", po_detail, po_qty, po_warehouse = frappe.db.get_value("Purchase Order Item", po_detail,
["qty", "warehouse"]) ["qty", "warehouse"])
return po_qty, po_warehouse return po_qty, po_warehouse
def bk_flush_supp_wh(self, sl_entries): def bk_flush_supp_wh(self, sl_entries):
for d in self.get('pr_raw_material_details'): for d in self.get('pr_raw_material_details'):
# negative quantity is passed as raw material qty has to be decreased # negative quantity is passed as raw material qty has to be decreased
# when PR is submitted and it has to be increased when PR is cancelled # when PR is submitted and it has to be increased when PR is cancelled
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"item_code": d.rm_item_code, "item_code": d.rm_item_code,
@ -231,22 +233,22 @@ class PurchaseReceipt(BuyingController):
frappe.db.set(self, 'status', 'Submitted') frappe.db.set(self, 'status', 'Submitted')
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_ordered_qty() self.update_ordered_qty()
self.update_stock() self.update_stock()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "purchase_receipt_details") update_serial_nos_after_submit(self, "purchase_receipt_details")
purchase_controller.update_last_purchase_rate(self, 1) purchase_controller.update_last_purchase_rate(self, 1)
self.make_gl_entries() self.make_gl_entries()
def check_next_docstatus(self): def check_next_docstatus(self):
submit_rv = frappe.db.sql("""select t1.name submit_rv = frappe.db.sql("""select t1.name
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""", where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""",
(self.name)) (self.name))
if submit_rv: if submit_rv:
msgprint("Purchase Invoice : " + cstr(self.submit_rv[0][0]) + " has already been submitted !") msgprint("Purchase Invoice : " + cstr(self.submit_rv[0][0]) + " has already been submitted !")
@ -258,25 +260,25 @@ class PurchaseReceipt(BuyingController):
self.check_for_stopped_status(pc_obj) self.check_for_stopped_status(pc_obj)
# Check if Purchase Invoice has been submitted against current Purchase Order # Check if Purchase Invoice has been submitted against current Purchase Order
submitted = frappe.db.sql("""select t1.name submitted = frappe.db.sql("""select t1.name
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""", where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""",
self.name) self.name)
if submitted: if submitted:
frappe.throw("Purchase Invoice : " + cstr(submitted[0][0]) + frappe.throw("Purchase Invoice : " + cstr(submitted[0][0]) +
" has already been submitted !") " has already been submitted !")
frappe.db.set(self,'status','Cancelled') frappe.db.set(self,'status','Cancelled')
self.update_ordered_qty() self.update_ordered_qty()
self.update_stock() self.update_stock()
self.update_prevdoc_status() self.update_prevdoc_status()
pc_obj.update_last_purchase_rate(self, 0) pc_obj.update_last_purchase_rate(self, 0)
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
def get_current_stock(self): def get_current_stock(self):
for d in self.get('pr_raw_material_details'): for d in self.get('pr_raw_material_details'):
if self.supplier_warehouse: if self.supplier_warehouse:
@ -285,42 +287,42 @@ class PurchaseReceipt(BuyingController):
def get_rate(self,arg): def get_rate(self,arg):
return frappe.get_doc('Purchase Common').get_rate(arg,self) return frappe.get_doc('Purchase Common').get_rate(arg,self)
def get_gl_entries(self, warehouse_account=None): def get_gl_entries(self, warehouse_account=None):
against_stock_account = self.get_company_default("stock_received_but_not_billed") against_stock_account = self.get_company_default("stock_received_but_not_billed")
gl_entries = super(PurchaseReceipt, self).get_gl_entries(warehouse_account, against_stock_account) gl_entries = super(PurchaseReceipt, self).get_gl_entries(warehouse_account, against_stock_account)
return gl_entries return gl_entries
@frappe.whitelist() @frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None): def make_purchase_invoice(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target): def set_missing_values(source, target):
doc = frappe.get_doc(target) doc = frappe.get_doc(target)
doc.run_method("set_missing_values") doc.run_method("set_missing_values")
doclist = get_mapped_doc("Purchase Receipt", source_name, { doclist = get_mapped_doc("Purchase Receipt", source_name, {
"Purchase Receipt": { "Purchase Receipt": {
"doctype": "Purchase Invoice", "doctype": "Purchase Invoice",
"validation": { "validation": {
"docstatus": ["=", 1], "docstatus": ["=", 1],
} }
}, },
"Purchase Receipt Item": { "Purchase Receipt Item": {
"doctype": "Purchase Invoice Item", "doctype": "Purchase Invoice Item",
"field_map": { "field_map": {
"name": "pr_detail", "name": "pr_detail",
"parent": "purchase_receipt", "parent": "purchase_receipt",
"prevdoc_detail_docname": "po_detail", "prevdoc_detail_docname": "po_detail",
"prevdoc_docname": "purchase_order", "prevdoc_docname": "purchase_order",
}, },
}, },
"Purchase Taxes and Charges": { "Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges", "doctype": "Purchase Taxes and Charges",
"add_if_empty": True "add_if_empty": True
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist return doclist

View File

@ -15,88 +15,88 @@ class TestPurchaseReceipt(unittest.TestCase):
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
pr = frappe.copy_doc(test_records[0]).insert() pr = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(frappe.ValidationError, make_purchase_invoice, self.assertRaises(frappe.ValidationError, make_purchase_invoice,
pr.name) pr.name)
pr = frappe.get_doc("Purchase Receipt", pr.name) pr = frappe.get_doc("Purchase Receipt", pr.name)
pr.submit() pr.submit()
pi = make_purchase_invoice(pr.name) pi = make_purchase_invoice(pr.name)
self.assertEquals(pi["doctype"], "Purchase Invoice") self.assertEquals(pi.doctype, "Purchase Invoice")
self.assertEquals(len(pi.get("entries")), len(pr.get("purchase_receipt_details"))) self.assertEquals(len(pi.get("entries")), len(pr.get("purchase_receipt_details")))
# modify rate # modify rate
pi.get("entries")[0]["rate"] = 200 pi.get("entries")[0].rate = 200
self.assertRaises(frappe.ValidationError, frappe.get_doc(pi).submit) self.assertRaises(frappe.ValidationError, frappe.get_doc(pi).submit)
def test_purchase_receipt_no_gl_entry(self): def test_purchase_receipt_no_gl_entry(self):
self._clear_stock_account_balance() self._clear_stock_account_balance()
set_perpetual_inventory(0) set_perpetual_inventory(0)
pr = frappe.copy_doc(test_records[0]) pr = frappe.copy_doc(test_records[0])
pr.insert() pr.insert()
pr.submit() pr.submit()
stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name, {"voucher_type": "Purchase Receipt", "voucher_no": pr.name,
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
["stock_value", "stock_value_difference"]) ["stock_value", "stock_value_difference"])
self.assertEqual(stock_value, 375) self.assertEqual(stock_value, 375)
self.assertEqual(stock_value_difference, 375) self.assertEqual(stock_value_difference, 375)
bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item", bin_stock_value = frappe.db.get_value("Bin", {"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC"}, "stock_value") "warehouse": "_Test Warehouse - _TC"}, "stock_value")
self.assertEqual(bin_stock_value, 375) self.assertEqual(bin_stock_value, 375)
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
def test_purchase_receipt_gl_entry(self): def test_purchase_receipt_gl_entry(self):
self._clear_stock_account_balance() self._clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1) self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1)
pr = frappe.copy_doc(test_records[0]) pr = frappe.copy_doc(test_records[0])
pr.insert() pr.insert()
pr.submit() pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name) gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
stock_in_hand_account = frappe.db.get_value("Account", stock_in_hand_account = frappe.db.get_value("Account",
{"master_name": pr.get("purchase_receipt_details")[0].warehouse}) {"master_name": pr.get("purchase_receipt_details")[0].warehouse})
fixed_asset_account = frappe.db.get_value("Account", fixed_asset_account = frappe.db.get_value("Account",
{"master_name": pr.get("purchase_receipt_details")[1].warehouse}) {"master_name": pr.get("purchase_receipt_details")[1].warehouse})
expected_values = { expected_values = {
stock_in_hand_account: [375.0, 0.0], stock_in_hand_account: [375.0, 0.0],
fixed_asset_account: [375.0, 0.0], fixed_asset_account: [375.0, 0.0],
"Stock Received But Not Billed - _TC": [0.0, 750.0] "Stock Received But Not Billed - _TC": [0.0, 750.0]
} }
for gle in gl_entries: for gle in gl_entries:
self.assertEquals(expected_values[gle.account][0], gle.debit) self.assertEquals(expected_values[gle.account][0], gle.debit)
self.assertEquals(expected_values[gle.account][1], gle.credit) self.assertEquals(expected_values[gle.account][1], gle.credit)
pr.cancel() pr.cancel()
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name)) self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
set_perpetual_inventory(0) set_perpetual_inventory(0)
def _clear_stock_account_balance(self): def _clear_stock_account_balance(self):
frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from `tabStock Ledger Entry`")
frappe.db.sql("""delete from `tabBin`""") frappe.db.sql("""delete from `tabBin`""")
frappe.db.sql("""delete from `tabGL Entry`""") frappe.db.sql("""delete from `tabGL Entry`""")
def test_subcontracting(self): def test_subcontracting(self):
pr = frappe.copy_doc(test_records[1]) pr = frappe.copy_doc(test_records[1])
pr.run_method("calculate_taxes_and_totals") pr.run_method("calculate_taxes_and_totals")
pr.insert() pr.insert()
self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 70000.0) self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 70000.0)
self.assertEquals(len(pr.get("pr_raw_material_details")), 2) self.assertEquals(len(pr.get("pr_raw_material_details")), 2)
def test_serial_no_supplier(self): def test_serial_no_supplier(self):
pr = frappe.copy_doc(test_records[0]) pr = frappe.copy_doc(test_records[0])
pr.get("purchase_receipt_details")[0].item_code = "_Test Serialized Item With Series" pr.get("purchase_receipt_details")[0].item_code = "_Test Serialized Item With Series"
@ -104,30 +104,30 @@ class TestPurchaseReceipt(unittest.TestCase):
pr.get("purchase_receipt_details")[0].received_qty = 1 pr.get("purchase_receipt_details")[0].received_qty = 1
pr.insert() pr.insert()
pr.submit() pr.submit()
self.assertEquals(frappe.db.get_value("Serial No", pr.get("purchase_receipt_details")[0].serial_no, self.assertEquals(frappe.db.get_value("Serial No", pr.get("purchase_receipt_details")[0].serial_no,
"supplier"), pr.supplier) "supplier"), pr.supplier)
return pr return pr
def test_serial_no_cancel(self): def test_serial_no_cancel(self):
pr = self.test_serial_no_supplier() pr = self.test_serial_no_supplier()
pr.cancel() pr.cancel()
self.assertFalse(frappe.db.get_value("Serial No", pr.get("purchase_receipt_details")[0].serial_no, self.assertFalse(frappe.db.get_value("Serial No", pr.get("purchase_receipt_details")[0].serial_no,
"warehouse")) "warehouse"))
def get_gl_entries(voucher_type, voucher_no): def get_gl_entries(voucher_type, voucher_no):
return frappe.db.sql("""select account, debit, credit return frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type=%s and voucher_no=%s from `tabGL Entry` where voucher_type=%s and voucher_no=%s
order by account desc""", (voucher_type, voucher_no), as_dict=1) order by account desc""", (voucher_type, voucher_no), as_dict=1)
def set_perpetual_inventory(enable=1): def set_perpetual_inventory(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.auto_accounting_for_stock = enable accounts_settings.auto_accounting_for_stock = enable
accounts_settings.save() accounts_settings.save()
test_dependencies = ["BOM"] test_dependencies = ["BOM"]
test_records = frappe.get_test_records('Purchase Receipt') test_records = frappe.get_test_records('Purchase Receipt')

View File

@ -407,14 +407,14 @@ class TestStockEntry(unittest.TestCase):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice, make_delivery_note from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice, make_delivery_note
actual_qty_0 = self._get_actual_qty() actual_qty_0 = self._get_actual_qty()
so = frappe.copy_doc(sales_order_test_records[0]) so = frappe.copy_doc(sales_order_test_records[0])
so.get("sales_order_details")[0].item_code = item_code so.get("sales_order_details")[0].item_code = item_code
so.get("sales_order_details")[0].qty = 5.0 so.get("sales_order_details")[0].qty = 5.0
so.insert() so.insert()
so.submit() so.submit()
dn_doc = make_delivery_note(so.name)
dn = frappe.get_doc(dn_doc) dn = make_delivery_note(so.name)
dn.status = "Draft" dn.status = "Draft"
dn.posting_date = so.delivery_date dn.posting_date = so.delivery_date
dn.insert() dn.insert()
@ -423,9 +423,7 @@ class TestStockEntry(unittest.TestCase):
actual_qty_1 = self._get_actual_qty() actual_qty_1 = self._get_actual_qty()
self.assertEquals(actual_qty_0 - delivered_qty, actual_qty_1) self.assertEquals(actual_qty_0 - delivered_qty, actual_qty_1)
si_doc = make_sales_invoice(so.name) si = make_sales_invoice(so.name)
si = frappe.get_doc(si_doc)
si.posting_date = dn.posting_date si.posting_date = dn.posting_date
si.debit_to = "_Test Customer - _TC" si.debit_to = "_Test Customer - _TC"
for d in si.get("entries"): for d in si.get("entries"):

View File

@ -14,15 +14,15 @@ from erpnext.accounts.utils import get_fiscal_year, get_stock_and_account_differ
class TestStockReconciliation(unittest.TestCase): class TestStockReconciliation(unittest.TestCase):
def test_reco_for_fifo(self): def test_reco_for_fifo(self):
frappe.defaults.set_global_default("auto_accounting_for_stock", 0) frappe.defaults.set_global_default("auto_accounting_for_stock", 0)
# [[qty, valuation_rate, posting_date, # [[qty, valuation_rate, posting_date,
# posting_time, expected_stock_value, bin_qty, bin_valuation]] # posting_time, expected_stock_value, bin_qty, bin_valuation]]
input_data = [ input_data = [
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000], [50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0], [5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000], [15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500], [25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000], [20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000], [50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000], [5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
["", 1000, "2012-12-26", "12:05", 15000, 10, 12000], ["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
[20, "", "2012-12-26", "12:05", 16000, 15, 18000], [20, "", "2012-12-26", "12:05", 16000, 15, 18000],
@ -30,142 +30,142 @@ class TestStockReconciliation(unittest.TestCase):
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200], [1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
[0, "", "2012-12-26", "12:10", 0, -5, 0] [0, "", "2012-12-26", "12:10", 0, -5, 0]
] ]
for d in input_data: for d in input_data:
self.cleanup_data() self.cleanup_data()
self.insert_existing_sle("FIFO") self.insert_existing_sle("FIFO")
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3]) stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
# check stock value # check stock value
res = frappe.db.sql("""select stock_value from `tabStock Ledger Entry` res = frappe.db.sql("""select stock_value from `tabStock Ledger Entry`
where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC' where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC'
and posting_date = %s and posting_time = %s order by name desc limit 1""", and posting_date = %s and posting_time = %s order by name desc limit 1""",
(d[2], d[3])) (d[2], d[3]))
self.assertEqual(res and flt(res[0][0]) or 0, d[4]) self.assertEqual(res and flt(res[0][0]) or 0, d[4])
# check bin qty and stock value # check bin qty and stock value
bin = frappe.db.sql("""select actual_qty, stock_value from `tabBin` bin = frappe.db.sql("""select actual_qty, stock_value from `tabBin`
where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC'""") where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC'""")
self.assertEqual(bin and [flt(bin[0][0]), flt(bin[0][1])] or [], [d[5], d[6]]) self.assertEqual(bin and [flt(bin[0][0]), flt(bin[0][1])] or [], [d[5], d[6]])
# no gl entries # no gl entries
gl_entries = frappe.db.sql("""select name from `tabGL Entry` gl_entries = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type = 'Stock Reconciliation' and voucher_no = %s""", where voucher_type = 'Stock Reconciliation' and voucher_no = %s""",
stock_reco.name) stock_reco.name)
self.assertFalse(gl_entries) self.assertFalse(gl_entries)
def test_reco_for_moving_average(self): def test_reco_for_moving_average(self):
frappe.defaults.set_global_default("auto_accounting_for_stock", 0) frappe.defaults.set_global_default("auto_accounting_for_stock", 0)
# [[qty, valuation_rate, posting_date, # [[qty, valuation_rate, posting_date,
# posting_time, expected_stock_value, bin_qty, bin_valuation]] # posting_time, expected_stock_value, bin_qty, bin_valuation]]
input_data = [ input_data = [
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000], [50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0], [5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000], [15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500], [25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000], [20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000], [50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000], [5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
["", 1000, "2012-12-26", "12:05", 15000, 10, 12000], ["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
[20, "", "2012-12-26", "12:05", 18000, 15, 18000], [20, "", "2012-12-26", "12:05", 18000, 15, 18000],
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000], [10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200], [1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
[0, "", "2012-12-26", "12:10", 0, -5, 0] [0, "", "2012-12-26", "12:10", 0, -5, 0]
] ]
for d in input_data: for d in input_data:
self.cleanup_data() self.cleanup_data()
self.insert_existing_sle("Moving Average") self.insert_existing_sle("Moving Average")
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3]) stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
# check stock value in sle # check stock value in sle
res = frappe.db.sql("""select stock_value from `tabStock Ledger Entry` res = frappe.db.sql("""select stock_value from `tabStock Ledger Entry`
where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC' where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC'
and posting_date = %s and posting_time = %s order by name desc limit 1""", and posting_date = %s and posting_time = %s order by name desc limit 1""",
(d[2], d[3])) (d[2], d[3]))
self.assertEqual(res and flt(res[0][0], 4) or 0, d[4]) self.assertEqual(res and flt(res[0][0], 4) or 0, d[4])
# bin qty and stock value # bin qty and stock value
bin = frappe.db.sql("""select actual_qty, stock_value from `tabBin` bin = frappe.db.sql("""select actual_qty, stock_value from `tabBin`
where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC'""") where item_code = '_Test Item' and warehouse = '_Test Warehouse - _TC'""")
self.assertEqual(bin and [flt(bin[0][0]), flt(bin[0][1], 4)] or [], self.assertEqual(bin and [flt(bin[0][0]), flt(bin[0][1], 4)] or [],
[flt(d[5]), flt(d[6])]) [flt(d[5]), flt(d[6])])
# no gl entries # no gl entries
gl_entries = frappe.db.sql("""select name from `tabGL Entry` gl_entries = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type = 'Stock Reconciliation' and voucher_no = %s""", where voucher_type = 'Stock Reconciliation' and voucher_no = %s""",
stock_reco.name) stock_reco.name)
self.assertFalse(gl_entries) self.assertFalse(gl_entries)
def test_reco_fifo_gl_entries(self): def test_reco_fifo_gl_entries(self):
frappe.defaults.set_global_default("auto_accounting_for_stock", 1) frappe.defaults.set_global_default("auto_accounting_for_stock", 1)
# [[qty, valuation_rate, posting_date, posting_time, stock_in_hand_debit]] # [[qty, valuation_rate, posting_date, posting_time, stock_in_hand_debit]]
input_data = [ input_data = [
[50, 1000, "2012-12-26", "12:00"], [50, 1000, "2012-12-26", "12:00"],
[5, 1000, "2012-12-26", "12:00"], [5, 1000, "2012-12-26", "12:00"],
[15, 1000, "2012-12-26", "12:00"], [15, 1000, "2012-12-26", "12:00"],
[25, 900, "2012-12-26", "12:00"], [25, 900, "2012-12-26", "12:00"],
[20, 500, "2012-12-26", "12:00"], [20, 500, "2012-12-26", "12:00"],
["", 1000, "2012-12-26", "12:05"], ["", 1000, "2012-12-26", "12:05"],
[20, "", "2012-12-26", "12:05"], [20, "", "2012-12-26", "12:05"],
[10, 2000, "2012-12-26", "12:10"], [10, 2000, "2012-12-26", "12:10"],
[0, "", "2012-12-26", "12:10"], [0, "", "2012-12-26", "12:10"],
[50, 1000, "2013-01-01", "12:00"], [50, 1000, "2013-01-01", "12:00"],
[5, 1000, "2013-01-01", "12:00"], [5, 1000, "2013-01-01", "12:00"],
[1, 1000, "2012-12-01", "00:00"], [1, 1000, "2012-12-01", "00:00"],
] ]
for d in input_data: for d in input_data:
self.cleanup_data() self.cleanup_data()
self.insert_existing_sle("FIFO") self.insert_existing_sle("FIFO")
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3]) stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
stock_reco.cancel() stock_reco.cancel()
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"])) self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
frappe.defaults.set_global_default("auto_accounting_for_stock", 0) frappe.defaults.set_global_default("auto_accounting_for_stock", 0)
def test_reco_moving_average_gl_entries(self): def test_reco_moving_average_gl_entries(self):
frappe.defaults.set_global_default("auto_accounting_for_stock", 1) frappe.defaults.set_global_default("auto_accounting_for_stock", 1)
# [[qty, valuation_rate, posting_date, # [[qty, valuation_rate, posting_date,
# posting_time, stock_in_hand_debit]] # posting_time, stock_in_hand_debit]]
input_data = [ input_data = [
[50, 1000, "2012-12-26", "12:00", 36500], [50, 1000, "2012-12-26", "12:00", 36500],
[5, 1000, "2012-12-26", "12:00", -8500], [5, 1000, "2012-12-26", "12:00", -8500],
[15, 1000, "2012-12-26", "12:00", 1500], [15, 1000, "2012-12-26", "12:00", 1500],
[25, 900, "2012-12-26", "12:00", 9000], [25, 900, "2012-12-26", "12:00", 9000],
[20, 500, "2012-12-26", "12:00", -3500], [20, 500, "2012-12-26", "12:00", -3500],
["", 1000, "2012-12-26", "12:05", 1500], ["", 1000, "2012-12-26", "12:05", 1500],
[20, "", "2012-12-26", "12:05", 4500], [20, "", "2012-12-26", "12:05", 4500],
[10, 2000, "2012-12-26", "12:10", 6500], [10, 2000, "2012-12-26", "12:10", 6500],
[0, "", "2012-12-26", "12:10", -13500], [0, "", "2012-12-26", "12:10", -13500],
[50, 1000, "2013-01-01", "12:00", 50000], [50, 1000, "2013-01-01", "12:00", 50000],
[5, 1000, "2013-01-01", "12:00", 5000], [5, 1000, "2013-01-01", "12:00", 5000],
[1, 1000, "2012-12-01", "00:00", 1000], [1, 1000, "2012-12-01", "00:00", 1000],
] ]
for d in input_data: for d in input_data:
self.cleanup_data() self.cleanup_data()
self.insert_existing_sle("Moving Average") self.insert_existing_sle("Moving Average")
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3]) stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"])) self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"]))
# cancel # cancel
stock_reco.cancel() stock_reco.cancel()
self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"])) self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"]))
frappe.defaults.set_global_default("auto_accounting_for_stock", 0) frappe.defaults.set_global_default("auto_accounting_for_stock", 0)
@ -173,7 +173,7 @@ class TestStockReconciliation(unittest.TestCase):
frappe.db.sql("delete from `tabStock Ledger Entry`") frappe.db.sql("delete from `tabStock Ledger Entry`")
frappe.db.sql("delete from tabBin") frappe.db.sql("delete from tabBin")
frappe.db.sql("delete from `tabGL Entry`") frappe.db.sql("delete from `tabGL Entry`")
def submit_stock_reconciliation(self, qty, rate, posting_date, posting_time): def submit_stock_reconciliation(self, qty, rate, posting_date, posting_time):
stock_reco = frappe.get_doc({ stock_reco = frappe.get_doc({
"doctype": "Stock Reconciliation", "doctype": "Stock Reconciliation",
@ -191,40 +191,40 @@ class TestStockReconciliation(unittest.TestCase):
stock_reco.insert() stock_reco.insert()
stock_reco.submit() stock_reco.submit()
return stock_reco return stock_reco
def insert_existing_sle(self, valuation_method): def insert_existing_sle(self, valuation_method):
frappe.db.set_value("Item", "_Test Item", "valuation_method", valuation_method) frappe.db.set_value("Item", "_Test Item", "valuation_method", valuation_method)
frappe.db.set_default("allow_negative_stock", 1) frappe.db.set_default("allow_negative_stock", 1)
stock_entry = [ stock_entry = {
{ "company": "_Test Company",
"company": "_Test Company", "doctype": "Stock Entry",
"doctype": "Stock Entry", "posting_date": "2012-12-12",
"posting_date": "2012-12-12", "posting_time": "01:00",
"posting_time": "01:00", "purpose": "Material Receipt",
"purpose": "Material Receipt", "fiscal_year": "_Test Fiscal Year 2012",
"fiscal_year": "_Test Fiscal Year 2012", "mtn_details": [
}, {
{ "conversion_factor": 1.0,
"conversion_factor": 1.0, "doctype": "Stock Entry Detail",
"doctype": "Stock Entry Detail", "item_code": "_Test Item",
"item_code": "_Test Item", "parentfield": "mtn_details",
"parentfield": "mtn_details", "incoming_rate": 1000,
"incoming_rate": 1000, "qty": 20.0,
"qty": 20.0, "stock_uom": "_Test UOM",
"stock_uom": "_Test UOM", "transfer_qty": 20.0,
"transfer_qty": 20.0, "uom": "_Test UOM",
"uom": "_Test UOM", "t_warehouse": "_Test Warehouse - _TC",
"t_warehouse": "_Test Warehouse - _TC", "expense_account": "Stock Adjustment - _TC",
"expense_account": "Stock Adjustment - _TC", "cost_center": "_Test Cost Center - _TC"
"cost_center": "_Test Cost Center - _TC" }
}, ]
] }
pr = frappe.copy_doc(stock_entry) pr = frappe.copy_doc(stock_entry)
pr.insert() pr.insert()
pr.submit() pr.submit()
pr1 = frappe.copy_doc(stock_entry) pr1 = frappe.copy_doc(stock_entry)
pr1.posting_date = "2012-12-15" pr1.posting_date = "2012-12-15"
pr1.posting_time = "02:00" pr1.posting_time = "02:00"
@ -233,7 +233,7 @@ class TestStockReconciliation(unittest.TestCase):
pr1.get("mtn_details")[0].incoming_rate = 700 pr1.get("mtn_details")[0].incoming_rate = 700
pr1.insert() pr1.insert()
pr1.submit() pr1.submit()
pr2 = frappe.copy_doc(stock_entry) pr2 = frappe.copy_doc(stock_entry)
pr2.posting_date = "2012-12-25" pr2.posting_date = "2012-12-25"
pr2.posting_time = "03:00" pr2.posting_time = "03:00"
@ -245,7 +245,7 @@ class TestStockReconciliation(unittest.TestCase):
pr2.get("mtn_details")[0].incoming_rate = 0 pr2.get("mtn_details")[0].incoming_rate = 0
pr2.insert() pr2.insert()
pr2.submit() pr2.submit()
pr3 = frappe.copy_doc(stock_entry) pr3 = frappe.copy_doc(stock_entry)
pr3.posting_date = "2012-12-31" pr3.posting_date = "2012-12-31"
pr3.posting_time = "08:00" pr3.posting_time = "08:00"
@ -257,8 +257,8 @@ class TestStockReconciliation(unittest.TestCase):
pr3.get("mtn_details")[0].incoming_rate = 0 pr3.get("mtn_details")[0].incoming_rate = 0
pr3.insert() pr3.insert()
pr3.submit() pr3.submit()
pr4 = frappe.copy_doc(stock_entry) pr4 = frappe.copy_doc(stock_entry)
pr4.posting_date = "2013-01-05" pr4.posting_date = "2013-01-05"
pr4.fiscal_year = "_Test Fiscal Year 2013" pr4.fiscal_year = "_Test Fiscal Year 2013"
@ -268,6 +268,6 @@ class TestStockReconciliation(unittest.TestCase):
pr4.get("mtn_details")[0].incoming_rate = 1200 pr4.get("mtn_details")[0].incoming_rate = 1200
pr4.insert() pr4.insert()
pr4.submit() pr4.submit()
test_dependencies = ["Item", "Warehouse"] test_dependencies = ["Item", "Warehouse"]

View File

@ -7,36 +7,36 @@ class TestNewsletter(unittest.TestCase):
def test_get_recipients_lead(self): def test_get_recipients_lead(self):
w = frappe.get_doc(test_records[0]) w = frappe.get_doc(test_records[0])
w.insert() w.insert()
self.assertTrue("test_lead@example.com" in w.controller.get_recipients()) self.assertTrue("test_lead@example.com" in w.get_recipients())
frappe.db.sql("""delete from `tabBulk Email`""") frappe.db.sql("""delete from `tabBulk Email`""")
w.controller.send_emails() w.send_emails()
self.assertTrue(frappe.db.get_value("Bulk Email", {"recipient": "test_lead@example.com"})) self.assertTrue(frappe.db.get_value("Bulk Email", {"recipient": "test_lead@example.com"}))
def test_get_recipients_lead_by_status(self): def test_get_recipients_lead_by_status(self):
w = frappe.get_doc(test_records[0]) w = frappe.get_doc(test_records[0])
w.lead_status="Converted" w.lead_status="Converted"
w.insert() w.insert()
self.assertTrue("test_lead3@example.com" in w.controller.get_recipients()) self.assertTrue("test_lead3@example.com" in w.get_recipients())
def test_get_recipients_contact_customer(self): def test_get_recipients_contact_customer(self):
w = frappe.get_doc(test_records[1]) w = frappe.get_doc(test_records[1])
w.insert() w.insert()
self.assertTrue("test_contact_customer@example.com" in w.controller.get_recipients()) self.assertTrue("test_contact_customer@example.com" in w.get_recipients())
def test_get_recipients_contact_supplier(self): def test_get_recipients_contact_supplier(self):
w = frappe.get_doc(test_records[1]) w = frappe.get_doc(test_records[1])
w.contact_type="Supplier" w.contact_type="Supplier"
w.insert() w.insert()
self.assertTrue("test_contact_supplier@example.com" in w.controller.get_recipients()) self.assertTrue("test_contact_supplier@example.com" in w.get_recipients())
def test_get_recipients_custom(self): def test_get_recipients_custom(self):
w = frappe.get_doc(test_records[2]) w = frappe.get_doc(test_records[2])
w.insert() w.insert()
self.assertTrue("test_custom2@example.com" in w.controller.get_recipients()) self.assertTrue("test_custom2@example.com" in w.get_recipients())
self.assertTrue(frappe.db.get("Lead", self.assertTrue(frappe.db.get("Lead",
{"email_id": "test_custom2@example.com"})) {"email_id": "test_custom2@example.com"}))
test_dependencies = ["Lead", "Contact"] test_dependencies = ["Lead", "Contact"]
test_records = frappe.get_test_records('Newsletter') test_records = frappe.get_test_records('Newsletter')