Fixed Stock Entry Test Cases frappe/frappe#478
This commit is contained in:
parent
103cc58cb6
commit
2ce39cf770
@ -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"))
|
||||||
}
|
}
|
||||||
|
@ -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)})
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)""")
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -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'
|
||||||
|
@ -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:
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user