Fixed Stock Entry Test Cases frappe/frappe#478

This commit is contained in:
Anand Doshi 2014-04-07 18:51:58 +05:30
parent 103cc58cb6
commit 2ce39cf770
9 changed files with 573 additions and 573 deletions

View File

@ -21,22 +21,22 @@ class JournalVoucher(AccountsController):
def validate(self): def validate(self):
if not self.is_opening: if not self.is_opening:
self.is_opening='No' self.is_opening='No'
self.clearance_date = None self.clearance_date = None
super(JournalVoucher, self).validate_date_with_fiscal_year() super(JournalVoucher, self).validate_date_with_fiscal_year()
self.validate_debit_credit() self.validate_debit_credit()
self.validate_cheque_info() self.validate_cheque_info()
self.validate_entries_for_advance() self.validate_entries_for_advance()
self.validate_against_jv() self.validate_against_jv()
self.set_against_account() self.set_against_account()
self.create_remarks() self.create_remarks()
self.set_aging_date() self.set_aging_date()
self.set_print_format_fields() self.set_print_format_fields()
def on_submit(self): def on_submit(self):
if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']: if self.voucher_type in ['Bank Voucher', 'Contra Voucher', 'Journal Entry']:
self.check_credit_days() self.check_credit_days()
@ -46,9 +46,9 @@ class JournalVoucher(AccountsController):
def on_cancel(self): def on_cancel(self):
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name, "against_jv") remove_against_link_from_jv(self.doctype, self.name, "against_jv")
self.make_gl_entries(1) self.make_gl_entries(1)
def on_trash(self): def on_trash(self):
pass pass
#if self.amended_from: #if self.amended_from:
@ -57,7 +57,7 @@ class JournalVoucher(AccountsController):
def validate_debit_credit(self): def validate_debit_credit(self):
for d in self.get('entries'): for d in self.get('entries'):
if d.debit and d.credit: if d.debit and d.credit:
msgprint("You cannot credit and debit same account at the same time.", msgprint("You cannot credit and debit same account at the same time.",
raise_exception=1) raise_exception=1)
def validate_cheque_info(self): def validate_cheque_info(self):
@ -65,7 +65,7 @@ class JournalVoucher(AccountsController):
if not self.cheque_no or not self.cheque_date: if not self.cheque_no or not self.cheque_date:
msgprint("Reference No & Reference Date is required for %s" % msgprint("Reference No & Reference Date is required for %s" %
self.voucher_type, raise_exception=1) self.voucher_type, raise_exception=1)
if self.cheque_date and not self.cheque_no: if self.cheque_date and not self.cheque_no:
msgprint("Reference No is mandatory if you entered Reference Date", raise_exception=1) msgprint("Reference No is mandatory if you entered Reference Date", raise_exception=1)
@ -85,11 +85,11 @@ class JournalVoucher(AccountsController):
if d.against_jv == self.name: if d.against_jv == self.name:
msgprint("You can not enter current voucher in 'Against JV' column", msgprint("You can not enter current voucher in 'Against JV' column",
raise_exception=1) raise_exception=1)
elif not frappe.db.sql("""select name from `tabJournal Voucher Detail` elif not frappe.db.sql("""select name from `tabJournal Voucher Detail`
where account = %s and docstatus = 1 and parent = %s""", where account = %s and docstatus = 1 and parent = %s""",
(d.account, d.against_jv)): (d.account, d.against_jv)):
msgprint("Against JV: %s is not valid." % d.against_jv, raise_exception=1) msgprint("Against JV: %s is not valid." % d.against_jv, raise_exception=1)
def set_against_account(self): def set_against_account(self):
# Debit = Credit # Debit = Credit
debit, credit = 0.0, 0.0 debit, credit = 0.0, 0.0
@ -104,9 +104,9 @@ class JournalVoucher(AccountsController):
self.total_credit = credit self.total_credit = credit
if abs(self.total_debit-self.total_credit) > 0.001: if abs(self.total_debit-self.total_credit) > 0.001:
msgprint("Debit must be equal to Credit. The difference is %s" % msgprint("Debit must be equal to Credit. The difference is %s" %
(self.total_debit-self.total_credit), raise_exception=1) (self.total_debit-self.total_credit), raise_exception=1)
# update against account # update against account
for d in self.get('entries'): for d in self.get('entries'):
if flt(d.debit) > 0: d.against_account = ', '.join(credit_list) if flt(d.debit) > 0: d.against_account = ', '.join(credit_list)
@ -114,28 +114,28 @@ class JournalVoucher(AccountsController):
def create_remarks(self): def create_remarks(self):
r = [] r = []
if self.cheque_no : if self.cheque_no:
if self.cheque_date: if self.cheque_date:
r.append('Via Reference #%s dated %s' % r.append('Via Reference #%s dated %s' %
(self.cheque_no, formatdate(self.cheque_date))) (self.cheque_no, formatdate(self.cheque_date)))
else : else :
msgprint("Please enter Reference date", raise_exception=1) msgprint("Please enter Reference date", raise_exception=1)
for d in self.get('entries'): for d in self.get('entries'):
if d.against_invoice and d.credit: if d.against_invoice and d.credit:
currency = frappe.db.get_value("Sales Invoice", d.against_invoice, "currency") currency = frappe.db.get_value("Sales Invoice", d.against_invoice, "currency")
r.append('%s %s against Invoice: %s' % r.append('%s %s against Invoice: %s' %
(cstr(currency), fmt_money(flt(d.credit)), d.against_invoice)) (cstr(currency), fmt_money(flt(d.credit)), d.against_invoice))
if d.against_voucher and d.debit: if d.against_voucher and d.debit:
bill_no = frappe.db.sql("""select bill_no, bill_date, currency bill_no = frappe.db.sql("""select bill_no, bill_date, currency
from `tabPurchase Invoice` where name=%s""", d.against_voucher) from `tabPurchase Invoice` where name=%s""", d.against_voucher)
if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \ if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \
not in ['na', 'not applicable', 'none']: not in ['na', 'not applicable', 'none']:
r.append('%s %s against Bill %s dated %s' % r.append('%s %s against Bill %s dated %s' %
(cstr(bill_no[0][2]), fmt_money(flt(d.debit)), bill_no[0][0], (cstr(bill_no[0][2]), fmt_money(flt(d.debit)), bill_no[0][0],
bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d')) or '')) bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d')) or ''))
if self.user_remark: if self.user_remark:
r.append("User Remark : %s"%self.user_remark) r.append("User Remark : %s"%self.user_remark)
@ -157,25 +157,25 @@ class JournalVoucher(AccountsController):
break break
# If customer/supplier account, aging date is mandatory # If customer/supplier account, aging date is mandatory
if exists and not self.aging_date: if exists and not self.aging_date:
msgprint("Aging Date is mandatory for opening entry", raise_exception=1) msgprint("Aging Date is mandatory for opening entry", raise_exception=1)
else: else:
self.aging_date = self.posting_date self.aging_date = self.posting_date
def set_print_format_fields(self): def set_print_format_fields(self):
for d in self.get('entries'): for d in self.get('entries'):
account_type, master_type = frappe.db.get_value("Account", d.account, account_type, master_type = frappe.db.get_value("Account", d.account,
["account_type", "master_type"]) ["account_type", "master_type"])
if master_type in ['Supplier', 'Customer']: if master_type in ['Supplier', 'Customer']:
if not self.pay_to_recd_from: if not self.pay_to_recd_from:
self.pay_to_recd_from = frappe.db.get_value(master_type, self.pay_to_recd_from = frappe.db.get_value(master_type,
' - '.join(d.account.split(' - ')[:-1]), ' - '.join(d.account.split(' - ')[:-1]),
master_type == 'Customer' and 'customer_name' or 'supplier_name') master_type == 'Customer' and 'customer_name' or 'supplier_name')
if account_type in ['Bank', 'Cash']: if account_type in ['Bank', 'Cash']:
company_currency = get_company_currency(self.company) company_currency = get_company_currency(self.company)
amt = flt(d.debit) and d.debit or d.credit amt = flt(d.debit) and d.debit or d.credit
self.total_amount = company_currency + ' ' + cstr(amt) self.total_amount = company_currency + ' ' + cstr(amt)
from frappe.utils import money_in_words from frappe.utils import money_in_words
self.total_amount_in_words = money_in_words(amt, company_currency) self.total_amount_in_words = money_in_words(amt, company_currency)
@ -184,29 +184,29 @@ class JournalVoucher(AccountsController):
date_diff = 0 date_diff = 0
if self.cheque_date: if self.cheque_date:
date_diff = (getdate(self.cheque_date)-getdate(self.posting_date)).days date_diff = (getdate(self.cheque_date)-getdate(self.posting_date)).days
if date_diff <= 0: return if date_diff <= 0: return
# Get List of Customer Account # Get List of Customer Account
acc_list = filter(lambda d: frappe.db.get_value("Account", d.account, acc_list = filter(lambda d: frappe.db.get_value("Account", d.account,
"master_type")=='Customer', self.get('entries')) "master_type")=='Customer', self.get('entries'))
for d in acc_list: for d in acc_list:
credit_days = self.get_credit_days_for(d.account) credit_days = self.get_credit_days_for(d.account)
# Check credit days # Check credit days
if credit_days > 0 and not self.get_authorized_user() and cint(date_diff) > credit_days: if credit_days > 0 and not self.get_authorized_user() and cint(date_diff) > credit_days:
msgprint("Credit Not Allowed: Cannot allow a check that is dated \ msgprint("Credit Not Allowed: Cannot allow a check that is dated \
more than %s days after the posting date" % credit_days, raise_exception=1) more than %s days after the posting date" % credit_days, raise_exception=1)
def get_credit_days_for(self, ac): def get_credit_days_for(self, ac):
if not self.credit_days_for.has_key(ac): if not self.credit_days_for.has_key(ac):
self.credit_days_for[ac] = cint(frappe.db.get_value("Account", ac, "credit_days")) self.credit_days_for[ac] = cint(frappe.db.get_value("Account", ac, "credit_days"))
if not self.credit_days_for[ac]: if not self.credit_days_for[ac]:
if self.credit_days_global==-1: if self.credit_days_global==-1:
self.credit_days_global = cint(frappe.db.get_value("Company", self.credit_days_global = cint(frappe.db.get_value("Company",
self.company, "credit_days")) self.company, "credit_days"))
return self.credit_days_global return self.credit_days_global
else: else:
return self.credit_days_for[ac] return self.credit_days_for[ac]
@ -216,33 +216,33 @@ class JournalVoucher(AccountsController):
self.is_approving_authority = 0 self.is_approving_authority = 0
# Fetch credit controller role # Fetch credit controller role
approving_authority = frappe.db.get_value("Global Defaults", None, approving_authority = frappe.db.get_value("Global Defaults", None,
"credit_controller") "credit_controller")
# Check logged-in user is authorized # Check logged-in user is authorized
if approving_authority in frappe.user.get_roles(): if approving_authority in frappe.user.get_roles():
self.is_approving_authority = 1 self.is_approving_authority = 1
return self.is_approving_authority return self.is_approving_authority
def check_account_against_entries(self): def check_account_against_entries(self):
for d in self.get("entries"): for d in self.get("entries"):
if d.against_invoice and frappe.db.get_value("Sales Invoice", if d.against_invoice and frappe.db.get_value("Sales Invoice",
d.against_invoice, "debit_to") != d.account: d.against_invoice, "debit_to") != d.account:
frappe.throw(_("Row #") + cstr(d.idx) + ": " + frappe.throw(_("Row #") + cstr(d.idx) + ": " +
_("Account is not matching with Debit To account of Sales Invoice")) _("Account is not matching with Debit To account of Sales Invoice"))
if d.against_voucher and frappe.db.get_value("Purchase Invoice", if d.against_voucher and frappe.db.get_value("Purchase Invoice",
d.against_voucher, "credit_to") != d.account: d.against_voucher, "credit_to") != d.account:
frappe.throw(_("Row #") + cstr(d.idx) + ": " + frappe.throw(_("Row #") + cstr(d.idx) + ": " +
_("Account is not matching with Credit To account of Purchase Invoice")) _("Account is not matching with Credit To account of Purchase Invoice"))
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
if not cancel: if not cancel:
self.check_account_against_entries() self.check_account_against_entries()
gl_map = [] gl_map = []
for d in self.get("entries"): for d in self.get("entries"):
if d.debit or d.credit: if d.debit or d.credit:
@ -252,8 +252,8 @@ class JournalVoucher(AccountsController):
"against": d.against_account, "against": d.against_account,
"debit": d.debit, "debit": d.debit,
"credit": d.credit, "credit": d.credit,
"against_voucher_type": ((d.against_voucher and "Purchase Invoice") "against_voucher_type": ((d.against_voucher and "Purchase Invoice")
or (d.against_invoice and "Sales Invoice") or (d.against_invoice and "Sales Invoice")
or (d.against_jv and "Journal Voucher")), or (d.against_jv and "Journal Voucher")),
"against_voucher": d.against_voucher or d.against_invoice or d.against_jv, "against_voucher": d.against_voucher or d.against_invoice or d.against_jv,
"remarks": self.remark, "remarks": self.remark,
@ -262,10 +262,10 @@ class JournalVoucher(AccountsController):
) )
if gl_map: if gl_map:
make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj) make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj)
def check_credit_limit(self): def check_credit_limit(self):
for d in self.get("entries"): for d in self.get("entries"):
master_type, master_name = frappe.db.get_value("Account", d.account, master_type, master_name = frappe.db.get_value("Account", d.account,
["master_type", "master_name"]) ["master_type", "master_name"])
if master_type == "Customer" and master_name: if master_type == "Customer" and master_name:
super(JournalVoucher, self).check_credit_limit(d.account) super(JournalVoucher, self).check_credit_limit(d.account)
@ -276,7 +276,7 @@ class JournalVoucher(AccountsController):
else: else:
flag, self.total_debit, self.total_credit = 0, 0, 0 flag, self.total_debit, self.total_credit = 0, 0, 0
diff = flt(self.difference, 2) diff = flt(self.difference, 2)
# If any row without amount, set the diff on that row # If any row without amount, set the diff on that row
for d in self.get('entries'): for d in self.get('entries'):
if not d.credit and not d.debit and diff != 0: if not d.credit and not d.debit and diff != 0:
@ -285,7 +285,7 @@ class JournalVoucher(AccountsController):
elif diff<0: elif diff<0:
d.debit = diff d.debit = diff
flag = 1 flag = 1
# Set the diff in a new row # Set the diff in a new row
if flag == 0 and diff != 0: if flag == 0 and diff != 0:
jd = self.append('entries', {}) jd = self.append('entries', {})
@ -293,7 +293,7 @@ class JournalVoucher(AccountsController):
jd.credit = abs(diff) jd.credit = abs(diff)
elif diff<0: elif diff<0:
jd.debit = abs(diff) jd.debit = abs(diff)
# Set the total debit, total credit and difference # Set the total debit, total credit and difference
for d in self.get('entries'): for d in self.get('entries'):
self.total_debit += flt(d.debit, 2) self.total_debit += flt(d.debit, 2)
@ -326,12 +326,12 @@ class JournalVoucher(AccountsController):
cond = (flt(self.write_off_amount) > 0) and \ cond = (flt(self.write_off_amount) > 0) and \
' and outstanding_amount <= '+ self.write_off_amount or '' ' and outstanding_amount <= '+ self.write_off_amount or ''
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
return frappe.db.sql("""select name, debit_to, outstanding_amount return frappe.db.sql("""select name, debit_to, outstanding_amount
from `tabSales Invoice` where docstatus = 1 and company = %s from `tabSales Invoice` where docstatus = 1 and company = %s
and outstanding_amount > 0 %s""" % ('%s', cond), self.company) and outstanding_amount > 0 %s""" % ('%s', cond), self.company)
elif self.write_off_based_on == 'Accounts Payable': elif self.write_off_based_on == 'Accounts Payable':
return frappe.db.sql("""select name, credit_to, outstanding_amount return frappe.db.sql("""select name, credit_to, outstanding_amount
from `tabPurchase Invoice` where docstatus = 1 and company = %s from `tabPurchase Invoice` where docstatus = 1 and company = %s
and outstanding_amount > 0 %s""" % ('%s', cond), self.company) and outstanding_amount > 0 %s""" % ('%s', cond), self.company)
@frappe.whitelist() @frappe.whitelist()
@ -344,7 +344,7 @@ def get_default_bank_cash_account(company, voucher_type):
"account": account, "account": account,
"balance": get_balance_on(account) "balance": get_balance_on(account)
} }
@frappe.whitelist() @frappe.whitelist()
def get_payment_entry_from_sales_invoice(sales_invoice): def get_payment_entry_from_sales_invoice(sales_invoice):
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
@ -360,7 +360,7 @@ def get_payment_entry_from_sales_invoice(sales_invoice):
# debit bank # debit bank
jv.get("entries")[1].debit = si.outstanding_amount jv.get("entries")[1].debit = si.outstanding_amount
return jv.as_dict() return jv.as_dict()
@frappe.whitelist() @frappe.whitelist()
@ -369,7 +369,7 @@ def get_payment_entry_from_purchase_invoice(purchase_invoice):
pi = frappe.get_doc("Purchase Invoice", purchase_invoice) pi = frappe.get_doc("Purchase Invoice", purchase_invoice)
jv = get_payment_entry(pi) jv = get_payment_entry(pi)
jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks) jv.remark = 'Payment against Purchase Invoice {0}. {1}'.format(pi.name, pi.remarks)
# credit supplier # credit supplier
jv.get("entries")[0].account = pi.credit_to jv.get("entries")[0].account = pi.credit_to
jv.get("entries")[0].balance = get_balance_on(pi.credit_to) jv.get("entries")[0].balance = get_balance_on(pi.credit_to)
@ -378,12 +378,12 @@ def get_payment_entry_from_purchase_invoice(purchase_invoice):
# credit bank # credit bank
jv.get("entries")[1].credit = pi.outstanding_amount jv.get("entries")[1].credit = pi.outstanding_amount
return jv.as_dict() return jv.as_dict()
def get_payment_entry(doc): def get_payment_entry(doc):
bank_account = get_default_bank_cash_account(doc.company, "Bank Voucher") bank_account = get_default_bank_cash_account(doc.company, "Bank Voucher")
jv = frappe.new_doc('Journal Voucher') jv = frappe.new_doc('Journal Voucher')
jv.voucher_type = 'Bank Voucher' jv.voucher_type = 'Bank Voucher'
@ -396,63 +396,63 @@ def get_payment_entry(doc):
if bank_account: if bank_account:
d2.account = bank_account["account"] d2.account = bank_account["account"]
d2.balance = bank_account["balance"] d2.balance = bank_account["balance"]
return jv return jv
@frappe.whitelist() @frappe.whitelist()
def get_opening_accounts(company): def get_opening_accounts(company):
"""get all balance sheet accounts for opening entry""" """get all balance sheet accounts for opening entry"""
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
accounts = frappe.db.sql_list("""select name from tabAccount accounts = frappe.db.sql_list("""select name from tabAccount
where group_or_ledger='Ledger' and report_type='Profit and Loss' and company=%s""", company) where group_or_ledger='Ledger' and report_type='Profit and Loss' and company=%s""", company)
return [{"account": a, "balance": get_balance_on(a)} for a in accounts] return [{"account": a, "balance": get_balance_on(a)} for a in accounts]
def get_against_purchase_invoice(doctype, txt, searchfield, start, page_len, filters): def get_against_purchase_invoice(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name, credit_to, outstanding_amount, bill_no, bill_date return frappe.db.sql("""select name, credit_to, outstanding_amount, bill_no, bill_date
from `tabPurchase Invoice` where credit_to = %s and docstatus = 1 from `tabPurchase Invoice` where credit_to = %s and docstatus = 1
and outstanding_amount > 0 and %s like %s order by name desc limit %s, %s""" % and outstanding_amount > 0 and %s like %s order by name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"), ("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len))
def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name, debit_to, outstanding_amount
from `tabSales Invoice` where debit_to = %s and docstatus = 1
and outstanding_amount > 0 and `%s` like %s order by name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len))
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail
where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1
and jv.%s like %s order by jv.name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len)) (filters["account"], "%%%s%%" % txt, start, page_len))
@frappe.whitelist() def get_against_sales_invoice(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name, debit_to, outstanding_amount
from `tabSales Invoice` where debit_to = %s and docstatus = 1
and outstanding_amount > 0 and `%s` like %s order by name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len))
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
from `tabJournal Voucher` jv, `tabJournal Voucher Detail` jv_detail
where jv_detail.parent = jv.name and jv_detail.account = %s and jv.docstatus = 1
and jv.%s like %s order by jv.name desc limit %s, %s""" %
("%s", searchfield, "%s", "%s", "%s"),
(filters["account"], "%%%s%%" % txt, start, page_len))
@frappe.whitelist()
def get_outstanding(args): def get_outstanding(args):
args = eval(args) args = eval(args)
if args.get("doctype") == "Journal Voucher" and args.get("account"): if args.get("doctype") == "Journal Voucher" and args.get("account"):
against_jv_amount = frappe.db.sql(""" against_jv_amount = frappe.db.sql("""
select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0)) select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
from `tabJournal Voucher Detail` where parent=%s and account=%s from `tabJournal Voucher Detail` where parent=%s and account=%s
and ifnull(against_invoice, '')='' and ifnull(against_voucher, '')='' and ifnull(against_invoice, '')='' and ifnull(against_voucher, '')=''
and ifnull(against_jv, '')=''""", (args['docname'], args['account'])) and ifnull(against_jv, '')=''""", (args['docname'], args['account']))
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0 against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
if against_jv_amount > 0: if against_jv_amount > 0:
return {"credit": against_jv_amount} return {"credit": against_jv_amount}
else: else:
return {"debit": -1* against_jv_amount} return {"debit": -1* against_jv_amount}
elif args.get("doctype") == "Sales Invoice": elif args.get("doctype") == "Sales Invoice":
return { return {
"credit": flt(frappe.db.get_value("Sales Invoice", args["docname"], "credit": flt(frappe.db.get_value("Sales Invoice", args["docname"],
"outstanding_amount")) "outstanding_amount"))
} }
elif args.get("doctype") == "Purchase Invoice": elif args.get("doctype") == "Purchase Invoice":
return { return {
"debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"], "debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"],
"outstanding_amount")) "outstanding_amount"))
} }

View File

@ -28,13 +28,13 @@ class PurchaseInvoice(BuyingController):
'source_field': 'amount', 'source_field': 'amount',
'percent_join_field': 'purchase_order', 'percent_join_field': 'purchase_order',
}] }]
def validate(self): def validate(self):
if not self.is_opening: if not self.is_opening:
self.is_opening = 'No' self.is_opening = 'No'
super(PurchaseInvoice, self).validate() super(PurchaseInvoice, self).validate()
self.po_required() self.po_required()
self.pr_required() self.pr_required()
self.check_active_purchase_items() self.check_active_purchase_items()
@ -51,22 +51,22 @@ class PurchaseInvoice(BuyingController):
self.validate_write_off_account() self.validate_write_off_account()
self.update_raw_material_cost() self.update_raw_material_cost()
self.update_valuation_rate("entries") self.update_valuation_rate("entries")
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount",
"purchase_receipt_details") "purchase_receipt_details")
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
if not self.credit_to: if not self.credit_to:
self.credit_to = get_party_account(self.company, self.supplier, "Supplier") self.credit_to = get_party_account(self.company, self.supplier, "Supplier")
if not self.due_date: if not self.due_date:
self.due_date = get_due_date(self.posting_date, self.supplier, "Supplier", self.due_date = get_due_date(self.posting_date, self.supplier, "Supplier",
self.credit_to, self.company) self.credit_to, self.company)
super(PurchaseInvoice, self).set_missing_values(for_validate) super(PurchaseInvoice, self).set_missing_values(for_validate)
def get_advances(self): def get_advances(self):
super(PurchaseInvoice, self).get_advances(self.credit_to, super(PurchaseInvoice, self).get_advances(self.credit_to,
"Purchase Invoice Advance", "advance_allocation_details", "debit") "Purchase Invoice Advance", "advance_allocation_details", "debit")
def check_active_purchase_items(self): def check_active_purchase_items(self):
for d in self.get('entries'): for d in self.get('entries'):
if d.item_code: # extra condn coz item_code is not mandatory in PV if d.item_code: # extra condn coz item_code is not mandatory in PV
@ -77,29 +77,29 @@ class PurchaseInvoice(BuyingController):
if not valid_item[0][1] == 'Yes': if not valid_item[0][1] == 'Yes':
msgprint("Item : '%s' is not Purchase Item"%(d.item_code)) msgprint("Item : '%s' is not Purchase Item"%(d.item_code))
raise Exception raise Exception
def check_conversion_rate(self): def check_conversion_rate(self):
default_currency = get_company_currency(self.company) default_currency = get_company_currency(self.company)
if not default_currency: if not default_currency:
msgprint('Message: Please enter default currency in Company Master') msgprint('Message: Please enter default currency in Company Master')
raise Exception raise Exception
if (self.currency == default_currency and flt(self.conversion_rate) != 1.00) or not self.conversion_rate or (self.currency != default_currency and flt(self.conversion_rate) == 1.00): if (self.currency == default_currency and flt(self.conversion_rate) != 1.00) or not self.conversion_rate or (self.currency != default_currency and flt(self.conversion_rate) == 1.00):
msgprint("Message: Please Enter Appropriate Conversion Rate.") msgprint("Message: Please Enter Appropriate Conversion Rate.")
raise Exception raise Exception
def validate_bill_no(self): def validate_bill_no(self):
if self.bill_no and self.bill_no.lower().strip() \ if self.bill_no and self.bill_no.lower().strip() \
not in ['na', 'not applicable', 'none']: not in ['na', 'not applicable', 'none']:
b_no = frappe.db.sql("""select bill_no, name, ifnull(is_opening,'') from `tabPurchase Invoice` b_no = frappe.db.sql("""select bill_no, name, ifnull(is_opening,'') from `tabPurchase Invoice`
where bill_no = %s and credit_to = %s and docstatus = 1 and name != %s""", where bill_no = %s and credit_to = %s and docstatus = 1 and name != %s""",
(self.bill_no, self.credit_to, self.name)) (self.bill_no, self.credit_to, self.name))
if b_no and cstr(b_no[0][2]) == cstr(self.is_opening): if b_no and cstr(b_no[0][2]) == cstr(self.is_opening):
msgprint("Please check you have already booked expense against Bill No. %s \ msgprint("Please check you have already booked expense against Bill No. %s \
in Purchase Invoice %s" % (cstr(b_no[0][0]), cstr(b_no[0][1])), in Purchase Invoice %s" % (cstr(b_no[0][0]), cstr(b_no[0][1])),
raise_exception=1) raise_exception=1)
if not self.remarks and self.bill_date: if not self.remarks and self.bill_date:
self.remarks = (self.remarks or '') + "\n" + ("Against Bill %s dated %s" self.remarks = (self.remarks or '') + "\n" + ("Against Bill %s dated %s"
% (self.bill_no, formatdate(self.bill_date))) % (self.bill_no, formatdate(self.bill_date)))
if not self.remarks: if not self.remarks:
@ -108,28 +108,28 @@ class PurchaseInvoice(BuyingController):
def validate_credit_acc(self): def validate_credit_acc(self):
if frappe.db.get_value("Account", self.credit_to, "report_type") != "Balance Sheet": if frappe.db.get_value("Account", self.credit_to, "report_type") != "Balance Sheet":
frappe.throw(_("Account must be a balance sheet account")) frappe.throw(_("Account must be a balance sheet account"))
# Validate Acc Head of Supplier and Credit To Account entered # Validate Acc Head of Supplier and Credit To Account entered
# ------------------------------------------------------------ # ------------------------------------------------------------
def check_for_acc_head_of_supplier(self): def check_for_acc_head_of_supplier(self):
if self.supplier and self.credit_to: if self.supplier and self.credit_to:
acc_head = frappe.db.sql("select master_name from `tabAccount` where name = %s", self.credit_to) acc_head = frappe.db.sql("select master_name from `tabAccount` where name = %s", self.credit_to)
if (acc_head and cstr(acc_head[0][0]) != cstr(self.supplier)) or (not acc_head and (self.credit_to != cstr(self.supplier) + " - " + self.company_abbr)): if (acc_head and cstr(acc_head[0][0]) != cstr(self.supplier)) or (not acc_head and (self.credit_to != cstr(self.supplier) + " - " + self.company_abbr)):
msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.credit_to,self.supplier,self.company), raise_exception=1) msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.credit_to,self.supplier,self.company), raise_exception=1)
# Check for Stopped PO # Check for Stopped PO
# --------------------- # ---------------------
def check_for_stopped_status(self): def check_for_stopped_status(self):
check_list = [] check_list = []
for d in self.get('entries'): for d in self.get('entries'):
if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt: if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt:
check_list.append(d.purhcase_order) check_list.append(d.purchase_order)
stopped = frappe.db.sql("select name from `tabPurchase Order` where status = 'Stopped' and name = %s", d.purchase_order) stopped = frappe.db.sql("select name from `tabPurchase Order` where status = 'Stopped' and name = %s", d.purchase_order)
if stopped: if stopped:
msgprint("One cannot do any transaction against 'Purchase Order' : %s, it's status is 'Stopped'" % (d.purhcase_order)) msgprint("One cannot do any transaction against 'Purchase Order' : %s, it's status is 'Stopped'" % (d.purhcase_order))
raise Exception raise Exception
def validate_with_previous_doc(self): def validate_with_previous_doc(self):
super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, { super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, {
"Purchase Order": { "Purchase Order": {
@ -152,7 +152,7 @@ class PurchaseInvoice(BuyingController):
"is_child_table": True "is_child_table": True
} }
}) })
if cint(frappe.defaults.get_global_default('maintain_same_rate')): if cint(frappe.defaults.get_global_default('maintain_same_rate')):
super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, { super(PurchaseInvoice, self).validate_with_previous_doc(self.tname, {
"Purchase Order Item": { "Purchase Order Item": {
@ -167,21 +167,21 @@ class PurchaseInvoice(BuyingController):
"is_child_table": True "is_child_table": True
} }
}) })
def set_aging_date(self): def set_aging_date(self):
if self.is_opening != 'Yes': if self.is_opening != 'Yes':
self.aging_date = self.posting_date self.aging_date = self.posting_date
elif not self.aging_date: elif not self.aging_date:
msgprint("Aging Date is mandatory for opening entry") msgprint("Aging Date is mandatory for opening entry")
raise Exception raise Exception
def set_against_expense_account(self): def set_against_expense_account(self):
auto_accounting_for_stock = cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) auto_accounting_for_stock = cint(frappe.defaults.get_global_default("auto_accounting_for_stock"))
if auto_accounting_for_stock: if auto_accounting_for_stock:
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed") stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
against_accounts = [] against_accounts = []
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
for item in self.get("entries"): for item in self.get("entries"):
@ -191,18 +191,18 @@ class PurchaseInvoice(BuyingController):
# Stock Received But Not Billed for a stock item # Stock Received But Not Billed for a stock item
item.expense_account = stock_not_billed_account item.expense_account = stock_not_billed_account
item.cost_center = None item.cost_center = None
if stock_not_billed_account not in against_accounts: if stock_not_billed_account not in against_accounts:
against_accounts.append(stock_not_billed_account) against_accounts.append(stock_not_billed_account)
elif not item.expense_account: elif not item.expense_account:
msgprint(_("Expense account is mandatory for item") + ": " + msgprint(_("Expense account is mandatory for item") + ": " +
(item.item_code or item.item_name), raise_exception=1) (item.item_code or item.item_name), raise_exception=1)
elif item.expense_account not in against_accounts: elif item.expense_account not in against_accounts:
# if no auto_accounting_for_stock or not a stock item # if no auto_accounting_for_stock or not a stock item
against_accounts.append(item.expense_account) against_accounts.append(item.expense_account)
self.against_expense_account = ",".join(against_accounts) self.against_expense_account = ",".join(against_accounts)
def po_required(self): def po_required(self):
@ -233,8 +233,8 @@ class PurchaseInvoice(BuyingController):
submitted = frappe.db.sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt) submitted = frappe.db.sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = %s", d.purchase_receipt)
if not submitted: if not submitted:
frappe.throw("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted") frappe.throw("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted")
def update_against_document_in_jv(self): def update_against_document_in_jv(self):
""" """
Links invoice and advance voucher: Links invoice and advance voucher:
@ -242,33 +242,33 @@ class PurchaseInvoice(BuyingController):
2. split into multiple rows if partially adjusted, assign against voucher 2. split into multiple rows if partially adjusted, assign against voucher
3. submit advance voucher 3. submit advance voucher
""" """
lst = [] lst = []
for d in self.get('advance_allocation_details'): for d in self.get('advance_allocation_details'):
if flt(d.allocated_amount) > 0: if flt(d.allocated_amount) > 0:
args = { args = {
'voucher_no' : d.journal_voucher, 'voucher_no' : d.journal_voucher,
'voucher_detail_no' : d.jv_detail_no, 'voucher_detail_no' : d.jv_detail_no,
'against_voucher_type' : 'Purchase Invoice', 'against_voucher_type' : 'Purchase Invoice',
'against_voucher' : self.name, 'against_voucher' : self.name,
'account' : self.credit_to, 'account' : self.credit_to,
'is_advance' : 'Yes', 'is_advance' : 'Yes',
'dr_or_cr' : 'debit', 'dr_or_cr' : 'debit',
'unadjusted_amt' : flt(d.advance_amount), 'unadjusted_amt' : flt(d.advance_amount),
'allocated_amt' : flt(d.allocated_amount) 'allocated_amt' : flt(d.allocated_amount)
} }
lst.append(args) lst.append(args)
if lst: if lst:
from erpnext.accounts.utils import reconcile_against_document from erpnext.accounts.utils import reconcile_against_document
reconcile_against_document(lst) reconcile_against_document(lst)
def on_submit(self): def on_submit(self):
self.check_prev_docstatus() self.check_prev_docstatus()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.grand_total) self.company, self.grand_total)
# this sequence because outstanding may get -negative # this sequence because outstanding may get -negative
self.make_gl_entries() self.make_gl_entries()
self.update_against_document_in_jv() self.update_against_document_in_jv()
@ -278,9 +278,9 @@ class PurchaseInvoice(BuyingController):
def make_gl_entries(self): def make_gl_entries(self):
auto_accounting_for_stock = \ auto_accounting_for_stock = \
cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) cint(frappe.defaults.get_global_default("auto_accounting_for_stock"))
gl_entries = [] gl_entries = []
# parent's gl entry # parent's gl entry
if self.grand_total: if self.grand_total:
gl_entries.append( gl_entries.append(
@ -293,7 +293,7 @@ class PurchaseInvoice(BuyingController):
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
}) })
) )
# tax table gl entries # tax table gl entries
valuation_tax = {} valuation_tax = {}
for tax in self.get("other_charges"): for tax in self.get("other_charges"):
@ -308,31 +308,31 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center "cost_center": tax.cost_center
}) })
) )
# accumulate valuation tax # accumulate valuation tax
if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount): if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount):
if auto_accounting_for_stock and not tax.cost_center: if auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Row %(row)s: Cost Center is mandatory \ frappe.throw(_("Row %(row)s: Cost Center is mandatory \
if tax/charges category is Valuation or Valuation and Total" % if tax/charges category is Valuation or Valuation and Total" %
{"row": tax.idx})) {"row": tax.idx}))
valuation_tax.setdefault(tax.cost_center, 0) valuation_tax.setdefault(tax.cost_center, 0)
valuation_tax[tax.cost_center] += \ valuation_tax[tax.cost_center] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount) (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount)
# item gl entries # item gl entries
stock_item_and_auto_accounting_for_stock = False stock_item_and_auto_accounting_for_stock = False
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
for item in self.get("entries"): for item in self.get("entries"):
if auto_accounting_for_stock and item.item_code in stock_items: if auto_accounting_for_stock and item.item_code in stock_items:
if flt(item.valuation_rate): if flt(item.valuation_rate):
# if auto inventory accounting enabled and stock item, # if auto inventory accounting enabled and stock item,
# then do stock related gl entries # then do stock related gl entries
# expense will be booked in sales invoice # expense will be booked in sales invoice
stock_item_and_auto_accounting_for_stock = True stock_item_and_auto_accounting_for_stock = True
valuation_amt = flt(item.base_amount + item.item_tax_amount + item.rm_supp_cost, valuation_amt = flt(item.base_amount + item.item_tax_amount + item.rm_supp_cost,
self.precision("base_amount", item)) self.precision("base_amount", item))
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": item.expense_account, "account": item.expense_account,
@ -341,7 +341,7 @@ class PurchaseInvoice(BuyingController):
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or "Accounting Entry for Stock"
}) })
) )
elif flt(item.base_amount): elif flt(item.base_amount):
# if not a stock item or auto inventory accounting disabled, book the expense # if not a stock item or auto inventory accounting disabled, book the expense
gl_entries.append( gl_entries.append(
@ -353,13 +353,13 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center "cost_center": item.cost_center
}) })
) )
if stock_item_and_auto_accounting_for_stock and valuation_tax: if stock_item_and_auto_accounting_for_stock and valuation_tax:
# credit valuation tax amount in "Expenses Included In Valuation" # credit valuation tax amount in "Expenses Included In Valuation"
# this will balance out valuation amount included in cost of goods sold # this will balance out valuation amount included in cost of goods sold
expenses_included_in_valuation = \ expenses_included_in_valuation = \
self.get_company_default("expenses_included_in_valuation") self.get_company_default("expenses_included_in_valuation")
for cost_center, amount in valuation_tax.items(): for cost_center, amount in valuation_tax.items():
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@ -370,8 +370,8 @@ class PurchaseInvoice(BuyingController):
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or "Accounting Entry for Stock"
}) })
) )
# writeoff account includes petty difference in the invoice amount # writeoff account includes petty difference in the invoice amount
# and the amount that is paid # and the amount that is paid
if self.write_off_account and flt(self.write_off_amount): if self.write_off_account and flt(self.write_off_amount):
gl_entries.append( gl_entries.append(
@ -383,7 +383,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": self.write_off_cost_center "cost_center": self.write_off_cost_center
}) })
) )
if gl_entries: if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries, cancel=(self.docstatus == 2)) make_gl_entries(gl_entries, cancel=(self.docstatus == 2))
@ -391,43 +391,43 @@ class PurchaseInvoice(BuyingController):
def on_cancel(self): def on_cancel(self):
from erpnext.accounts.utils import remove_against_link_from_jv from erpnext.accounts.utils import remove_against_link_from_jv
remove_against_link_from_jv(self.doctype, self.name, "against_voucher") remove_against_link_from_jv(self.doctype, self.name, "against_voucher")
self.update_prevdoc_status() self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
def on_update(self): def on_update(self):
pass pass
def update_raw_material_cost(self): def update_raw_material_cost(self):
if self.sub_contracted_items: if self.sub_contracted_items:
for d in self.get("entries"): for d in self.get("entries"):
rm_cost = frappe.db.sql("""select raw_material_cost / quantity rm_cost = frappe.db.sql("""select raw_material_cost / quantity
from `tabBOM` where item = %s and is_default = 1 and docstatus = 1 from `tabBOM` where item = %s and is_default = 1 and docstatus = 1
and is_active = 1 """, (d.item_code,)) and is_active = 1 """, (d.item_code,))
rm_cost = rm_cost and flt(rm_cost[0][0]) or 0 rm_cost = rm_cost and flt(rm_cost[0][0]) or 0
d.conversion_factor = d.conversion_factor or flt(frappe.db.get_value( d.conversion_factor = d.conversion_factor or flt(frappe.db.get_value(
"UOM Conversion Detail", {"parent": d.item_code, "uom": d.uom}, "UOM Conversion Detail", {"parent": d.item_code, "uom": d.uom},
"conversion_factor")) or 1 "conversion_factor")) or 1
d.rm_supp_cost = rm_cost * flt(d.qty) * flt(d.conversion_factor) d.rm_supp_cost = rm_cost * flt(d.qty) * flt(d.conversion_factor)
@frappe.whitelist() @frappe.whitelist()
def get_expense_account(doctype, txt, searchfield, start, page_len, filters): def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
# expense account can be any Debit account, # expense account can be any Debit account,
# but can also be a Liability account with account_type='Expense Account' in special circumstances. # but can also be a Liability account with account_type='Expense Account' in special circumstances.
# Hence the first condition is an "OR" # Hence the first condition is an "OR"
return frappe.db.sql("""select tabAccount.name from `tabAccount` return frappe.db.sql("""select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss" where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type = "Expense Account") or tabAccount.account_type = "Expense Account")
and tabAccount.group_or_ledger="Ledger" and tabAccount.group_or_ledger="Ledger"
and tabAccount.docstatus!=2 and tabAccount.docstatus!=2
and ifnull(tabAccount.master_type, "")="" and ifnull(tabAccount.master_type, "")=""
and ifnull(tabAccount.master_name, "")="" and ifnull(tabAccount.master_name, "")=""
and tabAccount.company = '%(company)s' and tabAccount.company = '%(company)s'
and tabAccount.%(key)s LIKE '%(txt)s' and tabAccount.%(key)s LIKE '%(txt)s'
%(mcond)s""" % {'company': filters['company'], 'key': searchfield, %(mcond)s""" % {'company': filters['company'], 'key': searchfield,
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)}) 'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)})

View File

@ -14,29 +14,29 @@ class StockController(AccountsController):
def make_gl_entries(self, repost_future_gle=True): def make_gl_entries(self, repost_future_gle=True):
if self.docstatus == 2: if self.docstatus == 2:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name) delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")): if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
warehouse_account = self.get_warehouse_account() warehouse_account = self.get_warehouse_account()
if self.docstatus==1: if self.docstatus==1:
gl_entries = self.get_gl_entries(warehouse_account) gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries) make_gl_entries(gl_entries)
if repost_future_gle: if repost_future_gle:
items, warehouse_account = self.get_items_and_warehouse_accounts(warehouse_account) items, warehouse_account = self.get_items_and_warehouse_accounts(warehouse_account)
update_gl_entries_after(self.posting_date, self.posting_time, update_gl_entries_after(self.posting_date, self.posting_time,
warehouse_account, items) warehouse_account, items)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None): default_cost_center=None):
from erpnext.accounts.general_ledger import process_gl_map from erpnext.accounts.general_ledger import process_gl_map
if not warehouse_account: if not warehouse_account:
warehouse_account = get_warehouse_account() warehouse_account = get_warehouse_account()
stock_ledger = self.get_stock_ledger_details() stock_ledger = self.get_stock_ledger_details()
voucher_details = self.get_voucher_details(stock_ledger, default_expense_account, voucher_details = self.get_voucher_details(stock_ledger, default_expense_account,
default_cost_center) default_cost_center)
gl_list = [] gl_list = []
warehouse_with_no_account = [] warehouse_with_no_account = []
for detail in voucher_details: for detail in voucher_details:
@ -63,13 +63,13 @@ class StockController(AccountsController):
})) }))
elif sle.warehouse not in warehouse_with_no_account: elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse) warehouse_with_no_account.append(sle.warehouse)
if warehouse_with_no_account: if warehouse_with_no_account:
msgprint(_("No accounting entries for following warehouses") + ": \n" + msgprint(_("No accounting entries for following warehouses") + ": \n" +
"\n".join(warehouse_with_no_account)) "\n".join(warehouse_with_no_account))
return process_gl_map(gl_list) return process_gl_map(gl_list)
def get_voucher_details(self, stock_ledger, default_expense_account, default_cost_center): def get_voucher_details(self, stock_ledger, default_expense_account, default_cost_center):
if not default_expense_account: if not default_expense_account:
details = self.get(self.fname) details = self.get(self.fname)
@ -77,18 +77,18 @@ class StockController(AccountsController):
self.check_expense_account(d) self.check_expense_account(d)
else: else:
details = [frappe._dict({ details = [frappe._dict({
"name":d, "name":d,
"expense_account": default_expense_account, "expense_account": default_expense_account,
"cost_center": default_cost_center "cost_center": default_cost_center
}) for d in stock_ledger.keys()] }) for d in stock_ledger.keys()]
return details return details
def get_items_and_warehouse_accounts(self, warehouse_account=None): def get_items_and_warehouse_accounts(self, warehouse_account=None):
items, warehouses = [], [] items, warehouses = [], []
if not warehouse_account: if not warehouse_account:
warehouse_account = get_warehouse_account() warehouse_account = get_warehouse_account()
if hasattr(self, "fname"): if hasattr(self, "fname"):
item_doclist = self.get(self.fname) item_doclist = self.get(self.fname)
elif self.doctype == "Stock Reconciliation": elif self.doctype == "Stock Reconciliation":
@ -98,26 +98,26 @@ class StockController(AccountsController):
for row in data[data.index(self.head_row)+1:]: for row in data[data.index(self.head_row)+1:]:
d = frappe._dict(zip(["item_code", "warehouse", "qty", "valuation_rate"], row)) d = frappe._dict(zip(["item_code", "warehouse", "qty", "valuation_rate"], row))
item_doclist.append(d) item_doclist.append(d)
if item_doclist: if item_doclist:
for d in item_doclist: for d in item_doclist:
if d.item_code and d.item_code not in items: if d.item_code and d.item_code not in items:
items.append(d.item_code) items.append(d.item_code)
if d.get("warehouse") and d.warehouse not in warehouses: if d.get("warehouse") and d.warehouse not in warehouses:
warehouses.append(d.warehouse) warehouses.append(d.warehouse)
if self.doctype == "Stock Entry": if self.doctype == "Stock Entry":
if d.get("s_warehouse") and d.s_warehouse not in warehouses: if d.get("s_warehouse") and d.s_warehouse not in warehouses:
warehouses.append(d.s_warehouse) warehouses.append(d.s_warehouse)
if d.get("t_warehouse") and d.t_warehouse not in warehouses: if d.get("t_warehouse") and d.t_warehouse not in warehouses:
warehouses.append(d.t_warehouse) warehouses.append(d.t_warehouse)
warehouse_account = {wh: warehouse_account[wh] for wh in warehouses warehouse_account = {wh: warehouse_account[wh] for wh in warehouses
if warehouse_account.get(wh)} if warehouse_account.get(wh)}
return items, warehouse_account return items, warehouse_account
def get_stock_ledger_details(self): def get_stock_ledger_details(self):
stock_ledger = {} stock_ledger = {}
for sle in frappe.db.sql("""select warehouse, stock_value_difference, voucher_detail_no for sle in frappe.db.sql("""select warehouse, stock_value_difference, voucher_detail_no
@ -125,12 +125,12 @@ class StockController(AccountsController):
(self.doctype, self.name), as_dict=True): (self.doctype, self.name), as_dict=True):
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle) stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
return stock_ledger return stock_ledger
def get_warehouse_account(self): def get_warehouse_account(self):
warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount
where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) where account_type = 'Warehouse' and ifnull(master_name, '') != ''"""))
return warehouse_account return warehouse_account
def update_gl_entries_after(self, warehouse_account=None): def update_gl_entries_after(self, warehouse_account=None):
future_stock_vouchers = self.get_future_stock_vouchers() future_stock_vouchers = self.get_future_stock_vouchers()
gle = self.get_voucherwise_gl_entries(future_stock_vouchers) gle = self.get_voucherwise_gl_entries(future_stock_vouchers)
@ -153,53 +153,53 @@ class StockController(AccountsController):
break break
else: else:
matched = False matched = False
if not matched: if not matched:
self.delete_gl_entries(voucher_type, voucher_no) self.delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(repost_future_gle=False) voucher_obj.make_gl_entries(repost_future_gle=False)
else: else:
self.delete_gl_entries(voucher_type, voucher_no) self.delete_gl_entries(voucher_type, voucher_no)
def get_future_stock_vouchers(self): def get_future_stock_vouchers(self):
future_stock_vouchers = [] future_stock_vouchers = []
if hasattr(self, "fname"): if hasattr(self, "fname"):
item_list = [d.item_code for d in self.get(self.fname)] item_list = [d.item_code for d in self.get(self.fname)]
condition = ''.join(['and item_code in (\'', '\', \''.join(item_list) ,'\')']) condition = ''.join(['and item_code in (\'', '\', \''.join(item_list) ,'\')'])
else: else:
condition = "" condition = ""
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s
order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" % order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" %
('%s', '%s', condition), (self.posting_date, self.posting_time), ('%s', '%s', condition), (self.posting_date, self.posting_time),
as_dict=True): as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no]) future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers return future_stock_vouchers
def get_voucherwise_gl_entries(self, future_stock_vouchers): def get_voucherwise_gl_entries(self, future_stock_vouchers):
gl_entries = {} gl_entries = {}
if future_stock_vouchers: if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry` for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" % where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))), ('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([self.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): tuple([self.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries return gl_entries
def delete_gl_entries(self, voucher_type, voucher_no): def delete_gl_entries(self, voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry` frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
def make_adjustment_entry(self, expected_gle, voucher_obj): def make_adjustment_entry(self, expected_gle, voucher_obj):
from erpnext.accounts.utils import get_stock_and_account_difference from erpnext.accounts.utils import get_stock_and_account_difference
account_list = [d.account for d in expected_gle] account_list = [d.account for d in expected_gle]
acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date) acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date)
cost_center = self.get_company_default("cost_center") cost_center = self.get_company_default("cost_center")
stock_adjustment_account = self.get_company_default("stock_adjustment_account") stock_adjustment_account = self.get_company_default("stock_adjustment_account")
@ -214,7 +214,7 @@ class StockController(AccountsController):
"debit": diff, "debit": diff,
"remarks": "Adjustment Accounting Entry for Stock", "remarks": "Adjustment Accounting Entry for Stock",
}), }),
# account against stock in hand # account against stock in hand
voucher_obj.get_gl_dict({ voucher_obj.get_gl_dict({
"account": stock_adjustment_account, "account": stock_adjustment_account,
@ -224,21 +224,21 @@ class StockController(AccountsController):
"remarks": "Adjustment Accounting Entry for Stock", "remarks": "Adjustment Accounting Entry for Stock",
}), }),
]) ])
if gl_entries: if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries) make_gl_entries(gl_entries)
def check_expense_account(self, item): def check_expense_account(self, item):
if item.meta.get_field("expense_account") and not item.expense_account: if item.meta.get_field("expense_account") and not item.expense_account:
msgprint(_("""Expense/Difference account is mandatory for item: """) + item.item_code, msgprint(_("""Expense/Difference account is mandatory for item: """) + item.item_code,
raise_exception=1) raise_exception=1)
if item.meta.get_field("expense_account") and not item.cost_center: if item.meta.get_field("expense_account") and not item.cost_center:
msgprint(_("""Cost Center is mandatory for item: """) + item.item_code, msgprint(_("""Cost Center is mandatory for item: """) + item.item_code,
raise_exception=1) raise_exception=1)
def get_sl_entries(self, d, args): def get_sl_entries(self, d, args):
sl_dict = { sl_dict = {
"item_code": d.item_code, "item_code": d.item_code,
"warehouse": d.get("warehouse", None), "warehouse": d.get("warehouse", None),
@ -257,27 +257,27 @@ class StockController(AccountsController):
"project": d.get("project_name"), "project": d.get("project_name"),
"is_cancelled": self.docstatus==2 and "Yes" or "No" "is_cancelled": self.docstatus==2 and "Yes" or "No"
} }
sl_dict.update(args) sl_dict.update(args)
return sl_dict return sl_dict
def make_sl_entries(self, sl_entries, is_amended=None): def make_sl_entries(self, sl_entries, is_amended=None):
from erpnext.stock.stock_ledger import make_sl_entries from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, is_amended) make_sl_entries(sl_entries, is_amended)
def make_cancel_gl_entries(self): def make_cancel_gl_entries(self):
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""", (self.doctype, self.name)): and voucher_no=%s""", (self.doctype, self.name)):
self.make_gl_entries() self.make_gl_entries()
def update_gl_entries_after(posting_date, posting_time, warehouse_account=None, for_items=None): def update_gl_entries_after(posting_date, posting_time, warehouse_account=None, for_items=None):
def _delete_gl_entries(voucher_type, voucher_no): def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry` frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no)) where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
if not warehouse_account: if not warehouse_account:
warehouse_account = get_warehouse_account() warehouse_account = get_warehouse_account()
future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time, future_stock_vouchers = get_future_stock_vouchers(posting_date, posting_time,
warehouse_account, for_items) warehouse_account, for_items)
gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date) gle = get_voucherwise_gl_entries(future_stock_vouchers, posting_date)
@ -286,13 +286,13 @@ def update_gl_entries_after(posting_date, posting_time, warehouse_account=None,
voucher_obj = frappe.get_doc(voucher_type, voucher_no) voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account) expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle: if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle, if not existing_gle or not compare_existing_and_expected_gle(existing_gle,
expected_gle): expected_gle):
_delete_gl_entries(voucher_type, voucher_no) _delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(repost_future_gle=False) voucher_obj.make_gl_entries(repost_future_gle=False)
else: else:
_delete_gl_entries(voucher_type, voucher_no) _delete_gl_entries(voucher_type, voucher_no)
def compare_existing_and_expected_gle(existing_gle, expected_gle): def compare_existing_and_expected_gle(existing_gle, expected_gle):
matched = True matched = True
for entry in expected_gle: for entry in expected_gle:
@ -306,36 +306,36 @@ def compare_existing_and_expected_gle(existing_gle, expected_gle):
def get_future_stock_vouchers(posting_date, posting_time, warehouse_account=None, for_items=None): def get_future_stock_vouchers(posting_date, posting_time, warehouse_account=None, for_items=None):
future_stock_vouchers = [] future_stock_vouchers = []
condition = "" condition = ""
if for_items: if for_items:
condition = ''.join([' and item_code in (\'', '\', \''.join(for_items) ,'\')']) condition = ''.join([' and item_code in (\'', '\', \''.join(for_items) ,'\')'])
if warehouse_account: if warehouse_account:
condition += ''.join([' and warehouse in (\'', '\', \''.join(warehouse_account.keys()) ,'\')']) condition += ''.join([' and warehouse in (\'', '\', \''.join(warehouse_account.keys()) ,'\')'])
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s
order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" % order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" %
('%s', '%s', condition), (posting_date, posting_time), ('%s', '%s', condition), (posting_date, posting_time),
as_dict=True): as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no]) future_stock_vouchers.append([d.voucher_type, d.voucher_no])
return future_stock_vouchers return future_stock_vouchers
def get_voucherwise_gl_entries(future_stock_vouchers, posting_date): def get_voucherwise_gl_entries(future_stock_vouchers, posting_date):
gl_entries = {} gl_entries = {}
if future_stock_vouchers: if future_stock_vouchers:
for d in frappe.db.sql("""select * from `tabGL Entry` for d in frappe.db.sql("""select * from `tabGL Entry`
where posting_date >= %s and voucher_no in (%s)""" % where posting_date >= %s and voucher_no in (%s)""" %
('%s', ', '.join(['%s']*len(future_stock_vouchers))), ('%s', ', '.join(['%s']*len(future_stock_vouchers))),
tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1): tuple([posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d) gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
return gl_entries return gl_entries
def get_warehouse_account(): def get_warehouse_account():
warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount
where account_type = 'Warehouse' and ifnull(master_name, '') != ''""")) where account_type = 'Warehouse' and ifnull(master_name, '') != ''"""))
return warehouse_account return warehouse_account

View File

@ -19,12 +19,12 @@ class StockOverReturnError(frappe.ValidationError): pass
class IncorrectValuationRateError(frappe.ValidationError): pass class IncorrectValuationRateError(frappe.ValidationError): pass
class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
class StockEntry(StockController): class StockEntry(StockController):
fname = 'mtn_details' fname = 'mtn_details'
def validate(self): def validate(self):
self.validate_posting_time() self.validate_posting_time()
self.validate_purpose() self.validate_purpose()
@ -44,7 +44,7 @@ class StockEntry(StockController):
self.validate_with_material_request() self.validate_with_material_request()
self.validate_fiscal_year() self.validate_fiscal_year()
self.set_total_amount() self.set_total_amount()
def on_submit(self): def on_submit(self):
self.update_stock_ledger() self.update_stock_ledger()
@ -57,32 +57,32 @@ class StockEntry(StockController):
self.update_stock_ledger() self.update_stock_ledger()
self.update_production_order() self.update_production_order()
self.make_cancel_gl_entries() self.make_cancel_gl_entries()
def validate_fiscal_year(self): def validate_fiscal_year(self):
from erpnext.accounts.utils import validate_fiscal_year from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.posting_date, self.fiscal_year, validate_fiscal_year(self.posting_date, self.fiscal_year,
self.meta.get_label("posting_date")) self.meta.get_label("posting_date"))
def validate_purpose(self): def validate_purpose(self):
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
"Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"] "Manufacture/Repack", "Subcontract", "Sales Return", "Purchase Return"]
if self.purpose not in valid_purposes: if self.purpose not in valid_purposes:
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes), msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
raise_exception=True) raise_exception=True)
def validate_item(self): def validate_item(self):
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
for item in self.get("mtn_details"): for item in self.get("mtn_details"):
if item.item_code not in stock_items: if item.item_code not in stock_items:
msgprint(_("""Only Stock Items are allowed for Stock Entry"""), msgprint(_("""Only Stock Items are allowed for Stock Entry"""),
raise_exception=True) raise_exception=True)
def validate_warehouse(self, pro_obj): def validate_warehouse(self, pro_obj):
"""perform various (sometimes conditional) validations on warehouse""" """perform various (sometimes conditional) validations on warehouse"""
source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return"] source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return"]
target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return"] target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return"]
validate_for_manufacture_repack = any([d.bom_no for d in self.get("mtn_details")]) validate_for_manufacture_repack = any([d.bom_no for d in self.get("mtn_details")])
if self.purpose in source_mandatory and self.purpose not in target_mandatory: if self.purpose in source_mandatory and self.purpose not in target_mandatory:
@ -101,11 +101,11 @@ class StockEntry(StockController):
if not (d.s_warehouse or d.t_warehouse): if not (d.s_warehouse or d.t_warehouse):
msgprint(_("Atleast one warehouse is mandatory"), raise_exception=1) msgprint(_("Atleast one warehouse is mandatory"), raise_exception=1)
if self.purpose in source_mandatory and not d.s_warehouse: if self.purpose in source_mandatory and not d.s_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx) msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1) + _("Source Warehouse") + _(" is mandatory"), raise_exception=1)
if self.purpose in target_mandatory and not d.t_warehouse: if self.purpose in target_mandatory and not d.t_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx) msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1) + _("Target Warehouse") + _(" is mandatory"), raise_exception=1)
@ -114,39 +114,39 @@ class StockEntry(StockController):
if validate_for_manufacture_repack: if validate_for_manufacture_repack:
if d.bom_no: if d.bom_no:
d.s_warehouse = None d.s_warehouse = None
if not d.t_warehouse: if not d.t_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx) msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Target Warehouse") + _(" is mandatory"), raise_exception=1) + _("Target Warehouse") + _(" is mandatory"), raise_exception=1)
elif pro_obj and cstr(d.t_warehouse) != pro_obj.fg_warehouse: elif pro_obj and cstr(d.t_warehouse) != pro_obj.fg_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx) msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Target Warehouse") + _(" should be same as that in ") + _("Target Warehouse") + _(" should be same as that in ")
+ _("Production Order"), raise_exception=1) + _("Production Order"), raise_exception=1)
else: else:
d.t_warehouse = None d.t_warehouse = None
if not d.s_warehouse: if not d.s_warehouse:
msgprint(_("Row # ") + "%s: " % cint(d.idx) msgprint(_("Row # ") + "%s: " % cint(d.idx)
+ _("Source Warehouse") + _(" is mandatory"), raise_exception=1) + _("Source Warehouse") + _(" is mandatory"), raise_exception=1)
if cstr(d.s_warehouse) == cstr(d.t_warehouse): if cstr(d.s_warehouse) == cstr(d.t_warehouse):
msgprint(_("Source and Target Warehouse cannot be same"), msgprint(_("Source and Target Warehouse cannot be same"),
raise_exception=1) raise_exception=1)
def validate_production_order(self, pro_obj=None): def validate_production_order(self, pro_obj=None):
if not pro_obj: if not pro_obj:
if self.production_order: if self.production_order:
pro_obj = frappe.get_doc('Production Order', self.production_order) pro_obj = frappe.get_doc('Production Order', self.production_order)
else: else:
return return
if self.purpose == "Manufacture/Repack": if self.purpose == "Manufacture/Repack":
# check for double entry # check for double entry
self.check_duplicate_entry_for_production_order() self.check_duplicate_entry_for_production_order()
elif self.purpose != "Material Transfer": elif self.purpose != "Material Transfer":
self.production_order = None self.production_order = None
def check_duplicate_entry_for_production_order(self): def check_duplicate_entry_for_production_order(self):
other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", { other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", {
"production_order": self.production_order, "production_order": self.production_order,
@ -154,24 +154,24 @@ class StockEntry(StockController):
"docstatus": ["!=", 2], "docstatus": ["!=", 2],
"name": ["!=", self.name] "name": ["!=", self.name]
}, "name")] }, "name")]
if other_ste: if other_ste:
production_item, qty = frappe.db.get_value("Production Order", production_item, qty = frappe.db.get_value("Production Order",
self.production_order, ["production_item", "qty"]) self.production_order, ["production_item", "qty"])
args = other_ste + [production_item] args = other_ste + [production_item]
fg_qty_already_entered = frappe.db.sql("""select sum(actual_qty) fg_qty_already_entered = frappe.db.sql("""select sum(actual_qty)
from `tabStock Entry Detail` from `tabStock Entry Detail`
where parent in (%s) where parent in (%s)
and item_code = %s and item_code = %s
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0] and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
if fg_qty_already_entered >= qty: if fg_qty_already_entered >= qty:
frappe.throw(_("Stock Entries already created for Production Order ") frappe.throw(_("Stock Entries already created for Production Order ")
+ self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError) + self.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
def set_total_amount(self): def set_total_amount(self):
self.total_amount = sum([flt(item.amount) for item in self.get("mtn_details")]) self.total_amount = sum([flt(item.amount) for item in self.get("mtn_details")])
def get_stock_and_rate(self): def get_stock_and_rate(self):
"""get stock and incoming rate on posting date""" """get stock and incoming rate on posting date"""
for d in self.get('mtn_details'): for d in self.get('mtn_details'):
@ -186,21 +186,21 @@ class StockEntry(StockController):
}) })
# get actual stock at source warehouse # get actual stock at source warehouse
d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0 d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0
# get incoming rate # get incoming rate
if not flt(d.incoming_rate): if not flt(d.incoming_rate):
d.incoming_rate = self.get_incoming_rate(args) d.incoming_rate = self.get_incoming_rate(args)
d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) d.amount = flt(d.transfer_qty) * flt(d.incoming_rate)
def get_incoming_rate(self, args): def get_incoming_rate(self, args):
incoming_rate = 0 incoming_rate = 0
if self.purpose == "Sales Return" and \ if self.purpose == "Sales Return" and \
(self.delivery_note_no or self.sales_invoice_no): (self.delivery_note_no or self.sales_invoice_no):
sle = frappe.db.sql("""select name, posting_date, posting_time, sle = frappe.db.sql("""select name, posting_date, posting_time,
actual_qty, stock_value, warehouse from `tabStock Ledger Entry` actual_qty, stock_value, warehouse from `tabStock Ledger Entry`
where voucher_type = %s and voucher_no = %s and where voucher_type = %s and voucher_no = %s and
item_code = %s limit 1""", item_code = %s limit 1""",
((self.delivery_note_no and "Delivery Note" or "Sales Invoice"), ((self.delivery_note_no and "Delivery Note" or "Sales Invoice"),
self.delivery_note_no or self.sales_invoice_no, args.item_code), as_dict=1) self.delivery_note_no or self.sales_invoice_no, args.item_code), as_dict=1)
if sle: if sle:
@ -215,14 +215,14 @@ class StockEntry(StockController):
flt(sle[0].actual_qty) flt(sle[0].actual_qty)
else: else:
incoming_rate = get_incoming_rate(args) incoming_rate = get_incoming_rate(args)
return incoming_rate return incoming_rate
def validate_incoming_rate(self): def validate_incoming_rate(self):
for d in self.get('mtn_details'): for d in self.get('mtn_details'):
if d.t_warehouse: if d.t_warehouse:
self.validate_value("incoming_rate", ">", 0, d, raise_exception=IncorrectValuationRateError) self.validate_value("incoming_rate", ">", 0, d, raise_exception=IncorrectValuationRateError)
def validate_bom(self): def validate_bom(self):
for d in self.get('mtn_details'): for d in self.get('mtn_details'):
if d.bom_no and not frappe.db.sql("""select name from `tabBOM` if d.bom_no and not frappe.db.sql("""select name from `tabBOM`
@ -231,72 +231,72 @@ class StockEntry(StockController):
msgprint(_("Item") + " %s: " % cstr(d.item_code) msgprint(_("Item") + " %s: " % cstr(d.item_code)
+ _("does not belong to BOM: ") + cstr(d.bom_no) + _("does not belong to BOM: ") + cstr(d.bom_no)
+ _(" or the BOM is cancelled or inactive"), raise_exception=1) + _(" or the BOM is cancelled or inactive"), raise_exception=1)
def validate_finished_goods(self): def validate_finished_goods(self):
"""validation: finished good quantity should be same as manufacturing quantity""" """validation: finished good quantity should be same as manufacturing quantity"""
for d in self.get('mtn_details'): for d in self.get('mtn_details'):
if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty): if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty):
msgprint(_("Row #") + " %s: " % d.idx msgprint(_("Row #") + " %s: " % d.idx
+ _("Quantity should be equal to Manufacturing Quantity. To fetch items again, click on 'Get Items' button or update the Quantity manually."), raise_exception=1) + _("Quantity should be equal to Manufacturing Quantity. To fetch items again, click on 'Get Items' button or update the Quantity manually."), raise_exception=1)
def validate_return_reference_doc(self): def validate_return_reference_doc(self):
"""validate item with reference doc""" """validate item with reference doc"""
ref = get_return_doc_and_details(self) ref = get_return_doc_and_details(self)
if ref.doc: if ref.doc:
# validate docstatus # validate docstatus
if ref.doc.docstatus != 1: if ref.doc.docstatus != 1:
frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": ' frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '
+ _("Status should be Submitted"), raise_exception=frappe.InvalidStatusError) + _("Status should be Submitted"), raise_exception=frappe.InvalidStatusError)
# update stock check # update stock check
if ref.doc.doctype == "Sales Invoice" and cint(ref.doc.update_stock) != 1: if ref.doc.doctype == "Sales Invoice" and cint(ref.doc.update_stock) != 1:
frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": ' frappe.msgprint(_(ref.doc.doctype) + ' "' + ref.doc.name + '": '
+ _("Update Stock should be checked."), + _("Update Stock should be checked."),
raise_exception=NotUpdateStockError) raise_exception=NotUpdateStockError)
# posting date check # posting date check
ref_posting_datetime = "%s %s" % (cstr(ref.doc.posting_date), ref_posting_datetime = "%s %s" % (cstr(ref.doc.posting_date),
cstr(ref.doc.posting_time) or "00:00:00") cstr(ref.doc.posting_time) or "00:00:00")
this_posting_datetime = "%s %s" % (cstr(self.posting_date), this_posting_datetime = "%s %s" % (cstr(self.posting_date),
cstr(self.posting_time)) cstr(self.posting_time))
if this_posting_datetime < ref_posting_datetime: if this_posting_datetime < ref_posting_datetime:
from frappe.utils.dateutils import datetime_in_user_format from frappe.utils.dateutils import datetime_in_user_format
frappe.msgprint(_("Posting Date Time cannot be before") frappe.msgprint(_("Posting Date Time cannot be before")
+ ": " + datetime_in_user_format(ref_posting_datetime), + ": " + datetime_in_user_format(ref_posting_datetime),
raise_exception=True) raise_exception=True)
stock_items = get_stock_items_for_return(ref.doc, ref.parentfields) stock_items = get_stock_items_for_return(ref.doc, ref.parentfields)
already_returned_item_qty = self.get_already_returned_item_qty(ref.fieldname) already_returned_item_qty = self.get_already_returned_item_qty(ref.fieldname)
for item in self.get("mtn_details"): for item in self.get("mtn_details"):
# validate if item exists in the ref doc and that it is a stock item # validate if item exists in the ref doc and that it is a stock item
if item.item_code not in stock_items: if item.item_code not in stock_items:
msgprint(_("Item") + ': "' + item.item_code + _("\" does not exist in ") + msgprint(_("Item") + ': "' + item.item_code + _("\" does not exist in ") +
ref.doc.doctype + ": " + ref.doc.name, ref.doc.doctype + ": " + ref.doc.name,
raise_exception=frappe.DoesNotExistError) raise_exception=frappe.DoesNotExistError)
# validate quantity <= ref item's qty - qty already returned # validate quantity <= ref item's qty - qty already returned
ref_item = ref.doc.getone({"item_code": item.item_code}) ref_item = ref.doc.getone({"item_code": item.item_code})
returnable_qty = ref_item.qty - flt(already_returned_item_qty.get(item.item_code)) returnable_qty = ref_item.qty - flt(already_returned_item_qty.get(item.item_code))
if not returnable_qty: if not returnable_qty:
frappe.throw("{item}: {item_code} {returned}".format( frappe.throw("{item}: {item_code} {returned}".format(
item=_("Item"), item_code=item.item_code, item=_("Item"), item_code=item.item_code,
returned=_("already returned though some other documents")), returned=_("already returned though some other documents")),
StockOverReturnError) StockOverReturnError)
elif item.transfer_qty > returnable_qty: elif item.transfer_qty > returnable_qty:
frappe.throw("{item}: {item_code}, {returned}: {qty}".format( frappe.throw("{item}: {item_code}, {returned}: {qty}".format(
item=_("Item"), item_code=item.item_code, item=_("Item"), item_code=item.item_code,
returned=_("Max Returnable Qty"), qty=returnable_qty), StockOverReturnError) returned=_("Max Returnable Qty"), qty=returnable_qty), StockOverReturnError)
def get_already_returned_item_qty(self, ref_fieldname): def get_already_returned_item_qty(self, ref_fieldname):
return dict(frappe.db.sql("""select item_code, sum(transfer_qty) as qty return dict(frappe.db.sql("""select item_code, sum(transfer_qty) as qty
from `tabStock Entry Detail` where parent in ( from `tabStock Entry Detail` where parent in (
select name from `tabStock Entry` where `%s`=%s and docstatus=1) select name from `tabStock Entry` where `%s`=%s and docstatus=1)
group by item_code""" % (ref_fieldname, "%s"), (self.get(ref_fieldname),))) group by item_code""" % (ref_fieldname, "%s"), (self.get(ref_fieldname),)))
def update_stock_ledger(self): def update_stock_ledger(self):
sl_entries = [] sl_entries = []
for d in self.get('mtn_details'): for d in self.get('mtn_details'):
if cstr(d.s_warehouse) and self.docstatus == 1: if cstr(d.s_warehouse) and self.docstatus == 1:
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
@ -304,57 +304,57 @@ class StockEntry(StockController):
"actual_qty": -flt(d.transfer_qty), "actual_qty": -flt(d.transfer_qty),
"incoming_rate": 0 "incoming_rate": 0
})) }))
if cstr(d.t_warehouse): if cstr(d.t_warehouse):
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.t_warehouse), "warehouse": cstr(d.t_warehouse),
"actual_qty": flt(d.transfer_qty), "actual_qty": flt(d.transfer_qty),
"incoming_rate": flt(d.incoming_rate) "incoming_rate": flt(d.incoming_rate)
})) }))
# On cancellation, make stock ledger entry for # On cancellation, make stock ledger entry for
# target warehouse first, to update serial no values properly # target warehouse first, to update serial no values properly
if cstr(d.s_warehouse) and self.docstatus == 2: if cstr(d.s_warehouse) and self.docstatus == 2:
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"warehouse": cstr(d.s_warehouse), "warehouse": cstr(d.s_warehouse),
"actual_qty": -flt(d.transfer_qty), "actual_qty": -flt(d.transfer_qty),
"incoming_rate": 0 "incoming_rate": 0
})) }))
self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No') self.make_sl_entries(sl_entries, self.amended_from and 'Yes' or 'No')
def update_production_order(self): def update_production_order(self):
def _validate_production_order(pro_doc): def _validate_production_order(pro_doc):
if flt(pro_doc.docstatus) != 1: if flt(pro_doc.docstatus) != 1:
frappe.throw(_("Production Order must be submitted") + ": " + frappe.throw(_("Production Order must be submitted") + ": " +
self.production_order) self.production_order)
if pro_doc.status == 'Stopped': if pro_doc.status == 'Stopped':
msgprint(_("Transaction not allowed against stopped Production Order") + ": " + msgprint(_("Transaction not allowed against stopped Production Order") + ": " +
self.production_order) self.production_order)
if self.production_order: if self.production_order:
pro_doc = frappe.get_doc("Production Order", self.production_order) pro_doc = frappe.get_doc("Production Order", self.production_order)
_validate_production_order(pro_doc) _validate_production_order(pro_doc)
self.update_produced_qty(pro_doc) self.update_produced_qty(pro_doc)
if self.purpose == "Manufacture/Repack": if self.purpose == "Manufacture/Repack":
self.update_planned_qty(pro_doc) self.update_planned_qty(pro_doc)
def update_produced_qty(self, pro_doc): def update_produced_qty(self, pro_doc):
if self.purpose == "Manufacture/Repack": if self.purpose == "Manufacture/Repack":
produced_qty = flt(pro_doc.produced_qty) + \ produced_qty = flt(pro_doc.produced_qty) + \
(self.docstatus==1 and 1 or -1 ) * flt(self.fg_completed_qty) (self.docstatus==1 and 1 or -1 ) * flt(self.fg_completed_qty)
if produced_qty > flt(pro_doc.qty): if produced_qty > flt(pro_doc.qty):
frappe.throw(_("Production Order") + ": " + self.production_order + "\n" + frappe.throw(_("Production Order") + ": " + self.production_order + "\n" +
_("Total Manufactured Qty can not be greater than Planned qty to manufacture") _("Total Manufactured Qty can not be greater than Planned qty to manufacture")
+ "(%s/%s)" % (produced_qty, flt(pro_doc.qty)), StockOverProductionError) + "(%s/%s)" % (produced_qty, flt(pro_doc.qty)), StockOverProductionError)
status = 'Completed' if flt(produced_qty) >= flt(pro_doc.qty) else 'In Process' status = 'Completed' if flt(produced_qty) >= flt(pro_doc.qty) else 'In Process'
frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s frappe.db.sql("""update `tabProduction Order` set status=%s, produced_qty=%s
where name=%s""", (status, produced_qty, self.production_order)) where name=%s""", (status, produced_qty, self.production_order))
def update_planned_qty(self, pro_doc): def update_planned_qty(self, pro_doc):
from erpnext.stock.utils import update_bin from erpnext.stock.utils import update_bin
update_bin({ update_bin({
@ -363,16 +363,16 @@ class StockEntry(StockController):
"posting_date": self.posting_date, "posting_date": self.posting_date,
"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty) "planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty)
}) })
def get_item_details(self, arg): def get_item_details(self, arg):
arg = json.loads(arg) arg = json.loads(arg)
item = frappe.db.sql("""select stock_uom, description, item_name, item = frappe.db.sql("""select stock_uom, description, item_name,
expense_account, buying_cost_center from `tabItem` expense_account, buying_cost_center from `tabItem`
where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00' where name = %s and (ifnull(end_of_life,'')='' or end_of_life ='0000-00-00'
or end_of_life > now())""", (arg.get('item_code')), as_dict = 1) or end_of_life > now())""", (arg.get('item_code')), as_dict = 1)
if not item: if not item:
msgprint("Item is not active", raise_exception=1) msgprint("Item is not active", raise_exception=1)
ret = { ret = {
'uom' : item and item[0]['stock_uom'] or '', 'uom' : item and item[0]['stock_uom'] or '',
'stock_uom' : item and item[0]['stock_uom'] or '', 'stock_uom' : item and item[0]['stock_uom'] or '',
@ -394,7 +394,7 @@ class StockEntry(StockController):
def get_uom_details(self, arg = ''): def get_uom_details(self, arg = ''):
arg, ret = eval(arg), {} arg, ret = eval(arg), {}
uom = frappe.db.sql("""select conversion_factor from `tabUOM Conversion Detail` uom = frappe.db.sql("""select conversion_factor from `tabUOM Conversion Detail`
where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1) where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1)
if not uom or not flt(uom[0].conversion_factor): if not uom or not flt(uom[0].conversion_factor):
msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'], msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'],
@ -406,7 +406,7 @@ class StockEntry(StockController):
'transfer_qty' : flt(arg['qty']) * flt(uom[0]['conversion_factor']), 'transfer_qty' : flt(arg['qty']) * flt(uom[0]['conversion_factor']),
} }
return ret return ret
def get_warehouse_details(self, args): def get_warehouse_details(self, args):
args = json.loads(args) args = json.loads(args)
ret = {} ret = {}
@ -416,14 +416,14 @@ class StockEntry(StockController):
"posting_time": self.posting_time, "posting_time": self.posting_time,
}) })
args = frappe._dict(args) args = frappe._dict(args)
ret = { ret = {
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0, "actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
"incoming_rate" : self.get_incoming_rate(args) "incoming_rate" : self.get_incoming_rate(args)
} }
return ret return ret
def get_items(self): def get_items(self):
pro_obj = None pro_obj = None
if self.production_order: if self.production_order:
# common validations # common validations
@ -434,7 +434,7 @@ class StockEntry(StockController):
else: else:
# invalid production order # invalid production order
self.production_order = None self.production_order = None
if self.bom_no: if self.bom_no:
if self.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack", if self.purpose in ["Material Issue", "Material Transfer", "Manufacture/Repack",
"Subcontract"]: "Subcontract"]:
@ -451,10 +451,10 @@ class StockEntry(StockController):
# add raw materials to Stock Entry Detail table # add raw materials to Stock Entry Detail table
idx = self.add_to_stock_entry_detail(item_dict) idx = self.add_to_stock_entry_detail(item_dict)
# add finished good item to Stock Entry Detail table -- along with bom_no # add finished good item to Stock Entry Detail table -- along with bom_no
if self.production_order and self.purpose == "Manufacture/Repack": if self.production_order and self.purpose == "Manufacture/Repack":
item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name", item = frappe.db.get_value("Item", pro_obj.production_item, ["item_name",
"description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1) "description", "stock_uom", "expense_account", "buying_cost_center"], as_dict=1)
self.add_to_stock_entry_detail({ self.add_to_stock_entry_detail({
cstr(pro_obj.production_item): { cstr(pro_obj.production_item): {
@ -468,14 +468,14 @@ class StockEntry(StockController):
"cost_center": item.buying_cost_center, "cost_center": item.buying_cost_center,
} }
}, bom_no=pro_obj.bom_no, idx=idx) }, bom_no=pro_obj.bom_no, idx=idx)
elif self.purpose in ["Material Receipt", "Manufacture/Repack"]: elif self.purpose in ["Material Receipt", "Manufacture/Repack"]:
if self.purpose=="Material Receipt": if self.purpose=="Material Receipt":
self.from_warehouse = "" self.from_warehouse = ""
item = frappe.db.sql("""select name, item_name, description, item = frappe.db.sql("""select name, item_name, description,
stock_uom, expense_account, buying_cost_center from `tabItem` stock_uom, expense_account, buying_cost_center from `tabItem`
where name=(select item from tabBOM where name=%s)""", where name=(select item from tabBOM where name=%s)""",
self.bom_no, as_dict=1) self.bom_no, as_dict=1)
self.add_to_stock_entry_detail({ self.add_to_stock_entry_detail({
item[0]["name"] : { item[0]["name"] : {
@ -488,20 +488,20 @@ class StockEntry(StockController):
"cost_center": item[0].buying_cost_center, "cost_center": item[0].buying_cost_center,
} }
}, bom_no=self.bom_no, idx=idx) }, bom_no=self.bom_no, idx=idx)
self.get_stock_and_rate() self.get_stock_and_rate()
def get_bom_raw_materials(self, qty): def get_bom_raw_materials(self, qty):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
# item dict = { item_code: {qty, description, stock_uom} } # item dict = { item_code: {qty, description, stock_uom} }
item_dict = get_bom_items_as_dict(self.bom_no, qty=qty, fetch_exploded = self.use_multi_level_bom) item_dict = get_bom_items_as_dict(self.bom_no, qty=qty, fetch_exploded = self.use_multi_level_bom)
for item in item_dict.values(): for item in item_dict.values():
item.from_warehouse = item.default_warehouse item.from_warehouse = item.default_warehouse
return item_dict return item_dict
def get_pending_raw_materials(self, pro_obj): def get_pending_raw_materials(self, pro_obj):
""" """
issue (item quantity) that is pending to issue or desire to transfer, issue (item quantity) that is pending to issue or desire to transfer,
@ -509,10 +509,10 @@ class StockEntry(StockController):
""" """
item_dict = self.get_bom_raw_materials(1) item_dict = self.get_bom_raw_materials(1)
issued_item_qty = self.get_issued_qty() issued_item_qty = self.get_issued_qty()
max_qty = flt(pro_obj.qty) max_qty = flt(pro_obj.qty)
only_pending_fetched = [] only_pending_fetched = []
for item in item_dict: for item in item_dict:
pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0) pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0)
desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"] desire_to_transfer = flt(self.fg_completed_qty) * item_dict[item]["qty"]
@ -522,17 +522,17 @@ class StockEntry(StockController):
item_dict[item]["qty"] = pending_to_issue item_dict[item]["qty"] = pending_to_issue
if pending_to_issue: if pending_to_issue:
only_pending_fetched.append(item) only_pending_fetched.append(item)
# delete items with 0 qty # delete items with 0 qty
for item in item_dict.keys(): for item in item_dict.keys():
if not item_dict[item]["qty"]: if not item_dict[item]["qty"]:
del item_dict[item] del item_dict[item]
# show some message # show some message
if not len(item_dict): if not len(item_dict):
frappe.msgprint(_("""All items have already been transferred \ frappe.msgprint(_("""All items have already been transferred \
for this Production Order.""")) for this Production Order."""))
elif only_pending_fetched: elif only_pending_fetched:
frappe.msgprint(_("""Only quantities pending to be transferred \ frappe.msgprint(_("""Only quantities pending to be transferred \
were fetched for the following items:\n""" + "\n".join(only_pending_fetched))) were fetched for the following items:\n""" + "\n".join(only_pending_fetched)))
@ -548,7 +548,7 @@ class StockEntry(StockController):
group by t1.item_code""", self.production_order) group by t1.item_code""", self.production_order)
for t in result: for t in result:
issued_item_qty[t[0]] = flt(t[1]) issued_item_qty[t[0]] = flt(t[1])
return issued_item_qty return issued_item_qty
def add_to_stock_entry_detail(self, item_dict, bom_no=None, idx=None): def add_to_stock_entry_detail(self, item_dict, bom_no=None, idx=None):
@ -569,79 +569,79 @@ class StockEntry(StockController):
se_child.qty = flt(item_dict[d]["qty"]) se_child.qty = flt(item_dict[d]["qty"])
se_child.expense_account = item_dict[d]["expense_account"] or expense_account se_child.expense_account = item_dict[d]["expense_account"] or expense_account
se_child.cost_center = item_dict[d]["cost_center"] or cost_center se_child.cost_center = item_dict[d]["cost_center"] or cost_center
# in stock uom # in stock uom
se_child.transfer_qty = flt(item_dict[d]["qty"]) se_child.transfer_qty = flt(item_dict[d]["qty"])
se_child.conversion_factor = 1.00 se_child.conversion_factor = 1.00
# to be assigned for finished item # to be assigned for finished item
se_child.bom_no = bom_no se_child.bom_no = bom_no
# increment idx by 1 # increment idx by 1
idx += 1 idx += 1
return idx return idx
def validate_with_material_request(self): def validate_with_material_request(self):
for item in self.get("mtn_details"): for item in self.get("mtn_details"):
if item.material_request: if item.material_request:
mreq_item = frappe.db.get_value("Material Request Item", mreq_item = frappe.db.get_value("Material Request Item",
{"name": item.material_request_item, "parent": item.material_request}, {"name": item.material_request_item, "parent": item.material_request},
["item_code", "warehouse", "idx"], as_dict=True) ["item_code", "warehouse", "idx"], as_dict=True)
if mreq_item.item_code != item.item_code or mreq_item.warehouse != item.t_warehouse: if mreq_item.item_code != item.item_code or mreq_item.warehouse != item.t_warehouse:
msgprint(_("Row #") + (" %d: " % item.idx) + _("does not match") msgprint(_("Row #") + (" %d: " % item.idx) + _("does not match")
+ " " + _("Row #") + (" %d %s " % (mreq_item.idx, _("of"))) + " " + _("Row #") + (" %d %s " % (mreq_item.idx, _("of")))
+ _("Material Request") + (" - %s" % item.material_request), + _("Material Request") + (" - %s" % item.material_request),
raise_exception=frappe.MappingMismatchError) raise_exception=frappe.MappingMismatchError)
@frappe.whitelist() @frappe.whitelist()
def get_party_details(ref_dt, ref_dn): def get_party_details(ref_dt, ref_dn):
if ref_dt in ["Delivery Note", "Sales Invoice"]: if ref_dt in ["Delivery Note", "Sales Invoice"]:
res = frappe.db.get_value(ref_dt, ref_dn, res = frappe.db.get_value(ref_dt, ref_dn,
["customer", "customer_name", "address_display as customer_address"], as_dict=1) ["customer", "customer_name", "address_display as customer_address"], as_dict=1)
else: else:
res = frappe.db.get_value(ref_dt, ref_dn, res = frappe.db.get_value(ref_dt, ref_dn,
["supplier", "supplier_name", "address_display as supplier_address"], as_dict=1) ["supplier", "supplier_name", "address_display as supplier_address"], as_dict=1)
return res or {} return res or {}
@frappe.whitelist() @frappe.whitelist()
def get_production_order_details(production_order): def get_production_order_details(production_order):
result = frappe.db.sql("""select bom_no, result = frappe.db.sql("""select bom_no,
ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom, ifnull(qty, 0) - ifnull(produced_qty, 0) as fg_completed_qty, use_multi_level_bom,
wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1) wip_warehouse from `tabProduction Order` where name = %s""", production_order, as_dict=1)
return result and result[0] or {} return result and result[0] or {}
def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters): def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters):
conditions = "" conditions = ""
if doctype == "Sales Invoice": if doctype == "Sales Invoice":
conditions = "and update_stock=1" conditions = "and update_stock=1"
return frappe.db.sql("""select name, customer, customer_name return frappe.db.sql("""select name, customer, customer_name
from `tab%s` where docstatus = 1 from `tab%s` where docstatus = 1
and (`%s` like %%(txt)s and (`%s` like %%(txt)s
or `customer` like %%(txt)s) %s %s or `customer` like %%(txt)s) %s %s
order by name, customer, customer_name order by name, customer, customer_name
limit %s""" % (doctype, searchfield, conditions, limit %s""" % (doctype, searchfield, conditions,
get_match_cond(doctype), "%(start)s, %(page_len)s"), get_match_cond(doctype), "%(start)s, %(page_len)s"),
{"txt": "%%%s%%" % txt, "start": start, "page_len": page_len}, {"txt": "%%%s%%" % txt, "start": start, "page_len": page_len},
as_list=True) as_list=True)
def query_purchase_return_doc(doctype, txt, searchfield, start, page_len, filters): def query_purchase_return_doc(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name, supplier, supplier_name return frappe.db.sql("""select name, supplier, supplier_name
from `tab%s` where docstatus = 1 from `tab%s` where docstatus = 1
and (`%s` like %%(txt)s and (`%s` like %%(txt)s
or `supplier` like %%(txt)s) %s or `supplier` like %%(txt)s) %s
order by name, supplier, supplier_name order by name, supplier, supplier_name
limit %s""" % (doctype, searchfield, get_match_cond(doctype), limit %s""" % (doctype, searchfield, get_match_cond(doctype),
"%(start)s, %(page_len)s"), {"txt": "%%%s%%" % txt, "start": "%(start)s, %(page_len)s"), {"txt": "%%%s%%" % txt, "start":
start, "page_len": page_len}, as_list=True) start, "page_len": page_len}, as_list=True)
def query_return_item(doctype, txt, searchfield, start, page_len, filters): def query_return_item(doctype, txt, searchfield, start, page_len, filters):
txt = txt.replace("%", "") txt = txt.replace("%", "")
ref = get_return_doc_and_details(filters) ref = get_return_doc_and_details(filters)
stock_items = get_stock_items_for_return(ref.doc, ref.parentfields) stock_items = get_stock_items_for_return(ref.doc, ref.parentfields)
result = [] result = []
for item in ref.doc.get_all_children(): for item in ref.doc.get_all_children():
if getattr(item, "item_code", None) in stock_items: if getattr(item, "item_code", None) in stock_items:
@ -649,8 +649,8 @@ def query_return_item(doctype, txt, searchfield, start, page_len, filters):
item.description = cstr(item.description) item.description = cstr(item.description)
if (txt in item.item_code) or (txt in item.item_name) or (txt in item.description): if (txt in item.item_code) or (txt in item.item_name) or (txt in item.description):
val = [ val = [
item.item_code, item.item_code,
(len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name, (len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name,
(len(item.description) > 40) and (item.description[:40] + "...") or \ (len(item.description) > 40) and (item.description[:40] + "...") or \
item.description item.description
] ]
@ -662,45 +662,45 @@ def query_return_item(doctype, txt, searchfield, start, page_len, filters):
def get_batch_no(doctype, txt, searchfield, start, page_len, filters): def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("posting_date"): if not filters.get("posting_date"):
filters["posting_date"] = nowdate() filters["posting_date"] = nowdate()
batch_nos = None batch_nos = None
args = { args = {
'item_code': filters['item_code'], 'item_code': filters['item_code'],
's_warehouse': filters['s_warehouse'], 's_warehouse': filters['s_warehouse'],
'posting_date': filters['posting_date'], 'posting_date': filters['posting_date'],
'txt': "%%%s%%" % txt, 'txt': "%%%s%%" % txt,
'mcond':get_match_cond(doctype), 'mcond':get_match_cond(doctype),
"start": start, "start": start,
"page_len": page_len "page_len": page_len
} }
if filters.get("s_warehouse"): if filters.get("s_warehouse"):
batch_nos = frappe.db.sql("""select batch_no batch_nos = frappe.db.sql("""select batch_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where item_code = '%(item_code)s' where item_code = '%(item_code)s'
and warehouse = '%(s_warehouse)s' and warehouse = '%(s_warehouse)s'
and batch_no like '%(txt)s' and batch_no like '%(txt)s'
and exists(select * from `tabBatch` and exists(select * from `tabBatch`
where name = sle.batch_no where name = sle.batch_no
and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
or expiry_date = '') or expiry_date = '')
and docstatus != 2) and docstatus != 2)
%(mcond)s %(mcond)s
group by batch_no having sum(actual_qty) > 0 group by batch_no having sum(actual_qty) > 0
order by batch_no desc order by batch_no desc
limit %(start)s, %(page_len)s """ limit %(start)s, %(page_len)s """
% args) % args)
if batch_nos: if batch_nos:
return batch_nos return batch_nos
else: else:
return frappe.db.sql("""select name from `tabBatch` return frappe.db.sql("""select name from `tabBatch`
where item = '%(item_code)s' where item = '%(item_code)s'
and docstatus < 2 and docstatus < 2
and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
or expiry_date = '' or expiry_date = "0000-00-00") or expiry_date = '' or expiry_date = "0000-00-00")
%(mcond)s %(mcond)s
order by name desc order by name desc
limit %(start)s, %(page_len)s limit %(start)s, %(page_len)s
""" % args) """ % args)
@ -708,19 +708,19 @@ def get_stock_items_for_return(ref_doc, parentfields):
"""return item codes filtered from doc, which are stock items""" """return item codes filtered from doc, which are stock items"""
if isinstance(parentfields, basestring): if isinstance(parentfields, basestring):
parentfields = [parentfields] parentfields = [parentfields]
all_items = list(set([d.item_code for d in all_items = list(set([d.item_code for d in
ref_doc.get_all_children() if d.get("item_code")])) ref_doc.get_all_children() if d.get("item_code")]))
stock_items = frappe.db.sql_list("""select name from `tabItem` stock_items = frappe.db.sql_list("""select name from `tabItem`
where is_stock_item='Yes' and name in (%s)""" % (", ".join(["%s"] * len(all_items))), where is_stock_item='Yes' and name in (%s)""" % (", ".join(["%s"] * len(all_items))),
tuple(all_items)) tuple(all_items))
return stock_items return stock_items
def get_return_doc_and_details(args): def get_return_doc_and_details(args):
ref = frappe._dict() ref = frappe._dict()
# get ref_doc # get ref_doc
if args.get("purpose") in return_map: if args.get("purpose") in return_map:
for fieldname, val in return_map[args.get("purpose")].items(): for fieldname, val in return_map[args.get("purpose")].items():
if args.get(fieldname): if args.get(fieldname):
@ -728,9 +728,9 @@ def get_return_doc_and_details(args):
ref.doc = frappe.get_doc(val[0], args.get(fieldname)) ref.doc = frappe.get_doc(val[0], args.get(fieldname))
ref.parentfields = val[1] ref.parentfields = val[1]
break break
return ref return ref
return_map = { return_map = {
"Sales Return": { "Sales Return": {
# [Ref DocType, [Item tables' parentfields]] # [Ref DocType, [Item tables' parentfields]]
@ -747,29 +747,28 @@ def make_return_jv(stock_entry):
se = frappe.get_doc("Stock Entry", stock_entry) se = frappe.get_doc("Stock Entry", stock_entry)
if not se.purpose in ["Sales Return", "Purchase Return"]: if not se.purpose in ["Sales Return", "Purchase Return"]:
return return
ref = get_return_doc_and_details(se) ref = get_return_doc_and_details(se)
if ref.doc.doctype == "Delivery Note": if ref.doc.doctype == "Delivery Note":
result = make_return_jv_from_delivery_note(se, ref) result = make_return_jv_from_delivery_note(se, ref)
elif ref.doc.doctype == "Sales Invoice": elif ref.doc.doctype == "Sales Invoice":
result = make_return_jv_from_sales_invoice(se, ref) result = make_return_jv_from_sales_invoice(se, ref)
elif ref.doc.doctype == "Purchase Receipt": elif ref.doc.doctype == "Purchase Receipt":
result = make_return_jv_from_purchase_receipt(se, ref) result = make_return_jv_from_purchase_receipt(se, ref)
# create jv doc and fetch balance for each unique row item # create jv doc and fetch balance for each unique row item
jv_list = [{ jv = frappe.new_doc("Journal Voucher")
"__islocal": 1, jv.update({
"doctype": "Journal Voucher",
"posting_date": se.posting_date, "posting_date": se.posting_date,
"voucher_type": se.purpose == "Sales Return" and "Credit Note" or "Debit Note", "voucher_type": se.purpose == "Sales Return" and "Credit Note" or "Debit Note",
"fiscal_year": se.fiscal_year, "fiscal_year": se.fiscal_year,
"company": se.company "company": se.company
}] })
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
for r in result: for r in result:
jv_list.append({ jv.append("entries", {
"__islocal": 1, "__islocal": 1,
"doctype": "Journal Voucher Detail", "doctype": "Journal Voucher Detail",
"parentfield": "entries", "parentfield": "entries",
@ -779,139 +778,140 @@ def make_return_jv(stock_entry):
"balance": get_balance_on(r.get("account"), se.posting_date) \ "balance": get_balance_on(r.get("account"), se.posting_date) \
if r.get("account") else 0 if r.get("account") else 0
}) })
return jv_list return jv
def make_return_jv_from_sales_invoice(se, ref): def make_return_jv_from_sales_invoice(se, ref):
# customer account entry # customer account entry
parent = { parent = {
"account": ref.doc.debit_to, "account": ref.doc.debit_to,
"against_invoice": ref.doc.name, "against_invoice": ref.doc.name,
} }
# income account entries # income account entries
children = [] children = []
for se_item in se.get("mtn_details"): for se_item in se.get("mtn_details"):
# find item in ref.doc # find item in ref.doc
ref_item = ref.doc.get({"item_code": se_item.item_code})[0] ref_item = ref.doc.get({"item_code": se_item.item_code})[0]
account = get_sales_account_from_item(ref.doc, ref_item) account = get_sales_account_from_item(ref.doc, ref_item)
if account not in children: if account not in children:
children.append(account) children.append(account)
return [parent] + [{"account": account} for account in children] return [parent] + [{"account": account} for account in children]
def get_sales_account_from_item(doc, ref_item): def get_sales_account_from_item(doc, ref_item):
account = None account = None
if not ref_item.income_account: if not getattr(ref_item, "income_account", None):
if ref_item.parent_item: if ref_item.parent_item:
parent_item = doc.get({"item_code": ref_item.parent_item})[0] parent_item = doc.get(doc.fname, {"item_code": ref_item.parent_item})[0]
account = parent_item.income_account account = parent_item.income_account
else: else:
account = ref_item.income_account account = ref_item.income_account
return account return account
def make_return_jv_from_delivery_note(se, ref): def make_return_jv_from_delivery_note(se, ref):
invoices_against_delivery = get_invoice_list("Sales Invoice Item", "delivery_note", invoices_against_delivery = get_invoice_list("Sales Invoice Item", "delivery_note",
ref.doc.name) ref.doc.name)
if not invoices_against_delivery: if not invoices_against_delivery:
sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if d.against_sales_order] sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if getattr(d, "against_sales_order", None)]
if sales_orders_against_delivery: if sales_orders_against_delivery:
invoices_against_delivery = get_invoice_list("Sales Invoice Item", "sales_order", invoices_against_delivery = get_invoice_list("Sales Invoice Item", "sales_order",
sales_orders_against_delivery) sales_orders_against_delivery)
if not invoices_against_delivery: if not invoices_against_delivery:
return [] return []
packing_item_parent_map = dict([[d.item_code, d.parent_item] for d in ref.doc.get(ref.parentfields[1])]) packing_item_parent_map = dict([[d.item_code, d.parent_item] for d in ref.doc.get(ref.parentfields[1])])
parent = {} parent = {}
children = [] children = []
for se_item in se.get("mtn_details"): for se_item in se.get("mtn_details"):
for sales_invoice in invoices_against_delivery: for sales_invoice in invoices_against_delivery:
si = frappe.get_doc("Sales Invoice", sales_invoice) si = frappe.get_doc("Sales Invoice", sales_invoice)
if se_item.item_code in packing_item_parent_map: if se_item.item_code in packing_item_parent_map:
ref_item = si.get({"item_code": packing_item_parent_map[se_item.item_code]}) ref_item = si.get({"item_code": packing_item_parent_map[se_item.item_code]})
else: else:
ref_item = si.get({"item_code": se_item.item_code}) ref_item = si.get({"item_code": se_item.item_code})
if not ref_item: if not ref_item:
continue continue
ref_item = ref_item[0] ref_item = ref_item[0]
account = get_sales_account_from_item(si, ref_item) account = get_sales_account_from_item(si, ref_item)
if account not in children: if account not in children:
children.append(account) children.append(account)
if not parent: if not parent:
parent = {"account": si.debit_to} parent = {"account": si.debit_to}
break break
if len(invoices_against_delivery) == 1: if len(invoices_against_delivery) == 1:
parent["against_invoice"] = invoices_against_delivery[0] parent["against_invoice"] = invoices_against_delivery[0]
result = [parent] + [{"account": account} for account in children] result = [parent] + [{"account": account} for account in children]
return result return result
def get_invoice_list(doctype, link_field, value): def get_invoice_list(doctype, link_field, value):
if isinstance(value, basestring): if isinstance(value, basestring):
value = [value] value = [value]
return frappe.db.sql_list("""select distinct parent from `tab%s` return frappe.db.sql_list("""select distinct parent from `tab%s`
where docstatus = 1 and `%s` in (%s)""" % (doctype, link_field, where docstatus = 1 and `%s` in (%s)""" % (doctype, link_field,
", ".join(["%s"]*len(value))), tuple(value)) ", ".join(["%s"]*len(value))), tuple(value))
def make_return_jv_from_purchase_receipt(se, ref): def make_return_jv_from_purchase_receipt(se, ref):
invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_receipt", invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_receipt",
ref.doc.name) ref.doc.name)
if not invoice_against_receipt: if not invoice_against_receipt:
purchase_orders_against_receipt = [d.prevdoc_docname for d in purchase_orders_against_receipt = [d.prevdoc_docname for d in
ref.get({"prevdoc_doctype": "Purchase Order"}) if d.prevdoc_docname] ref.doc.get(ref.doc.fname, {"prevdoc_doctype": "Purchase Order"})
if getattr(d, "prevdoc_docname", None)]
if purchase_orders_against_receipt: if purchase_orders_against_receipt:
invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_order", invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_order",
purchase_orders_against_receipt) purchase_orders_against_receipt)
if not invoice_against_receipt: if not invoice_against_receipt:
return [] return []
parent = {} parent = {}
children = [] children = []
for se_item in se.get("mtn_details"): for se_item in se.get("mtn_details"):
for purchase_invoice in invoice_against_receipt: for purchase_invoice in invoice_against_receipt:
pi = frappe.get_doc("Purchase Invoice", purchase_invoice) pi = frappe.get_doc("Purchase Invoice", purchase_invoice)
ref_item = pi.get({"item_code": se_item.item_code}) ref_item = pi.get({"item_code": se_item.item_code})
if not ref_item: if not ref_item:
continue continue
ref_item = ref_item[0] ref_item = ref_item[0]
account = ref_item.expense_account account = ref_item.expense_account
if account not in children: if account not in children:
children.append(account) children.append(account)
if not parent: if not parent:
parent = {"account": pi.credit_to} parent = {"account": pi.credit_to}
break break
if len(invoice_against_receipt) == 1: if len(invoice_against_receipt) == 1:
parent["against_voucher"] = invoice_against_receipt[0] parent["against_voucher"] = invoice_against_receipt[0]
result = [parent] + [{"account": account} for account in children] result = [parent] + [{"account": account} for account in children]
return result return result

View File

@ -3,6 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, unittest import frappe, unittest
import frappe.defaults
from frappe.utils import flt, getdate from frappe.utils import flt, getdate
from erpnext.stock.doctype.serial_no.serial_no import * from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
@ -31,7 +32,6 @@ class TestStockEntry(unittest.TestCase):
st2.submit() st2.submit()
from erpnext.stock.utils import reorder_item from erpnext.stock.utils import reorder_item
reorder_item() reorder_item()
mr_name = frappe.db.sql("""select parent from `tabMaterial Request Item` mr_name = frappe.db.sql("""select parent from `tabMaterial Request Item`
@ -39,8 +39,6 @@ class TestStockEntry(unittest.TestCase):
self.assertTrue(mr_name) self.assertTrue(mr_name)
frappe.db.set_default("company", self.old_default_company)
def test_material_receipt_gl_entry(self): def test_material_receipt_gl_entry(self):
self._clear_stock_account_balance() self._clear_stock_account_balance()
set_perpetual_inventory() set_perpetual_inventory()
@ -367,14 +365,14 @@ class TestStockEntry(unittest.TestCase):
def _test_sales_return_jv(self, se): def _test_sales_return_jv(self, se):
from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv
jv_list = make_return_jv(se.name) jv = make_return_jv(se.name)
self.assertEqual(len(jv_list), 3) self.assertEqual(len(jv.get("entries")), 2)
self.assertEqual(jv_list[0].get("voucher_type"), "Credit Note") self.assertEqual(jv.get("voucher_type"), "Credit Note")
self.assertEqual(jv_list[0].get("posting_date"), se.posting_date) self.assertEqual(jv.get("posting_date"), se.posting_date)
self.assertEqual(jv_list[1].get("account"), "_Test Customer - _TC") self.assertEqual(jv.get("entries")[0].get("account"), "_Test Customer - _TC")
self.assertEqual(jv_list[2].get("account"), "Sales - _TC") self.assertEqual(jv.get("entries")[1].get("account"), "Sales - _TC")
self.assertTrue(jv_list[1].get("against_invoice")) self.assertTrue(jv.get("entries")[0].get("against_invoice"))
def test_make_return_jv_for_sales_invoice_non_packing_item(self): def test_make_return_jv_for_sales_invoice_non_packing_item(self):
self._clear_stock_account_balance() self._clear_stock_account_balance()
@ -527,14 +525,14 @@ class TestStockEntry(unittest.TestCase):
def _test_purchase_return_jv(self, se): def _test_purchase_return_jv(self, se):
from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv
jv_list = make_return_jv(se.name) jv = make_return_jv(se.name)
self.assertEqual(len(jv_list), 3) self.assertEqual(len(jv.get("entries")), 2)
self.assertEqual(jv_list[0].get("voucher_type"), "Debit Note") self.assertEqual(jv.get("voucher_type"), "Debit Note")
self.assertEqual(jv_list[0].get("posting_date"), se.posting_date) self.assertEqual(jv.get("posting_date"), se.posting_date)
self.assertEqual(jv_list[1].get("account"), "_Test Supplier - _TC") self.assertEqual(jv.get("entries")[0].get("account"), "_Test Supplier - _TC")
self.assertEqual(jv_list[2].get("account"), "_Test Account Cost for Goods Sold - _TC") self.assertEqual(jv.get("entries")[1].get("account"), "_Test Account Cost for Goods Sold - _TC")
self.assertTrue(jv_list[1].get("against_voucher")) self.assertTrue(jv.get("entries")[0].get("against_voucher"))
def test_make_return_jv_for_purchase_receipt(self): def test_make_return_jv_for_purchase_receipt(self):
self._clear_stock_account_balance() self._clear_stock_account_balance()
@ -774,10 +772,9 @@ class TestStockEntry(unittest.TestCase):
# permission tests # permission tests
def test_warehouse_user(self): def test_warehouse_user(self):
import frappe.defaults
set_perpetual_inventory(0) set_perpetual_inventory(0)
frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", "Restriction") frappe.defaults.add_default("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com", "Restriction")
frappe.defaults.add_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", "Restriction") frappe.defaults.add_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", "Restriction")
frappe.get_doc("User", "test@example.com")\ frappe.get_doc("User", "test@example.com")\
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager") .add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
@ -797,15 +794,17 @@ class TestStockEntry(unittest.TestCase):
st1.insert() st1.insert()
st1.submit() st1.submit()
frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC1", "test@example.com", parenttype="Restriction") frappe.defaults.clear_default("Warehouse", "_Test Warehouse 1 - _TC",
frappe.defaults.clear_default("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com", parenttype="Restriction") "test@example.com", parenttype="Restriction")
frappe.defaults.clear_default("Warehouse", "_Test Warehouse 2 - _TC1",
"test2@example.com", parenttype="Restriction")
def test_freeze_stocks (self): def test_freeze_stocks (self):
self._clear_stock_account_balance() self._clear_stock_account_balance()
frappe.db.set_value('Stock Settings', None,'stock_auth_role', '') frappe.db.set_value('Stock Settings', None,'stock_auth_role', '')
# test freeze_stocks_upto # test freeze_stocks_upto
date_newer_than_test_records = add_days(getdate(test_records[0][0]['posting_date']), 5) date_newer_than_test_records = add_days(getdate(test_records[0]['posting_date']), 5)
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", date_newer_than_test_records) frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", date_newer_than_test_records)
se = frappe.copy_doc(test_records[0]).insert() se = frappe.copy_doc(test_records[0]).insert()
self.assertRaises (StockFreezeError, se.submit) self.assertRaises (StockFreezeError, se.submit)

View File

@ -19,15 +19,15 @@ class StockLedgerEntry(DocListController):
self.validate_item() self.validate_item()
validate_warehouse_company(self.warehouse, self.company) validate_warehouse_company(self.warehouse, self.company)
self.scrub_posting_time() self.scrub_posting_time()
from erpnext.accounts.utils import validate_fiscal_year from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.posting_date, self.fiscal_year, validate_fiscal_year(self.posting_date, self.fiscal_year,
self.meta.get_label("posting_date")) self.meta.get_label("posting_date"))
def on_submit(self): def on_submit(self):
self.check_stock_frozen_date() self.check_stock_frozen_date()
self.actual_amt_check() self.actual_amt_check()
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
process_serial_no(self) process_serial_no(self)
@ -58,7 +58,7 @@ class StockLedgerEntry(DocListController):
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1) msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
elif k == 'warehouse': elif k == 'warehouse':
if not frappe.db.exists("Warehouse", self.get(k)): if not frappe.db.exists("Warehouse", self.get(k)):
msgprint("Warehouse: '%s' does not exist in the system. Please check." % msgprint("Warehouse: '%s' does not exist in the system. Please check." %
self.get(k), raise_exception = 1) self.get(k), raise_exception = 1)
def validate_item(self): def validate_item(self):
@ -76,9 +76,9 @@ class StockLedgerEntry(DocListController):
frappe.throw("Batch number is mandatory for Item '%s'" % self.item_code) frappe.throw("Batch number is mandatory for Item '%s'" % self.item_code)
# check if batch belongs to item # check if batch belongs to item
if not frappe.db.get_value("Batch", if not frappe.db.get_value("Batch",
{"item": self.item_code, "name": self.batch_no}): {"item": self.item_code, "name": self.batch_no}):
frappe.throw("'%s' is not a valid Batch Number for Item '%s'" % frappe.throw("'%s' is not a valid Batch Number for Item '%s'" %
(self.batch_no, self.item_code)) (self.batch_no, self.item_code))
if not self.stock_uom: if not self.stock_uom:
@ -108,4 +108,4 @@ def on_doctype_update():
where Key_name="posting_sort_index" """): where Key_name="posting_sort_index" """):
frappe.db.commit() frappe.db.commit()
frappe.db.sql("""alter table `tabStock Ledger Entry` frappe.db.sql("""alter table `tabStock Ledger Entry`
add index posting_sort_index(posting_date, posting_time, name)""") add index posting_sort_index(posting_date, posting_time, name)""")

View File

@ -1,25 +1,25 @@
[ [
{ {
"company": "_Test Company", "company": "_Test Company",
"create_account_under": "Stock Assets - _TC", "create_account_under": "Stock Assets - _TC",
"doctype": "Warehouse", "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse" "warehouse_name": "_Test Warehouse"
}, },
{ {
"company": "_Test Company", "company": "_Test Company",
"create_account_under": "Fixed Assets - _TC", "create_account_under": "Fixed Assets - _TC",
"doctype": "Warehouse", "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse 1" "warehouse_name": "_Test Warehouse 1"
}, },
{ {
"company": "_Test Company 1", "company": "_Test Company 1",
"create_account_under": "Stock Assets - _TC", "create_account_under": "Stock Assets - _TC",
"doctype": "Warehouse", "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse 2" "warehouse_name": "_Test Warehouse 2"
}, },
{ {
"company": "_Test Company", "company": "_Test Company",
"doctype": "Warehouse", "doctype": "Warehouse",
"warehouse_name": "_Test Warehouse No Account" "warehouse_name": "_Test Warehouse No Account"
} }
] ]

View File

@ -169,9 +169,13 @@ def get_sle_before_datetime(args, for_update=False):
def get_sle_after_datetime(args, for_update=False): def get_sle_after_datetime(args, for_update=False):
"""get Stock Ledger Entries after a particular datetime, for reposting""" """get Stock Ledger Entries after a particular datetime, for reposting"""
# NOTE: using for update of # NOTE: using for update of
return get_stock_ledger_entries(args, conditions = ["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"]
["timestamp(posting_date, posting_time) > timestamp(%(posting_date)s, %(posting_time)s)"],
"asc", for_update=for_update) # Excluding name: Workaround for MariaDB timestamp() floating microsecond issue
if args.get("name"):
conditions.append("name!=%(name)s")
return get_stock_ledger_entries(args, conditions, "asc", for_update=for_update)
def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, for_update=False): def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, for_update=False):
"""get stock ledger entries filtered by specific posting datetime conditions""" """get stock ledger entries filtered by specific posting datetime conditions"""
@ -180,7 +184,7 @@ def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, fo
if not args.get("posting_time"): if not args.get("posting_time"):
args["posting_time"] = "00:00" args["posting_time"] = "00:00"
return frappe.db.sql("""select * from `tabStock Ledger Entry` return frappe.db.sql("""select *, timestamp(posting_date, posting_time) as "timestamp" from `tabStock Ledger Entry`
where item_code = %%(item_code)s where item_code = %%(item_code)s
and warehouse = %%(warehouse)s and warehouse = %%(warehouse)s
and ifnull(is_cancelled, 'No')='No' and ifnull(is_cancelled, 'No')='No'

View File

@ -9,34 +9,34 @@ from frappe.defaults import get_global_default
from frappe.utils.email_lib import sendmail from frappe.utils.email_lib import sendmail
class InvalidWarehouseCompany(frappe.ValidationError): pass class InvalidWarehouseCompany(frappe.ValidationError): pass
def get_stock_balance_on(warehouse, posting_date=None): def get_stock_balance_on(warehouse, posting_date=None):
if not posting_date: posting_date = nowdate() if not posting_date: posting_date = nowdate()
stock_ledger_entries = frappe.db.sql(""" stock_ledger_entries = frappe.db.sql("""
SELECT SELECT
item_code, stock_value item_code, stock_value
FROM FROM
`tabStock Ledger Entry` `tabStock Ledger Entry`
WHERE WHERE
warehouse=%s AND posting_date <= %s warehouse=%s AND posting_date <= %s
ORDER BY timestamp(posting_date, posting_time) DESC, name DESC ORDER BY timestamp(posting_date, posting_time) DESC, name DESC
""", (warehouse, posting_date), as_dict=1) """, (warehouse, posting_date), as_dict=1)
sle_map = {} sle_map = {}
for sle in stock_ledger_entries: for sle in stock_ledger_entries:
sle_map.setdefault(sle.item_code, flt(sle.stock_value)) sle_map.setdefault(sle.item_code, flt(sle.stock_value))
return sum(sle_map.values()) return sum(sle_map.values())
def get_latest_stock_balance(): def get_latest_stock_balance():
bin_map = {} bin_map = {}
for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value for d in frappe.db.sql("""SELECT item_code, warehouse, stock_value as stock_value
FROM tabBin""", as_dict=1): FROM tabBin""", as_dict=1):
bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value)) bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value))
return bin_map return bin_map
def get_bin(item_code, warehouse): def get_bin(item_code, warehouse):
bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}) bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin: if not bin:
@ -58,18 +58,18 @@ def update_bin(args):
bin.update_stock(args) bin.update_stock(args)
return bin return bin
else: else:
msgprint("[Stock Update] Ignored %s since it is not a stock item" msgprint("[Stock Update] Ignored %s since it is not a stock item"
% args.get("item_code")) % args.get("item_code"))
def get_incoming_rate(args): def get_incoming_rate(args):
"""Get Incoming Rate based on valuation method""" """Get Incoming Rate based on valuation method"""
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
in_rate = 0 in_rate = 0
if args.get("serial_no"): if args.get("serial_no"):
in_rate = get_avg_purchase_rate(args.get("serial_no")) in_rate = get_avg_purchase_rate(args.get("serial_no"))
elif args.get("bom_no"): elif args.get("bom_no"):
result = frappe.db.sql("""select ifnull(total_cost, 0) / ifnull(quantity, 1) result = frappe.db.sql("""select ifnull(total_cost, 0) / ifnull(quantity, 1)
from `tabBOM` where name = %s and docstatus=1 and is_active=1""", args.get("bom_no")) from `tabBOM` where name = %s and docstatus=1 and is_active=1""", args.get("bom_no"))
in_rate = result and flt(result[0][0]) or 0 in_rate = result and flt(result[0][0]) or 0
else: else:
@ -84,12 +84,12 @@ def get_incoming_rate(args):
elif valuation_method == 'Moving Average': elif valuation_method == 'Moving Average':
in_rate = previous_sle.get('valuation_rate') or 0 in_rate = previous_sle.get('valuation_rate') or 0
return in_rate return in_rate
def get_avg_purchase_rate(serial_nos): def get_avg_purchase_rate(serial_nos):
"""get average value of serial numbers""" """get average value of serial numbers"""
serial_nos = get_valid_serial_nos(serial_nos) serial_nos = get_valid_serial_nos(serial_nos)
return flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) from `tabSerial No` return flt(frappe.db.sql("""select avg(ifnull(purchase_rate, 0)) from `tabSerial No`
where name in (%s)""" % ", ".join(["%s"] * len(serial_nos)), where name in (%s)""" % ", ".join(["%s"] * len(serial_nos)),
tuple(serial_nos))[0][0]) tuple(serial_nos))[0][0])
@ -99,11 +99,11 @@ def get_valuation_method(item_code):
if not val_method: if not val_method:
val_method = get_global_default('valuation_method') or "FIFO" val_method = get_global_default('valuation_method') or "FIFO"
return val_method return val_method
def get_fifo_rate(previous_stock_queue, qty): def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue""" """get FIFO (average) Rate from Queue"""
if qty >= 0: if qty >= 0:
total = sum(f[0] for f in previous_stock_queue) total = sum(f[0] for f in previous_stock_queue)
return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0 return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0
else: else:
outgoing_cost = 0 outgoing_cost = 0
@ -123,12 +123,12 @@ def get_fifo_rate(previous_stock_queue, qty):
qty_to_pop = 0 qty_to_pop = 0
# if queue gets blank and qty_to_pop remaining, get average rate of full queue # if queue gets blank and qty_to_pop remaining, get average rate of full queue
return outgoing_cost / abs(qty) - qty_to_pop return outgoing_cost / abs(qty) - qty_to_pop
def get_valid_serial_nos(sr_nos, qty=0, item_code=''): def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
"""split serial nos, validate and return list of valid serial nos""" """split serial nos, validate and return list of valid serial nos"""
# TODO: remove duplicates in client side # TODO: remove duplicates in client side
serial_nos = cstr(sr_nos).strip().replace(',', '\n').split('\n') serial_nos = cstr(sr_nos).strip().replace(',', '\n').split('\n')
valid_serial_nos = [] valid_serial_nos = []
for val in serial_nos: for val in serial_nos:
if val: if val:
@ -137,12 +137,12 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
msgprint("You have entered duplicate serial no: '%s'" % val, raise_exception=1) msgprint("You have entered duplicate serial no: '%s'" % val, raise_exception=1)
else: else:
valid_serial_nos.append(val) valid_serial_nos.append(val)
if qty and len(valid_serial_nos) != abs(qty): if qty and len(valid_serial_nos) != abs(qty):
msgprint("Please enter serial nos for " msgprint("Please enter serial nos for "
+ cstr(abs(qty)) + " quantity against item code: " + item_code, + cstr(abs(qty)) + " quantity against item code: " + item_code,
raise_exception=1) raise_exception=1)
return valid_serial_nos return valid_serial_nos
def validate_warehouse_company(warehouse, company): def validate_warehouse_company(warehouse, company):
@ -151,48 +151,48 @@ def validate_warehouse_company(warehouse, company):
frappe.msgprint(_("Warehouse does not belong to company.") + " (" + \ frappe.msgprint(_("Warehouse does not belong to company.") + " (" + \
warehouse + ", " + company +")", raise_exception=InvalidWarehouseCompany) warehouse + ", " + company +")", raise_exception=InvalidWarehouseCompany)
def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no, def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,
stock_ledger_entries, item_sales_bom): stock_ledger_entries, item_sales_bom):
# sales bom item # sales bom item
buying_amount = 0.0 buying_amount = 0.0
for bom_item in item_sales_bom[item_code]: for bom_item in item_sales_bom[item_code]:
if bom_item.get("parent_detail_docname")==voucher_detail_no: if bom_item.get("parent_detail_docname")==voucher_detail_no:
buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no, buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,
stock_ledger_entries.get((bom_item.item_code, warehouse), [])) stock_ledger_entries.get((bom_item.item_code, warehouse), []))
return buying_amount return buying_amount
def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries): def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries):
# IMP NOTE # IMP NOTE
# stock_ledger_entries should already be filtered by item_code and warehouse and # stock_ledger_entries should already be filtered by item_code and warehouse and
# sorted by posting_date desc, posting_time desc # sorted by posting_date desc, posting_time desc
for i, sle in enumerate(stock_ledger_entries): for i, sle in enumerate(stock_ledger_entries):
if sle.voucher_type == voucher_type and sle.voucher_no == voucher_no and \ if sle.voucher_type == voucher_type and sle.voucher_no == voucher_no and \
sle.voucher_detail_no == item_row: sle.voucher_detail_no == item_row:
previous_stock_value = len(stock_ledger_entries) > i+1 and \ previous_stock_value = len(stock_ledger_entries) > i+1 and \
flt(stock_ledger_entries[i+1].stock_value) or 0.0 flt(stock_ledger_entries[i+1].stock_value) or 0.0
buying_amount = previous_stock_value - flt(sle.stock_value) buying_amount = previous_stock_value - flt(sle.stock_value)
return buying_amount return buying_amount
return 0.0 return 0.0
def reorder_item(): def reorder_item():
""" Reorder item if stock reaches reorder level""" """ Reorder item if stock reaches reorder level"""
if getattr(frappe.local, "auto_indent", None) is None: if getattr(frappe.local, "auto_indent", None) is None:
frappe.local.auto_indent = cint(frappe.db.get_value('Stock Settings', None, 'auto_indent')) frappe.local.auto_indent = cint(frappe.db.get_value('Stock Settings', None, 'auto_indent'))
if frappe.local.auto_indent: if frappe.local.auto_indent:
material_requests = {} material_requests = {}
bin_list = frappe.db.sql("""select item_code, warehouse, projected_qty bin_list = frappe.db.sql("""select item_code, warehouse, projected_qty
from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != '' from tabBin where ifnull(item_code, '') != '' and ifnull(warehouse, '') != ''
and exists (select name from `tabItem` and exists (select name from `tabItem`
where `tabItem`.name = `tabBin`.item_code and where `tabItem`.name = `tabBin`.item_code and
is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and
(ifnull(end_of_life, '')='' or end_of_life > now()))""", as_dict=True) (ifnull(end_of_life, '')='' or end_of_life > curdate()))""", as_dict=True)
for bin in bin_list: for bin in bin_list:
#check if re-order is required #check if re-order is required
item_reorder = frappe.db.get("Item Reorder", item_reorder = frappe.db.get("Item Reorder",
{"parent": bin.item_code, "warehouse": bin.warehouse}) {"parent": bin.item_code, "warehouse": bin.warehouse})
if item_reorder: if item_reorder:
reorder_level = item_reorder.warehouse_reorder_level reorder_level = item_reorder.warehouse_reorder_level
@ -202,15 +202,15 @@ def reorder_item():
reorder_level, reorder_qty = frappe.db.get_value("Item", bin.item_code, reorder_level, reorder_qty = frappe.db.get_value("Item", bin.item_code,
["re_order_level", "re_order_qty"]) ["re_order_level", "re_order_qty"])
material_request_type = "Purchase" material_request_type = "Purchase"
if flt(reorder_level) and flt(bin.projected_qty) < flt(reorder_level): if flt(reorder_level) and flt(bin.projected_qty) < flt(reorder_level):
if flt(reorder_level) - flt(bin.projected_qty) > flt(reorder_qty): if flt(reorder_level) - flt(bin.projected_qty) > flt(reorder_qty):
reorder_qty = flt(reorder_level) - flt(bin.projected_qty) reorder_qty = flt(reorder_level) - flt(bin.projected_qty)
company = frappe.db.get_value("Warehouse", bin.warehouse, "company") or \ company = frappe.db.get_value("Warehouse", bin.warehouse, "company") or \
frappe.defaults.get_defaults()["company"] or \ frappe.defaults.get_defaults()["company"] or \
frappe.db.sql("""select name from tabCompany limit 1""")[0][0] frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
material_requests.setdefault(material_request_type, frappe._dict()).setdefault( material_requests.setdefault(material_request_type, frappe._dict()).setdefault(
company, []).append(frappe._dict({ company, []).append(frappe._dict({
"item_code": bin.item_code, "item_code": bin.item_code,
@ -218,7 +218,7 @@ def reorder_item():
"reorder_qty": reorder_qty "reorder_qty": reorder_qty
}) })
) )
create_material_request(material_requests) create_material_request(material_requests)
def create_material_request(material_requests): def create_material_request(material_requests):
@ -234,21 +234,19 @@ def create_material_request(material_requests):
items = material_requests[request_type][company] items = material_requests[request_type][company]
if not items: if not items:
continue continue
mr = [{ mr = frappe.new_doc("Material Request")
"doctype": "Material Request", mr.update({
"company": company, "company": company,
"fiscal_year": current_fiscal_year, "fiscal_year": current_fiscal_year,
"transaction_date": nowdate(), "transaction_date": nowdate(),
"material_request_type": request_type "material_request_type": request_type
}] })
for d in items: for d in items:
item = frappe.get_doc("Item", d.item_code) item = frappe.get_doc("Item", d.item_code)
mr.append({ mr.append("indent_details", {
"doctype": "Material Request Item", "doctype": "Material Request Item",
"parenttype": "Material Request",
"parentfield": "indent_details",
"item_code": d.item_code, "item_code": d.item_code,
"schedule_date": add_days(nowdate(),cint(item.lead_time_days)), "schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
"uom": item.stock_uom, "uom": item.stock_uom,
@ -259,11 +257,10 @@ def create_material_request(material_requests):
"qty": d.reorder_qty, "qty": d.reorder_qty,
"brand": item.brand, "brand": item.brand,
}) })
mr_doc = frappe.get_doc(mr) mr.insert()
mr_doc.insert() mr.submit()
mr_doc.submit() mr_list.append(mr)
mr_list.append(mr_doc)
except: except:
if frappe.local.message_log: if frappe.local.message_log:
@ -274,24 +271,24 @@ def create_material_request(material_requests):
if mr_list: if mr_list:
if getattr(frappe.local, "reorder_email_notify", None) is None: if getattr(frappe.local, "reorder_email_notify", None) is None:
frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None, frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None,
'reorder_email_notify')) 'reorder_email_notify'))
if(frappe.local.reorder_email_notify): if(frappe.local.reorder_email_notify):
send_email_notification(mr_list) send_email_notification(mr_list)
if exceptions_list: if exceptions_list:
notify_errors(exceptions_list) notify_errors(exceptions_list)
def send_email_notification(mr_list): def send_email_notification(mr_list):
""" Notify user about auto creation of indent""" """ Notify user about auto creation of indent"""
email_list = frappe.db.sql_list("""select distinct r.parent email_list = frappe.db.sql_list("""select distinct r.parent
from tabUserRole r, tabUser p from tabUserRole r, tabUser p
where p.name = r.parent and p.enabled = 1 and p.docstatus < 2 where p.name = r.parent and p.enabled = 1 and p.docstatus < 2
and r.role in ('Purchase Manager','Material Manager') and r.role in ('Purchase Manager','Material Manager')
and p.name not in ('Administrator', 'All', 'Guest')""") and p.name not in ('Administrator', 'All', 'Guest')""")
msg="""<h3>Following Material Requests has been raised automatically \ msg="""<h3>Following Material Requests has been raised automatically \
based on item reorder level:</h3>""" based on item reorder level:</h3>"""
for mr in mr_list: for mr in mr_list:
@ -302,13 +299,13 @@ def send_email_notification(mr_list):
cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>" cstr(item.qty) + "</td><td>" + cstr(item.uom) + "</td></tr>"
msg += "</table>" msg += "</table>"
sendmail(email_list, subject='Auto Material Request Generation Notification', msg = msg) sendmail(email_list, subject='Auto Material Request Generation Notification', msg = msg)
def notify_errors(exceptions_list): def notify_errors(exceptions_list):
subject = "[Important] [ERPNext] Error(s) while creating Material Requests based on Re-order Levels" subject = "[Important] [ERPNext] Error(s) while creating Material Requests based on Re-order Levels"
msg = """Dear System Manager, msg = """Dear System Manager,
An error occured for certain Items while creating Material Requests based on Re-order level. An error occured for certain Items while creating Material Requests based on Re-order level.
Please rectify these issues: Please rectify these issues:
--- ---