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:
Anand Doshi
2013-12-26 18:30:39 +05:30
1813 changed files with 12064 additions and 18858 deletions

View File

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

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

View 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);
}
}
}
}
}

View 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})

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

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

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

View 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 []