mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-18 12:39:18 +00:00
Merge remote-tracking branch 'webnotes/4.0.0-wip' into permissions
Conflicts: erpnext/accounts/page/accounts_browser/accounts_browser.css erpnext/controllers/buying_controller.py erpnext/manufacturing/doctype/production_order/production_order.py erpnext/patches/patch_list.py erpnext/selling/doctype/customer/customer.txt erpnext/selling/doctype/sales_order/sales_order.py erpnext/selling/doctype/sales_order/test_sales_order.py erpnext/setup/doctype/features_setup/features_setup.txt erpnext/stock/doctype/stock_entry/test_stock_entry.py erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py startup/query_handlers.py
This commit is contained in:
0
erpnext/controllers/__init__.py
Normal file
0
erpnext/controllers/__init__.py
Normal file
439
erpnext/controllers/accounts_controller.py
Normal file
439
erpnext/controllers/accounts_controller.py
Normal file
@@ -0,0 +1,439 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes import _, msgprint
|
||||
from webnotes.utils import flt, cint, today, cstr
|
||||
from webnotes.model.code import get_obj
|
||||
from erpnext.setup.utils import get_company_currency
|
||||
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
|
||||
from erpnext.utilities.transaction_base import TransactionBase, validate_conversion_rate
|
||||
import json
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
def validate(self):
|
||||
self.set_missing_values(for_validate=True)
|
||||
self.validate_date_with_fiscal_year()
|
||||
if self.meta.get_field("currency"):
|
||||
self.calculate_taxes_and_totals()
|
||||
self.validate_value("grand_total", ">=", 0)
|
||||
self.set_total_in_words()
|
||||
|
||||
self.validate_for_freezed_account()
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
for fieldname in ["posting_date", "transaction_date"]:
|
||||
if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname):
|
||||
self.doc.fields[fieldname] = today()
|
||||
if not self.doc.fiscal_year:
|
||||
self.doc.fiscal_year = get_fiscal_year(self.doc.fields[fieldname])[0]
|
||||
|
||||
def validate_date_with_fiscal_year(self):
|
||||
if self.meta.get_field("fiscal_year") :
|
||||
date_field = ""
|
||||
if self.meta.get_field("posting_date"):
|
||||
date_field = "posting_date"
|
||||
elif self.meta.get_field("transaction_date"):
|
||||
date_field = "transaction_date"
|
||||
|
||||
if date_field and self.doc.fields[date_field]:
|
||||
validate_fiscal_year(self.doc.fields[date_field], self.doc.fiscal_year,
|
||||
label=self.meta.get_label(date_field))
|
||||
|
||||
def validate_for_freezed_account(self):
|
||||
for fieldname in ["customer", "supplier"]:
|
||||
if self.meta.get_field(fieldname) and self.doc.fields.get(fieldname):
|
||||
accounts = webnotes.conn.get_values("Account", {"master_type": fieldname.title(),
|
||||
"master_name": self.doc.fields[fieldname], "company": self.doc.company},
|
||||
"freeze_account", as_dict=1)
|
||||
|
||||
if accounts:
|
||||
if not filter(lambda x: cstr(x.freeze_account) in ["", "No"], accounts):
|
||||
msgprint(_("Account for this ") + fieldname + _(" has been freezed. ") +
|
||||
self.doc.doctype + _(" can not be made."), raise_exception=1)
|
||||
|
||||
def set_price_list_currency(self, buying_or_selling):
|
||||
if self.meta.get_field("currency"):
|
||||
company_currency = get_company_currency(self.doc.company)
|
||||
|
||||
# price list part
|
||||
fieldname = "selling_price_list" if buying_or_selling.lower() == "selling" \
|
||||
else "buying_price_list"
|
||||
if self.meta.get_field(fieldname) and self.doc.fields.get(fieldname):
|
||||
self.doc.price_list_currency = webnotes.conn.get_value("Price List",
|
||||
self.doc.fields.get(fieldname), "currency")
|
||||
|
||||
if self.doc.price_list_currency == company_currency:
|
||||
self.doc.plc_conversion_rate = 1.0
|
||||
|
||||
elif not self.doc.plc_conversion_rate:
|
||||
self.doc.plc_conversion_rate = self.get_exchange_rate(
|
||||
self.doc.price_list_currency, company_currency)
|
||||
|
||||
# currency
|
||||
if not self.doc.currency:
|
||||
self.doc.currency = self.doc.price_list_currency
|
||||
self.doc.conversion_rate = self.doc.plc_conversion_rate
|
||||
elif self.doc.currency == company_currency:
|
||||
self.doc.conversion_rate = 1.0
|
||||
elif not self.doc.conversion_rate:
|
||||
self.doc.conversion_rate = self.get_exchange_rate(self.doc.currency,
|
||||
company_currency)
|
||||
|
||||
def get_exchange_rate(self, from_currency, to_currency):
|
||||
exchange = "%s-%s" % (from_currency, to_currency)
|
||||
return flt(webnotes.conn.get_value("Currency Exchange", exchange, "exchange_rate"))
|
||||
|
||||
def set_missing_item_details(self, get_item_details):
|
||||
"""set missing item values"""
|
||||
for item in self.doclist.get({"parentfield": self.fname}):
|
||||
if item.fields.get("item_code"):
|
||||
args = item.fields.copy().update(self.doc.fields)
|
||||
ret = get_item_details(args)
|
||||
for fieldname, value in ret.items():
|
||||
if self.meta.get_field(fieldname, parentfield=self.fname) and \
|
||||
item.fields.get(fieldname) is None and value is not None:
|
||||
item.fields[fieldname] = value
|
||||
|
||||
def set_taxes(self, tax_parentfield, tax_master_field):
|
||||
if not self.meta.get_field(tax_parentfield):
|
||||
return
|
||||
|
||||
tax_master_doctype = self.meta.get_field(tax_master_field).options
|
||||
|
||||
if not self.doclist.get({"parentfield": tax_parentfield}):
|
||||
if not self.doc.fields.get(tax_master_field):
|
||||
# get the default tax master
|
||||
self.doc.fields[tax_master_field] = \
|
||||
webnotes.conn.get_value(tax_master_doctype, {"is_default": 1})
|
||||
|
||||
self.append_taxes_from_master(tax_parentfield, tax_master_field, tax_master_doctype)
|
||||
|
||||
def append_taxes_from_master(self, tax_parentfield, tax_master_field, tax_master_doctype=None):
|
||||
if self.doc.fields.get(tax_master_field):
|
||||
if not tax_master_doctype:
|
||||
tax_master_doctype = self.meta.get_field(tax_master_field).options
|
||||
|
||||
tax_doctype = self.meta.get_field(tax_parentfield).options
|
||||
|
||||
from webnotes.model import default_fields
|
||||
tax_master = webnotes.bean(tax_master_doctype, self.doc.fields.get(tax_master_field))
|
||||
|
||||
for i, tax in enumerate(tax_master.doclist.get({"parentfield": tax_parentfield})):
|
||||
for fieldname in default_fields:
|
||||
tax.fields[fieldname] = None
|
||||
|
||||
tax.fields.update({
|
||||
"doctype": tax_doctype,
|
||||
"parentfield": tax_parentfield,
|
||||
"idx": i+1
|
||||
})
|
||||
|
||||
self.doclist.append(tax)
|
||||
|
||||
def calculate_taxes_and_totals(self):
|
||||
# validate conversion rate
|
||||
company_currency = get_company_currency(self.doc.company)
|
||||
if not self.doc.currency or self.doc.currency == company_currency:
|
||||
self.doc.currency = company_currency
|
||||
self.doc.conversion_rate = 1.0
|
||||
else:
|
||||
validate_conversion_rate(self.doc.currency, self.doc.conversion_rate,
|
||||
self.meta.get_label("conversion_rate"), self.doc.company)
|
||||
|
||||
self.doc.conversion_rate = flt(self.doc.conversion_rate)
|
||||
self.item_doclist = self.doclist.get({"parentfield": self.fname})
|
||||
self.tax_doclist = self.doclist.get({"parentfield": self.other_fname})
|
||||
|
||||
self.calculate_item_values()
|
||||
self.initialize_taxes()
|
||||
|
||||
if hasattr(self, "determine_exclusive_rate"):
|
||||
self.determine_exclusive_rate()
|
||||
|
||||
self.calculate_net_total()
|
||||
self.calculate_taxes()
|
||||
self.calculate_totals()
|
||||
self._cleanup()
|
||||
|
||||
# TODO
|
||||
# print format: show net_total_export instead of net_total
|
||||
|
||||
def initialize_taxes(self):
|
||||
for tax in self.tax_doclist:
|
||||
tax.item_wise_tax_detail = {}
|
||||
for fieldname in ["tax_amount", "total",
|
||||
"tax_amount_for_current_item", "grand_total_for_current_item",
|
||||
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]:
|
||||
tax.fields[fieldname] = 0.0
|
||||
|
||||
self.validate_on_previous_row(tax)
|
||||
self.validate_inclusive_tax(tax)
|
||||
self.round_floats_in(tax)
|
||||
|
||||
def validate_on_previous_row(self, tax):
|
||||
"""
|
||||
validate if a valid row id is mentioned in case of
|
||||
On Previous Row Amount and On Previous Row Total
|
||||
"""
|
||||
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"] and \
|
||||
(not tax.row_id or cint(tax.row_id) >= tax.idx):
|
||||
msgprint((_("Row") + " # %(idx)s [%(taxes_doctype)s]: " + \
|
||||
_("Please specify a valid") + " %(row_id_label)s") % {
|
||||
"idx": tax.idx,
|
||||
"taxes_doctype": tax.doctype,
|
||||
"row_id_label": self.meta.get_label("row_id",
|
||||
parentfield=self.other_fname)
|
||||
}, raise_exception=True)
|
||||
|
||||
def validate_inclusive_tax(self, tax):
|
||||
def _on_previous_row_error(row_range):
|
||||
msgprint((_("Row") + " # %(idx)s [%(doctype)s]: " +
|
||||
_("to be included in Item's rate, it is required that: ") +
|
||||
" [" + _("Row") + " # %(row_range)s] " + _("also be included in Item's rate")) % {
|
||||
"idx": tax.idx,
|
||||
"doctype": tax.doctype,
|
||||
"inclusive_label": self.meta.get_label("included_in_print_rate",
|
||||
parentfield=self.other_fname),
|
||||
"charge_type_label": self.meta.get_label("charge_type",
|
||||
parentfield=self.other_fname),
|
||||
"charge_type": tax.charge_type,
|
||||
"row_range": row_range
|
||||
}, raise_exception=True)
|
||||
|
||||
if cint(tax.included_in_print_rate):
|
||||
if tax.charge_type == "Actual":
|
||||
# inclusive tax cannot be of type Actual
|
||||
msgprint((_("Row")
|
||||
+ " # %(idx)s [%(doctype)s]: %(charge_type_label)s = \"%(charge_type)s\" "
|
||||
+ "cannot be included in Item's rate") % {
|
||||
"idx": tax.idx,
|
||||
"doctype": tax.doctype,
|
||||
"charge_type_label": self.meta.get_label("charge_type",
|
||||
parentfield=self.other_fname),
|
||||
"charge_type": tax.charge_type,
|
||||
}, raise_exception=True)
|
||||
elif tax.charge_type == "On Previous Row Amount" and \
|
||||
not cint(self.tax_doclist[tax.row_id - 1].included_in_print_rate):
|
||||
# referred row should also be inclusive
|
||||
_on_previous_row_error(tax.row_id)
|
||||
elif tax.charge_type == "On Previous Row Total" and \
|
||||
not all([cint(t.included_in_print_rate) for t in self.tax_doclist[:tax.row_id - 1]]):
|
||||
# all rows about the reffered tax should be inclusive
|
||||
_on_previous_row_error("1 - %d" % (tax.row_id,))
|
||||
|
||||
def calculate_taxes(self):
|
||||
for item in self.item_doclist:
|
||||
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
|
||||
|
||||
for i, tax in enumerate(self.tax_doclist):
|
||||
# tax_amount represents the amount of tax for the current step
|
||||
current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map)
|
||||
|
||||
if hasattr(self, "set_item_tax_amount"):
|
||||
self.set_item_tax_amount(item, tax, current_tax_amount)
|
||||
|
||||
# case when net total is 0 but there is an actual type charge
|
||||
# in this case add the actual amount to tax.tax_amount
|
||||
# and tax.grand_total_for_current_item for the first such iteration
|
||||
if tax.charge_type=="Actual" and \
|
||||
not (current_tax_amount or self.doc.net_total or tax.tax_amount):
|
||||
zero_net_total_adjustment = flt(tax.rate, self.precision("tax_amount", tax))
|
||||
current_tax_amount += zero_net_total_adjustment
|
||||
|
||||
# store tax_amount for current item as it will be used for
|
||||
# charge type = 'On Previous Row Amount'
|
||||
tax.tax_amount_for_current_item = current_tax_amount
|
||||
|
||||
# accumulate tax amount into tax.tax_amount
|
||||
tax.tax_amount += current_tax_amount
|
||||
|
||||
if tax.category:
|
||||
# if just for valuation, do not add the tax amount in total
|
||||
# hence, setting it as 0 for further steps
|
||||
current_tax_amount = 0.0 if (tax.category == "Valuation") else current_tax_amount
|
||||
|
||||
current_tax_amount *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
|
||||
|
||||
# Calculate tax.total viz. grand total till that step
|
||||
# note: grand_total_for_current_item contains the contribution of
|
||||
# item's amount, previously applied tax and the current tax on that item
|
||||
if i==0:
|
||||
tax.grand_total_for_current_item = flt(item.amount +
|
||||
current_tax_amount, self.precision("total", tax))
|
||||
|
||||
else:
|
||||
tax.grand_total_for_current_item = \
|
||||
flt(self.tax_doclist[i-1].grand_total_for_current_item +
|
||||
current_tax_amount, self.precision("total", tax))
|
||||
|
||||
# in tax.total, accumulate grand total of each item
|
||||
tax.total += tax.grand_total_for_current_item
|
||||
|
||||
def get_current_tax_amount(self, item, tax, item_tax_map):
|
||||
tax_rate = self._get_tax_rate(tax, item_tax_map)
|
||||
current_tax_amount = 0.0
|
||||
|
||||
if tax.charge_type == "Actual":
|
||||
# distribute the tax amount proportionally to each item row
|
||||
actual = flt(tax.rate, self.precision("tax_amount", tax))
|
||||
current_tax_amount = (self.doc.net_total
|
||||
and ((item.amount / self.doc.net_total) * actual)
|
||||
or 0)
|
||||
elif tax.charge_type == "On Net Total":
|
||||
current_tax_amount = (tax_rate / 100.0) * item.amount
|
||||
elif tax.charge_type == "On Previous Row Amount":
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.tax_doclist[cint(tax.row_id) - 1].tax_amount_for_current_item
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.tax_doclist[cint(tax.row_id) - 1].grand_total_for_current_item
|
||||
|
||||
current_tax_amount = flt(current_tax_amount, self.precision("tax_amount", tax))
|
||||
|
||||
# store tax breakup for each item
|
||||
key = item.item_code or item.item_name
|
||||
if tax.item_wise_tax_detail.get(key):
|
||||
item_wise_tax_amount = tax.item_wise_tax_detail[key][1] + current_tax_amount
|
||||
tax.item_wise_tax_detail[key] = [tax_rate, item_wise_tax_amount]
|
||||
else:
|
||||
tax.item_wise_tax_detail[key] = [tax_rate, current_tax_amount]
|
||||
|
||||
return current_tax_amount
|
||||
|
||||
def _load_item_tax_rate(self, item_tax_rate):
|
||||
return json.loads(item_tax_rate) if item_tax_rate else {}
|
||||
|
||||
def _get_tax_rate(self, tax, item_tax_map):
|
||||
if item_tax_map.has_key(tax.account_head):
|
||||
return flt(item_tax_map.get(tax.account_head), self.precision("rate", tax))
|
||||
else:
|
||||
return tax.rate
|
||||
|
||||
def _cleanup(self):
|
||||
for tax in self.tax_doclist:
|
||||
for fieldname in ("grand_total_for_current_item",
|
||||
"tax_amount_for_current_item",
|
||||
"tax_fraction_for_current_item",
|
||||
"grand_total_fraction_for_current_item"):
|
||||
if fieldname in tax.fields:
|
||||
del tax.fields[fieldname]
|
||||
|
||||
tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail)
|
||||
|
||||
def _set_in_company_currency(self, item, print_field, base_field):
|
||||
"""set values in base currency"""
|
||||
item.fields[base_field] = flt((flt(item.fields[print_field],
|
||||
self.precision(print_field, item)) * self.doc.conversion_rate),
|
||||
self.precision(base_field, item))
|
||||
|
||||
def calculate_total_advance(self, parenttype, advance_parentfield):
|
||||
if self.doc.doctype == parenttype and self.doc.docstatus < 2:
|
||||
sum_of_allocated_amount = sum([flt(adv.allocated_amount, self.precision("allocated_amount", adv))
|
||||
for adv in self.doclist.get({"parentfield": advance_parentfield})])
|
||||
|
||||
self.doc.total_advance = flt(sum_of_allocated_amount, self.precision("total_advance"))
|
||||
|
||||
self.calculate_outstanding_amount()
|
||||
|
||||
def get_gl_dict(self, args):
|
||||
"""this method populates the common properties of a gl entry record"""
|
||||
gl_dict = webnotes._dict({
|
||||
'company': self.doc.company,
|
||||
'posting_date': self.doc.posting_date,
|
||||
'voucher_type': self.doc.doctype,
|
||||
'voucher_no': self.doc.name,
|
||||
'aging_date': self.doc.fields.get("aging_date") or self.doc.posting_date,
|
||||
'remarks': self.doc.remarks,
|
||||
'fiscal_year': self.doc.fiscal_year,
|
||||
'debit': 0,
|
||||
'credit': 0,
|
||||
'is_opening': self.doc.fields.get("is_opening") or "No",
|
||||
})
|
||||
gl_dict.update(args)
|
||||
return gl_dict
|
||||
|
||||
def clear_unallocated_advances(self, childtype, parentfield):
|
||||
self.doclist.remove_items({"parentfield": parentfield, "allocated_amount": ["in", [0, None, ""]]})
|
||||
|
||||
webnotes.conn.sql("""delete from `tab%s` where parentfield=%s and parent = %s
|
||||
and ifnull(allocated_amount, 0) = 0""" % (childtype, '%s', '%s'), (parentfield, self.doc.name))
|
||||
|
||||
def get_advances(self, account_head, child_doctype, parentfield, dr_or_cr):
|
||||
res = webnotes.conn.sql("""select t1.name as jv_no, t1.remark,
|
||||
t2.%s as amount, t2.name as jv_detail_no
|
||||
from `tabJournal Voucher` t1, `tabJournal Voucher Detail` t2
|
||||
where t1.name = t2.parent and t2.account = %s and t2.is_advance = 'Yes'
|
||||
and (t2.against_voucher is null or t2.against_voucher = '')
|
||||
and (t2.against_invoice is null or t2.against_invoice = '')
|
||||
and (t2.against_jv is null or t2.against_jv = '')
|
||||
and t1.docstatus = 1 order by t1.posting_date""" %
|
||||
(dr_or_cr, '%s'), account_head, as_dict=1)
|
||||
|
||||
self.doclist = self.doc.clear_table(self.doclist, parentfield)
|
||||
for d in res:
|
||||
self.doclist.append({
|
||||
"doctype": child_doctype,
|
||||
"parentfield": parentfield,
|
||||
"journal_voucher": d.jv_no,
|
||||
"jv_detail_no": d.jv_detail_no,
|
||||
"remarks": d.remark,
|
||||
"advance_amount": flt(d.amount),
|
||||
"allocate_amount": 0
|
||||
})
|
||||
|
||||
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
|
||||
for item in self.doclist.get({"parentfield": "entries"}):
|
||||
if item.fields.get(item_ref_dn):
|
||||
already_billed = webnotes.conn.sql("""select sum(%s) from `tab%s`
|
||||
where %s=%s and docstatus=1""" % (based_on, self.tname, item_ref_dn, '%s'),
|
||||
item.fields[item_ref_dn])[0][0]
|
||||
|
||||
max_allowed_amt = flt(webnotes.conn.get_value(ref_dt + " Item",
|
||||
item.fields[item_ref_dn], based_on), self.precision(based_on, item))
|
||||
|
||||
total_billed_amt = flt(flt(already_billed) + flt(item.fields[based_on]),
|
||||
self.precision(based_on, item))
|
||||
|
||||
if max_allowed_amt and total_billed_amt - max_allowed_amt > 0.02:
|
||||
webnotes.msgprint(_("Row ")+ cstr(item.idx) + ": " + cstr(item.item_code) +
|
||||
_(" will be over-billed against mentioned ") + cstr(ref_dt) +
|
||||
_(". Max allowed " + cstr(based_on) + ": " + cstr(max_allowed_amt)),
|
||||
raise_exception=1)
|
||||
|
||||
def get_company_default(self, fieldname):
|
||||
from erpnext.accounts.utils import get_company_default
|
||||
return get_company_default(self.doc.company, fieldname)
|
||||
|
||||
def get_stock_items(self):
|
||||
stock_items = []
|
||||
item_codes = list(set(item.item_code for item in
|
||||
self.doclist.get({"parentfield": self.fname})))
|
||||
if item_codes:
|
||||
stock_items = [r[0] for r in webnotes.conn.sql("""select name
|
||||
from `tabItem` where name in (%s) and is_stock_item='Yes'""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
|
||||
return stock_items
|
||||
|
||||
@property
|
||||
def company_abbr(self):
|
||||
if not hasattr(self, "_abbr"):
|
||||
self._abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr")
|
||||
|
||||
return self._abbr
|
||||
|
||||
def check_credit_limit(self, account):
|
||||
total_outstanding = webnotes.conn.sql("""
|
||||
select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
from `tabGL Entry` where account = %s""", account)
|
||||
|
||||
total_outstanding = total_outstanding[0][0] if total_outstanding else 0
|
||||
if total_outstanding:
|
||||
get_obj('Account', account).check_credit_limit(total_outstanding)
|
||||
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_tax_rate(account_head):
|
||||
return webnotes.conn.get_value("Account", account_head, "tax_rate")
|
||||
283
erpnext/controllers/buying_controller.py
Normal file
283
erpnext/controllers/buying_controller.py
Normal file
@@ -0,0 +1,283 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes import _, msgprint
|
||||
from webnotes.utils import flt, _round
|
||||
|
||||
from erpnext.buying.utils import get_item_details
|
||||
from erpnext.setup.utils import get_company_currency
|
||||
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
|
||||
class BuyingController(StockController):
|
||||
def onload_post_render(self):
|
||||
# contact, address, item details
|
||||
self.set_missing_values()
|
||||
|
||||
def validate(self):
|
||||
super(BuyingController, self).validate()
|
||||
if self.doc.supplier and not self.doc.supplier_name:
|
||||
self.doc.supplier_name = webnotes.conn.get_value("Supplier",
|
||||
self.doc.supplier, "supplier_name")
|
||||
self.is_item_table_empty()
|
||||
self.validate_stock_or_nonstock_items()
|
||||
self.validate_warehouse()
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
super(BuyingController, self).set_missing_values(for_validate)
|
||||
|
||||
self.set_supplier_from_item_default()
|
||||
self.set_price_list_currency("Buying")
|
||||
|
||||
# set contact and address details for supplier, if they are not mentioned
|
||||
if self.doc.supplier and not (self.doc.contact_person and self.doc.supplier_address):
|
||||
for fieldname, val in self.get_supplier_defaults().items():
|
||||
if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname):
|
||||
self.doc.fields[fieldname] = val
|
||||
|
||||
self.set_missing_item_details(get_item_details)
|
||||
if self.doc.fields.get("__islocal"):
|
||||
self.set_taxes("purchase_tax_details", "purchase_other_charges")
|
||||
|
||||
def set_supplier_from_item_default(self):
|
||||
if self.meta.get_field("supplier") and not self.doc.supplier:
|
||||
for d in self.doclist.get({"doctype": self.tname}):
|
||||
supplier = webnotes.conn.get_value("Item", d.item_code, "default_supplier")
|
||||
if supplier:
|
||||
self.doc.supplier = supplier
|
||||
break
|
||||
|
||||
def validate_warehouse(self):
|
||||
from erpnext.stock.utils import validate_warehouse_company
|
||||
|
||||
warehouses = list(set([d.warehouse for d in
|
||||
self.doclist.get({"doctype": self.tname}) if d.warehouse]))
|
||||
|
||||
for w in warehouses:
|
||||
validate_warehouse_company(w, self.doc.company)
|
||||
|
||||
def get_purchase_tax_details(self):
|
||||
self.doclist = self.doc.clear_table(self.doclist, "purchase_tax_details")
|
||||
self.set_taxes("purchase_tax_details", "purchase_other_charges")
|
||||
|
||||
def validate_stock_or_nonstock_items(self):
|
||||
if not self.get_stock_items():
|
||||
tax_for_valuation = [d.account_head for d in
|
||||
self.doclist.get({"parentfield": "purchase_tax_details"})
|
||||
if d.category in ["Valuation", "Valuation and Total"]]
|
||||
if tax_for_valuation:
|
||||
webnotes.msgprint(_("""Tax Category can not be 'Valuation' or 'Valuation and Total' as all items are non-stock items"""), raise_exception=1)
|
||||
|
||||
def set_total_in_words(self):
|
||||
from webnotes.utils import money_in_words
|
||||
company_currency = get_company_currency(self.doc.company)
|
||||
if self.meta.get_field("in_words"):
|
||||
self.doc.in_words = money_in_words(self.doc.grand_total, company_currency)
|
||||
if self.meta.get_field("in_words_import"):
|
||||
self.doc.in_words_import = money_in_words(self.doc.grand_total_import,
|
||||
self.doc.currency)
|
||||
|
||||
def calculate_taxes_and_totals(self):
|
||||
self.other_fname = "purchase_tax_details"
|
||||
super(BuyingController, self).calculate_taxes_and_totals()
|
||||
self.calculate_total_advance("Purchase Invoice", "advance_allocation_details")
|
||||
|
||||
def calculate_item_values(self):
|
||||
# hack! - cleaned up in _cleanup()
|
||||
if self.doc.doctype != "Purchase Invoice":
|
||||
df = self.meta.get_field("purchase_rate", parentfield=self.fname)
|
||||
df.fieldname = "rate"
|
||||
|
||||
for item in self.item_doclist:
|
||||
# hack! - cleaned up in _cleanup()
|
||||
if self.doc.doctype != "Purchase Invoice":
|
||||
item.rate = item.purchase_rate
|
||||
|
||||
self.round_floats_in(item)
|
||||
|
||||
if item.discount_rate == 100.0:
|
||||
item.import_rate = 0.0
|
||||
elif not item.import_rate:
|
||||
item.import_rate = flt(item.import_ref_rate * (1.0 - (item.discount_rate / 100.0)),
|
||||
self.precision("import_rate", item))
|
||||
|
||||
item.import_amount = flt(item.import_rate * item.qty,
|
||||
self.precision("import_amount", item))
|
||||
item.item_tax_amount = 0.0;
|
||||
|
||||
self._set_in_company_currency(item, "import_amount", "amount")
|
||||
self._set_in_company_currency(item, "import_ref_rate", "purchase_ref_rate")
|
||||
self._set_in_company_currency(item, "import_rate", "rate")
|
||||
|
||||
|
||||
def calculate_net_total(self):
|
||||
self.doc.net_total = self.doc.net_total_import = 0.0
|
||||
|
||||
for item in self.item_doclist:
|
||||
self.doc.net_total += item.amount
|
||||
self.doc.net_total_import += item.import_amount
|
||||
|
||||
self.round_floats_in(self.doc, ["net_total", "net_total_import"])
|
||||
|
||||
def calculate_totals(self):
|
||||
self.doc.grand_total = flt(self.tax_doclist and \
|
||||
self.tax_doclist[-1].total or self.doc.net_total, self.precision("grand_total"))
|
||||
self.doc.grand_total_import = flt(self.doc.grand_total / self.doc.conversion_rate,
|
||||
self.precision("grand_total_import"))
|
||||
|
||||
self.doc.total_tax = flt(self.doc.grand_total - self.doc.net_total,
|
||||
self.precision("total_tax"))
|
||||
|
||||
if self.meta.get_field("rounded_total"):
|
||||
self.doc.rounded_total = _round(self.doc.grand_total)
|
||||
|
||||
if self.meta.get_field("rounded_total_import"):
|
||||
self.doc.rounded_total_import = _round(self.doc.grand_total_import)
|
||||
|
||||
def calculate_outstanding_amount(self):
|
||||
if self.doc.doctype == "Purchase Invoice" and self.doc.docstatus < 2:
|
||||
self.doc.total_advance = flt(self.doc.total_advance,
|
||||
self.precision("total_advance"))
|
||||
self.doc.total_amount_to_pay = flt(self.doc.grand_total - flt(self.doc.write_off_amount,
|
||||
self.precision("write_off_amount")), self.precision("total_amount_to_pay"))
|
||||
self.doc.outstanding_amount = flt(self.doc.total_amount_to_pay - self.doc.total_advance,
|
||||
self.precision("outstanding_amount"))
|
||||
|
||||
def _cleanup(self):
|
||||
super(BuyingController, self)._cleanup()
|
||||
|
||||
# except in purchase invoice, rate field is purchase_rate
|
||||
# reset fieldname of rate
|
||||
if self.doc.doctype != "Purchase Invoice":
|
||||
df = self.meta.get_field("rate", parentfield=self.fname)
|
||||
df.fieldname = "purchase_rate"
|
||||
|
||||
for item in self.item_doclist:
|
||||
item.purchase_rate = item.rate
|
||||
del item.fields["rate"]
|
||||
|
||||
if not self.meta.get_field("item_tax_amount", parentfield=self.fname):
|
||||
for item in self.item_doclist:
|
||||
del item.fields["item_tax_amount"]
|
||||
|
||||
def set_item_tax_amount(self, item, tax, current_tax_amount):
|
||||
"""
|
||||
item_tax_amount is the total tax amount applied on that item
|
||||
stored for valuation
|
||||
|
||||
TODO: rename item_tax_amount to valuation_tax_amount
|
||||
"""
|
||||
if tax.category in ["Valuation", "Valuation and Total"] and \
|
||||
self.meta.get_field("item_tax_amount", parentfield=self.fname):
|
||||
item.item_tax_amount += flt(current_tax_amount, self.precision("item_tax_amount", item))
|
||||
|
||||
# update valuation rate
|
||||
def update_valuation_rate(self, parentfield):
|
||||
for item in self.doclist.get({"parentfield": parentfield}):
|
||||
item.conversion_factor = item.conversion_factor or flt(webnotes.conn.get_value(
|
||||
"UOM Conversion Detail", {"parent": item.item_code, "uom": item.uom},
|
||||
"conversion_factor")) or 1
|
||||
|
||||
if item.item_code and item.qty:
|
||||
self.round_floats_in(item)
|
||||
|
||||
# if no item code, which is sometimes the case in purchase invoice,
|
||||
# then it is not possible to track valuation against it
|
||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||
item.valuation_rate = ((item.amount + item.item_tax_amount + item.rm_supp_cost)
|
||||
/ qty_in_stock_uom)
|
||||
else:
|
||||
item.valuation_rate = 0.0
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if not self.doc.is_subcontracted and self.sub_contracted_items:
|
||||
webnotes.msgprint(_("""Please enter whether %s is made for subcontracting or purchasing,
|
||||
in 'Is Subcontracted' field""" % self.doc.doctype), raise_exception=1)
|
||||
|
||||
if self.doc.doctype == "Purchase Receipt" and self.doc.is_subcontracted=="Yes" \
|
||||
and not self.doc.supplier_warehouse:
|
||||
webnotes.msgprint(_("Supplier Warehouse mandatory subcontracted purchase receipt"),
|
||||
raise_exception=1)
|
||||
|
||||
def update_raw_materials_supplied(self, raw_material_table):
|
||||
self.doclist = self.doc.clear_table(self.doclist, raw_material_table)
|
||||
if self.doc.is_subcontracted=="Yes":
|
||||
for item in self.doclist.get({"parentfield": self.fname}):
|
||||
if item.item_code in self.sub_contracted_items:
|
||||
self.add_bom_items(item, raw_material_table)
|
||||
|
||||
def add_bom_items(self, d, raw_material_table):
|
||||
bom_items = self.get_items_from_default_bom(d.item_code)
|
||||
raw_materials_cost = 0
|
||||
for item in bom_items:
|
||||
required_qty = flt(item.qty_consumed_per_unit) * flt(d.qty) * flt(d.conversion_factor)
|
||||
rm_doclist = {
|
||||
"parentfield": raw_material_table,
|
||||
"doctype": self.doc.doctype + " Item Supplied",
|
||||
"reference_name": d.name,
|
||||
"bom_detail_no": item.name,
|
||||
"main_item_code": d.item_code,
|
||||
"rm_item_code": item.item_code,
|
||||
"stock_uom": item.stock_uom,
|
||||
"required_qty": required_qty,
|
||||
"conversion_factor": d.conversion_factor,
|
||||
"rate": item.rate,
|
||||
"amount": required_qty * flt(item.rate)
|
||||
}
|
||||
if self.doc.doctype == "Purchase Receipt":
|
||||
rm_doclist.update({
|
||||
"consumed_qty": required_qty,
|
||||
"description": item.description,
|
||||
})
|
||||
|
||||
self.doclist.append(rm_doclist)
|
||||
|
||||
raw_materials_cost += required_qty * flt(item.rate)
|
||||
|
||||
if self.doc.doctype == "Purchase Receipt":
|
||||
d.rm_supp_cost = raw_materials_cost
|
||||
|
||||
def get_items_from_default_bom(self, item_code):
|
||||
# print webnotes.conn.sql("""select name from `tabBOM` where item = '_Test FG Item'""")
|
||||
bom_items = webnotes.conn.sql("""select t2.item_code, t2.qty_consumed_per_unit,
|
||||
t2.rate, t2.stock_uom, t2.name, t2.description
|
||||
from `tabBOM` t1, `tabBOM Item` t2
|
||||
where t2.parent = t1.name and t1.item = %s and t1.is_default = 1
|
||||
and t1.docstatus = 1 and t1.is_active = 1""", item_code, as_dict=1)
|
||||
if not bom_items:
|
||||
msgprint(_("No default BOM exists for item: ") + item_code, raise_exception=1)
|
||||
|
||||
return bom_items
|
||||
|
||||
@property
|
||||
def sub_contracted_items(self):
|
||||
if not hasattr(self, "_sub_contracted_items"):
|
||||
self._sub_contracted_items = []
|
||||
item_codes = list(set(item.item_code for item in
|
||||
self.doclist.get({"parentfield": self.fname})))
|
||||
if item_codes:
|
||||
self._sub_contracted_items = [r[0] for r in webnotes.conn.sql("""select name
|
||||
from `tabItem` where name in (%s) and is_sub_contracted_item='Yes'""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
@property
|
||||
def purchase_items(self):
|
||||
if not hasattr(self, "_purchase_items"):
|
||||
self._purchase_items = []
|
||||
item_codes = list(set(item.item_code for item in
|
||||
self.doclist.get({"parentfield": self.fname})))
|
||||
if item_codes:
|
||||
self._purchase_items = [r[0] for r in webnotes.conn.sql("""select name
|
||||
from `tabItem` where name in (%s) and is_purchase_item='Yes'""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
|
||||
return self._purchase_items
|
||||
|
||||
|
||||
def is_item_table_empty(self):
|
||||
if not len(self.doclist.get({"parentfield": self.fname})):
|
||||
webnotes.throw(_("Item table can not be blank"))
|
||||
56
erpnext/controllers/js/contact_address_common.js
Normal file
56
erpnext/controllers/js/contact_address_common.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
cur_frm.add_fetch('customer', 'customer_name', 'customer_name');
|
||||
cur_frm.add_fetch('supplier', 'supplier_name', 'supplier_name');
|
||||
|
||||
cur_frm.fields_dict.customer.get_query = erpnext.queries.customer;
|
||||
cur_frm.fields_dict.supplier.get_query = erpnext.queries.supplier;
|
||||
|
||||
if(cur_frm.fields_dict.lead) {
|
||||
cur_frm.fields_dict.lead.get_query = erpnext.queries.lead;
|
||||
cur_frm.add_fetch('lead', 'lead_name', 'lead_name');
|
||||
}
|
||||
|
||||
if(doc.__islocal) {
|
||||
var last_route = wn.route_history.slice(-2, -1)[0];
|
||||
if(last_route && last_route[0]==="Form") {
|
||||
var doctype = last_route[1],
|
||||
docname = last_route.slice(2).join("/");
|
||||
|
||||
if(["Customer", "Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
|
||||
"Installation Note", "Opportunity", "Customer Issue", "Maintenance Visit",
|
||||
"Maintenance Schedule"]
|
||||
.indexOf(doctype)!==-1) {
|
||||
var refdoc = wn.model.get_doc(doctype, docname);
|
||||
|
||||
if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Customer" : true) {
|
||||
cur_frm.set_value("customer", refdoc.customer || refdoc.name);
|
||||
cur_frm.set_value("customer_name", refdoc.customer_name);
|
||||
if(cur_frm.doc.doctype==="Address")
|
||||
cur_frm.set_value("address_title", cur_frm.doc.customer_name);
|
||||
}
|
||||
}
|
||||
if(["Supplier", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
||||
.indexOf(doctype)!==-1) {
|
||||
var refdoc = wn.model.get_doc(doctype, docname);
|
||||
cur_frm.set_value("supplier", refdoc.supplier || refdoc.name);
|
||||
cur_frm.set_value("supplier_name", refdoc.supplier_name);
|
||||
if(cur_frm.doc.doctype==="Address")
|
||||
cur_frm.set_value("address_title", cur_frm.doc.supplier_name);
|
||||
}
|
||||
if(["Lead", "Quotation"]
|
||||
.indexOf(doctype)!==-1) {
|
||||
var refdoc = wn.model.get_doc(doctype, docname);
|
||||
|
||||
if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Lead" : true) {
|
||||
cur_frm.set_value("lead", refdoc.lead || refdoc.name);
|
||||
cur_frm.set_value("lead_name", refdoc.customer_name || refdoc.company_name || refdoc.lead_name);
|
||||
if(cur_frm.doc.doctype==="Address")
|
||||
cur_frm.set_value("address_title", cur_frm.doc.lead_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
218
erpnext/controllers/queries.py
Normal file
218
erpnext/controllers/queries.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.widgets.reportview import get_match_cond
|
||||
|
||||
def get_filters_cond(doctype, filters, conditions):
|
||||
if filters:
|
||||
if isinstance(filters, dict):
|
||||
filters = filters.items()
|
||||
flt = []
|
||||
for f in filters:
|
||||
if isinstance(f[1], basestring) and f[1][0] == '!':
|
||||
flt.append([doctype, f[0], '!=', f[1][1:]])
|
||||
else:
|
||||
flt.append([doctype, f[0], '=', f[1]])
|
||||
|
||||
from webnotes.widgets.reportview import build_filter_conditions
|
||||
build_filter_conditions(flt, conditions)
|
||||
cond = ' and ' + ' and '.join(conditions)
|
||||
else:
|
||||
cond = ''
|
||||
return cond
|
||||
|
||||
# searches for active employees
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return webnotes.conn.sql("""select name, employee_name from `tabEmployee`
|
||||
where status = 'Active'
|
||||
and docstatus < 2
|
||||
and (%(key)s like "%(txt)s"
|
||||
or employee_name like "%(txt)s")
|
||||
%(mcond)s
|
||||
order by
|
||||
case when name like "%(txt)s" then 0 else 1 end,
|
||||
case when employee_name like "%(txt)s" then 0 else 1 end,
|
||||
name
|
||||
limit %(start)s, %(page_len)s""" % {'key': searchfield, 'txt': "%%%s%%" % txt,
|
||||
'mcond':get_match_cond(doctype, searchfield), 'start': start, 'page_len': page_len})
|
||||
|
||||
# searches for leads which are not converted
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return webnotes.conn.sql("""select name, lead_name, company_name from `tabLead`
|
||||
where docstatus < 2
|
||||
and ifnull(status, '') != 'Converted'
|
||||
and (%(key)s like "%(txt)s"
|
||||
or lead_name like "%(txt)s"
|
||||
or company_name like "%(txt)s")
|
||||
%(mcond)s
|
||||
order by
|
||||
case when name like "%(txt)s" then 0 else 1 end,
|
||||
case when lead_name like "%(txt)s" then 0 else 1 end,
|
||||
case when company_name like "%(txt)s" then 0 else 1 end,
|
||||
lead_name asc
|
||||
limit %(start)s, %(page_len)s""" % {'key': searchfield, 'txt': "%%%s%%" % txt,
|
||||
'mcond':get_match_cond(doctype, searchfield), 'start': start, 'page_len': page_len})
|
||||
|
||||
# searches for customer
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
cust_master_name = webnotes.defaults.get_user_default("cust_master_name")
|
||||
|
||||
if cust_master_name == "Customer Name":
|
||||
fields = ["name", "customer_group", "territory"]
|
||||
else:
|
||||
fields = ["name", "customer_name", "customer_group", "territory"]
|
||||
|
||||
fields = ", ".join(fields)
|
||||
|
||||
return webnotes.conn.sql("""select %(field)s from `tabCustomer`
|
||||
where docstatus < 2
|
||||
and (%(key)s like "%(txt)s"
|
||||
or customer_name like "%(txt)s")
|
||||
%(mcond)s
|
||||
order by
|
||||
case when name like "%(txt)s" then 0 else 1 end,
|
||||
case when customer_name like "%(txt)s" then 0 else 1 end,
|
||||
name, customer_name
|
||||
limit %(start)s, %(page_len)s""" % {'field': fields,'key': searchfield,
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield),
|
||||
'start': start, 'page_len': page_len})
|
||||
|
||||
# searches for supplier
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
supp_master_name = webnotes.defaults.get_user_default("supp_master_name")
|
||||
if supp_master_name == "Supplier Name":
|
||||
fields = ["name", "supplier_type"]
|
||||
else:
|
||||
fields = ["name", "supplier_name", "supplier_type"]
|
||||
fields = ", ".join(fields)
|
||||
|
||||
return webnotes.conn.sql("""select %(field)s from `tabSupplier`
|
||||
where docstatus < 2
|
||||
and (%(key)s like "%(txt)s"
|
||||
or supplier_name like "%(txt)s")
|
||||
%(mcond)s
|
||||
order by
|
||||
case when name like "%(txt)s" then 0 else 1 end,
|
||||
case when supplier_name like "%(txt)s" then 0 else 1 end,
|
||||
name, supplier_name
|
||||
limit %(start)s, %(page_len)s """ % {'field': fields,'key': searchfield,
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield), 'start': start,
|
||||
'page_len': page_len})
|
||||
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return webnotes.conn.sql("""select name, parent_account, debit_or_credit
|
||||
from tabAccount
|
||||
where tabAccount.docstatus!=2
|
||||
and (account_type in (%s) or
|
||||
(ifnull(is_pl_account, 'No') = 'Yes' and debit_or_credit = %s) )
|
||||
and group_or_ledger = 'Ledger'
|
||||
and company = %s
|
||||
and `%s` LIKE %s
|
||||
limit %s, %s""" %
|
||||
(", ".join(['%s']*len(filters.get("account_type"))),
|
||||
"%s", "%s", searchfield, "%s", "%s", "%s"),
|
||||
tuple(filters.get("account_type") + [filters.get("debit_or_credit"),
|
||||
filters.get("company"), "%%%s%%" % txt, start, page_len]))
|
||||
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from webnotes.utils import nowdate
|
||||
|
||||
conditions = []
|
||||
|
||||
return webnotes.conn.sql("""select tabItem.name,
|
||||
if(length(tabItem.item_name) > 40,
|
||||
concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name,
|
||||
if(length(tabItem.description) > 40, \
|
||||
concat(substr(tabItem.description, 1, 40), "..."), description) as decription
|
||||
from tabItem
|
||||
where tabItem.docstatus < 2
|
||||
and (ifnull(tabItem.end_of_life, '') = '' or tabItem.end_of_life > %(today)s)
|
||||
and (tabItem.`{key}` LIKE %(txt)s
|
||||
or tabItem.item_name LIKE %(txt)s)
|
||||
{fcond} {mcond}
|
||||
limit %(start)s, %(page_len)s """.format(key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filters, conditions),
|
||||
mcond=get_match_cond(doctype, searchfield)),
|
||||
{
|
||||
"today": nowdate(),
|
||||
"txt": "%%%s%%" % txt,
|
||||
"start": start,
|
||||
"page_len": page_len
|
||||
})
|
||||
|
||||
def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
|
||||
return webnotes.conn.sql("""select tabBOM.name, tabBOM.item
|
||||
from tabBOM
|
||||
where tabBOM.docstatus=1
|
||||
and tabBOM.is_active=1
|
||||
and tabBOM.%(key)s like "%(txt)s"
|
||||
%(fcond)s %(mcond)s
|
||||
limit %(start)s, %(page_len)s """ % {'key': searchfield, 'txt': "%%%s%%" % txt,
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond':get_match_cond(doctype, searchfield), 'start': start, 'page_len': page_len})
|
||||
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ''
|
||||
if filters['customer']:
|
||||
cond = '(`tabProject`.customer = "' + filters['customer'] + '" or ifnull(`tabProject`.customer,"")="") and'
|
||||
|
||||
return webnotes.conn.sql("""select `tabProject`.name from `tabProject`
|
||||
where `tabProject`.status not in ("Completed", "Cancelled")
|
||||
and %(cond)s `tabProject`.name like "%(txt)s" %(mcond)s
|
||||
order by `tabProject`.name asc
|
||||
limit %(start)s, %(page_len)s """ % {'cond': cond,'txt': "%%%s%%" % txt,
|
||||
'mcond':get_match_cond(doctype, searchfield),'start': start, 'page_len': page_len})
|
||||
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters):
|
||||
return webnotes.conn.sql("""select `tabDelivery Note`.name, `tabDelivery Note`.customer_name
|
||||
from `tabDelivery Note`
|
||||
where `tabDelivery Note`.`%(key)s` like %(txt)s and
|
||||
`tabDelivery Note`.docstatus = 1 %(fcond)s and
|
||||
(ifnull((select sum(qty) from `tabDelivery Note Item` where
|
||||
`tabDelivery Note Item`.parent=`tabDelivery Note`.name), 0) >
|
||||
ifnull((select sum(qty) from `tabSales Invoice Item` where
|
||||
`tabSales Invoice Item`.docstatus = 1 and
|
||||
`tabSales Invoice Item`.delivery_note=`tabDelivery Note`.name), 0))
|
||||
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc
|
||||
limit %(start)s, %(page_len)s""" % {
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, []),
|
||||
"mcond": get_match_cond(doctype),
|
||||
"start": "%(start)s", "page_len": "%(page_len)s", "txt": "%(txt)s"
|
||||
}, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) })
|
||||
|
||||
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
|
||||
if filters.has_key('warehouse'):
|
||||
return webnotes.conn.sql("""select batch_no from `tabStock Ledger Entry` sle
|
||||
where item_code = '%(item_code)s'
|
||||
and warehouse = '%(warehouse)s'
|
||||
and batch_no like '%(txt)s'
|
||||
and exists(select * from `tabBatch`
|
||||
where name = sle.batch_no
|
||||
and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
|
||||
and docstatus != 2)
|
||||
%(mcond)s
|
||||
group by batch_no having sum(actual_qty) > 0
|
||||
order by batch_no desc
|
||||
limit %(start)s, %(page_len)s """ % {'item_code': filters['item_code'],
|
||||
'warehouse': filters['warehouse'], 'posting_date': filters['posting_date'],
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype, searchfield),
|
||||
'start': start, 'page_len': page_len})
|
||||
else:
|
||||
return webnotes.conn.sql("""select name from tabBatch
|
||||
where docstatus != 2
|
||||
and item = '%(item_code)s'
|
||||
and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
|
||||
and name like '%(txt)s'
|
||||
%(mcond)s
|
||||
order by name desc
|
||||
limit %(start)s, %(page_len)s""" % {'item_code': filters['item_code'],
|
||||
'posting_date': filters['posting_date'], 'txt': "%%%s%%" % txt,
|
||||
'mcond':get_match_cond(doctype, searchfield),'start': start,
|
||||
'page_len': page_len})
|
||||
361
erpnext/controllers/selling_controller.py
Normal file
361
erpnext/controllers/selling_controller.py
Normal file
@@ -0,0 +1,361 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import cint, flt, comma_or, _round, cstr
|
||||
from erpnext.setup.utils import get_company_currency
|
||||
from erpnext.selling.utils import get_item_details
|
||||
from webnotes import msgprint, _
|
||||
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
|
||||
class SellingController(StockController):
|
||||
def onload_post_render(self):
|
||||
# contact, address, item details and pos details (if applicable)
|
||||
self.set_missing_values()
|
||||
|
||||
def validate(self):
|
||||
super(SellingController, self).validate()
|
||||
self.validate_max_discount()
|
||||
check_active_sales_items(self)
|
||||
|
||||
def get_sender(self, comm):
|
||||
return webnotes.conn.get_value('Sales Email Settings', None, 'email_id')
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
super(SellingController, self).set_missing_values(for_validate)
|
||||
|
||||
# set contact and address details for customer, if they are not mentioned
|
||||
self.set_missing_lead_customer_details()
|
||||
self.set_price_list_and_item_details()
|
||||
if self.doc.fields.get("__islocal"):
|
||||
self.set_taxes("other_charges", "charge")
|
||||
|
||||
def set_missing_lead_customer_details(self):
|
||||
if self.doc.customer:
|
||||
if not (self.doc.contact_person and self.doc.customer_address and self.doc.customer_name):
|
||||
for fieldname, val in self.get_customer_defaults().items():
|
||||
if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname):
|
||||
self.doc.fields[fieldname] = val
|
||||
|
||||
elif self.doc.lead:
|
||||
if not (self.doc.customer_address and self.doc.customer_name and \
|
||||
self.doc.contact_display):
|
||||
for fieldname, val in self.get_lead_defaults().items():
|
||||
if not self.doc.fields.get(fieldname) and self.meta.get_field(fieldname):
|
||||
self.doc.fields[fieldname] = val
|
||||
|
||||
def set_price_list_and_item_details(self):
|
||||
self.set_price_list_currency("Selling")
|
||||
self.set_missing_item_details(get_item_details)
|
||||
|
||||
def get_other_charges(self):
|
||||
self.doclist = self.doc.clear_table(self.doclist, "other_charges")
|
||||
self.set_taxes("other_charges", "charge")
|
||||
|
||||
def apply_shipping_rule(self):
|
||||
if self.doc.shipping_rule:
|
||||
shipping_rule = webnotes.bean("Shipping Rule", self.doc.shipping_rule)
|
||||
value = self.doc.net_total
|
||||
|
||||
# TODO
|
||||
# shipping rule calculation based on item's net weight
|
||||
|
||||
shipping_amount = 0.0
|
||||
for condition in shipping_rule.doclist.get({"parentfield": "shipping_rule_conditions"}):
|
||||
if not condition.to_value or (flt(condition.from_value) <= value <= flt(condition.to_value)):
|
||||
shipping_amount = condition.shipping_amount
|
||||
break
|
||||
|
||||
self.doclist.append({
|
||||
"doctype": "Sales Taxes and Charges",
|
||||
"parentfield": "other_charges",
|
||||
"charge_type": "Actual",
|
||||
"account_head": shipping_rule.doc.account,
|
||||
"cost_center": shipping_rule.doc.cost_center,
|
||||
"description": shipping_rule.doc.label,
|
||||
"rate": shipping_amount
|
||||
})
|
||||
|
||||
def set_total_in_words(self):
|
||||
from webnotes.utils import money_in_words
|
||||
company_currency = get_company_currency(self.doc.company)
|
||||
|
||||
disable_rounded_total = cint(webnotes.conn.get_value("Global Defaults", None,
|
||||
"disable_rounded_total"))
|
||||
|
||||
if self.meta.get_field("in_words"):
|
||||
self.doc.in_words = money_in_words(disable_rounded_total and
|
||||
self.doc.grand_total or self.doc.rounded_total, company_currency)
|
||||
if self.meta.get_field("in_words_export"):
|
||||
self.doc.in_words_export = money_in_words(disable_rounded_total and
|
||||
self.doc.grand_total_export or self.doc.rounded_total_export, self.doc.currency)
|
||||
|
||||
def calculate_taxes_and_totals(self):
|
||||
self.other_fname = "other_charges"
|
||||
|
||||
super(SellingController, self).calculate_taxes_and_totals()
|
||||
|
||||
self.calculate_total_advance("Sales Invoice", "advance_adjustment_details")
|
||||
self.calculate_commission()
|
||||
self.calculate_contribution()
|
||||
|
||||
def determine_exclusive_rate(self):
|
||||
if not any((cint(tax.included_in_print_rate) for tax in self.tax_doclist)):
|
||||
# no inclusive tax
|
||||
return
|
||||
|
||||
for item in self.item_doclist:
|
||||
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
|
||||
cumulated_tax_fraction = 0
|
||||
for i, tax in enumerate(self.tax_doclist):
|
||||
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
|
||||
if i==0:
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||
else:
|
||||
tax.grand_total_fraction_for_current_item = \
|
||||
self.tax_doclist[i-1].grand_total_fraction_for_current_item \
|
||||
+ tax.tax_fraction_for_current_item
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
|
||||
if cumulated_tax_fraction:
|
||||
item.amount = flt((item.export_amount * self.doc.conversion_rate) /
|
||||
(1 + cumulated_tax_fraction), self.precision("amount", item))
|
||||
|
||||
item.basic_rate = flt(item.amount / item.qty, self.precision("basic_rate", item))
|
||||
|
||||
if item.adj_rate == 100:
|
||||
item.base_ref_rate = item.basic_rate
|
||||
item.basic_rate = 0.0
|
||||
else:
|
||||
item.base_ref_rate = flt(item.basic_rate / (1 - (item.adj_rate / 100.0)),
|
||||
self.precision("base_ref_rate", item))
|
||||
|
||||
def get_current_tax_fraction(self, tax, item_tax_map):
|
||||
"""
|
||||
Get tax fraction for calculating tax exclusive amount
|
||||
from tax inclusive amount
|
||||
"""
|
||||
current_tax_fraction = 0
|
||||
|
||||
if cint(tax.included_in_print_rate):
|
||||
tax_rate = self._get_tax_rate(tax, item_tax_map)
|
||||
|
||||
if tax.charge_type == "On Net Total":
|
||||
current_tax_fraction = tax_rate / 100.0
|
||||
|
||||
elif tax.charge_type == "On Previous Row Amount":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.tax_doclist[cint(tax.row_id) - 1].tax_fraction_for_current_item
|
||||
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.tax_doclist[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||
|
||||
return current_tax_fraction
|
||||
|
||||
def calculate_item_values(self):
|
||||
for item in self.item_doclist:
|
||||
self.round_floats_in(item)
|
||||
|
||||
if item.adj_rate == 100:
|
||||
item.export_rate = 0
|
||||
elif not item.export_rate:
|
||||
item.export_rate = flt(item.ref_rate * (1.0 - (item.adj_rate / 100.0)),
|
||||
self.precision("export_rate", item))
|
||||
|
||||
item.export_amount = flt(item.export_rate * item.qty,
|
||||
self.precision("export_amount", item))
|
||||
|
||||
self._set_in_company_currency(item, "ref_rate", "base_ref_rate")
|
||||
self._set_in_company_currency(item, "export_rate", "basic_rate")
|
||||
self._set_in_company_currency(item, "export_amount", "amount")
|
||||
|
||||
def calculate_net_total(self):
|
||||
self.doc.net_total = self.doc.net_total_export = 0.0
|
||||
|
||||
for item in self.item_doclist:
|
||||
self.doc.net_total += item.amount
|
||||
self.doc.net_total_export += item.export_amount
|
||||
|
||||
self.round_floats_in(self.doc, ["net_total", "net_total_export"])
|
||||
|
||||
def calculate_totals(self):
|
||||
self.doc.grand_total = flt(self.tax_doclist and \
|
||||
self.tax_doclist[-1].total or self.doc.net_total, self.precision("grand_total"))
|
||||
self.doc.grand_total_export = flt(self.doc.grand_total / self.doc.conversion_rate,
|
||||
self.precision("grand_total_export"))
|
||||
|
||||
self.doc.other_charges_total = flt(self.doc.grand_total - self.doc.net_total,
|
||||
self.precision("other_charges_total"))
|
||||
self.doc.other_charges_total_export = flt(self.doc.grand_total_export - self.doc.net_total_export,
|
||||
self.precision("other_charges_total_export"))
|
||||
|
||||
self.doc.rounded_total = _round(self.doc.grand_total)
|
||||
self.doc.rounded_total_export = _round(self.doc.grand_total_export)
|
||||
|
||||
def calculate_outstanding_amount(self):
|
||||
# NOTE:
|
||||
# write_off_amount is only for POS Invoice
|
||||
# total_advance is only for non POS Invoice
|
||||
if self.doc.doctype == "Sales Invoice" and self.doc.docstatus < 2:
|
||||
self.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount",
|
||||
"paid_amount"])
|
||||
total_amount_to_pay = self.doc.grand_total - self.doc.write_off_amount
|
||||
self.doc.outstanding_amount = flt(total_amount_to_pay - self.doc.total_advance \
|
||||
- self.doc.paid_amount, self.precision("outstanding_amount"))
|
||||
|
||||
def calculate_commission(self):
|
||||
if self.meta.get_field("commission_rate"):
|
||||
self.round_floats_in(self.doc, ["net_total", "commission_rate"])
|
||||
if self.doc.commission_rate > 100.0:
|
||||
msgprint(_(self.meta.get_label("commission_rate")) + " " +
|
||||
_("cannot be greater than 100"), raise_exception=True)
|
||||
|
||||
self.doc.total_commission = flt(self.doc.net_total * self.doc.commission_rate / 100.0,
|
||||
self.precision("total_commission"))
|
||||
|
||||
def calculate_contribution(self):
|
||||
total = 0.0
|
||||
sales_team = self.doclist.get({"parentfield": "sales_team"})
|
||||
for sales_person in sales_team:
|
||||
self.round_floats_in(sales_person)
|
||||
|
||||
sales_person.allocated_amount = flt(
|
||||
self.doc.net_total * sales_person.allocated_percentage / 100.0,
|
||||
self.precision("allocated_amount", sales_person))
|
||||
|
||||
total += sales_person.allocated_percentage
|
||||
|
||||
if sales_team and total != 100.0:
|
||||
msgprint(_("Total") + " " +
|
||||
_(self.meta.get_label("allocated_percentage", parentfield="sales_team")) +
|
||||
" " + _("should be 100%"), raise_exception=True)
|
||||
|
||||
def validate_order_type(self):
|
||||
valid_types = ["Sales", "Maintenance", "Shopping Cart"]
|
||||
if not self.doc.order_type:
|
||||
self.doc.order_type = "Sales"
|
||||
elif self.doc.order_type not in valid_types:
|
||||
msgprint(_(self.meta.get_label("order_type")) + " " +
|
||||
_("must be one of") + ": " + comma_or(valid_types), raise_exception=True)
|
||||
|
||||
def check_credit(self, grand_total):
|
||||
customer_account = webnotes.conn.get_value("Account", {"company": self.doc.company,
|
||||
"master_name": self.doc.customer}, "name")
|
||||
if customer_account:
|
||||
total_outstanding = webnotes.conn.sql("""select
|
||||
sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
from `tabGL Entry` where account = %s""", customer_account)
|
||||
total_outstanding = total_outstanding[0][0] if total_outstanding else 0
|
||||
|
||||
outstanding_including_current = flt(total_outstanding) + flt(grand_total)
|
||||
webnotes.bean('Account', customer_account).run_method("check_credit_limit",
|
||||
outstanding_including_current)
|
||||
|
||||
def validate_max_discount(self):
|
||||
for d in self.doclist.get({"parentfield": self.fname}):
|
||||
discount = flt(webnotes.conn.get_value("Item", d.item_code, "max_discount"))
|
||||
|
||||
if discount and flt(d.adj_rate) > discount:
|
||||
webnotes.throw(_("You cannot give more than ") + cstr(discount) + "% " +
|
||||
_("discount on Item Code") + ": " + cstr(d.item_code))
|
||||
|
||||
def get_item_list(self):
|
||||
il = []
|
||||
for d in self.doclist.get({"parentfield": self.fname}):
|
||||
reserved_warehouse = ""
|
||||
reserved_qty_for_main_item = 0
|
||||
|
||||
if self.doc.doctype == "Sales Order":
|
||||
if (webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == 'Yes' or
|
||||
self.has_sales_bom(d.item_code)) and not d.reserved_warehouse:
|
||||
webnotes.throw(_("Please enter Reserved Warehouse for item ") +
|
||||
d.item_code + _(" as it is stock Item or packing item"))
|
||||
reserved_warehouse = d.reserved_warehouse
|
||||
if flt(d.qty) > flt(d.delivered_qty):
|
||||
reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty)
|
||||
|
||||
if self.doc.doctype == "Delivery Note" and d.against_sales_order:
|
||||
# if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12.
|
||||
# But in this case reserved qty should only be reduced by 10 and not 12
|
||||
|
||||
already_delivered_qty = self.get_already_delivered_qty(self.doc.name,
|
||||
d.against_sales_order, d.prevdoc_detail_docname)
|
||||
so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.prevdoc_detail_docname)
|
||||
|
||||
if already_delivered_qty + d.qty > so_qty:
|
||||
reserved_qty_for_main_item = -(so_qty - already_delivered_qty)
|
||||
else:
|
||||
reserved_qty_for_main_item = -flt(d.qty)
|
||||
|
||||
if self.has_sales_bom(d.item_code):
|
||||
for p in self.doclist.get({"parentfield": "packing_details"}):
|
||||
if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
|
||||
# the packing details table's qty is already multiplied with parent's qty
|
||||
il.append(webnotes._dict({
|
||||
'warehouse': p.warehouse,
|
||||
'reserved_warehouse': reserved_warehouse,
|
||||
'item_code': p.item_code,
|
||||
'qty': flt(p.qty),
|
||||
'reserved_qty': (flt(p.qty)/flt(d.qty)) * reserved_qty_for_main_item,
|
||||
'uom': p.uom,
|
||||
'batch_no': cstr(p.batch_no).strip(),
|
||||
'serial_no': cstr(p.serial_no).strip(),
|
||||
'name': d.name
|
||||
}))
|
||||
else:
|
||||
il.append(webnotes._dict({
|
||||
'warehouse': d.warehouse,
|
||||
'reserved_warehouse': reserved_warehouse,
|
||||
'item_code': d.item_code,
|
||||
'qty': d.qty,
|
||||
'reserved_qty': reserved_qty_for_main_item,
|
||||
'uom': d.stock_uom,
|
||||
'batch_no': cstr(d.batch_no).strip(),
|
||||
'serial_no': cstr(d.serial_no).strip(),
|
||||
'name': d.name
|
||||
}))
|
||||
return il
|
||||
|
||||
def has_sales_bom(self, item_code):
|
||||
return webnotes.conn.sql("""select name from `tabSales BOM`
|
||||
where new_item_code=%s and docstatus != 2""", item_code)
|
||||
|
||||
def get_already_delivered_qty(self, dn, so, so_detail):
|
||||
qty = webnotes.conn.sql("""select sum(qty) from `tabDelivery Note Item`
|
||||
where prevdoc_detail_docname = %s and docstatus = 1
|
||||
and against_sales_order = %s
|
||||
and parent != %s""", (so_detail, so, dn))
|
||||
return qty and flt(qty[0][0]) or 0.0
|
||||
|
||||
def get_so_qty_and_warehouse(self, so_detail):
|
||||
so_item = webnotes.conn.sql("""select qty, reserved_warehouse from `tabSales Order Item`
|
||||
where name = %s and docstatus = 1""", so_detail, as_dict=1)
|
||||
so_qty = so_item and flt(so_item[0]["qty"]) or 0.0
|
||||
so_warehouse = so_item and so_item[0]["reserved_warehouse"] or ""
|
||||
return so_qty, so_warehouse
|
||||
|
||||
def check_stop_sales_order(self, ref_fieldname):
|
||||
for d in self.doclist.get({"parentfield": self.fname}):
|
||||
if d.fields.get(ref_fieldname):
|
||||
status = webnotes.conn.get_value("Sales Order", d.fields[ref_fieldname], "status")
|
||||
if status == "Stopped":
|
||||
webnotes.throw(self.doc.doctype +
|
||||
_(" can not be created/modified against stopped Sales Order ") +
|
||||
d.fields[ref_fieldname])
|
||||
|
||||
def check_active_sales_items(obj):
|
||||
for d in obj.doclist.get({"parentfield": obj.fname}):
|
||||
if d.item_code:
|
||||
item = webnotes.conn.sql("""select docstatus, is_sales_item,
|
||||
is_service_item, default_income_account from tabItem where name = %s""",
|
||||
d.item_code, as_dict=True)[0]
|
||||
if item.is_sales_item == 'No' and item.is_service_item == 'No':
|
||||
webnotes.throw(_("Item is neither Sales nor Service Item") + ": " + d.item_code)
|
||||
if d.income_account and not item.default_income_account:
|
||||
webnotes.conn.set_value("Item", d.item_code, "default_income_account",
|
||||
d.income_account)
|
||||
248
erpnext/controllers/status_updater.py
Normal file
248
erpnext/controllers/status_updater.py
Normal file
@@ -0,0 +1,248 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import flt, cstr
|
||||
from webnotes import msgprint
|
||||
|
||||
from webnotes.model.controller import DocListController
|
||||
|
||||
status_map = {
|
||||
"Contact": [
|
||||
["Replied", "communication_sent"],
|
||||
["Open", "communication_received"]
|
||||
],
|
||||
"Job Applicant": [
|
||||
["Replied", "communication_sent"],
|
||||
["Open", "communication_received"]
|
||||
],
|
||||
"Lead": [
|
||||
["Replied", "communication_sent"],
|
||||
["Converted", "has_customer"],
|
||||
["Opportunity", "has_opportunity"],
|
||||
["Open", "communication_received"],
|
||||
],
|
||||
"Opportunity": [
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.doc.docstatus==1"],
|
||||
["Lost", "eval:self.doc.status=='Lost'"],
|
||||
["Quotation", "has_quotation"],
|
||||
["Replied", "communication_sent"],
|
||||
["Cancelled", "eval:self.doc.docstatus==2"],
|
||||
["Open", "communication_received"],
|
||||
],
|
||||
"Quotation": [
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.doc.docstatus==1"],
|
||||
["Lost", "eval:self.doc.status=='Lost'"],
|
||||
["Ordered", "has_sales_order"],
|
||||
["Replied", "communication_sent"],
|
||||
["Cancelled", "eval:self.doc.docstatus==2"],
|
||||
["Open", "communication_received"],
|
||||
],
|
||||
"Sales Order": [
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.doc.docstatus==1"],
|
||||
["Stopped", "eval:self.doc.status=='Stopped'"],
|
||||
["Cancelled", "eval:self.doc.docstatus==2"],
|
||||
],
|
||||
"Support Ticket": [
|
||||
["Replied", "communication_sent"],
|
||||
["Open", "communication_received"]
|
||||
],
|
||||
}
|
||||
|
||||
class StatusUpdater(DocListController):
|
||||
"""
|
||||
Updates the status of the calling records
|
||||
Delivery Note: Update Delivered Qty, Update Percent and Validate over delivery
|
||||
Sales Invoice: Update Billed Amt, Update Percent and Validate over billing
|
||||
Installation Note: Update Installed Qty, Update Percent Qty and Validate over installation
|
||||
"""
|
||||
|
||||
def update_prevdoc_status(self):
|
||||
self.update_qty()
|
||||
self.validate_qty()
|
||||
|
||||
def set_status(self, update=False):
|
||||
if self.doc.get("__islocal"):
|
||||
return
|
||||
|
||||
if self.doc.doctype in status_map:
|
||||
sl = status_map[self.doc.doctype][:]
|
||||
sl.reverse()
|
||||
for s in sl:
|
||||
if not s[1]:
|
||||
self.doc.status = s[0]
|
||||
break
|
||||
elif s[1].startswith("eval:"):
|
||||
if eval(s[1][5:]):
|
||||
self.doc.status = s[0]
|
||||
break
|
||||
elif getattr(self, s[1])():
|
||||
self.doc.status = s[0]
|
||||
break
|
||||
|
||||
if update:
|
||||
webnotes.conn.set_value(self.doc.doctype, self.doc.name, "status", self.doc.status)
|
||||
|
||||
def on_communication(self):
|
||||
self.communication_set = True
|
||||
self.set_status(update=True)
|
||||
del self.communication_set
|
||||
|
||||
def communication_received(self):
|
||||
if getattr(self, "communication_set", False):
|
||||
last_comm = self.doclist.get({"doctype":"Communication"})
|
||||
if last_comm:
|
||||
return last_comm[-1].sent_or_received == "Received"
|
||||
|
||||
def communication_sent(self):
|
||||
if getattr(self, "communication_set", False):
|
||||
last_comm = self.doclist.get({"doctype":"Communication"})
|
||||
if last_comm:
|
||||
return last_comm[-1].sent_or_received == "Sent"
|
||||
|
||||
def validate_qty(self):
|
||||
"""
|
||||
Validates qty at row level
|
||||
"""
|
||||
self.tolerance = {}
|
||||
self.global_tolerance = None
|
||||
|
||||
for args in self.status_updater:
|
||||
# get unique transactions to update
|
||||
for d in self.doclist:
|
||||
if d.doctype == args['source_dt'] and d.fields.get(args["join_field"]):
|
||||
args['name'] = d.fields[args['join_field']]
|
||||
|
||||
# get all qty where qty > target_field
|
||||
item = webnotes.conn.sql("""select item_code, `%(target_ref_field)s`,
|
||||
`%(target_field)s`, parenttype, parent from `tab%(target_dt)s`
|
||||
where `%(target_ref_field)s` < `%(target_field)s`
|
||||
and name="%(name)s" and docstatus=1""" % args, as_dict=1)
|
||||
if item:
|
||||
item = item[0]
|
||||
item['idx'] = d.idx
|
||||
item['target_ref_field'] = args['target_ref_field'].replace('_', ' ')
|
||||
|
||||
if not item[args['target_ref_field']]:
|
||||
msgprint("""As %(target_ref_field)s for item: %(item_code)s in \
|
||||
%(parenttype)s: %(parent)s is zero, system will not check \
|
||||
over-delivery or over-billed""" % item)
|
||||
elif args.get('no_tolerance'):
|
||||
item['reduce_by'] = item[args['target_field']] - \
|
||||
item[args['target_ref_field']]
|
||||
if item['reduce_by'] > .01:
|
||||
msgprint("""
|
||||
Row #%(idx)s: Max %(target_ref_field)s allowed for <b>Item \
|
||||
%(item_code)s</b> against <b>%(parenttype)s %(parent)s</b> \
|
||||
is <b>""" % item + cstr(item[args['target_ref_field']]) +
|
||||
"""</b>.<br>You must reduce the %(target_ref_field)s by \
|
||||
%(reduce_by)s""" % item, raise_exception=1)
|
||||
|
||||
else:
|
||||
self.check_overflow_with_tolerance(item, args)
|
||||
|
||||
def check_overflow_with_tolerance(self, item, args):
|
||||
"""
|
||||
Checks if there is overflow condering a relaxation tolerance
|
||||
"""
|
||||
|
||||
# check if overflow is within tolerance
|
||||
tolerance = self.get_tolerance_for(item['item_code'])
|
||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||
item[args['target_ref_field']]) * 100
|
||||
|
||||
if overflow_percent - tolerance > 0.01:
|
||||
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+tolerance)/100)
|
||||
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
|
||||
|
||||
msgprint("""
|
||||
Row #%(idx)s: Max %(target_ref_field)s allowed for <b>Item %(item_code)s</b> \
|
||||
against <b>%(parenttype)s %(parent)s</b> is <b>%(max_allowed)s</b>.
|
||||
|
||||
If you want to increase your overflow tolerance, please increase tolerance %% in \
|
||||
Global Defaults or Item master.
|
||||
|
||||
Or, you must reduce the %(target_ref_field)s by %(reduce_by)s
|
||||
|
||||
Also, please check if the order item has already been billed in the Sales Order""" %
|
||||
item, raise_exception=1)
|
||||
|
||||
def get_tolerance_for(self, item_code):
|
||||
"""
|
||||
Returns the tolerance for the item, if not set, returns global tolerance
|
||||
"""
|
||||
if self.tolerance.get(item_code): return self.tolerance[item_code]
|
||||
|
||||
tolerance = flt(webnotes.conn.get_value('Item',item_code,'tolerance') or 0)
|
||||
|
||||
if not tolerance:
|
||||
if self.global_tolerance == None:
|
||||
self.global_tolerance = flt(webnotes.conn.get_value('Global Defaults', None,
|
||||
'tolerance'))
|
||||
tolerance = self.global_tolerance
|
||||
|
||||
self.tolerance[item_code] = tolerance
|
||||
return tolerance
|
||||
|
||||
|
||||
def update_qty(self, change_modified=True):
|
||||
"""
|
||||
Updates qty at row level
|
||||
"""
|
||||
for args in self.status_updater:
|
||||
# condition to include current record (if submit or no if cancel)
|
||||
if self.doc.docstatus == 1:
|
||||
args['cond'] = ' or parent="%s"' % self.doc.name
|
||||
else:
|
||||
args['cond'] = ' and parent!="%s"' % self.doc.name
|
||||
|
||||
args['modified_cond'] = ''
|
||||
if change_modified:
|
||||
args['modified_cond'] = ', modified = now()'
|
||||
|
||||
# update quantities in child table
|
||||
for d in self.doclist:
|
||||
if d.doctype == args['source_dt']:
|
||||
# updates qty in the child table
|
||||
args['detail_id'] = d.fields.get(args['join_field'])
|
||||
|
||||
args['second_source_condition'] = ""
|
||||
if args.get('second_source_dt') and args.get('second_source_field') \
|
||||
and args.get('second_join_field'):
|
||||
args['second_source_condition'] = """ + (select sum(%(second_source_field)s)
|
||||
from `tab%(second_source_dt)s`
|
||||
where `%(second_join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1))""" % args
|
||||
|
||||
if args['detail_id']:
|
||||
webnotes.conn.sql("""update `tab%(target_dt)s`
|
||||
set %(target_field)s = (select sum(%(source_field)s)
|
||||
from `tab%(source_dt)s` where `%(join_field)s`="%(detail_id)s"
|
||||
and (docstatus=1 %(cond)s)) %(second_source_condition)s
|
||||
where name='%(detail_id)s'""" % args)
|
||||
|
||||
# get unique transactions to update
|
||||
for name in set([d.fields.get(args['percent_join_field']) for d in self.doclist
|
||||
if d.doctype == args['source_dt']]):
|
||||
if name:
|
||||
args['name'] = name
|
||||
|
||||
# update percent complete in the parent table
|
||||
webnotes.conn.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(target_parent_field)s = (select sum(if(%(target_ref_field)s >
|
||||
ifnull(%(target_field)s, 0), %(target_field)s,
|
||||
%(target_ref_field)s))/sum(%(target_ref_field)s)*100
|
||||
from `tab%(target_dt)s` where parent="%(name)s") %(modified_cond)s
|
||||
where name='%(name)s'""" % args)
|
||||
|
||||
# update field
|
||||
if args.get('status_field'):
|
||||
webnotes.conn.sql("""update `tab%(target_parent_dt)s`
|
||||
set %(status_field)s = if(ifnull(%(target_parent_field)s,0)<0.001,
|
||||
'Not %(keyword)s', if(%(target_parent_field)s>=99.99,
|
||||
'Fully %(keyword)s', 'Partly %(keyword)s'))
|
||||
where name='%(name)s'""" % args)
|
||||
269
erpnext/controllers/stock_controller.py
Normal file
269
erpnext/controllers/stock_controller.py
Normal file
@@ -0,0 +1,269 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import cint, flt, cstr
|
||||
from webnotes import msgprint, _
|
||||
import webnotes.defaults
|
||||
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries
|
||||
|
||||
class StockController(AccountsController):
|
||||
def make_gl_entries(self, update_gl_entries_after=True):
|
||||
if self.doc.docstatus == 2:
|
||||
delete_gl_entries(voucher_type=self.doc.doctype, voucher_no=self.doc.name)
|
||||
|
||||
if cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")):
|
||||
warehouse_account = self.get_warehouse_account()
|
||||
|
||||
if self.doc.docstatus==1:
|
||||
gl_entries = self.get_gl_entries(warehouse_account)
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
if update_gl_entries_after:
|
||||
self.update_gl_entries_after(warehouse_account)
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
|
||||
default_cost_center=None):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
if not warehouse_account:
|
||||
warehouse_account = self.get_warehouse_account()
|
||||
|
||||
stock_ledger = self.get_stock_ledger_details()
|
||||
voucher_details = self.get_voucher_details(stock_ledger, default_expense_account,
|
||||
default_cost_center)
|
||||
|
||||
gl_list = []
|
||||
warehouse_with_no_account = []
|
||||
for detail in voucher_details:
|
||||
sle_list = stock_ledger.get(detail.name)
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
if warehouse_account.get(sle.warehouse):
|
||||
# from warehouse account
|
||||
gl_list.append(self.get_gl_dict({
|
||||
"account": warehouse_account[sle.warehouse],
|
||||
"against": detail.expense_account,
|
||||
"cost_center": detail.cost_center,
|
||||
"remarks": self.doc.remarks or "Accounting Entry for Stock",
|
||||
"debit": flt(sle.stock_value_difference, 2)
|
||||
}))
|
||||
|
||||
# to target warehouse / expense account
|
||||
gl_list.append(self.get_gl_dict({
|
||||
"account": detail.expense_account,
|
||||
"against": warehouse_account[sle.warehouse],
|
||||
"cost_center": detail.cost_center,
|
||||
"remarks": self.doc.remarks or "Accounting Entry for Stock",
|
||||
"credit": flt(sle.stock_value_difference, 2)
|
||||
}))
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
msgprint(_("No accounting entries for following warehouses") + ": \n" +
|
||||
"\n".join(warehouse_with_no_account))
|
||||
|
||||
return process_gl_map(gl_list)
|
||||
|
||||
def get_voucher_details(self, stock_ledger, default_expense_account, default_cost_center):
|
||||
if not default_expense_account:
|
||||
details = self.doclist.get({"parentfield": self.fname})
|
||||
for d in details:
|
||||
self.check_expense_account(d)
|
||||
else:
|
||||
details = [webnotes._dict({
|
||||
"name":d,
|
||||
"expense_account": default_expense_account,
|
||||
"cost_center": default_cost_center
|
||||
}) for d in stock_ledger.keys()]
|
||||
|
||||
return details
|
||||
|
||||
def get_stock_ledger_details(self):
|
||||
stock_ledger = {}
|
||||
for sle in webnotes.conn.sql("""select warehouse, stock_value_difference, voucher_detail_no
|
||||
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
|
||||
(self.doc.doctype, self.doc.name), as_dict=True):
|
||||
stock_ledger.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
return stock_ledger
|
||||
|
||||
def get_warehouse_account(self):
|
||||
warehouse_account = dict(webnotes.conn.sql("""select master_name, name from tabAccount
|
||||
where account_type = 'Warehouse' and ifnull(master_name, '') != ''"""))
|
||||
return warehouse_account
|
||||
|
||||
def update_gl_entries_after(self, warehouse_account=None):
|
||||
future_stock_vouchers = self.get_future_stock_vouchers()
|
||||
gle = self.get_voucherwise_gl_entries(future_stock_vouchers)
|
||||
if not warehouse_account:
|
||||
warehouse_account = self.get_warehouse_account()
|
||||
for voucher_type, voucher_no in future_stock_vouchers:
|
||||
existing_gle = gle.get((voucher_type, voucher_no), [])
|
||||
voucher_obj = webnotes.get_obj(voucher_type, voucher_no)
|
||||
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
|
||||
if expected_gle:
|
||||
matched = True
|
||||
if existing_gle:
|
||||
for entry in expected_gle:
|
||||
for e in existing_gle:
|
||||
if entry.account==e.account \
|
||||
and entry.against_account==e.against_account\
|
||||
and entry.cost_center==e.cost_center:
|
||||
if entry.debit != e.debit or entry.credit != e.credit:
|
||||
matched = False
|
||||
break
|
||||
else:
|
||||
matched = False
|
||||
|
||||
if not matched:
|
||||
self.delete_gl_entries(voucher_type, voucher_no)
|
||||
voucher_obj.make_gl_entries(update_gl_entries_after=False)
|
||||
else:
|
||||
self.delete_gl_entries(voucher_type, voucher_no)
|
||||
|
||||
|
||||
def get_future_stock_vouchers(self):
|
||||
future_stock_vouchers = []
|
||||
|
||||
if hasattr(self, "fname"):
|
||||
item_list = [d.item_code for d in self.doclist.get({"parentfield": self.fname})]
|
||||
condition = ''.join(['and item_code in (\'', '\', \''.join(item_list) ,'\')'])
|
||||
else:
|
||||
condition = ""
|
||||
|
||||
for d in webnotes.conn.sql("""select distinct sle.voucher_type, sle.voucher_no
|
||||
from `tabStock Ledger Entry` sle
|
||||
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) %s
|
||||
order by timestamp(sle.posting_date, sle.posting_time) asc, name asc""" %
|
||||
('%s', '%s', condition), (self.doc.posting_date, self.doc.posting_time),
|
||||
as_dict=True):
|
||||
future_stock_vouchers.append([d.voucher_type, d.voucher_no])
|
||||
|
||||
return future_stock_vouchers
|
||||
|
||||
def get_voucherwise_gl_entries(self, future_stock_vouchers):
|
||||
gl_entries = {}
|
||||
if future_stock_vouchers:
|
||||
for d in webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
where posting_date >= %s and voucher_no in (%s)""" %
|
||||
('%s', ', '.join(['%s']*len(future_stock_vouchers))),
|
||||
tuple([self.doc.posting_date] + [d[1] for d in future_stock_vouchers]), as_dict=1):
|
||||
gl_entries.setdefault((d.voucher_type, d.voucher_no), []).append(d)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def delete_gl_entries(self, voucher_type, voucher_no):
|
||||
webnotes.conn.sql("""delete from `tabGL Entry`
|
||||
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
|
||||
|
||||
def make_adjustment_entry(self, expected_gle, voucher_obj):
|
||||
from erpnext.accounts.utils import get_stock_and_account_difference
|
||||
account_list = [d.account for d in expected_gle]
|
||||
acc_diff = get_stock_and_account_difference(account_list, expected_gle[0].posting_date)
|
||||
|
||||
cost_center = self.get_company_default("cost_center")
|
||||
stock_adjustment_account = self.get_company_default("stock_adjustment_account")
|
||||
|
||||
gl_entries = []
|
||||
for account, diff in acc_diff.items():
|
||||
if diff:
|
||||
gl_entries.append([
|
||||
# stock in hand account
|
||||
voucher_obj.get_gl_dict({
|
||||
"account": account,
|
||||
"against": stock_adjustment_account,
|
||||
"debit": diff,
|
||||
"remarks": "Adjustment Accounting Entry for Stock",
|
||||
}),
|
||||
|
||||
# account against stock in hand
|
||||
voucher_obj.get_gl_dict({
|
||||
"account": stock_adjustment_account,
|
||||
"against": account,
|
||||
"credit": diff,
|
||||
"cost_center": cost_center or None,
|
||||
"remarks": "Adjustment Accounting Entry for Stock",
|
||||
}),
|
||||
])
|
||||
|
||||
if gl_entries:
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
def check_expense_account(self, item):
|
||||
if item.fields.has_key("expense_account") and not item.expense_account:
|
||||
msgprint(_("""Expense/Difference account is mandatory for item: """) + item.item_code,
|
||||
raise_exception=1)
|
||||
|
||||
if item.fields.has_key("expense_account") and not item.cost_center:
|
||||
msgprint(_("""Cost Center is mandatory for item: """) + item.item_code,
|
||||
raise_exception=1)
|
||||
|
||||
def get_sl_entries(self, d, args):
|
||||
sl_dict = {
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.doc.posting_date,
|
||||
"posting_time": self.doc.posting_time,
|
||||
"voucher_type": self.doc.doctype,
|
||||
"voucher_no": self.doc.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"actual_qty": (self.doc.docstatus==1 and 1 or -1)*flt(d.stock_qty),
|
||||
"stock_uom": d.stock_uom,
|
||||
"incoming_rate": 0,
|
||||
"company": self.doc.company,
|
||||
"fiscal_year": self.doc.fiscal_year,
|
||||
"batch_no": cstr(d.batch_no).strip(),
|
||||
"serial_no": d.serial_no,
|
||||
"project": d.project_name,
|
||||
"is_cancelled": self.doc.docstatus==2 and "Yes" or "No"
|
||||
}
|
||||
|
||||
sl_dict.update(args)
|
||||
return sl_dict
|
||||
|
||||
def make_sl_entries(self, sl_entries, is_amended=None):
|
||||
from erpnext.stock.stock_ledger import make_sl_entries
|
||||
make_sl_entries(sl_entries, is_amended)
|
||||
|
||||
def get_stock_ledger_entries(self, item_list=None, warehouse_list=None):
|
||||
out = {}
|
||||
|
||||
if not (item_list and warehouse_list):
|
||||
item_list, warehouse_list = self.get_distinct_item_warehouse()
|
||||
|
||||
if item_list and warehouse_list:
|
||||
res = webnotes.conn.sql("""select item_code, voucher_type, voucher_no,
|
||||
voucher_detail_no, posting_date, posting_time, stock_value,
|
||||
warehouse, actual_qty as qty from `tabStock Ledger Entry`
|
||||
where company = %s and item_code in (%s) and warehouse in (%s)
|
||||
order by item_code desc, warehouse desc, posting_date desc,
|
||||
posting_time desc, name desc""" %
|
||||
('%s', ', '.join(['%s']*len(item_list)), ', '.join(['%s']*len(warehouse_list))),
|
||||
tuple([self.doc.company] + item_list + warehouse_list), as_dict=1)
|
||||
|
||||
for r in res:
|
||||
if (r.item_code, r.warehouse) not in out:
|
||||
out[(r.item_code, r.warehouse)] = []
|
||||
|
||||
out[(r.item_code, r.warehouse)].append(r)
|
||||
|
||||
return out
|
||||
|
||||
def get_distinct_item_warehouse(self):
|
||||
item_list = []
|
||||
warehouse_list = []
|
||||
for item in self.doclist.get({"parentfield": self.fname}) \
|
||||
+ self.doclist.get({"parentfield": "packing_details"}):
|
||||
item_list.append(item.item_code)
|
||||
warehouse_list.append(item.warehouse)
|
||||
|
||||
return list(set(item_list)), list(set(warehouse_list))
|
||||
|
||||
def make_cancel_gl_entries(self):
|
||||
if webnotes.conn.sql("""select name from `tabGL Entry` where voucher_type=%s
|
||||
and voucher_no=%s""", (self.doc.doctype, self.doc.name)):
|
||||
self.make_gl_entries()
|
||||
260
erpnext/controllers/trends.py
Normal file
260
erpnext/controllers/trends.py
Normal file
@@ -0,0 +1,260 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import add_days, add_months, cstr, getdate
|
||||
from webnotes import _
|
||||
|
||||
def get_columns(filters, trans):
|
||||
validate_filters(filters)
|
||||
|
||||
# get conditions for based_on filter cond
|
||||
based_on_details = based_wise_colums_query(filters.get("based_on"), trans)
|
||||
# get conditions for periodic filter cond
|
||||
period_cols, period_select = period_wise_colums_query(filters, trans)
|
||||
# get conditions for grouping filter cond
|
||||
group_by_cols = group_wise_column(filters.get("group_by"))
|
||||
|
||||
columns = based_on_details["based_on_cols"] + period_cols + ["Total(Qty):Float:120", "Total(Amt):Currency:120"]
|
||||
if group_by_cols:
|
||||
columns = based_on_details["based_on_cols"] + group_by_cols + period_cols + \
|
||||
["Total(Qty):Float:120", "Total(Amt):Currency:120"]
|
||||
|
||||
conditions = {"based_on_select": based_on_details["based_on_select"], "period_wise_select": period_select,
|
||||
"columns": columns, "group_by": based_on_details["based_on_group_by"], "grbc": group_by_cols, "trans": trans,
|
||||
"addl_tables": based_on_details["addl_tables"]}
|
||||
|
||||
return conditions
|
||||
|
||||
def validate_filters(filters):
|
||||
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
|
||||
if not filters.get(f.lower().replace(" ", "_")):
|
||||
webnotes.msgprint(f + _(" is mandatory"), raise_exception=1)
|
||||
|
||||
if filters.get("based_on") == filters.get("group_by"):
|
||||
webnotes.msgprint("'Based On' and 'Group By' can not be same", raise_exception=1)
|
||||
|
||||
def get_data(filters, conditions):
|
||||
data = []
|
||||
inc, cond= '',''
|
||||
query_details = conditions["based_on_select"] + conditions["period_wise_select"]
|
||||
|
||||
if conditions["based_on_select"] in ["t1.project_name,", "t2.project_name,"]:
|
||||
cond = 'and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
|
||||
|
||||
if filters.get("group_by"):
|
||||
sel_col = ''
|
||||
ind = conditions["columns"].index(conditions["grbc"][0])
|
||||
|
||||
if filters.get("group_by") == 'Item':
|
||||
sel_col = 't2.item_code'
|
||||
elif filters.get("group_by") == 'Customer':
|
||||
sel_col = 't1.customer'
|
||||
elif filters.get("group_by") == 'Supplier':
|
||||
sel_col = 't1.supplier'
|
||||
|
||||
if filters.get('based_on') in ['Item','Customer','Supplier']:
|
||||
inc = 2
|
||||
else :
|
||||
inc = 1
|
||||
data1 = webnotes.conn.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and t1.fiscal_year = %s and
|
||||
t1.docstatus = 1 %s
|
||||
group by %s
|
||||
""" % (query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"], "%s",
|
||||
"%s", cond, conditions["group_by"]), (filters.get("company"),
|
||||
filters["fiscal_year"]),as_list=1)
|
||||
|
||||
for d in range(len(data1)):
|
||||
#to add blanck column
|
||||
dt = data1[d]
|
||||
dt.insert(ind,'')
|
||||
data.append(dt)
|
||||
|
||||
#to get distinct value of col specified by group_by in filter
|
||||
row = webnotes.conn.sql("""select DISTINCT(%s) from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and t1.fiscal_year = %s
|
||||
and t1.docstatus = 1 and %s = %s
|
||||
""" %
|
||||
(sel_col, conditions["trans"], conditions["trans"], conditions["addl_tables"],
|
||||
"%s", "%s", conditions["group_by"], "%s"),
|
||||
(filters.get("company"), filters.get("fiscal_year"), data1[d][0]), as_list=1)
|
||||
|
||||
for i in range(len(row)):
|
||||
des = ['' for q in range(len(conditions["columns"]))]
|
||||
|
||||
#get data for group_by filter
|
||||
row1 = webnotes.conn.sql(""" select %s , %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and t1.fiscal_year = %s
|
||||
and t1.docstatus = 1 and %s = %s and %s = %s
|
||||
""" %
|
||||
(sel_col, conditions["period_wise_select"], conditions["trans"],
|
||||
conditions["trans"], conditions["addl_tables"], "%s", "%s", sel_col,
|
||||
"%s", conditions["group_by"], "%s"),
|
||||
(filters.get("company"), filters.get("fiscal_year"), row[i][0],
|
||||
data1[d][0]), as_list=1)
|
||||
|
||||
des[ind] = row[i]
|
||||
for j in range(1,len(conditions["columns"])-inc):
|
||||
des[j+inc] = row1[0][j]
|
||||
|
||||
data.append(des)
|
||||
else:
|
||||
data = webnotes.conn.sql(""" select %s from `tab%s` t1, `tab%s Item` t2 %s
|
||||
where t2.parent = t1.name and t1.company = %s and t1.fiscal_year = %s and
|
||||
t1.docstatus = 1 %s
|
||||
group by %s
|
||||
""" %
|
||||
(query_details, conditions["trans"], conditions["trans"], conditions["addl_tables"],
|
||||
"%s", "%s", cond,conditions["group_by"]),
|
||||
(filters.get("company"), filters.get("fiscal_year")), as_list=1)
|
||||
|
||||
return data
|
||||
|
||||
def get_mon(dt):
|
||||
return getdate(dt).strftime("%b")
|
||||
|
||||
def period_wise_colums_query(filters, trans):
|
||||
query_details = ''
|
||||
pwc = []
|
||||
bet_dates = get_period_date_ranges(filters.get("period"), filters.get("fiscal_year"))
|
||||
|
||||
if trans in ['Purchase Receipt', 'Delivery Note', 'Purchase Invoice', 'Sales Invoice']:
|
||||
trans_date = 'posting_date'
|
||||
else:
|
||||
trans_date = 'transaction_date'
|
||||
|
||||
if filters.get("period") != 'Yearly':
|
||||
for dt in bet_dates:
|
||||
get_period_wise_columns(dt, filters.get("period"), pwc)
|
||||
query_details = get_period_wise_query(dt, trans_date, query_details)
|
||||
else:
|
||||
pwc = [filters.get("fiscal_year") + " (Qty):Float:120",
|
||||
filters.get("fiscal_year") + " (Amt):Currency:120"]
|
||||
query_details = " SUM(t2.qty), SUM(t1.grand_total),"
|
||||
|
||||
query_details += 'SUM(t2.qty), SUM(t1.grand_total)'
|
||||
return pwc, query_details
|
||||
|
||||
def get_period_wise_columns(bet_dates, period, pwc):
|
||||
if period == 'Monthly':
|
||||
pwc += [get_mon(bet_dates[0]) + " (Qty):Float:120",
|
||||
get_mon(bet_dates[0]) + " (Amt):Currency:120"]
|
||||
else:
|
||||
pwc += [get_mon(bet_dates[0]) + "-" + get_mon(bet_dates[1]) + " (Qty):Float:120",
|
||||
get_mon(bet_dates[0]) + "-" + get_mon(bet_dates[1]) + " (Amt):Currency:120"]
|
||||
|
||||
def get_period_wise_query(bet_dates, trans_date, query_details):
|
||||
query_details += """SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t2.qty, NULL)),
|
||||
SUM(IF(t1.%(trans_date)s BETWEEN '%(sd)s' AND '%(ed)s', t1.grand_total, NULL)),
|
||||
""" % {"trans_date": trans_date, "sd": bet_dates[0],"ed": bet_dates[1]}
|
||||
return query_details
|
||||
|
||||
@webnotes.whitelist(allow_guest=True)
|
||||
def get_period_date_ranges(period, fiscal_year=None, year_start_date=None):
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
if not year_start_date:
|
||||
year_start_date, year_end_date = webnotes.conn.get_value("Fiscal Year",
|
||||
fiscal_year, ["year_start_date", "year_end_date"])
|
||||
|
||||
increment = {
|
||||
"Monthly": 1,
|
||||
"Quarterly": 3,
|
||||
"Half-Yearly": 6,
|
||||
"Yearly": 12
|
||||
}.get(period)
|
||||
|
||||
period_date_ranges = []
|
||||
for i in xrange(1, 13, increment):
|
||||
period_end_date = getdate(year_start_date) + relativedelta(months=increment, days=-1)
|
||||
if period_end_date > getdate(year_end_date):
|
||||
period_end_date = year_end_date
|
||||
period_date_ranges.append([year_start_date, period_end_date])
|
||||
year_start_date = period_end_date + relativedelta(days=1)
|
||||
if period_end_date == year_end_date:
|
||||
break
|
||||
|
||||
return period_date_ranges
|
||||
|
||||
def get_period_month_ranges(period, fiscal_year):
|
||||
from dateutil.relativedelta import relativedelta
|
||||
period_month_ranges = []
|
||||
|
||||
for start_date, end_date in get_period_date_ranges(period, fiscal_year):
|
||||
months_in_this_period = []
|
||||
while start_date <= end_date:
|
||||
months_in_this_period.append(start_date.strftime("%B"))
|
||||
start_date += relativedelta(months=1)
|
||||
period_month_ranges.append(months_in_this_period)
|
||||
|
||||
return period_month_ranges
|
||||
|
||||
def based_wise_colums_query(based_on, trans):
|
||||
based_on_details = {}
|
||||
|
||||
# based_on_cols, based_on_select, based_on_group_by, addl_tables
|
||||
if based_on == "Item":
|
||||
based_on_details["based_on_cols"] = ["Item:Link/Item:120", "Item Name:Data:120"]
|
||||
based_on_details["based_on_select"] = "t2.item_code, t2.item_name,"
|
||||
based_on_details["based_on_group_by"] = 't2.item_code'
|
||||
based_on_details["addl_tables"] = ''
|
||||
|
||||
elif based_on == "Item Group":
|
||||
based_on_details["based_on_cols"] = ["Item Group:Link/Item Group:120"]
|
||||
based_on_details["based_on_select"] = "t2.item_group,"
|
||||
based_on_details["based_on_group_by"] = 't2.item_group'
|
||||
based_on_details["addl_tables"] = ''
|
||||
|
||||
elif based_on == "Customer":
|
||||
based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"]
|
||||
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
|
||||
based_on_details["based_on_group_by"] = 't1.customer_name'
|
||||
based_on_details["addl_tables"] = ''
|
||||
|
||||
elif based_on == "Customer Group":
|
||||
based_on_details["based_on_cols"] = ["Customer Group:Link/Customer Group"]
|
||||
based_on_details["based_on_select"] = "t1.customer_group,"
|
||||
based_on_details["based_on_group_by"] = 't1.customer_group'
|
||||
based_on_details["addl_tables"] = ''
|
||||
|
||||
elif based_on == 'Supplier':
|
||||
based_on_details["based_on_cols"] = ["Supplier:Link/Supplier:120", "Supplier Type:Link/Supplier Type:140"]
|
||||
based_on_details["based_on_select"] = "t1.supplier, t3.supplier_type,"
|
||||
based_on_details["based_on_group_by"] = 't1.supplier'
|
||||
based_on_details["addl_tables"] = ',`tabSupplier` t3'
|
||||
|
||||
elif based_on == 'Supplier Type':
|
||||
based_on_details["based_on_cols"] = ["Supplier Type:Link/Supplier Type:140"]
|
||||
based_on_details["based_on_select"] = "t3.supplier_type,"
|
||||
based_on_details["based_on_group_by"] = 't3.supplier_type'
|
||||
based_on_details["addl_tables"] =',`tabSupplier` t3'
|
||||
|
||||
elif based_on == "Territory":
|
||||
based_on_details["based_on_cols"] = ["Territory:Link/Territory:120"]
|
||||
based_on_details["based_on_select"] = "t1.territory,"
|
||||
based_on_details["based_on_group_by"] = 't1.territory'
|
||||
based_on_details["addl_tables"] = ''
|
||||
|
||||
elif based_on == "Project":
|
||||
if trans in ['Sales Invoice', 'Delivery Note', 'Sales Order']:
|
||||
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
|
||||
based_on_details["based_on_select"] = "t1.project_name,"
|
||||
based_on_details["based_on_group_by"] = 't1.project_name'
|
||||
based_on_details["addl_tables"] = ''
|
||||
elif trans in ['Purchase Order', 'Purchase Invoice', 'Purchase Receipt']:
|
||||
based_on_details["based_on_cols"] = ["Project:Link/Project:120"]
|
||||
based_on_details["based_on_select"] = "t2.project_name,"
|
||||
based_on_details["based_on_group_by"] = 't2.project_name'
|
||||
based_on_details["addl_tables"] = ''
|
||||
else:
|
||||
webnotes.msgprint("Project-wise data is not available for Quotation", raise_exception=1)
|
||||
|
||||
return based_on_details
|
||||
|
||||
def group_wise_column(group_by):
|
||||
if group_by:
|
||||
return [group_by+":Link/"+group_by+":120"]
|
||||
else:
|
||||
return []
|
||||
Reference in New Issue
Block a user