Fixed rest of the test cases frappe/frapp#478

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

View File

@ -17,17 +17,20 @@ from erpnext.accounts.party import get_party_account, get_due_date
class PurchaseInvoice(BuyingController):
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:

View File

@ -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()

View File

@ -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"))

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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:

View File

@ -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"

View File

@ -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": {

View File

@ -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

View File

@ -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 \

View File

@ -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)

View File

@ -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 ''

View File

@ -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`

View File

@ -11,76 +11,76 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_
def _insert_purchase_receipt(item_code=None):
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')

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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')

View File

@ -407,14 +407,14 @@ class TestStockEntry(unittest.TestCase):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice, make_delivery_note
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"):

View File

@ -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"]

View File

@ -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')