Fixed rest of the test cases frappe/frapp#478
This commit is contained in:
parent
d29465029d
commit
9fd50bcfb6
@ -17,17 +17,20 @@ from erpnext.accounts.party import get_party_account, get_due_date
|
||||
class PurchaseInvoice(BuyingController):
|
||||
tname = 'Purchase Invoice Item'
|
||||
fname = 'entries'
|
||||
status_updater = [{
|
||||
'source_dt': 'Purchase Invoice Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'po_detail',
|
||||
'target_field': 'billed_amt',
|
||||
'target_parent_dt': 'Purchase Order',
|
||||
'target_parent_field': 'per_billed',
|
||||
'target_ref_field': 'amount',
|
||||
'source_field': 'amount',
|
||||
'percent_join_field': 'purchase_order',
|
||||
}]
|
||||
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(PurchaseInvoice, self).__init__(arg1, arg2)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Purchase Invoice Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'po_detail',
|
||||
'target_field': 'billed_amt',
|
||||
'target_parent_dt': 'Purchase Order',
|
||||
'target_parent_field': 'per_billed',
|
||||
'target_ref_field': 'amount',
|
||||
'source_field': 'amount',
|
||||
'percent_join_field': 'purchase_order',
|
||||
}]
|
||||
|
||||
def validate(self):
|
||||
if not self.is_opening:
|
||||
|
@ -20,20 +20,23 @@ from erpnext.controllers.selling_controller import SellingController
|
||||
class SalesInvoice(SellingController):
|
||||
tname = 'Sales Invoice Item'
|
||||
fname = 'entries'
|
||||
status_updater = [{
|
||||
'source_dt': 'Sales Invoice Item',
|
||||
'target_field': 'billed_amt',
|
||||
'target_ref_field': 'amount',
|
||||
'target_dt': 'Sales Order Item',
|
||||
'join_field': 'so_detail',
|
||||
'target_parent_dt': 'Sales Order',
|
||||
'target_parent_field': 'per_billed',
|
||||
'source_field': 'amount',
|
||||
'join_field': 'so_detail',
|
||||
'percent_join_field': 'sales_order',
|
||||
'status_field': 'billing_status',
|
||||
'keyword': 'Billed'
|
||||
}]
|
||||
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(SalesInvoice, self).__init__(arg1, arg2)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Sales Invoice Item',
|
||||
'target_field': 'billed_amt',
|
||||
'target_ref_field': 'amount',
|
||||
'target_dt': 'Sales Order Item',
|
||||
'join_field': 'so_detail',
|
||||
'target_parent_dt': 'Sales Order',
|
||||
'target_parent_field': 'per_billed',
|
||||
'source_field': 'amount',
|
||||
'join_field': 'so_detail',
|
||||
'percent_join_field': 'sales_order',
|
||||
'status_field': 'billing_status',
|
||||
'keyword': 'Billed'
|
||||
}]
|
||||
|
||||
def validate(self):
|
||||
super(SalesInvoice, self).validate()
|
||||
|
@ -11,13 +11,13 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
|
||||
class PurchaseCommon(BuyingController):
|
||||
|
||||
|
||||
def update_last_purchase_rate(self, obj, is_submit):
|
||||
"""updates last_purchase_rate in item table for each item"""
|
||||
|
||||
|
||||
import frappe.utils
|
||||
this_purchase_date = frappe.utils.getdate(obj.get('posting_date') or obj.get('transaction_date'))
|
||||
|
||||
|
||||
for d in obj.get(obj.fname):
|
||||
# get last purchase details
|
||||
last_purchase_details = get_last_purchase_details(d.item_code, obj.name)
|
||||
@ -33,19 +33,19 @@ class PurchaseCommon(BuyingController):
|
||||
if flt(d.conversion_factor):
|
||||
last_purchase_rate = flt(d.base_rate) / flt(d.conversion_factor)
|
||||
else:
|
||||
frappe.throw(_("Row ") + cstr(d.idx) + ": " +
|
||||
frappe.throw(_("Row ") + cstr(d.idx) + ": " +
|
||||
_("UOM Conversion Factor is mandatory"))
|
||||
|
||||
# update last purchsae rate
|
||||
if last_purchase_rate:
|
||||
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
|
||||
(flt(last_purchase_rate), d.item_code))
|
||||
|
||||
|
||||
def get_last_purchase_rate(self, obj):
|
||||
"""get last purchase rates for all items"""
|
||||
doc_name = obj.name
|
||||
conversion_rate = flt(obj.get('conversion_rate')) or 1.0
|
||||
|
||||
|
||||
for d in obj.get(obj.fname):
|
||||
if d.item_code:
|
||||
last_purchase_details = get_last_purchase_details(d.item_code, doc_name)
|
||||
@ -59,113 +59,113 @@ class PurchaseCommon(BuyingController):
|
||||
else:
|
||||
# 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
|
||||
|
||||
|
||||
item_last_purchase_rate = frappe.db.get_value("Item",
|
||||
d.item_code, "last_purchase_rate")
|
||||
if item_last_purchase_rate:
|
||||
d.base_price_list_rate = d.base_rate = d.price_list_rate \
|
||||
= d.rate = item_last_purchase_rate
|
||||
|
||||
|
||||
def validate_for_items(self, obj):
|
||||
check_list, chk_dupl_itm=[],[]
|
||||
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)):
|
||||
frappe.throw("Please enter valid qty for item %s" % cstr(d.item_code))
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
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':
|
||||
f_lst.pop('received_qty')
|
||||
for x in f_lst :
|
||||
if d.meta.get_field(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)
|
||||
if not item:
|
||||
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
|
||||
validate_end_of_life(d.item_code, item[0][3])
|
||||
|
||||
|
||||
# validate stock item
|
||||
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)
|
||||
|
||||
|
||||
# validate purchase item
|
||||
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))
|
||||
|
||||
|
||||
# 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,
|
||||
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 '',
|
||||
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_detail_docname') and d.prevdoc_detail_docname or '',
|
||||
d.meta.get_field('batch_no') and d.batch_no or '']
|
||||
|
||||
|
||||
# if is not stock item
|
||||
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)
|
||||
|
||||
if ch and ch[0][0] == 'Yes':
|
||||
|
||||
if ch and ch[0][0] == 'Yes':
|
||||
# check for same items
|
||||
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)
|
||||
else:
|
||||
check_list.append(e)
|
||||
|
||||
|
||||
elif ch and ch[0][0] == 'No':
|
||||
# check for same items
|
||||
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)
|
||||
else:
|
||||
chk_dupl_itm.append(f)
|
||||
|
||||
|
||||
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
|
||||
#------------------------------
|
||||
# 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
|
||||
# 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'
|
||||
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'),
|
||||
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'),
|
||||
(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
|
||||
#--------------------
|
||||
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)
|
||||
max_qty = max_qty and flt(max_qty[0][0]) or 0
|
||||
|
||||
|
||||
return cstr(qty)+'~~~'+cstr(max_qty)
|
||||
|
||||
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)
|
||||
if stopped:
|
||||
frappe.throw("One cannot do any transaction against %s : %s, it's status is 'Stopped'" %
|
||||
(doctype, docname))
|
||||
|
||||
frappe.throw("One cannot do any transaction against %s : %s, it's status is 'Stopped'" %
|
||||
(doctype, docname), exc=frappe.InvalidStatusError)
|
||||
|
||||
def check_docstatus(self, check, doctype, docname, detail_doctype = ''):
|
||||
if check == 'Next':
|
||||
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"""
|
||||
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"""
|
||||
% (doctype, detail_doctype, '%s'), docname)
|
||||
if submitted:
|
||||
frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0])
|
||||
frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0])
|
||||
+ _("has already been submitted."))
|
||||
|
||||
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)
|
||||
if not submitted:
|
||||
frappe.throw(cstr(doctype) + ": " + cstr(submitted[0][0]) + _("not submitted"))
|
||||
|
@ -3,27 +3,27 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
from frappe import msgprint
|
||||
|
||||
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
|
||||
class PurchaseOrder(BuyingController):
|
||||
tname = 'Purchase Order Item'
|
||||
fname = 'po_details'
|
||||
status_updater = [{
|
||||
'source_dt': 'Purchase Order Item',
|
||||
'target_dt': 'Material Request Item',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_field': 'ordered_qty',
|
||||
'target_parent_dt': 'Material Request',
|
||||
'target_parent_field': 'per_ordered',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'prevdoc_docname',
|
||||
}]
|
||||
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(PurchaseOrder, self).__init__(arg1, arg2)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Purchase Order Item',
|
||||
'target_dt': 'Material Request Item',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_field': 'ordered_qty',
|
||||
'target_parent_dt': 'Material Request',
|
||||
'target_parent_field': 'per_ordered',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'prevdoc_docname',
|
||||
}]
|
||||
|
||||
def validate(self):
|
||||
super(PurchaseOrder, self).validate()
|
||||
@ -156,7 +156,7 @@ class PurchaseOrder(BuyingController):
|
||||
frappe.db.set(self,'status','Submitted')
|
||||
|
||||
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)
|
||||
|
||||
# Check if Purchase Receipt has been submitted against current Purchase Order
|
||||
|
@ -64,11 +64,11 @@ class StatusUpdater(DocListController):
|
||||
def update_prevdoc_status(self):
|
||||
self.update_qty()
|
||||
self.validate_qty()
|
||||
|
||||
|
||||
def set_status(self, update=False):
|
||||
if self.get("__islocal"):
|
||||
return
|
||||
|
||||
|
||||
if self.doctype in status_map:
|
||||
sl = status_map[self.doctype][:]
|
||||
sl.reverse()
|
||||
@ -83,15 +83,15 @@ class StatusUpdater(DocListController):
|
||||
elif getattr(self, s[1])():
|
||||
self.status = s[0]
|
||||
break
|
||||
|
||||
|
||||
if update:
|
||||
frappe.db.set_value(self.doctype, self.name, "status", self.status)
|
||||
|
||||
|
||||
def on_communication(self):
|
||||
self.communication_set = True
|
||||
self.set_status(update=True)
|
||||
del self.communication_set
|
||||
|
||||
|
||||
def communication_received(self):
|
||||
if getattr(self, "communication_set", False):
|
||||
last_comm = self.get("communications")
|
||||
@ -103,14 +103,14 @@ class StatusUpdater(DocListController):
|
||||
last_comm = self.get("communications")
|
||||
if last_comm:
|
||||
return last_comm[-1].sent_or_received == "Sent"
|
||||
|
||||
|
||||
def validate_qty(self):
|
||||
"""
|
||||
Validates qty at row level
|
||||
"""
|
||||
self.tolerance = {}
|
||||
self.global_tolerance = None
|
||||
|
||||
|
||||
for args in self.status_updater:
|
||||
# get unique transactions to update
|
||||
for d in self.get_all_children():
|
||||
@ -118,10 +118,10 @@ class StatusUpdater(DocListController):
|
||||
args['name'] = d.get(args['join_field'])
|
||||
|
||||
# get all qty where qty > target_field
|
||||
item = frappe.db.sql("""select item_code, `{target_ref_field}`,
|
||||
`{target_field}`, parenttype, parent from `tab{target_dt}`
|
||||
where `{target_ref_field}` < `{target_field}`
|
||||
and name=%s and docstatus=1""".format(**args),
|
||||
item = frappe.db.sql("""select item_code, `{target_ref_field}`,
|
||||
`{target_field}`, parenttype, parent from `tab{target_dt}`
|
||||
where `{target_ref_field}` < `{target_field}`
|
||||
and name=%s and docstatus=1""".format(**args),
|
||||
args['name'], as_dict=1)
|
||||
if item:
|
||||
item = item[0]
|
||||
@ -142,38 +142,37 @@ class StatusUpdater(DocListController):
|
||||
is <b>""" % item + cstr(item[args['target_ref_field']]) +
|
||||
"""</b>.<br>You must reduce the %(target_ref_field)s by \
|
||||
%(reduce_by)s""" % item, raise_exception=1)
|
||||
|
||||
|
||||
else:
|
||||
self.check_overflow_with_tolerance(item, args)
|
||||
|
||||
|
||||
def check_overflow_with_tolerance(self, item, args):
|
||||
"""
|
||||
Checks if there is overflow condering a relaxation 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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
if overflow_percent - tolerance > 0.01:
|
||||
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+tolerance)/100)
|
||||
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
|
||||
|
||||
|
||||
msgprint("""
|
||||
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 \
|
||||
Global Defaults or Item master.
|
||||
|
||||
Global Defaults or Item master.
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
def update_qty(self, change_modified=True):
|
||||
"""
|
||||
@ -185,103 +184,103 @@ class StatusUpdater(DocListController):
|
||||
args['cond'] = ' or parent="%s"' % self.name.replace('"', '\"')
|
||||
else:
|
||||
args['cond'] = ' and parent!="%s"' % self.name.replace('"', '\"')
|
||||
|
||||
|
||||
args['modified_cond'] = ''
|
||||
if change_modified:
|
||||
args['modified_cond'] = ', modified = now()'
|
||||
|
||||
|
||||
# update quantities in child table
|
||||
for d in self.get_all_children():
|
||||
if d.doctype == args['source_dt']:
|
||||
# updates qty in the child table
|
||||
args['detail_id'] = d.get(args['join_field'])
|
||||
|
||||
|
||||
args['second_source_condition'] = ""
|
||||
if args.get('second_source_dt') and args.get('second_source_field') \
|
||||
and args.get('second_join_field'):
|
||||
args['second_source_condition'] = """ + (select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
args['second_source_condition'] = """ + (select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1))""" % args
|
||||
|
||||
|
||||
if args['detail_id']:
|
||||
frappe.db.sql("""update `tab%(target_dt)s`
|
||||
set %(target_field)s = (select sum(%(source_field)s)
|
||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||
frappe.db.sql("""update `tab%(target_dt)s`
|
||||
set %(target_field)s = (select sum(%(source_field)s)
|
||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1 %(cond)s)) %(second_source_condition)s
|
||||
where name='%(detail_id)s'""" % args)
|
||||
|
||||
|
||||
# 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'])]):
|
||||
if name:
|
||||
args['name'] = name
|
||||
|
||||
|
||||
# update percent complete in the parent table
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
|
||||
ifnull(%(target_field)s, 0), %(target_field)s,
|
||||
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
|
||||
ifnull(%(target_field)s, 0), %(target_field)s,
|
||||
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
|
||||
from `tab%(target_dt)s` where parent="%(name)s") %(modified_cond)s
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
# update field
|
||||
if args.get('status_field'):
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001,
|
||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
||||
frappe.db.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001,
|
||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
||||
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
|
||||
|
||||
|
||||
def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
|
||||
ref_fieldname = ref_dt.lower().replace(" ", "_")
|
||||
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)
|
||||
|
||||
|
||||
for item in self.get("entries"):
|
||||
if item.get(ref_fieldname) \
|
||||
and item.get(ref_fieldname) in all_zero_amount_refdoc \
|
||||
and item.get(ref_fieldname) not in zero_amount_refdoc:
|
||||
zero_amount_refdoc.append(item.get(ref_fieldname))
|
||||
|
||||
|
||||
if zero_amount_refdoc:
|
||||
self.update_biling_status(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:
|
||||
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])
|
||||
|
||||
billed_qty = flt(frappe.db.sql("""select sum(ifnull(qty, 0))
|
||||
from `tab%s Item` where %s=%s and docstatus=1""" %
|
||||
|
||||
billed_qty = flt(frappe.db.sql("""select sum(ifnull(qty, 0))
|
||||
from `tab%s Item` where %s=%s and docstatus=1""" %
|
||||
(self.doctype, ref_fieldname, '%s'), (ref_dn))[0][0])
|
||||
|
||||
|
||||
per_billed = ((ref_doc_qty if billed_qty > ref_doc_qty else billed_qty)\
|
||||
/ ref_doc_qty)*100
|
||||
frappe.db.set_value(ref_dt, ref_dn, "per_billed", per_billed)
|
||||
|
||||
|
||||
if frappe.get_meta(ref_dt).get_field("billing_status"):
|
||||
if per_billed < 0.001: billing_status = "Not Billed"
|
||||
elif per_billed >= 99.99: billing_status = "Fully Billed"
|
||||
else: billing_status = "Partly Billed"
|
||||
|
||||
|
||||
frappe.db.set_value(ref_dt, ref_dn, "billing_status", billing_status)
|
||||
|
||||
|
||||
def get_tolerance_for(item_code, item_tolerance={}, global_tolerance=None):
|
||||
"""
|
||||
Returns the tolerance for the item, if not set, returns global tolerance
|
||||
"""
|
||||
if item_tolerance.get(item_code):
|
||||
return item_tolerance[item_code], item_tolerance, global_tolerance
|
||||
|
||||
|
||||
tolerance = flt(frappe.db.get_value('Item',item_code,'tolerance') or 0)
|
||||
|
||||
if not tolerance:
|
||||
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 = global_tolerance
|
||||
|
||||
|
||||
item_tolerance[item_code] = tolerance
|
||||
return tolerance, item_tolerance, global_tolerance
|
||||
return tolerance, item_tolerance, global_tolerance
|
||||
|
@ -1,16 +1,16 @@
|
||||
# ERPNext - web based ERP (http://erpnext.com)
|
||||
# Copyright (C) 2012 Web Notes Technologies Pvt Ltd
|
||||
#
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
@ -22,35 +22,35 @@ feed_dict = {
|
||||
# Project
|
||||
'Project': ['[%(status)s]', '#000080'],
|
||||
'Task': ['[%(status)s] %(subject)s', '#000080'],
|
||||
|
||||
|
||||
# Sales
|
||||
'Lead': ['%(lead_name)s', '#000080'],
|
||||
'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'],
|
||||
|
||||
|
||||
# Purchase
|
||||
'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'],
|
||||
|
||||
|
||||
# Stock
|
||||
'Delivery Note': ['[%(status)s] To %(customer_name)s', '#4169E1'],
|
||||
'Purchase Receipt': ['[%(status)s] From %(supplier)s', '#4169E1'],
|
||||
|
||||
|
||||
# Accounts
|
||||
'Journal Voucher': ['[%(voucher_type)s] %(name)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'],
|
||||
|
||||
|
||||
# HR
|
||||
'Expense Claim': ['[%(approval_status)s] %(name)s by %(employee_name)s', '#4169E1'],
|
||||
'Salary Slip': ['%(employee_name)s for %(month)s %(fiscal_year)s', '#4169E1'],
|
||||
'Leave Transaction': ['%(leave_type)s for %(employee)s', '#4169E1'],
|
||||
|
||||
|
||||
# Support
|
||||
'Customer Issue': ['[%(status)s] %(description)s by %(customer_name)s', '#000080'],
|
||||
'Maintenance Visit': ['To %(customer_name)s', '#4169E1'],
|
||||
'Support Ticket': ["[%(status)s] %(subject)s", '#000080'],
|
||||
|
||||
|
||||
# Website
|
||||
'Web Page': ['%(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'):
|
||||
# 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')""")
|
||||
else:
|
||||
# one feed per item
|
||||
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))
|
||||
|
||||
f = frappe.new_doc('Feed')
|
||||
@ -79,9 +79,9 @@ def make_feed(feedtype, doctype, name, owner, subject, color):
|
||||
f.subject = subject
|
||||
f.color = color
|
||||
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"
|
||||
if method in ['on_update', 'on_submit']:
|
||||
subject, color = feed_dict.get(doc.doctype, [None, None])
|
||||
|
@ -7,70 +7,73 @@ import frappe
|
||||
from frappe.utils import cstr, getdate
|
||||
|
||||
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
|
||||
|
||||
class InstallationNote(TransactionBase):
|
||||
tname = 'Installation Note Item'
|
||||
fname = 'installed_item_details'
|
||||
status_updater = [{
|
||||
'source_dt': 'Installation Note Item',
|
||||
'target_dt': 'Delivery Note Item',
|
||||
'target_field': 'installed_qty',
|
||||
'target_ref_field': 'qty',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_parent_dt': 'Delivery Note',
|
||||
'target_parent_field': 'per_installed',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'prevdoc_docname',
|
||||
'status_field': 'installation_status',
|
||||
'keyword': 'Installed'
|
||||
}]
|
||||
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(InstallationNote, self).__init__(arg1, arg2)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Installation Note Item',
|
||||
'target_dt': 'Delivery Note Item',
|
||||
'target_field': 'installed_qty',
|
||||
'target_ref_field': 'qty',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_parent_dt': 'Delivery Note',
|
||||
'target_parent_field': 'per_installed',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'prevdoc_docname',
|
||||
'status_field': 'installation_status',
|
||||
'keyword': 'Installed'
|
||||
}]
|
||||
|
||||
def validate(self):
|
||||
self.validate_fiscal_year()
|
||||
self.validate_installation_date()
|
||||
self.check_item_table()
|
||||
|
||||
|
||||
from erpnext.controllers.selling_controller import check_active_sales_items
|
||||
check_active_sales_items(self)
|
||||
|
||||
def validate_fiscal_year(self):
|
||||
from erpnext.accounts.utils import validate_fiscal_year
|
||||
validate_fiscal_year(self.inst_date, self.fiscal_year, "Installation Date")
|
||||
|
||||
|
||||
def is_serial_no_added(self, item_code, serial_no):
|
||||
ar_required = frappe.db.get_value("Item", item_code, "has_serial_no")
|
||||
if ar_required == 'Yes' and not serial_no:
|
||||
msgprint("Serial No is mandatory for item: " + item_code, raise_exception=1)
|
||||
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)
|
||||
|
||||
|
||||
def is_serial_no_exist(self, item_code, serial_no):
|
||||
for x in serial_no:
|
||||
if not frappe.db.exists("Serial No", x):
|
||||
msgprint("Serial No " + x + " does not exist in the system", raise_exception=1)
|
||||
|
||||
|
||||
def is_serial_no_installed(self,cur_s_no,item_code):
|
||||
for x in cur_s_no:
|
||||
status = frappe.db.sql("select status from `tabSerial No` where name = %s", x)
|
||||
status = status and status[0][0] or ''
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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")
|
||||
return get_valid_serial_nos(serial_nos)
|
||||
|
||||
|
||||
def is_serial_no_match(self, cur_s_no, prevdoc_s_no, prevdoc_docname):
|
||||
for sr in cur_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)
|
||||
|
||||
def validate_serial_no(self):
|
||||
@ -80,33 +83,33 @@ class InstallationNote(TransactionBase):
|
||||
if d.serial_no:
|
||||
sr_list = get_valid_serial_nos(d.serial_no, d.qty, d.item_code)
|
||||
self.is_serial_no_exist(d.item_code, sr_list)
|
||||
|
||||
|
||||
prevdoc_s_no = self.get_prevdoc_serial_no(d.prevdoc_detail_docname)
|
||||
if prevdoc_s_no:
|
||||
self.is_serial_no_match(sr_list, prevdoc_s_no, d.prevdoc_docname)
|
||||
|
||||
|
||||
self.is_serial_no_installed(sr_list, d.item_code)
|
||||
|
||||
def validate_installation_date(self):
|
||||
for d in self.get('installed_item_details'):
|
||||
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):
|
||||
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)
|
||||
|
||||
|
||||
def check_item_table(self):
|
||||
if not(self.get('installed_item_details')):
|
||||
msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
|
||||
|
||||
|
||||
def on_update(self):
|
||||
frappe.db.set(self, 'status', 'Draft')
|
||||
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_serial_no()
|
||||
self.update_prevdoc_status()
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
for d in self.get('installed_item_details'):
|
||||
if d.serial_no:
|
||||
|
@ -21,9 +21,9 @@ class TestQuotation(unittest.TestCase):
|
||||
sales_order = make_sales_order(quotation.name)
|
||||
|
||||
self.assertEquals(sales_order.doctype, "Sales Order")
|
||||
self.assertEquals(len(sales_order.get("sales_order_details")), 2)
|
||||
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(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].prevdoc_docname, quotation.name)
|
||||
self.assertEquals(sales_order.customer, "_Test Customer")
|
||||
|
||||
sales_order.delivery_date = "2014-01-01"
|
||||
|
@ -281,10 +281,10 @@ def make_material_request(source_name, target_doc=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_note(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.base_amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.base_rate)
|
||||
target.amount = (flt(obj.qty) - flt(obj.delivered_qty)) * flt(obj.rate)
|
||||
target.qty = flt(obj.qty) - flt(obj.delivered_qty)
|
||||
def update_item(source, target, source_parent):
|
||||
target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
|
||||
target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
|
||||
target.qty = flt(source.qty) - flt(source.delivered_qty)
|
||||
|
||||
doclist = get_mapped_doc("Sales Order", source_name, {
|
||||
"Sales Order": {
|
||||
@ -326,10 +326,10 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
doc.is_pos = 0
|
||||
doc.run_method("onload_post_render")
|
||||
|
||||
def update_item(obj, target, source_parent):
|
||||
target.amount = flt(obj.amount) - flt(obj.billed_amt)
|
||||
def update_item(source, target, source_parent):
|
||||
target.amount = flt(source.amount) - flt(source.billed_amt)
|
||||
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, {
|
||||
"Sales Order": {
|
||||
|
@ -106,7 +106,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
|
||||
def test_reserved_qty_for_so(self):
|
||||
# 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"])
|
||||
|
||||
# submit
|
||||
@ -120,7 +120,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
|
||||
def test_reserved_qty_for_partial_delivery(self):
|
||||
# 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"])
|
||||
|
||||
# submit so
|
||||
@ -150,7 +150,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
|
||||
def test_reserved_qty_for_over_delivery(self):
|
||||
# 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"])
|
||||
|
||||
# submit so
|
||||
|
@ -5,22 +5,33 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
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'
|
||||
|
||||
|
||||
def autoname(self):
|
||||
self.name = self.item_group_name
|
||||
|
||||
def validate(self):
|
||||
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)
|
||||
|
||||
|
||||
def on_update(self):
|
||||
NestedSet.on_update(self)
|
||||
|
||||
WebsiteGenerator.on_update(self)
|
||||
self.validate_name_with_item()
|
||||
|
||||
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):
|
||||
if frappe.db.exists("Item", self.name):
|
||||
frappe.msgprint("An item exists with same name (%s), please change the \
|
||||
|
@ -13,15 +13,14 @@ class TestItem(unittest.TestCase):
|
||||
def test_basic_tree(self, records=None):
|
||||
min_lft = 1
|
||||
max_rgt = frappe.db.sql("select max(rgt) from `tabItem Group`")[0][0]
|
||||
|
||||
|
||||
if not records:
|
||||
records = test_records[2:]
|
||||
|
||||
|
||||
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"])
|
||||
|
||||
|
||||
if parent_item_group:
|
||||
parent_lft, parent_rgt = frappe.db.get_value("Item Group", parent_item_group,
|
||||
["lft", "rgt"])
|
||||
@ -29,7 +28,7 @@ class TestItem(unittest.TestCase):
|
||||
# root
|
||||
parent_lft = min_lft - 1
|
||||
parent_rgt = max_rgt + 1
|
||||
|
||||
|
||||
self.assertTrue(lft)
|
||||
self.assertTrue(rgt)
|
||||
self.assertTrue(lft < rgt)
|
||||
@ -38,69 +37,69 @@ class TestItem(unittest.TestCase):
|
||||
self.assertTrue(rgt < parent_rgt)
|
||||
self.assertTrue(lft >= min_lft)
|
||||
self.assertTrue(rgt <= max_rgt)
|
||||
|
||||
|
||||
no_of_children = self.get_no_of_children(item_group["item_group_name"])
|
||||
self.assertTrue(rgt == (lft + 1 + (2 * no_of_children)))
|
||||
|
||||
|
||||
no_of_children = self.get_no_of_children(parent_item_group)
|
||||
self.assertTrue(parent_rgt == (parent_lft + 1 + (2 * no_of_children)))
|
||||
|
||||
|
||||
def get_no_of_children(self, item_group):
|
||||
def get_no_of_children(item_groups, no_of_children):
|
||||
children = []
|
||||
for ig in item_groups:
|
||||
children += frappe.db.sql_list("""select name from `tabItem Group`
|
||||
where ifnull(parent_item_group, '')=%s""", ig or '')
|
||||
|
||||
|
||||
if len(children):
|
||||
return get_no_of_children(children, no_of_children + len(children))
|
||||
else:
|
||||
return no_of_children
|
||||
|
||||
|
||||
return get_no_of_children([item_group], 0)
|
||||
|
||||
|
||||
def test_recursion(self):
|
||||
group_b = frappe.get_doc("Item Group", "_Test Item Group B")
|
||||
group_b.parent_item_group = "_Test Item Group B - 3"
|
||||
self.assertRaises(NestedSetRecursionError, group_b.save)
|
||||
|
||||
|
||||
# cleanup
|
||||
group_b.parent_item_group = "All Item Groups"
|
||||
group_b.save()
|
||||
|
||||
|
||||
def test_rebuild_tree(self):
|
||||
rebuild_tree("Item Group", "parent_item_group")
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
def move_it_back(self):
|
||||
group_b = frappe.get_doc("Item Group", "_Test Item Group B")
|
||||
group_b.parent_item_group = "All Item Groups"
|
||||
group_b.save()
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
def test_move_group_into_another(self):
|
||||
# before move
|
||||
old_lft, old_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
|
||||
|
||||
|
||||
# put B under C
|
||||
group_b = frappe.get_doc("Item Group", "_Test Item Group B")
|
||||
lft, rgt = group_b.lft, group_b.rgt
|
||||
|
||||
|
||||
group_b.parent_item_group = "_Test Item Group C"
|
||||
group_b.save()
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
# after move
|
||||
new_lft, new_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
|
||||
|
||||
|
||||
# lft should reduce
|
||||
self.assertEquals(old_lft - new_lft, rgt - lft + 1)
|
||||
|
||||
|
||||
# adjacent siblings, hence rgt diff will be 0
|
||||
self.assertEquals(new_rgt - old_rgt, 0)
|
||||
|
||||
|
||||
self.move_it_back()
|
||||
|
||||
|
||||
def test_move_group_into_root(self):
|
||||
group_b = frappe.get_doc("Item Group", "_Test Item Group B")
|
||||
group_b.parent_item_group = ""
|
||||
@ -108,101 +107,101 @@ class TestItem(unittest.TestCase):
|
||||
|
||||
# trick! works because it hasn't been rolled back :D
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
self.move_it_back()
|
||||
|
||||
|
||||
def print_tree(self):
|
||||
import json
|
||||
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):
|
||||
# before move
|
||||
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")
|
||||
lft, rgt = group_b_3.lft, group_b_3.rgt
|
||||
|
||||
|
||||
# child of right sibling is moved into it
|
||||
group_b_3.parent_item_group = "_Test Item Group C"
|
||||
group_b_3.save()
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
new_lft, new_rgt = frappe.db.get_value("Item Group", "_Test Item Group C", ["lft", "rgt"])
|
||||
|
||||
|
||||
# lft should remain the same
|
||||
self.assertEquals(old_lft - new_lft, 0)
|
||||
|
||||
|
||||
# rgt should increase
|
||||
self.assertEquals(new_rgt - old_rgt, rgt - lft + 1)
|
||||
|
||||
|
||||
# move it back
|
||||
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.save()
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
def test_delete_leaf(self):
|
||||
# for checking later
|
||||
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")
|
||||
|
||||
|
||||
ancestors = get_ancestors_of("Item Group", "_Test Item Group B - 3")
|
||||
ancestors = frappe.db.sql("""select name, rgt from `tabItem Group`
|
||||
where name in ({})""".format(", ".join(["%s"]*len(ancestors))), tuple(ancestors), as_dict=True)
|
||||
|
||||
|
||||
frappe.delete_doc("Item Group", "_Test Item Group B - 3")
|
||||
records_to_test = test_records[2:]
|
||||
del records_to_test[4]
|
||||
self.test_basic_tree(records=records_to_test)
|
||||
|
||||
|
||||
# rgt of each ancestor would reduce by 2
|
||||
for item_group in ancestors:
|
||||
new_lft, new_rgt = frappe.db.get_value("Item Group", item_group.name, ["lft", "rgt"])
|
||||
self.assertEquals(new_rgt, item_group.rgt - 2)
|
||||
|
||||
|
||||
# insert it back
|
||||
frappe.copy_doc(test_records[6]).insert()
|
||||
|
||||
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
def test_delete_group(self):
|
||||
# cannot delete group with child, but can delete leaf
|
||||
self.assertRaises(NestedSetChildExistsError, frappe.delete_doc, "Item Group", "_Test Item Group B")
|
||||
|
||||
|
||||
def test_merge_groups(self):
|
||||
frappe.rename_doc("Item Group", "_Test Item Group B", "_Test Item Group C", merge=True)
|
||||
records_to_test = test_records[2:]
|
||||
del records_to_test[1]
|
||||
self.test_basic_tree(records=records_to_test)
|
||||
|
||||
|
||||
# insert Group B back
|
||||
frappe.copy_doc(test_records[3]).insert()
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
# move its children back
|
||||
for name in frappe.db.sql_list("""select name from `tabItem Group`
|
||||
where parent_item_group='_Test Item Group C'"""):
|
||||
|
||||
|
||||
doc = frappe.get_doc("Item Group", name)
|
||||
doc.parent_item_group = "_Test Item Group B"
|
||||
doc.save()
|
||||
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
def test_merge_leaves(self):
|
||||
frappe.rename_doc("Item Group", "_Test Item Group B - 2", "_Test Item Group B - 1", merge=True)
|
||||
records_to_test = test_records[2:]
|
||||
del records_to_test[3]
|
||||
self.test_basic_tree(records=records_to_test)
|
||||
|
||||
|
||||
# insert Group B - 2back
|
||||
frappe.copy_doc(test_records[5]).insert()
|
||||
self.test_basic_tree()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_merge_group_into_leaf(self):
|
||||
self.assertRaises(NestedSetInvalidMergeError, frappe.rename_doc, "Item Group", "_Test Item Group B",
|
||||
"_Test Item Group B - 3", merge=True)
|
||||
self.assertRaises(NestedSetInvalidMergeError, frappe.rename_doc, "Item Group", "_Test Item Group B",
|
||||
"_Test Item Group B - 3", merge=True)
|
||||
|
@ -4,9 +4,12 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
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):
|
||||
if self.partner_website and not self.partner_website.startswith("http"):
|
||||
self.partner_website = "http://" + self.partner_website
|
||||
@ -14,8 +17,8 @@ class SalesPartner(Document):
|
||||
def get_contacts(self, nm):
|
||||
if nm:
|
||||
return frappe.db.convert_to_lists(frappe.db.sql("""
|
||||
select name, CONCAT(IFNULL(first_name,''),
|
||||
' ',IFNULL(last_name,'')),contact_no,email_id
|
||||
select name, CONCAT(IFNULL(first_name,''),
|
||||
' ',IFNULL(last_name,'')),contact_no,email_id
|
||||
from `tabContact` where sales_partner = %s""", nm))
|
||||
else:
|
||||
return ''
|
||||
|
@ -15,19 +15,22 @@ from erpnext.controllers.selling_controller import SellingController
|
||||
class DeliveryNote(SellingController):
|
||||
tname = 'Delivery Note Item'
|
||||
fname = 'delivery_note_details'
|
||||
status_updater = [{
|
||||
'source_dt': 'Delivery Note Item',
|
||||
'target_dt': 'Sales Order Item',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_field': 'delivered_qty',
|
||||
'target_parent_dt': 'Sales Order',
|
||||
'target_parent_field': 'per_delivered',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'against_sales_order',
|
||||
'status_field': 'delivery_status',
|
||||
'keyword': 'Delivered'
|
||||
}]
|
||||
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(DeliveryNote, self).__init__(arg1, arg2)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Delivery Note Item',
|
||||
'target_dt': 'Sales Order Item',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_field': 'delivered_qty',
|
||||
'target_parent_dt': 'Sales Order',
|
||||
'target_parent_field': 'per_delivered',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'against_sales_order',
|
||||
'status_field': 'delivery_status',
|
||||
'keyword': 'Delivered'
|
||||
}]
|
||||
|
||||
def onload(self):
|
||||
billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabSales Invoice Item`
|
||||
|
@ -11,76 +11,76 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_
|
||||
|
||||
def _insert_purchase_receipt(item_code=None):
|
||||
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.get("purchase_receipt_details")[0].item_code = item_code
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
|
||||
class TestDeliveryNote(unittest.TestCase):
|
||||
def test_over_billing_against_dn(self):
|
||||
self.clear_stock_account_balance()
|
||||
_insert_purchase_receipt()
|
||||
|
||||
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
_insert_purchase_receipt()
|
||||
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 = frappe.get_doc("Delivery Note", dn.name)
|
||||
dn.submit()
|
||||
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
|
||||
si[1]["rate"] = 200
|
||||
si.get("entries")[0].rate = 200
|
||||
self.assertRaises(frappe.ValidationError, frappe.get_doc(si).insert)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_delivery_note_no_gl_entry(self):
|
||||
self.clear_stock_account_balance()
|
||||
set_perpetual_inventory(0)
|
||||
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 0)
|
||||
|
||||
|
||||
_insert_purchase_receipt()
|
||||
|
||||
|
||||
dn = frappe.copy_doc(test_records[0])
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn.name,
|
||||
|
||||
stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn.name,
|
||||
"item_code": "_Test Item"}, ["stock_value", "stock_value_difference"])
|
||||
self.assertEqual(stock_value, 0)
|
||||
self.assertEqual(stock_value_difference, -375)
|
||||
|
||||
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
|
||||
|
||||
|
||||
def test_delivery_note_gl_entry(self):
|
||||
self.clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1)
|
||||
frappe.db.set_value("Item", "_Test Item", "valuation_method", "FIFO")
|
||||
|
||||
|
||||
_insert_purchase_receipt()
|
||||
|
||||
|
||||
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].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})
|
||||
|
||||
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
prev_bal = get_balance_on(stock_in_hand_account, dn.posting_date)
|
||||
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
|
||||
gl_entries = get_gl_entries("Delivery Note", dn.name)
|
||||
self.assertTrue(gl_entries)
|
||||
expected_values = {
|
||||
@ -89,20 +89,20 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
|
||||
# check stock in hand balance
|
||||
bal = get_balance_on(stock_in_hand_account, dn.posting_date)
|
||||
self.assertEquals(bal, prev_bal - 375.0)
|
||||
|
||||
|
||||
# back dated purchase receipt
|
||||
pr = frappe.copy_doc(pr_test_records[0])
|
||||
pr.posting_date = "2013-01-01"
|
||||
pr.get("purchase_receipt_details")[0].rate = 100
|
||||
pr.get("purchase_receipt_details")[0].base_amount = 100
|
||||
|
||||
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
|
||||
gl_entries = get_gl_entries("Delivery Note", dn.name)
|
||||
self.assertTrue(gl_entries)
|
||||
expected_values = {
|
||||
@ -111,71 +111,71 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
|
||||
dn.cancel()
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def test_delivery_note_gl_entry_packing_item(self):
|
||||
self.clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
|
||||
_insert_purchase_receipt()
|
||||
_insert_purchase_receipt("_Test Item Home Desktop 100")
|
||||
|
||||
|
||||
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].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})
|
||||
|
||||
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
prev_bal = get_balance_on(stock_in_hand_account, dn.posting_date)
|
||||
|
||||
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
|
||||
gl_entries = get_gl_entries("Delivery Note", dn.name)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
expected_values = {
|
||||
stock_in_hand_account: [0.0, 525],
|
||||
"Cost of Goods Sold - _TC": [525.0, 0.0]
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
|
||||
# check stock in hand balance
|
||||
bal = get_balance_on(stock_in_hand_account, dn.posting_date)
|
||||
self.assertEquals(bal, prev_bal - 525.0)
|
||||
|
||||
|
||||
dn.cancel()
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
|
||||
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def test_serialized(self):
|
||||
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
|
||||
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no)
|
||||
|
||||
|
||||
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].qty = 1
|
||||
dn.get("delivery_note_details")[0].serial_no = serial_nos[0]
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
|
||||
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.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)
|
||||
|
||||
|
||||
return dn
|
||||
|
||||
|
||||
def test_serialized_cancel(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
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], "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"))
|
||||
|
||||
def test_serialize_status(self):
|
||||
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
|
||||
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.get("mtn_details")[0].serial_no)
|
||||
|
||||
|
||||
sr = frappe.get_doc("Serial No", serial_nos[0])
|
||||
sr.status = "Not Available"
|
||||
sr.save()
|
||||
|
||||
|
||||
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].qty = 1
|
||||
@ -206,7 +206,7 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
dn.insert()
|
||||
|
||||
self.assertRaises(SerialNoStatusError, dn.submit)
|
||||
|
||||
|
||||
def clear_stock_account_balance(self):
|
||||
frappe.db.sql("""delete from `tabBin`""")
|
||||
frappe.db.sql("delete from `tabStock Ledger Entry`")
|
||||
@ -214,4 +214,4 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
|
||||
test_dependencies = ["Sales BOM"]
|
||||
|
||||
test_records = frappe.get_test_records('Delivery Note')
|
||||
test_records = frappe.get_test_records('Delivery Note')
|
||||
|
@ -3,32 +3,29 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cstr, flt, getdate, now_datetime, formatdate
|
||||
|
||||
from frappe import msgprint, _
|
||||
|
||||
from frappe.model.controller import DocListController
|
||||
from frappe.utils import cstr, flt, getdate, now_datetime, formatdate
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
|
||||
class WarehouseNotSet(Exception): pass
|
||||
|
||||
class Item(DocListController):
|
||||
class Item(WebsiteGenerator):
|
||||
def onload(self):
|
||||
self.set("__sle_exists", self.check_if_sle_exists())
|
||||
|
||||
|
||||
def autoname(self):
|
||||
if frappe.db.get_default("item_naming_by")=="Naming Series":
|
||||
from frappe.model.naming import make_autoname
|
||||
self.item_code = make_autoname(self.naming_series+'.#####')
|
||||
elif not self.item_code:
|
||||
msgprint(_("Item Code (item_code) is mandatory because Item naming is not sequential."), raise_exception=1)
|
||||
|
||||
|
||||
self.name = self.item_code
|
||||
|
||||
|
||||
def validate(self):
|
||||
if not self.stock_uom:
|
||||
msgprint(_("Please enter Default Unit of Measure"), raise_exception=1)
|
||||
|
||||
|
||||
self.check_warehouse_is_set_for_stock_item()
|
||||
self.check_stock_uom_with_bin()
|
||||
self.add_default_uom_in_conversion_factor_table()
|
||||
@ -40,14 +37,15 @@ class Item(DocListController):
|
||||
self.validate_barcode()
|
||||
self.cant_change()
|
||||
self.validate_item_type_for_reorder()
|
||||
|
||||
|
||||
if not self.parent_website_route:
|
||||
self.parent_website_route = frappe.get_website_route("Item Group", self.item_group)
|
||||
|
||||
if self.name:
|
||||
self.old_page_name = frappe.db.get_value('Item', self.name, 'page_name')
|
||||
|
||||
|
||||
def on_update(self):
|
||||
super(Item, self).on_update()
|
||||
self.validate_name_with_item_group()
|
||||
self.update_item_price()
|
||||
|
||||
@ -55,46 +53,46 @@ class Item(DocListController):
|
||||
if self.is_stock_item=="Yes" and not self.default_warehouse:
|
||||
frappe.msgprint(_("Default Warehouse is mandatory for Stock Item."),
|
||||
raise_exception=WarehouseNotSet)
|
||||
|
||||
|
||||
def add_default_uom_in_conversion_factor_table(self):
|
||||
uom_conv_list = [d.uom for d in self.get("uom_conversion_details")]
|
||||
if self.stock_uom not in uom_conv_list:
|
||||
ch = self.append('uom_conversion_details', {})
|
||||
ch.uom = self.stock_uom
|
||||
ch.conversion_factor = 1
|
||||
|
||||
|
||||
to_remove = []
|
||||
for d in self.get("uom_conversion_details"):
|
||||
if d.conversion_factor == 1 and d.uom != self.stock_uom:
|
||||
to_remove.append(d)
|
||||
|
||||
|
||||
[self.remove(d) for d in to_remove]
|
||||
|
||||
|
||||
|
||||
def check_stock_uom_with_bin(self):
|
||||
if not self.get("__islocal"):
|
||||
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")
|
||||
if ref_uom:
|
||||
if cstr(ref_uom) != cstr(self.stock_uom):
|
||||
matched = False
|
||||
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)
|
||||
for bin in bin_list:
|
||||
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):
|
||||
matched = False
|
||||
break
|
||||
|
||||
|
||||
if matched and bin_list:
|
||||
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""",
|
||||
(self.stock_uom, self.name))
|
||||
|
||||
|
||||
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."))
|
||||
|
||||
|
||||
def validate_conversion_factor(self):
|
||||
check_list = []
|
||||
for d in self.get('uom_conversion_details'):
|
||||
@ -105,12 +103,12 @@ class Item(DocListController):
|
||||
check_list.append(cstr(d.uom))
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
def validate_item_type(self):
|
||||
if cstr(self.is_manufactured_item) == "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':
|
||||
msgprint("'Has Serial No' can not be 'Yes' for non-stock item", raise_exception=1)
|
||||
|
||||
|
||||
def check_for_active_boms(self):
|
||||
if self.is_purchase_item != "Yes":
|
||||
bom_mat = frappe.db.sql("""select distinct 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
|
||||
bom_mat = frappe.db.sql("""select distinct 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 t2.docstatus = 1 and t1.docstatus =1 """, self.name)
|
||||
|
||||
|
||||
if bom_mat and bom_mat[0][0]:
|
||||
frappe.throw(_("Item must be a purchase item, \
|
||||
as it is present in one or many Active BOMs"))
|
||||
|
||||
|
||||
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,))
|
||||
if bom and bom[0][0]:
|
||||
frappe.throw(_("""Allow Bill of Materials should be 'Yes'. Because one or many \
|
||||
active BOMs present for this item"""))
|
||||
|
||||
|
||||
def fill_customer_code(self):
|
||||
""" Append all the customer codes and insert into "customer_code" field of item table """
|
||||
cust_code=[]
|
||||
@ -153,7 +151,7 @@ class Item(DocListController):
|
||||
for d in self.get('item_tax'):
|
||||
if d.tax_type:
|
||||
account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
|
||||
|
||||
|
||||
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)
|
||||
else:
|
||||
@ -161,34 +159,34 @@ class Item(DocListController):
|
||||
msgprint("Rate is entered twice for: '%s'" % d.tax_type, raise_exception=1)
|
||||
else:
|
||||
check_list.append(d.tax_type)
|
||||
|
||||
|
||||
def validate_barcode(self):
|
||||
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))
|
||||
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)
|
||||
|
||||
def cant_change(self):
|
||||
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)
|
||||
|
||||
if vals and ((self.is_stock_item == "No" and vals.is_stock_item == "Yes") or
|
||||
vals.has_serial_no != self.has_serial_no 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
|
||||
cstr(vals.valuation_method) != cstr(self.valuation_method)):
|
||||
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'"))
|
||||
|
||||
|
||||
def validate_item_type_for_reorder(self):
|
||||
if self.re_order_level or len(self.get("item_reorder", {"material_request_type": "Purchase"})):
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
return sle and 'exists' or 'not exists'
|
||||
|
||||
@ -196,11 +194,11 @@ class Item(DocListController):
|
||||
# causes problem with tree build
|
||||
if frappe.db.exists("Item Group", self.name):
|
||||
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)
|
||||
|
||||
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""",
|
||||
(self.item_name, self.description, self.name))
|
||||
|
||||
@ -209,9 +207,9 @@ class Item(DocListController):
|
||||
page_name_from = self.name
|
||||
else:
|
||||
page_name_from = self.name + " " + self.item_name
|
||||
|
||||
|
||||
return page_name_from
|
||||
|
||||
|
||||
def get_tax_rate(self, tax_type):
|
||||
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 ''
|
||||
}
|
||||
return ret
|
||||
|
||||
|
||||
def on_trash(self):
|
||||
super(Item, self).on_trash()
|
||||
frappe.db.sql("""delete from tabBin where item_code=%s""", self.item_code)
|
||||
|
||||
def before_rename(self, olddn, newdn, merge=False):
|
||||
@ -232,7 +231,7 @@ class Item(DocListController):
|
||||
# Validate properties before merging
|
||||
if not frappe.db.exists("Item", newdn):
|
||||
frappe.throw(_("Item ") + newdn +_(" does not exists"))
|
||||
|
||||
|
||||
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)]
|
||||
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)
|
||||
|
||||
def after_rename(self, olddn, newdn, merge):
|
||||
super(Item, self).after_rename(olddn, newdn, merge)
|
||||
|
||||
frappe.db.set_value("Item", newdn, "item_code", newdn)
|
||||
|
||||
if merge:
|
||||
self.set_last_purchase_rate(newdn)
|
||||
self.recalculate_bin_qty(newdn)
|
||||
|
||||
|
||||
def set_last_purchase_rate(self, newdn):
|
||||
last_purchase_rate = get_last_purchase_details(newdn).get("base_rate", 0)
|
||||
frappe.db.set_value("Item", newdn, "last_purchase_rate", last_purchase_rate)
|
||||
|
||||
|
||||
def recalculate_bin_qty(self, newdn):
|
||||
from erpnext.utilities.repost_stock import repost_stock
|
||||
frappe.db.auto_commit_on_many_writes = 1
|
||||
frappe.db.set_default("allow_negative_stock", 1)
|
||||
|
||||
|
||||
for warehouse in frappe.db.sql("select name from `tabWarehouse`"):
|
||||
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.auto_commit_on_many_writes = 0
|
||||
|
||||
def validate_end_of_life(item_code, end_of_life=None, verbose=1):
|
||||
if not 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():
|
||||
msg = (_("Item") + " %(item_code)s: " + _("reached its end of life on") + \
|
||||
" %(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),
|
||||
"end_of_life_label": frappe.get_meta("Item").get_label("end_of_life")
|
||||
}
|
||||
|
||||
|
||||
_msgprint(msg, verbose)
|
||||
|
||||
|
||||
def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
|
||||
if not is_stock_item:
|
||||
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
|
||||
|
||||
|
||||
if is_stock_item != "Yes":
|
||||
msg = (_("Item") + " %(item_code)s: " + _("is not a Stock Item")) % {
|
||||
"item_code": item_code,
|
||||
}
|
||||
|
||||
|
||||
_msgprint(msg, verbose)
|
||||
|
||||
|
||||
def validate_cancelled_item(item_code, docstatus=None, verbose=1):
|
||||
if docstatus is None:
|
||||
docstatus = frappe.db.get_value("Item", item_code, "docstatus")
|
||||
|
||||
|
||||
if docstatus == 2:
|
||||
msg = (_("Item") + " %(item_code)s: " + _("is a cancelled Item")) % {
|
||||
"item_code": item_code,
|
||||
}
|
||||
|
||||
|
||||
_msgprint(msg, verbose)
|
||||
|
||||
def _msgprint(msg, verbose):
|
||||
@ -306,22 +306,22 @@ def _msgprint(msg, verbose):
|
||||
msgprint(msg, raise_exception=True)
|
||||
else:
|
||||
raise frappe.ValidationError, msg
|
||||
|
||||
|
||||
|
||||
|
||||
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
"""returns last purchase details in stock uom"""
|
||||
# get last purchase order item details
|
||||
last_purchase_order = frappe.db.sql("""\
|
||||
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
|
||||
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
|
||||
order by po.transaction_date desc, po.name desc
|
||||
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("""\
|
||||
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,
|
||||
@ -342,16 +342,16 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
|
||||
# use purchase order
|
||||
last_purchase = last_purchase_order[0]
|
||||
purchase_date = purchase_order_date
|
||||
|
||||
|
||||
elif (purchase_receipt_date > purchase_order_date) or \
|
||||
(last_purchase_receipt and not last_purchase_order):
|
||||
# use purchase receipt
|
||||
last_purchase = last_purchase_receipt[0]
|
||||
purchase_date = purchase_receipt_date
|
||||
|
||||
|
||||
else:
|
||||
return frappe._dict()
|
||||
|
||||
|
||||
conversion_factor = flt(last_purchase.conversion_factor)
|
||||
out = frappe._dict({
|
||||
"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,
|
||||
"base_rate": out.base_rate
|
||||
})
|
||||
|
||||
return out
|
||||
|
||||
return out
|
||||
|
@ -169,11 +169,10 @@ def update_completed_qty(doc, method):
|
||||
|
||||
for mr_name, mr_items in material_request_map.items():
|
||||
mr_obj = frappe.get_doc("Material Request", mr_name)
|
||||
mr_doctype = frappe.get_meta("Material Request")
|
||||
|
||||
if mr_obj.status in ["Stopped", "Cancelled"]:
|
||||
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)
|
||||
|
||||
_update_requested_qty(doc, mr_obj, mr_items)
|
||||
|
@ -24,7 +24,7 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
mr.submit()
|
||||
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")))
|
||||
|
||||
def test_make_supplier_quotation(self):
|
||||
@ -38,7 +38,7 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
mr.submit()
|
||||
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")))
|
||||
|
||||
|
||||
@ -55,14 +55,9 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
mr.submit()
|
||||
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")))
|
||||
|
||||
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):
|
||||
self.assertEqual(flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
|
||||
"warehouse": "_Test Warehouse - _TC"}, "indented_qty")), qty1)
|
||||
@ -116,28 +111,34 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
mr.submit()
|
||||
|
||||
# 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)
|
||||
|
||||
# map a purchase order
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
po_doc = make_purchase_order(mr.name)
|
||||
po_doc["supplier"] = "_Test Supplier"
|
||||
po_doc["transaction_date"] = "2013-07-07"
|
||||
po_doc.get("po_details")[0]["qty"] = 27.0
|
||||
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")[1]["schedule_date"] = "2013-07-09"
|
||||
po_doc.supplier = "_Test Supplier"
|
||||
po_doc.transaction_date = "2013-07-07"
|
||||
po_doc.get("po_details")[0].qty = 27.0
|
||||
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")[1].schedule_date = "2013-07-09"
|
||||
|
||||
|
||||
# check for stopped status of Material Request
|
||||
po = frappe.copy_doc(po_doc)
|
||||
po.insert()
|
||||
po.load_from_db()
|
||||
mr.update_status('Stopped')
|
||||
self.assertRaises(frappe.ValidationError, po.submit)
|
||||
self.assertRaises(frappe.ValidationError, po.cancel)
|
||||
self.assertRaises(frappe.InvalidStatusError, po.submit)
|
||||
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')
|
||||
po = frappe.copy_doc(po_doc)
|
||||
po.insert()
|
||||
@ -145,13 +146,18 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
|
||||
# check if per complete is as expected
|
||||
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)
|
||||
|
||||
po.cancel()
|
||||
# check if per complete is as expected
|
||||
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)
|
||||
|
||||
def test_completed_qty_for_transfer(self):
|
||||
@ -165,7 +171,9 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
mr.submit()
|
||||
|
||||
# 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)
|
||||
|
||||
@ -198,8 +206,12 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
se = frappe.copy_doc(se_doc)
|
||||
se.insert()
|
||||
mr.update_status('Stopped')
|
||||
self.assertRaises(frappe.ValidationError, se.submit)
|
||||
self.assertRaises(frappe.ValidationError, se.cancel)
|
||||
self.assertRaises(frappe.InvalidStatusError, se.submit)
|
||||
|
||||
mr.update_status('Submitted')
|
||||
se.submit()
|
||||
mr.update_status('Stopped')
|
||||
self.assertRaises(frappe.InvalidStatusError, se.cancel)
|
||||
|
||||
mr.update_status('Submitted')
|
||||
se = frappe.copy_doc(se_doc)
|
||||
@ -208,13 +220,19 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
|
||||
# check if per complete is as expected
|
||||
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)
|
||||
|
||||
# check if per complete is as expected for Stock Entry cancelled
|
||||
se.cancel()
|
||||
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)
|
||||
|
||||
def test_completed_qty_for_over_transfer(self):
|
||||
@ -228,7 +246,9 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
mr.submit()
|
||||
|
||||
# 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)
|
||||
|
||||
@ -261,8 +281,8 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
se = frappe.copy_doc(se_doc)
|
||||
se.insert()
|
||||
mr.update_status('Stopped')
|
||||
self.assertRaises(frappe.ValidationError, se.submit)
|
||||
self.assertRaises(frappe.ValidationError, se.cancel)
|
||||
self.assertRaises(frappe.InvalidStatusError, se.submit)
|
||||
self.assertRaises(frappe.InvalidStatusError, se.cancel)
|
||||
|
||||
mr.update_status('Submitted')
|
||||
se = frappe.copy_doc(se_doc)
|
||||
@ -271,13 +291,19 @@ class TestMaterialRequest(unittest.TestCase):
|
||||
|
||||
# check if per complete is as expected
|
||||
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)
|
||||
|
||||
# check if per complete is as expected for Stock Entry cancelled
|
||||
se.cancel()
|
||||
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)
|
||||
|
||||
def test_incorrect_mapping_of_stock_entry(self):
|
||||
|
@ -14,19 +14,21 @@ from erpnext.controllers.buying_controller import BuyingController
|
||||
class PurchaseReceipt(BuyingController):
|
||||
tname = 'Purchase Receipt Item'
|
||||
fname = 'purchase_receipt_details'
|
||||
count = 0
|
||||
status_updater = [{
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_field': 'received_qty',
|
||||
'target_parent_dt': 'Purchase Order',
|
||||
'target_parent_field': 'per_received',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'prevdoc_docname',
|
||||
}]
|
||||
|
||||
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(PurchaseReceipt, self).__init__(arg1, arg2)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
'join_field': 'prevdoc_detail_docname',
|
||||
'target_field': 'received_qty',
|
||||
'target_parent_dt': 'Purchase Order',
|
||||
'target_parent_field': 'per_received',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'qty',
|
||||
'percent_join_field': 'prevdoc_docname',
|
||||
}]
|
||||
|
||||
def onload(self):
|
||||
billed_qty = frappe.db.sql("""select sum(ifnull(qty, 0)) from `tabPurchase Invoice Item`
|
||||
where purchase_receipt=%s""", self.name)
|
||||
@ -36,7 +38,7 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
def validate(self):
|
||||
super(PurchaseReceipt, self).validate()
|
||||
|
||||
|
||||
self.po_required()
|
||||
|
||||
if not self.status:
|
||||
@ -60,7 +62,7 @@ class PurchaseReceipt(BuyingController):
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
self.update_raw_materials_supplied("pr_raw_material_details")
|
||||
|
||||
|
||||
self.update_valuation_rate("purchase_receipt_details")
|
||||
|
||||
def validate_rejected_warehouse(self):
|
||||
@ -68,7 +70,7 @@ class PurchaseReceipt(BuyingController):
|
||||
if flt(d.rejected_qty) and not d.rejected_warehouse:
|
||||
d.rejected_warehouse = self.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
|
||||
def validate_accepted_rejected_qty(self):
|
||||
@ -99,7 +101,7 @@ class PurchaseReceipt(BuyingController):
|
||||
if exists:
|
||||
frappe.msgprint("Another Purchase Receipt using the same Challan No. already exists.\
|
||||
Please enter a valid Challan No.", raise_exception=1)
|
||||
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(PurchaseReceipt, self).validate_with_previous_doc(self.tname, {
|
||||
"Purchase Order": {
|
||||
@ -112,7 +114,7 @@ class PurchaseReceipt(BuyingController):
|
||||
"is_child_table": True
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if cint(frappe.defaults.get_global_default('maintain_same_rate')):
|
||||
super(PurchaseReceipt, self).validate_with_previous_doc(self.tname, {
|
||||
"Purchase Order Item": {
|
||||
@ -121,7 +123,7 @@ class PurchaseReceipt(BuyingController):
|
||||
"is_child_table": True
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
def po_required(self):
|
||||
if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
|
||||
@ -133,18 +135,18 @@ class PurchaseReceipt(BuyingController):
|
||||
def update_stock(self):
|
||||
sl_entries = []
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
|
||||
for d in self.get('purchase_receipt_details'):
|
||||
if d.item_code in stock_items and d.warehouse:
|
||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
|
||||
if pr_qty:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"actual_qty": flt(pr_qty),
|
||||
"serial_no": cstr(d.serial_no).strip(),
|
||||
"incoming_rate": d.valuation_rate
|
||||
}))
|
||||
|
||||
|
||||
if flt(d.rejected_qty) > 0:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"warehouse": d.rejected_warehouse,
|
||||
@ -152,28 +154,28 @@ class PurchaseReceipt(BuyingController):
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"incoming_rate": d.valuation_rate
|
||||
}))
|
||||
|
||||
|
||||
self.bk_flush_supp_wh(sl_entries)
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
|
||||
def update_ordered_qty(self):
|
||||
stock_items = self.get_stock_items()
|
||||
for d in self.get("purchase_receipt_details"):
|
||||
if d.item_code in stock_items and d.warehouse \
|
||||
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)
|
||||
po_qty, ordered_warehouse = self.get_po_qty_and_warehouse(d.prevdoc_detail_docname)
|
||||
|
||||
|
||||
if not ordered_warehouse:
|
||||
frappe.throw(_("Warehouse is missing in Purchase Order"))
|
||||
|
||||
|
||||
if already_received_qty + d.qty > po_qty:
|
||||
ordered_qty = - (po_qty - already_received_qty) * flt(d.conversion_factor)
|
||||
else:
|
||||
ordered_qty = - flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
|
||||
update_bin({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": ordered_warehouse,
|
||||
@ -182,20 +184,20 @@ class PurchaseReceipt(BuyingController):
|
||||
})
|
||||
|
||||
def get_already_received_qty(self, po, po_detail):
|
||||
qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
|
||||
where prevdoc_detail_docname = %s and docstatus = 1
|
||||
and prevdoc_doctype='Purchase Order' and prevdoc_docname=%s
|
||||
qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
|
||||
where prevdoc_detail_docname = %s and docstatus = 1
|
||||
and prevdoc_doctype='Purchase Order' and prevdoc_docname=%s
|
||||
and parent != %s""", (po_detail, po, self.name))
|
||||
return qty and flt(qty[0][0]) or 0.0
|
||||
|
||||
|
||||
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"])
|
||||
return po_qty, po_warehouse
|
||||
|
||||
|
||||
def bk_flush_supp_wh(self, sl_entries):
|
||||
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
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"item_code": d.rm_item_code,
|
||||
@ -231,22 +233,22 @@ class PurchaseReceipt(BuyingController):
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
|
||||
self.update_ordered_qty()
|
||||
|
||||
|
||||
self.update_stock()
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||
update_serial_nos_after_submit(self, "purchase_receipt_details")
|
||||
|
||||
purchase_controller.update_last_purchase_rate(self, 1)
|
||||
|
||||
|
||||
self.make_gl_entries()
|
||||
|
||||
def check_next_docstatus(self):
|
||||
submit_rv = frappe.db.sql("""select t1.name
|
||||
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
|
||||
where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""",
|
||||
submit_rv = frappe.db.sql("""select t1.name
|
||||
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
|
||||
where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""",
|
||||
(self.name))
|
||||
if submit_rv:
|
||||
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)
|
||||
# Check if Purchase Invoice has been submitted against current Purchase Order
|
||||
submitted = frappe.db.sql("""select t1.name
|
||||
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
|
||||
where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""",
|
||||
submitted = frappe.db.sql("""select t1.name
|
||||
from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2
|
||||
where t1.name = t2.parent and t2.purchase_receipt = %s and t1.docstatus = 1""",
|
||||
self.name)
|
||||
if submitted:
|
||||
frappe.throw("Purchase Invoice : " + cstr(submitted[0][0]) +
|
||||
frappe.throw("Purchase Invoice : " + cstr(submitted[0][0]) +
|
||||
" has already been submitted !")
|
||||
|
||||
|
||||
frappe.db.set(self,'status','Cancelled')
|
||||
|
||||
self.update_ordered_qty()
|
||||
|
||||
|
||||
self.update_stock()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
pc_obj.update_last_purchase_rate(self, 0)
|
||||
|
||||
|
||||
self.make_cancel_gl_entries()
|
||||
|
||||
|
||||
def get_current_stock(self):
|
||||
for d in self.get('pr_raw_material_details'):
|
||||
if self.supplier_warehouse:
|
||||
@ -285,42 +287,42 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
def get_rate(self,arg):
|
||||
return frappe.get_doc('Purchase Common').get_rate(arg,self)
|
||||
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
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)
|
||||
return gl_entries
|
||||
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
|
||||
def set_missing_values(source, target):
|
||||
doc = frappe.get_doc(target)
|
||||
doc.run_method("set_missing_values")
|
||||
|
||||
doclist = get_mapped_doc("Purchase Receipt", source_name, {
|
||||
"Purchase Receipt": {
|
||||
"doctype": "Purchase Invoice",
|
||||
"doctype": "Purchase Invoice",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
},
|
||||
"Purchase Receipt Item": {
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"field_map": {
|
||||
"name": "pr_detail",
|
||||
"parent": "purchase_receipt",
|
||||
"prevdoc_detail_docname": "po_detail",
|
||||
"prevdoc_docname": "purchase_order",
|
||||
"name": "pr_detail",
|
||||
"parent": "purchase_receipt",
|
||||
"prevdoc_detail_docname": "po_detail",
|
||||
"prevdoc_docname": "purchase_order",
|
||||
},
|
||||
},
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"add_if_empty": True
|
||||
}
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist
|
||||
return doclist
|
||||
|
@ -15,88 +15,88 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||
|
||||
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 = frappe.get_doc("Purchase Receipt", pr.name)
|
||||
pr.submit()
|
||||
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")))
|
||||
|
||||
|
||||
# modify rate
|
||||
pi.get("entries")[0]["rate"] = 200
|
||||
pi.get("entries")[0].rate = 200
|
||||
self.assertRaises(frappe.ValidationError, frappe.get_doc(pi).submit)
|
||||
|
||||
|
||||
def test_purchase_receipt_no_gl_entry(self):
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory(0)
|
||||
pr = frappe.copy_doc(test_records[0])
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name,
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
|
||||
|
||||
stock_value, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name,
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
|
||||
["stock_value", "stock_value_difference"])
|
||||
self.assertEqual(stock_value, 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")
|
||||
self.assertEqual(bin_stock_value, 375)
|
||||
|
||||
|
||||
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
|
||||
|
||||
|
||||
def test_purchase_receipt_gl_entry(self):
|
||||
self._clear_stock_account_balance()
|
||||
|
||||
|
||||
set_perpetual_inventory()
|
||||
self.assertEqual(cint(frappe.defaults.get_global_default("auto_accounting_for_stock")), 1)
|
||||
|
||||
|
||||
pr = frappe.copy_doc(test_records[0])
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
|
||||
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
|
||||
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
stock_in_hand_account = frappe.db.get_value("Account",
|
||||
{"master_name": pr.get("purchase_receipt_details")[0].warehouse})
|
||||
fixed_asset_account = frappe.db.get_value("Account",
|
||||
|
||||
stock_in_hand_account = frappe.db.get_value("Account",
|
||||
{"master_name": pr.get("purchase_receipt_details")[0].warehouse})
|
||||
fixed_asset_account = frappe.db.get_value("Account",
|
||||
{"master_name": pr.get("purchase_receipt_details")[1].warehouse})
|
||||
|
||||
|
||||
expected_values = {
|
||||
stock_in_hand_account: [375.0, 0.0],
|
||||
fixed_asset_account: [375.0, 0.0],
|
||||
"Stock Received But Not Billed - _TC": [0.0, 750.0]
|
||||
}
|
||||
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
|
||||
pr.cancel()
|
||||
self.assertFalse(get_gl_entries("Purchase Receipt", pr.name))
|
||||
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def _clear_stock_account_balance(self):
|
||||
frappe.db.sql("delete from `tabStock Ledger Entry`")
|
||||
frappe.db.sql("""delete from `tabBin`""")
|
||||
frappe.db.sql("""delete from `tabGL Entry`""")
|
||||
|
||||
|
||||
def test_subcontracting(self):
|
||||
pr = frappe.copy_doc(test_records[1])
|
||||
pr.run_method("calculate_taxes_and_totals")
|
||||
pr.insert()
|
||||
|
||||
|
||||
self.assertEquals(pr.get("purchase_receipt_details")[0].rm_supp_cost, 70000.0)
|
||||
self.assertEquals(len(pr.get("pr_raw_material_details")), 2)
|
||||
|
||||
|
||||
def test_serial_no_supplier(self):
|
||||
pr = frappe.copy_doc(test_records[0])
|
||||
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.insert()
|
||||
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)
|
||||
|
||||
|
||||
return pr
|
||||
|
||||
|
||||
def test_serial_no_cancel(self):
|
||||
pr = self.test_serial_no_supplier()
|
||||
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"))
|
||||
|
||||
|
||||
def get_gl_entries(voucher_type, voucher_no):
|
||||
return frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
order by account desc""", (voucher_type, voucher_no), as_dict=1)
|
||||
|
||||
|
||||
def set_perpetual_inventory(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
accounts_settings.auto_accounting_for_stock = enable
|
||||
accounts_settings.save()
|
||||
|
||||
|
||||
|
||||
|
||||
test_dependencies = ["BOM"]
|
||||
|
||||
test_records = frappe.get_test_records('Purchase Receipt')
|
||||
test_records = frappe.get_test_records('Purchase Receipt')
|
||||
|
@ -407,14 +407,14 @@ class TestStockEntry(unittest.TestCase):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice, make_delivery_note
|
||||
|
||||
actual_qty_0 = self._get_actual_qty()
|
||||
|
||||
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].qty = 5.0
|
||||
so.insert()
|
||||
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.posting_date = so.delivery_date
|
||||
dn.insert()
|
||||
@ -423,9 +423,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
actual_qty_1 = self._get_actual_qty()
|
||||
self.assertEquals(actual_qty_0 - delivered_qty, actual_qty_1)
|
||||
|
||||
si_doc = make_sales_invoice(so.name)
|
||||
|
||||
si = frappe.get_doc(si_doc)
|
||||
si = make_sales_invoice(so.name)
|
||||
si.posting_date = dn.posting_date
|
||||
si.debit_to = "_Test Customer - _TC"
|
||||
for d in si.get("entries"):
|
||||
|
@ -14,15 +14,15 @@ from erpnext.accounts.utils import get_fiscal_year, get_stock_and_account_differ
|
||||
class TestStockReconciliation(unittest.TestCase):
|
||||
def test_reco_for_fifo(self):
|
||||
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]]
|
||||
input_data = [
|
||||
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
|
||||
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
|
||||
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
|
||||
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
|
||||
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
|
||||
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
|
||||
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
|
||||
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
|
||||
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
|
||||
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
|
||||
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
|
||||
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
|
||||
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
|
||||
["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
|
||||
[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],
|
||||
[0, "", "2012-12-26", "12:10", 0, -5, 0]
|
||||
]
|
||||
|
||||
|
||||
for d in input_data:
|
||||
self.cleanup_data()
|
||||
self.insert_existing_sle("FIFO")
|
||||
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
|
||||
|
||||
|
||||
# check stock value
|
||||
res = frappe.db.sql("""select stock_value from `tabStock Ledger Entry`
|
||||
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]))
|
||||
self.assertEqual(res and flt(res[0][0]) or 0, d[4])
|
||||
|
||||
|
||||
# check bin qty and stock value
|
||||
bin = frappe.db.sql("""select actual_qty, stock_value from `tabBin`
|
||||
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]])
|
||||
|
||||
|
||||
# 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""",
|
||||
stock_reco.name)
|
||||
self.assertFalse(gl_entries)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_reco_for_moving_average(self):
|
||||
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]]
|
||||
input_data = [
|
||||
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
|
||||
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
|
||||
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
|
||||
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
|
||||
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
|
||||
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
|
||||
[50, 1000, "2012-12-26", "12:00", 50000, 45, 48000],
|
||||
[5, 1000, "2012-12-26", "12:00", 5000, 0, 0],
|
||||
[15, 1000, "2012-12-26", "12:00", 15000, 10, 12000],
|
||||
[25, 900, "2012-12-26", "12:00", 22500, 20, 22500],
|
||||
[20, 500, "2012-12-26", "12:00", 10000, 15, 18000],
|
||||
[50, 1000, "2013-01-01", "12:00", 50000, 65, 68000],
|
||||
[5, 1000, "2013-01-01", "12:00", 5000, 20, 23000],
|
||||
["", 1000, "2012-12-26", "12:05", 15000, 10, 12000],
|
||||
[20, "", "2012-12-26", "12:05", 18000, 15, 18000],
|
||||
[10, 2000, "2012-12-26", "12:10", 20000, 5, 6000],
|
||||
[1, 1000, "2012-12-01", "00:00", 1000, 11, 13200],
|
||||
[0, "", "2012-12-26", "12:10", 0, -5, 0]
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
||||
for d in input_data:
|
||||
self.cleanup_data()
|
||||
self.insert_existing_sle("Moving Average")
|
||||
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
|
||||
|
||||
|
||||
# check stock value in sle
|
||||
res = frappe.db.sql("""select stock_value from `tabStock Ledger Entry`
|
||||
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]))
|
||||
|
||||
|
||||
self.assertEqual(res and flt(res[0][0], 4) or 0, d[4])
|
||||
|
||||
|
||||
# bin qty and stock value
|
||||
bin = frappe.db.sql("""select actual_qty, stock_value from `tabBin`
|
||||
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])])
|
||||
|
||||
|
||||
# no gl entries
|
||||
gl_entries = frappe.db.sql("""select name from `tabGL Entry`
|
||||
where voucher_type = 'Stock Reconciliation' and voucher_no = %s""",
|
||||
gl_entries = frappe.db.sql("""select name from `tabGL Entry`
|
||||
where voucher_type = 'Stock Reconciliation' and voucher_no = %s""",
|
||||
stock_reco.name)
|
||||
self.assertFalse(gl_entries)
|
||||
|
||||
|
||||
def test_reco_fifo_gl_entries(self):
|
||||
frappe.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
|
||||
# [[qty, valuation_rate, posting_date, posting_time, stock_in_hand_debit]]
|
||||
input_data = [
|
||||
[50, 1000, "2012-12-26", "12:00"],
|
||||
[5, 1000, "2012-12-26", "12:00"],
|
||||
[15, 1000, "2012-12-26", "12:00"],
|
||||
[25, 900, "2012-12-26", "12:00"],
|
||||
[20, 500, "2012-12-26", "12:00"],
|
||||
[50, 1000, "2012-12-26", "12:00"],
|
||||
[5, 1000, "2012-12-26", "12:00"],
|
||||
[15, 1000, "2012-12-26", "12:00"],
|
||||
[25, 900, "2012-12-26", "12:00"],
|
||||
[20, 500, "2012-12-26", "12:00"],
|
||||
["", 1000, "2012-12-26", "12:05"],
|
||||
[20, "", "2012-12-26", "12:05"],
|
||||
[10, 2000, "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"],
|
||||
[1, 1000, "2012-12-01", "00:00"],
|
||||
]
|
||||
|
||||
|
||||
for d in input_data:
|
||||
self.cleanup_data()
|
||||
self.insert_existing_sle("FIFO")
|
||||
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])
|
||||
|
||||
|
||||
|
||||
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
|
||||
|
||||
stock_reco.cancel()
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
|
||||
|
||||
|
||||
frappe.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
|
||||
|
||||
def test_reco_moving_average_gl_entries(self):
|
||||
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]]
|
||||
input_data = [
|
||||
[50, 1000, "2012-12-26", "12:00", 36500],
|
||||
[5, 1000, "2012-12-26", "12:00", -8500],
|
||||
[15, 1000, "2012-12-26", "12:00", 1500],
|
||||
[25, 900, "2012-12-26", "12:00", 9000],
|
||||
[20, 500, "2012-12-26", "12:00", -3500],
|
||||
[50, 1000, "2012-12-26", "12:00", 36500],
|
||||
[5, 1000, "2012-12-26", "12:00", -8500],
|
||||
[15, 1000, "2012-12-26", "12:00", 1500],
|
||||
[25, 900, "2012-12-26", "12:00", 9000],
|
||||
[20, 500, "2012-12-26", "12:00", -3500],
|
||||
["", 1000, "2012-12-26", "12:05", 1500],
|
||||
[20, "", "2012-12-26", "12:05", 4500],
|
||||
[10, 2000, "2012-12-26", "12:10", 6500],
|
||||
[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],
|
||||
[1, 1000, "2012-12-01", "00:00", 1000],
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
||||
for d in input_data:
|
||||
self.cleanup_data()
|
||||
self.insert_existing_sle("Moving Average")
|
||||
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"]))
|
||||
|
||||
|
||||
# cancel
|
||||
stock_reco.cancel()
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"]))
|
||||
|
||||
|
||||
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 tabBin")
|
||||
frappe.db.sql("delete from `tabGL Entry`")
|
||||
|
||||
|
||||
def submit_stock_reconciliation(self, qty, rate, posting_date, posting_time):
|
||||
stock_reco = frappe.get_doc({
|
||||
"doctype": "Stock Reconciliation",
|
||||
@ -191,40 +191,40 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
stock_reco.insert()
|
||||
stock_reco.submit()
|
||||
return stock_reco
|
||||
|
||||
|
||||
def insert_existing_sle(self, valuation_method):
|
||||
frappe.db.set_value("Item", "_Test Item", "valuation_method", valuation_method)
|
||||
frappe.db.set_default("allow_negative_stock", 1)
|
||||
|
||||
stock_entry = [
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"doctype": "Stock Entry",
|
||||
"posting_date": "2012-12-12",
|
||||
"posting_time": "01:00",
|
||||
"purpose": "Material Receipt",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
"doctype": "Stock Entry Detail",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "mtn_details",
|
||||
"incoming_rate": 1000,
|
||||
"qty": 20.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"transfer_qty": 20.0,
|
||||
"uom": "_Test UOM",
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
stock_entry = {
|
||||
"company": "_Test Company",
|
||||
"doctype": "Stock Entry",
|
||||
"posting_date": "2012-12-12",
|
||||
"posting_time": "01:00",
|
||||
"purpose": "Material Receipt",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
"mtn_details": [
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
"doctype": "Stock Entry Detail",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "mtn_details",
|
||||
"incoming_rate": 1000,
|
||||
"qty": 20.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"transfer_qty": 20.0,
|
||||
"uom": "_Test UOM",
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
pr = frappe.copy_doc(stock_entry)
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
|
||||
pr1 = frappe.copy_doc(stock_entry)
|
||||
pr1.posting_date = "2012-12-15"
|
||||
pr1.posting_time = "02:00"
|
||||
@ -233,7 +233,7 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
pr1.get("mtn_details")[0].incoming_rate = 700
|
||||
pr1.insert()
|
||||
pr1.submit()
|
||||
|
||||
|
||||
pr2 = frappe.copy_doc(stock_entry)
|
||||
pr2.posting_date = "2012-12-25"
|
||||
pr2.posting_time = "03:00"
|
||||
@ -245,7 +245,7 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
pr2.get("mtn_details")[0].incoming_rate = 0
|
||||
pr2.insert()
|
||||
pr2.submit()
|
||||
|
||||
|
||||
pr3 = frappe.copy_doc(stock_entry)
|
||||
pr3.posting_date = "2012-12-31"
|
||||
pr3.posting_time = "08:00"
|
||||
@ -257,8 +257,8 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
pr3.get("mtn_details")[0].incoming_rate = 0
|
||||
pr3.insert()
|
||||
pr3.submit()
|
||||
|
||||
|
||||
|
||||
|
||||
pr4 = frappe.copy_doc(stock_entry)
|
||||
pr4.posting_date = "2013-01-05"
|
||||
pr4.fiscal_year = "_Test Fiscal Year 2013"
|
||||
@ -268,6 +268,6 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
pr4.get("mtn_details")[0].incoming_rate = 1200
|
||||
pr4.insert()
|
||||
pr4.submit()
|
||||
|
||||
|
||||
test_dependencies = ["Item", "Warehouse"]
|
||||
|
||||
|
||||
test_dependencies = ["Item", "Warehouse"]
|
||||
|
@ -7,36 +7,36 @@ class TestNewsletter(unittest.TestCase):
|
||||
def test_get_recipients_lead(self):
|
||||
w = frappe.get_doc(test_records[0])
|
||||
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`""")
|
||||
w.controller.send_emails()
|
||||
w.send_emails()
|
||||
self.assertTrue(frappe.db.get_value("Bulk Email", {"recipient": "test_lead@example.com"}))
|
||||
|
||||
def test_get_recipients_lead_by_status(self):
|
||||
w = frappe.get_doc(test_records[0])
|
||||
w.lead_status="Converted"
|
||||
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):
|
||||
w = frappe.get_doc(test_records[1])
|
||||
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):
|
||||
w = frappe.get_doc(test_records[1])
|
||||
w.contact_type="Supplier"
|
||||
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):
|
||||
w = frappe.get_doc(test_records[2])
|
||||
w.insert()
|
||||
self.assertTrue("test_custom2@example.com" in w.controller.get_recipients())
|
||||
self.assertTrue(frappe.db.get("Lead",
|
||||
self.assertTrue("test_custom2@example.com" in w.get_recipients())
|
||||
self.assertTrue(frappe.db.get("Lead",
|
||||
{"email_id": "test_custom2@example.com"}))
|
||||
|
||||
|
||||
test_dependencies = ["Lead", "Contact"]
|
||||
|
||||
test_records = frappe.get_test_records('Newsletter')
|
||||
test_records = frappe.get_test_records('Newsletter')
|
||||
|
Loading…
x
Reference in New Issue
Block a user